linux-kernel.vger.kernel.org archive mirror
 help / color / mirror / Atom feed
* [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
       [not found] <1446444460-21600-1-git-send-email-okaya@codeaurora.org>
@ 2015-11-02  6:07 ` Sinan Kaya
  2015-11-02 15:57   ` Rob Herring
  2015-11-03 10:22   ` Andy Shevchenko
  2015-11-02  6:07 ` [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions Sinan Kaya
  2015-11-02  6:07 ` [PATCH V2 3/3] dma: add Qualcomm Technologies HIDMA channel driver Sinan Kaya
  2 siblings, 2 replies; 41+ messages in thread
From: Sinan Kaya @ 2015-11-02  6:07 UTC (permalink / raw)
  To: dmaengine, timur, cov, jcm
  Cc: Sinan Kaya, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Vinod Koul, Dan Williams, devicetree, linux-kernel

The Qualcomm Technologies HIDMA device has been designed
to support virtualization technology. The driver has been
divided into two to follow the hardware design. The management
driver is executed in hypervisor context and is the main
management entity for all channels provided by the device.
The channel driver is executed in the hypervisor/guest OS
context.

All channel devices get probed in the hypervisor
context during power up. They show up as DMA engine
channels. Then, before starting the virtualization; each
channel device is unbound from the hypervisor by VFIO
and assigned to the guest machine for control.

This management driver will be used by the system
admin to monitor/reset the execution state of the DMA
channels. This will be the management interface.

Signed-off-by: Sinan Kaya <okaya@codeaurora.org>
---
 .../devicetree/bindings/dma/qcom_hidma_mgmt.txt    |  56 ++
 drivers/dma/Kconfig                                |  11 +
 drivers/dma/Makefile                               |   1 +
 drivers/dma/qcom_hidma_mgmt.c                      | 747 +++++++++++++++++++++
 4 files changed, 815 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt
 create mode 100644 drivers/dma/qcom_hidma_mgmt.c

diff --git a/Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt b/Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt
new file mode 100644
index 0000000..514d37d
--- /dev/null
+++ b/Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt
@@ -0,0 +1,56 @@
+Qualcomm Technologies HIDMA Management interface
+
+The Qualcomm Technologies HIDMA device has been designed
+to support virtualization technology. The driver has been
+divided into two to follow the hardware design. The management
+driver is executed in hypervisor context and is the main
+management entity for all channels provided by the device.
+The channel driver is executed in the hypervisor/guest OS
+context.
+
+All channel devices get probed in the hypervisor
+context during power up. They show up as DMA engine
+DMA channels. Then, before starting the virtualization; each
+channel device is unbound from the hypervisor by VFIO
+and assign to the guest machine for control.
+
+This management driver will  be used by the system
+admin to monitor/reset the execution state of the DMA
+channels. This will be the management interface.
+
+
+Required properties:
+- compatible: must contain "qcom,hidma-mgmt"
+- reg: Address range for DMA device
+- dma-channels: Number of channels supported by this DMA controller.
+- max-write-burst-bytes: Maximum write burst in bytes. A memcpy requested is
+  fragmented to multiples of this amount.
+- max-read-burst-bytes: Maximum read burst in bytes. A memcpy request is
+  fragmented to multiples of this amount.
+- max-write-transactions: Maximum write transactions to perform in a burst
+- max-read-transactions: Maximum read transactions to perform in a burst
+- channel-reset-timeout: Channel reset timeout for this SOC.
+- channel-priority: Priority of the channel.
+  Each dma channel share the same HW bandwidth with other dma channels.
+  If two requests reach to the HW at the same time from a low priority and
+  high priority channel, high priority channel will claim the bus.
+  0=low priority, 1=high priority
+- channel-weight: Round robin weight of the channel
+  Since there are only two priority levels supported, scheduling among
+  the equal priority channels is done via weights.
+
+Example:
+
+	hidma-mgmt@f9984000 = {
+		compatible = "qcom,hidma-mgmt-1.0";
+		reg = <0xf9984000 0x15000>;
+		dma-channels = 6;
+		max-write-burst-bytes = 1024;
+		max-read-burst-bytes = 1024;
+		max-write-transactions = 31;
+		max-read-transactions = 31;
+		channel-reset-timeout = 0x500;
+		channel-priority = < 1 1 0 0 0 0>;
+		channel-weight = < 1 13 10 3 4 5>;
+	};
+
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index b458475..4c6f0b5 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -501,6 +501,17 @@ config XGENE_DMA
 	help
 	  Enable support for the APM X-Gene SoC DMA engine.
 
+config QCOM_HIDMA_MGMT
+	tristate "Qualcomm Technologies HIDMA Management support"
+	select DMA_ENGINE
+	help
+	  Enable support for the Qualcomm Technologies HIDMA Management.
+	  Each DMA device requires one management interface driver
+	  for basic initialization before QCOM_HIDMA channel driver can
+	  start managing the channels. In a virtualized environment,
+	  the guest OS would run QCOM_HIDMA channel driver and the
+	  hypervisor would run the QCOM_HIDMA_MGMT management driver.
+
 config XILINX_VDMA
 	tristate "Xilinx AXI VDMA Engine"
 	depends on (ARCH_ZYNQ || MICROBLAZE)
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 7711a71..3d25ffd 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -53,6 +53,7 @@ obj-$(CONFIG_PL330_DMA) += pl330.o
 obj-$(CONFIG_PPC_BESTCOMM) += bestcomm/
 obj-$(CONFIG_PXA_DMA) += pxa_dma.o
 obj-$(CONFIG_QCOM_BAM_DMA) += qcom_bam_dma.o
+obj-$(CONFIG_QCOM_HIDMA_MGMT) += qcom_hidma_mgmt.o
 obj-$(CONFIG_RENESAS_DMA) += sh/
 obj-$(CONFIG_SIRF_DMA) += sirf-dma.o
 obj-$(CONFIG_STE_DMA40) += ste_dma40.o ste_dma40_ll.o
diff --git a/drivers/dma/qcom_hidma_mgmt.c b/drivers/dma/qcom_hidma_mgmt.c
new file mode 100644
index 0000000..7652702
--- /dev/null
+++ b/drivers/dma/qcom_hidma_mgmt.c
@@ -0,0 +1,747 @@
+/*
+ * Qualcomm Technologies HIDMA DMA engine Management interface
+ *
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/dmaengine.h>
+#include <linux/acpi.h>
+#include <linux/of.h>
+#include <linux/property.h>
+#include <linux/debugfs.h>
+#include <linux/interrupt.h>
+#include <linux/platform_device.h>
+#include <linux/module.h>
+#include <linux/uaccess.h>
+#include <linux/slab.h>
+#include <linux/pm_runtime.h>
+
+#define MHICFG_OFFSET			0x10
+#define QOS_N_OFFSET			0x300
+#define CFG_OFFSET			0x400
+#define HW_PARAM_OFFSET		0x408
+#define MAX_BUS_REQ_LEN_OFFSET		0x41C
+#define MAX_XACTIONS_OFFSET		0x420
+#define SW_VERSION_OFFSET		0x424
+#define CHRESET_TIMEOUT_OFFSET		0x418
+#define MHID_BUS_ERR0_OFFSET		0x1020
+#define MHID_BUS_ERR1_OFFSET		0x1024
+#define MHID_BUS_ERR_CLR_OFFSET	0x102C
+#define EVT_BUS_ERR0_OFFSET		0x1030
+#define EVT_BUS_ERR1_OFFSET		0x1034
+#define EVT_BUS_ERR_CLR_OFFSET		0x103C
+#define IDE_BUS_ERR0_OFFSET		0x1040
+#define IDE_BUS_ERR1_OFFSET		0x1044
+#define IDE_BUS_ERR2_OFFSET		0x1048
+#define IDE_BUS_ERR_CLR_OFFSET		0x104C
+#define ODE_BUS_ERR0_OFFSET		0x1050
+#define ODE_BUS_ERR1_OFFSET		0x1054
+#define ODE_BUS_ERR2_OFFSET		0x1058
+#define ODE_BUS_ERR_CLR_OFFSET		0x105C
+#define MSI_BUS_ERR0_OFFSET		0x1060
+#define MSI_BUS_ERR_CLR_OFFSET		0x106C
+#define TRE_ERR0_OFFSET		0x1070
+#define TRE_ERR_CLR_OFFSET		0x107C
+#define HW_EVENTS_CFG_OFFSET		0x1080
+
+#define HW_EVENTS_CFG_MASK		0xFF
+#define TRE_ERR_TRCHID_MASK		0xF
+#define TRE_ERR_EVRIDX_MASK		0xFF
+#define TRE_ERR_TYPE_MASK		0xFF
+#define MSI_ERR_RESP_MASK		0xFF
+#define MSI_ERR_TRCHID_MASK		0xFF
+#define ODE_ERR_REQLEN_MASK		0xFFFF
+#define ODE_ERR_RESP_MASK		0xFF
+#define ODE_ERR_TRCHID_MASK		0xFF
+#define IDE_ERR_REQLEN_MASK		0xFFFF
+#define IDE_ERR_RESP_MASK		0xFF
+#define IDE_ERR_TRCHID_MASK		0xFF
+#define EVT_ERR_RESP_MASK		0xFF
+#define EVT_ERR_TRCHID_MASK		0xFF
+#define MHID_ERR_RESP_MASK		0xFF
+#define MHID_ERR_TRCHID_MASK		0xFF
+#define MAX_WR_XACTIONS_MASK		0x1F
+#define MAX_RD_XACTIONS_MASK		0x1F
+#define MAX_JOBSIZE_MASK		0xFF
+#define MAX_COIDX_MASK			0xFF
+#define TREQ_CAPACITY_MASK		0xFF
+#define WEIGHT_MASK			0x7F
+#define TREQ_LIMIT_MASK		0x1FF
+#define NR_CHANNEL_MASK		0xFFFF
+#define MAX_BUS_REQ_LEN_MASK		0xFFFF
+#define CHRESET_TIMEOUUT_MASK		0xFFFFF
+
+#define TRE_ERR_TRCHID_BIT_POS		28
+#define TRE_ERR_IEOB_BIT_POS		16
+#define TRE_ERR_EVRIDX_BIT_POS		8
+#define MSI_ERR_RESP_BIT_POS		8
+#define ODE_ERR_REQLEN_BIT_POS		16
+#define ODE_ERR_RESP_BIT_POS		8
+#define IDE_ERR_REQLEN_BIT_POS		16
+#define IDE_ERR_RESP_BIT_POS		8
+#define EVT_ERR_RESP_BIT_POS		8
+#define MHID_ERR_RESP_BIT_POS		8
+#define MAX_WR_XACTIONS_BIT_POS	16
+#define TREQ_CAPACITY_BIT_POS		8
+#define MAX_JOB_SIZE_BIT_POS		16
+#define NR_EV_CHANNEL_BIT_POS		16
+#define MAX_BUS_WR_REQ_BIT_POS		16
+#define WRR_BIT_POS			8
+#define PRIORITY_BIT_POS		15
+#define TREQ_LIMIT_BIT_POS		16
+#define TREQ_LIMIT_EN_BIT_POS		23
+#define STOP_BIT_POS			24
+
+#define AUTOSUSPEND_TIMEOUT		2000
+
+struct qcom_hidma_mgmt_dev {
+	u32 max_wr_xactions;
+	u32 max_rd_xactions;
+	u32 max_write_request;
+	u32 max_read_request;
+	u32 dma_channels;
+	u32 chreset_timeout;
+	u32 sw_version;
+	u32 *priority;
+	u32 *weight;
+
+	/* Hardware device constants */
+	void __iomem *dev_virtaddr;
+	resource_size_t dev_addrsize;
+
+	struct dentry	*debugfs;
+	struct platform_device *pdev;
+};
+
+static unsigned int debug_pm;
+module_param(debug_pm, uint, 0644);
+MODULE_PARM_DESC(debug_pm,
+		 "debug runtime power management transitions (default: 0)");
+
+#if IS_ENABLED(CONFIG_DEBUG_FS)
+
+static inline void HIDMA_READ_SHOW(struct seq_file *s,
+		void __iomem *dev_virtaddr, char *name, int offset)
+{
+	u32 val;
+
+	val = readl(dev_virtaddr + offset);
+	seq_printf(s, "%s=0x%x\n", name, val);
+}
+
+/**
+ * qcom_hidma_mgmt_info: display HIDMA device info
+ *
+ * Display the info for the current HIDMA device.
+ */
+static int qcom_hidma_mgmt_info(struct seq_file *s, void *unused)
+{
+	struct qcom_hidma_mgmt_dev *mgmtdev = s->private;
+	u32 val;
+	u32 i;
+
+	pm_runtime_get_sync(&mgmtdev->pdev->dev);
+	seq_printf(s, "sw_version=0x%x\n", mgmtdev->sw_version);
+
+	val = readl(mgmtdev->dev_virtaddr + CFG_OFFSET);
+	seq_printf(s, "ENABLE=%d\n", val & 0x1);
+
+	val = readl(mgmtdev->dev_virtaddr + CHRESET_TIMEOUT_OFFSET);
+	seq_printf(s, "reset_timeout=%d\n", val & CHRESET_TIMEOUUT_MASK);
+
+	val = readl(mgmtdev->dev_virtaddr + MHICFG_OFFSET);
+	seq_printf(s, "nr_event_channel=%d\n",
+		(val >> NR_EV_CHANNEL_BIT_POS) & NR_CHANNEL_MASK);
+	seq_printf(s, "nr_tr_channel=%d\n", (val & NR_CHANNEL_MASK));
+	seq_printf(s, "dma_channels=%d\n", mgmtdev->dma_channels);
+	seq_printf(s, "dev_addrsize=%pap\n", &mgmtdev->dev_addrsize);
+
+	val = readl(mgmtdev->dev_virtaddr + HW_PARAM_OFFSET);
+	seq_printf(s, "MAX_JOB_SIZE=%d\n",
+		(val >> MAX_JOB_SIZE_BIT_POS) & MAX_JOBSIZE_MASK);
+	seq_printf(s, "TREQ_CAPACITY=%d\n",
+		(val >> TREQ_CAPACITY_BIT_POS) & TREQ_CAPACITY_MASK);
+	seq_printf(s, "MAX_COIDX_DEPTH=%d\n", val & MAX_COIDX_MASK);
+
+	val = readl(mgmtdev->dev_virtaddr + MAX_BUS_REQ_LEN_OFFSET);
+	seq_printf(s, "MAX_BUS_WR_REQ_LEN=%d\n",
+		(val >> MAX_BUS_WR_REQ_BIT_POS) & MAX_BUS_REQ_LEN_MASK);
+	seq_printf(s, "MAX_BUS_RD_REQ_LEN=%d\n", val & MAX_BUS_REQ_LEN_MASK);
+
+	val = readl(mgmtdev->dev_virtaddr + MAX_XACTIONS_OFFSET);
+	seq_printf(s, "MAX_WR_XACTIONS=%d\n",
+		(val >> MAX_WR_XACTIONS_BIT_POS) & MAX_WR_XACTIONS_MASK);
+	seq_printf(s, "MAX_RD_XACTIONS=%d\n", val & MAX_RD_XACTIONS_MASK);
+
+	for (i = 0; i < mgmtdev->dma_channels; i++) {
+		void __iomem *offset;
+
+		offset = mgmtdev->dev_virtaddr + QOS_N_OFFSET + (4 * i);
+		val = readl(offset);
+
+		seq_printf(s, "CH#%d STOP=%d\n",
+			i, (val & (1 << STOP_BIT_POS)) ? 1 : 0);
+		seq_printf(s, "CH#%d TREQ LIMIT EN=%d\n", i,
+			(val & (1 << TREQ_LIMIT_EN_BIT_POS)) ? 1 : 0);
+		seq_printf(s, "CH#%d TREQ LIMIT=%d\n",
+			i, (val >> TREQ_LIMIT_BIT_POS) & TREQ_LIMIT_MASK);
+		seq_printf(s, "CH#%d priority=%d\n", i,
+			(val & (1 << PRIORITY_BIT_POS)) ? 1 : 0);
+		seq_printf(s, "CH#%d WRR=%d\n", i,
+			(val >> WRR_BIT_POS) & WEIGHT_MASK);
+		seq_printf(s, "CH#%d USE_DLA=%d\n", i, (val & 1) ? 1 : 0);
+	}
+	pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
+	pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
+
+	return 0;
+}
+
+static int qcom_hidma_mgmt_info_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, qcom_hidma_mgmt_info, inode->i_private);
+}
+
+static const struct file_operations qcom_hidma_mgmt_fops = {
+	.open = qcom_hidma_mgmt_info_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+/**
+ * qcom_hidma_mgmt_err: display HIDMA error info
+ *
+ * Display the error info for the current HIDMA device.
+ */
+static int qcom_hidma_mgmt_err(struct seq_file *s, void *unused)
+{
+	u32 val;
+	struct qcom_hidma_mgmt_dev *mgmtdev = s->private;
+
+	pm_runtime_get_sync(&mgmtdev->pdev->dev);
+	val = readl(mgmtdev->dev_virtaddr + MHID_BUS_ERR0_OFFSET);
+	seq_printf(s, "MHID TR_CHID=%d\n", val & MHID_ERR_TRCHID_MASK);
+	seq_printf(s, "MHID RESP_ERROR=%d\n",
+		(val >> MHID_ERR_RESP_BIT_POS) & MHID_ERR_RESP_MASK);
+	HIDMA_READ_SHOW(s, mgmtdev->dev_virtaddr, "MHID READ_PTR",
+		MHID_BUS_ERR1_OFFSET);
+
+	val = readl(mgmtdev->dev_virtaddr + EVT_BUS_ERR0_OFFSET);
+	seq_printf(s, "EVT TR_CHID=%d\n", val & EVT_ERR_TRCHID_MASK);
+	seq_printf(s, "EVT RESP_ERROR=%d\n",
+		(val >> EVT_ERR_RESP_BIT_POS) & EVT_ERR_RESP_MASK);
+	HIDMA_READ_SHOW(s, mgmtdev->dev_virtaddr, "EVT WRITE_PTR",
+		EVT_BUS_ERR1_OFFSET);
+
+	val = readl(mgmtdev->dev_virtaddr + IDE_BUS_ERR0_OFFSET);
+	seq_printf(s, "IDE TR_CHID=%d\n", val & IDE_ERR_TRCHID_MASK);
+	seq_printf(s, "IDE RESP_ERROR=%d\n",
+		(val >> IDE_ERR_RESP_BIT_POS) & IDE_ERR_RESP_MASK);
+	seq_printf(s, "IDE REQ_LENGTH=%d\n",
+		(val >> IDE_ERR_REQLEN_BIT_POS) & IDE_ERR_REQLEN_MASK);
+	HIDMA_READ_SHOW(s, mgmtdev->dev_virtaddr, "IDE ADDR_LSB",
+		IDE_BUS_ERR1_OFFSET);
+	HIDMA_READ_SHOW(s, mgmtdev->dev_virtaddr, "IDE ADDR_MSB",
+		IDE_BUS_ERR2_OFFSET);
+
+	val = readl(mgmtdev->dev_virtaddr + ODE_BUS_ERR0_OFFSET);
+	seq_printf(s, "ODE TR_CHID=%d\n", val & ODE_ERR_TRCHID_MASK);
+	seq_printf(s, "ODE RESP_ERROR=%d\n",
+		(val >> ODE_ERR_RESP_BIT_POS) & ODE_ERR_RESP_MASK);
+	seq_printf(s, "ODE REQ_LENGTH=%d\n",
+		(val >> ODE_ERR_REQLEN_BIT_POS) & ODE_ERR_REQLEN_MASK);
+	HIDMA_READ_SHOW(s, mgmtdev->dev_virtaddr, "ODE ADDR_LSB",
+		ODE_BUS_ERR1_OFFSET);
+	HIDMA_READ_SHOW(s, mgmtdev->dev_virtaddr, "ODE ADDR_MSB",
+		ODE_BUS_ERR2_OFFSET);
+
+	val = readl(mgmtdev->dev_virtaddr + MSI_BUS_ERR0_OFFSET);
+	seq_printf(s, "MSI TR_CHID=%d\n", val & MSI_ERR_TRCHID_MASK);
+	seq_printf(s, "MSI RESP_ERROR=%d\n",
+		(val >> MSI_ERR_RESP_BIT_POS) & MSI_ERR_RESP_MASK);
+
+	val = readl(mgmtdev->dev_virtaddr + TRE_ERR0_OFFSET);
+	seq_printf(s, "TRE TRE_TYPE=%d\n", val & TRE_ERR_TYPE_MASK);
+	seq_printf(s, "TRE TRE_EVRIDX=%d\n",
+		(val >> TRE_ERR_EVRIDX_BIT_POS) & TRE_ERR_EVRIDX_MASK);
+	seq_printf(s, "TRE TRE_IEOB=%d\n",
+		(val >> TRE_ERR_IEOB_BIT_POS) & 1);
+	seq_printf(s, "TRE TRCHID=%d\n",
+		(val >> TRE_ERR_TRCHID_BIT_POS) & TRE_ERR_TRCHID_MASK);
+
+	HIDMA_READ_SHOW(s, mgmtdev->dev_virtaddr, "HW_EVENTS_CFG_OFFSET",
+			HW_EVENTS_CFG_OFFSET);
+
+	pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
+	pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
+	return 0;
+}
+
+static int qcom_hidma_mgmt_err_open(struct inode *inode, struct file *file)
+{
+	return single_open(file, qcom_hidma_mgmt_err, inode->i_private);
+}
+
+static const struct file_operations qcom_hidma_mgmt_err_fops = {
+	.open = qcom_hidma_mgmt_err_open,
+	.read = seq_read,
+	.llseek = seq_lseek,
+	.release = single_release,
+};
+
+static ssize_t qcom_hidma_mgmt_mhiderr_clr(struct file *file,
+	const char __user *user_buf, size_t count, loff_t *ppos)
+{
+	struct qcom_hidma_mgmt_dev *mgmtdev = file->f_inode->i_private;
+
+	pm_runtime_get_sync(&mgmtdev->pdev->dev);
+	writel(1, mgmtdev->dev_virtaddr + MHID_BUS_ERR_CLR_OFFSET);
+	pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
+	pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
+	return count;
+}
+
+static const struct file_operations qcom_hidma_mgmt_mhiderr_clrfops = {
+	.write = qcom_hidma_mgmt_mhiderr_clr,
+};
+
+static ssize_t qcom_hidma_mgmt_evterr_clr(struct file *file,
+	const char __user *user_buf, size_t count, loff_t *ppos)
+{
+	struct qcom_hidma_mgmt_dev *mgmtdev = file->f_inode->i_private;
+
+	pm_runtime_get_sync(&mgmtdev->pdev->dev);
+	writel(1, mgmtdev->dev_virtaddr + EVT_BUS_ERR_CLR_OFFSET);
+	pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
+	pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
+	return count;
+}
+
+static const struct file_operations qcom_hidma_mgmt_evterr_clrfops = {
+	.write = qcom_hidma_mgmt_evterr_clr,
+};
+
+static ssize_t qcom_hidma_mgmt_ideerr_clr(struct file *file,
+	const char __user *user_buf, size_t count, loff_t *ppos)
+{
+	struct qcom_hidma_mgmt_dev *mgmtdev = file->f_inode->i_private;
+
+	pm_runtime_get_sync(&mgmtdev->pdev->dev);
+	writel(1, mgmtdev->dev_virtaddr + IDE_BUS_ERR_CLR_OFFSET);
+	pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
+	pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
+	return count;
+}
+
+static const struct file_operations qcom_hidma_mgmt_ideerr_clrfops = {
+	.write = qcom_hidma_mgmt_ideerr_clr,
+};
+
+static ssize_t qcom_hidma_mgmt_odeerr_clr(struct file *file,
+	const char __user *user_buf, size_t count, loff_t *ppos)
+{
+	struct qcom_hidma_mgmt_dev *mgmtdev = file->f_inode->i_private;
+
+	pm_runtime_get_sync(&mgmtdev->pdev->dev);
+	writel(1, mgmtdev->dev_virtaddr + ODE_BUS_ERR_CLR_OFFSET);
+	pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
+	pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
+	return count;
+}
+
+static const struct file_operations qcom_hidma_mgmt_odeerr_clrfops = {
+	.write = qcom_hidma_mgmt_odeerr_clr,
+};
+
+static ssize_t qcom_hidma_mgmt_msierr_clr(struct file *file,
+	const char __user *user_buf, size_t count, loff_t *ppos)
+{
+	struct qcom_hidma_mgmt_dev *mgmtdev = file->f_inode->i_private;
+
+	pm_runtime_get_sync(&mgmtdev->pdev->dev);
+	writel(1, mgmtdev->dev_virtaddr + MSI_BUS_ERR_CLR_OFFSET);
+	pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
+	pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
+	return count;
+}
+
+static const struct file_operations qcom_hidma_mgmt_msierr_clrfops = {
+	.write = qcom_hidma_mgmt_msierr_clr,
+};
+
+static ssize_t qcom_hidma_mgmt_treerr_clr(struct file *file,
+	const char __user *user_buf, size_t count, loff_t *ppos)
+{
+	struct qcom_hidma_mgmt_dev *mgmtdev = file->f_inode->i_private;
+
+	pm_runtime_get_sync(&mgmtdev->pdev->dev);
+	writel(1, mgmtdev->dev_virtaddr + TRE_ERR_CLR_OFFSET);
+	pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
+	pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
+	return count;
+}
+
+static const struct file_operations qcom_hidma_mgmt_treerr_clrfops = {
+	.write = qcom_hidma_mgmt_treerr_clr,
+};
+
+static ssize_t qcom_hidma_mgmt_evtena(struct file *file,
+	const char __user *user_buf, size_t count, loff_t *ppos)
+{
+	char temp_buf[16+1];
+	struct qcom_hidma_mgmt_dev *mgmtdev = file->f_inode->i_private;
+	u32 event;
+	ssize_t ret;
+	unsigned long val;
+
+	temp_buf[16] = '\0';
+	if (copy_from_user(temp_buf, user_buf, min_t(int, count, 16)))
+		goto out;
+
+	ret = kstrtoul(temp_buf, 16, &val);
+	if (ret) {
+		dev_warn(&mgmtdev->pdev->dev, "unknown event\n");
+		goto out;
+	}
+
+	event = (u32)val & HW_EVENTS_CFG_MASK;
+
+	pm_runtime_get_sync(&mgmtdev->pdev->dev);
+	writel(event, mgmtdev->dev_virtaddr + HW_EVENTS_CFG_OFFSET);
+	pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
+	pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
+out:
+	return count;
+}
+
+static const struct file_operations qcom_hidma_mgmt_evtena_fops = {
+	.write = qcom_hidma_mgmt_evtena,
+};
+
+struct fileinfo {
+	char *name;
+	int mode;
+	const struct file_operations *ops;
+};
+
+static struct fileinfo files[] = {
+	{"info", S_IRUGO, &qcom_hidma_mgmt_fops},
+	{"err", S_IRUGO,  &qcom_hidma_mgmt_err_fops},
+	{"mhiderrclr", S_IWUSR, &qcom_hidma_mgmt_mhiderr_clrfops},
+	{"evterrclr", S_IWUSR, &qcom_hidma_mgmt_evterr_clrfops},
+	{"ideerrclr", S_IWUSR, &qcom_hidma_mgmt_ideerr_clrfops},
+	{"odeerrclr", S_IWUSR, &qcom_hidma_mgmt_odeerr_clrfops},
+	{"msierrclr", S_IWUSR, &qcom_hidma_mgmt_msierr_clrfops},
+	{"treerrclr", S_IWUSR, &qcom_hidma_mgmt_treerr_clrfops},
+	{"evtena", S_IWUSR, &qcom_hidma_mgmt_evtena_fops},
+};
+
+static void qcom_hidma_mgmt_debug_uninit(struct qcom_hidma_mgmt_dev *mgmtdev)
+{
+	debugfs_remove_recursive(mgmtdev->debugfs);
+}
+
+static int qcom_hidma_mgmt_debug_init(struct qcom_hidma_mgmt_dev *mgmtdev)
+{
+	int rc = 0;
+	u32 i;
+	struct dentry	*fs_entry;
+
+	mgmtdev->debugfs = debugfs_create_dir(dev_name(&mgmtdev->pdev->dev),
+						NULL);
+	if (!mgmtdev->debugfs) {
+		rc = -ENODEV;
+		return rc;
+	}
+
+	for (i = 0; i < ARRAY_SIZE(files); i++) {
+		fs_entry = debugfs_create_file(files[i].name,
+					files[i].mode, mgmtdev->debugfs,
+					mgmtdev, files[i].ops);
+		if (!fs_entry) {
+			rc = -ENOMEM;
+			goto cleanup;
+		}
+	}
+
+	return 0;
+cleanup:
+	qcom_hidma_mgmt_debug_uninit(mgmtdev);
+	return rc;
+}
+#else
+static void qcom_hidma_mgmt_debug_uninit(struct qcom_hidma_mgmt_dev *mgmtdev)
+{
+}
+static int qcom_hidma_mgmt_debug_init(struct qcom_hidma_mgmt_dev *mgmtdev)
+{
+	return 0;
+}
+#endif
+
+static int qcom_hidma_mgmt_setup(struct qcom_hidma_mgmt_dev *mgmtdev)
+{
+	u32 val;
+	u32 i;
+
+	val = readl(mgmtdev->dev_virtaddr + MAX_BUS_REQ_LEN_OFFSET);
+	val = val &
+		~(MAX_BUS_REQ_LEN_MASK << MAX_BUS_WR_REQ_BIT_POS);
+	val = val |
+		(mgmtdev->max_write_request << MAX_BUS_WR_REQ_BIT_POS);
+	val = val & ~(MAX_BUS_REQ_LEN_MASK);
+	val = val | (mgmtdev->max_read_request);
+	writel(val, mgmtdev->dev_virtaddr + MAX_BUS_REQ_LEN_OFFSET);
+
+	val = readl(mgmtdev->dev_virtaddr + MAX_XACTIONS_OFFSET);
+	val = val &
+		~(MAX_WR_XACTIONS_MASK << MAX_WR_XACTIONS_BIT_POS);
+	val = val |
+		(mgmtdev->max_wr_xactions << MAX_WR_XACTIONS_BIT_POS);
+	val = val & ~(MAX_RD_XACTIONS_MASK);
+	val = val | (mgmtdev->max_rd_xactions);
+	writel(val, mgmtdev->dev_virtaddr + MAX_XACTIONS_OFFSET);
+
+	mgmtdev->sw_version = readl(mgmtdev->dev_virtaddr + SW_VERSION_OFFSET);
+
+	for (i = 0; i < mgmtdev->dma_channels; i++) {
+		val = readl(mgmtdev->dev_virtaddr + QOS_N_OFFSET + (4 * i));
+		val = val & ~(1 << PRIORITY_BIT_POS);
+		val = val |
+			((mgmtdev->priority[i] & 0x1) << PRIORITY_BIT_POS);
+		val = val & ~(WEIGHT_MASK << WRR_BIT_POS);
+		val = val
+			| ((mgmtdev->weight[i] & WEIGHT_MASK) << WRR_BIT_POS);
+		writel(val, mgmtdev->dev_virtaddr + QOS_N_OFFSET + (4 * i));
+	}
+
+	val = readl(mgmtdev->dev_virtaddr + CHRESET_TIMEOUT_OFFSET);
+	val = val & ~CHRESET_TIMEOUUT_MASK;
+	val = val | (mgmtdev->chreset_timeout & CHRESET_TIMEOUUT_MASK);
+	writel(val, mgmtdev->dev_virtaddr + CHRESET_TIMEOUT_OFFSET);
+
+	val = readl(mgmtdev->dev_virtaddr + CFG_OFFSET);
+	val = val | 1;
+	writel(val, mgmtdev->dev_virtaddr + CFG_OFFSET);
+
+	return 0;
+}
+
+static int qcom_hidma_mgmt_probe(struct platform_device *pdev)
+{
+	struct resource *dma_resource;
+	int irq;
+	int rc;
+	u32 i;
+	struct qcom_hidma_mgmt_dev *mgmtdev;
+
+	pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT);
+	pm_runtime_use_autosuspend(&pdev->dev);
+	pm_runtime_set_active(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+	dma_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!dma_resource) {
+		dev_err(&pdev->dev, "No memory resources found\n");
+		rc = -ENODEV;
+		goto out;
+	}
+
+	irq = platform_get_irq(pdev, 0);
+	if (irq < 0) {
+		dev_err(&pdev->dev, "irq resources not found\n");
+		rc = -ENODEV;
+		goto out;
+	}
+
+	mgmtdev = devm_kzalloc(&pdev->dev, sizeof(*mgmtdev), GFP_KERNEL);
+	if (!mgmtdev) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	mgmtdev->pdev = pdev;
+
+	pm_runtime_get_sync(&mgmtdev->pdev->dev);
+	mgmtdev->dev_addrsize = resource_size(dma_resource);
+	mgmtdev->dev_virtaddr = devm_ioremap_resource(&pdev->dev,
+							dma_resource);
+	if (IS_ERR(mgmtdev->dev_virtaddr)) {
+		dev_err(&pdev->dev, "can't map i/o memory at %pa\n",
+			&dma_resource->start);
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	if (device_property_read_u32(&pdev->dev, "dma-channels",
+		&mgmtdev->dma_channels)) {
+		dev_err(&pdev->dev, "number of channels missing\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (device_property_read_u32(&pdev->dev, "channel-reset-timeout",
+		&mgmtdev->chreset_timeout)) {
+		dev_err(&pdev->dev, "channel reset timeout missing\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (device_property_read_u32(&pdev->dev, "max-write-burst-bytes",
+		&mgmtdev->max_write_request)) {
+		dev_err(&pdev->dev, "max-write-burst-bytes missing\n");
+		rc = -EINVAL;
+		goto out;
+	}
+	if ((mgmtdev->max_write_request != 128) &&
+		(mgmtdev->max_write_request != 256) &&
+		(mgmtdev->max_write_request != 512) &&
+		(mgmtdev->max_write_request != 1024)) {
+		dev_err(&pdev->dev, "invalid write request %d\n",
+			mgmtdev->max_write_request);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (device_property_read_u32(&pdev->dev, "max-read-burst-bytes",
+		&mgmtdev->max_read_request)) {
+		dev_err(&pdev->dev, "max-read-burst-bytes missing\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if ((mgmtdev->max_read_request != 128) &&
+		(mgmtdev->max_read_request != 256) &&
+		(mgmtdev->max_read_request != 512) &&
+		(mgmtdev->max_read_request != 1024)) {
+		dev_err(&pdev->dev, "invalid read request %d\n",
+			mgmtdev->max_read_request);
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (device_property_read_u32(&pdev->dev, "max-write-transactions",
+		&mgmtdev->max_wr_xactions)) {
+		dev_err(&pdev->dev, "max-write-transactions missing\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (device_property_read_u32(&pdev->dev, "max-read-transactions",
+		&mgmtdev->max_rd_xactions)) {
+		dev_err(&pdev->dev, "max-read-transactions missing\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	mgmtdev->priority = devm_kcalloc(&pdev->dev,
+		mgmtdev->dma_channels, sizeof(*mgmtdev->priority), GFP_KERNEL);
+	if (!mgmtdev->priority) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	mgmtdev->weight = devm_kcalloc(&pdev->dev,
+		mgmtdev->dma_channels, sizeof(*mgmtdev->weight), GFP_KERNEL);
+	if (!mgmtdev->weight) {
+		rc = -ENOMEM;
+		goto out;
+	}
+
+	if (device_property_read_u32_array(&pdev->dev, "channel-priority",
+				mgmtdev->priority, mgmtdev->dma_channels)) {
+		dev_err(&pdev->dev, "channel-priority missing\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	if (device_property_read_u32_array(&pdev->dev, "channel-weight",
+				mgmtdev->weight, mgmtdev->dma_channels)) {
+		dev_err(&pdev->dev, "channel-weight missing\n");
+		rc = -EINVAL;
+		goto out;
+	}
+
+	for (i = 0; i < mgmtdev->dma_channels; i++) {
+		if (mgmtdev->weight[i] > 15) {
+			dev_err(&pdev->dev,
+				"max value of weight can be 15.\n");
+			rc = -EINVAL;
+			goto out;
+		}
+
+		/* weight needs to be at least one */
+		if (mgmtdev->weight[i] == 0)
+			mgmtdev->weight[i] = 1;
+	}
+
+	rc = qcom_hidma_mgmt_setup(mgmtdev);
+	if (rc) {
+		dev_err(&pdev->dev, "setup failed\n");
+		goto out;
+	}
+
+	rc = qcom_hidma_mgmt_debug_init(mgmtdev);
+	if (rc) {
+		dev_err(&pdev->dev, "debugfs init failed\n");
+		goto out;
+	}
+
+	dev_info(&pdev->dev,
+		"HI-DMA engine management driver registration complete\n");
+	platform_set_drvdata(pdev, mgmtdev);
+	pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
+	pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
+	return 0;
+out:
+	pm_runtime_disable(&pdev->dev);
+	pm_runtime_put_sync_suspend(&pdev->dev);
+	return rc;
+}
+
+static int qcom_hidma_mgmt_remove(struct platform_device *pdev)
+{
+	struct qcom_hidma_mgmt_dev *mgmtdev = platform_get_drvdata(pdev);
+
+	pm_runtime_get_sync(&mgmtdev->pdev->dev);
+	qcom_hidma_mgmt_debug_uninit(mgmtdev);
+	pm_runtime_put_sync_suspend(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+
+	dev_info(&pdev->dev, "HI-DMA engine management driver removed\n");
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_ACPI)
+static const struct acpi_device_id qcom_hidma_mgmt_acpi_ids[] = {
+	{"QCOM8060"},
+	{},
+};
+#endif
+
+static const struct of_device_id qcom_hidma_mgmt_match[] = {
+	{ .compatible = "qcom,hidma-mgmt-1.0", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, qcom_hidma_mgmt_match);
+
+static struct platform_driver qcom_hidma_mgmt_driver = {
+	.probe = qcom_hidma_mgmt_probe,
+	.remove = qcom_hidma_mgmt_remove,
+	.driver = {
+		.name = "hidma-mgmt",
+		.of_match_table = qcom_hidma_mgmt_match,
+		.acpi_match_table = ACPI_PTR(qcom_hidma_mgmt_acpi_ids),
+	},
+};
+module_platform_driver(qcom_hidma_mgmt_driver);
+MODULE_LICENSE("GPL v2");
-- 
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project


^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
       [not found] <1446444460-21600-1-git-send-email-okaya@codeaurora.org>
  2015-11-02  6:07 ` [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver Sinan Kaya
@ 2015-11-02  6:07 ` Sinan Kaya
  2015-11-03  4:15   ` Vinod Koul
  2015-11-02  6:07 ` [PATCH V2 3/3] dma: add Qualcomm Technologies HIDMA channel driver Sinan Kaya
  2 siblings, 1 reply; 41+ messages in thread
From: Sinan Kaya @ 2015-11-02  6:07 UTC (permalink / raw)
  To: dmaengine, timur, cov, jcm
  Cc: Sinan Kaya, Vinod Koul, Dan Williams, linux-kernel

This patch adds supporting utility functions
for selftest. The intention is to share the self
test code between different drivers.

Supported test cases include:
1. dma_map_single
2. streaming DMA
3. coherent DMA
4. scatter-gather DMA

Signed-off-by: Sinan Kaya <okaya@codeaurora.org>
---
 drivers/dma/dmaengine.h   |   2 +
 drivers/dma/dmaselftest.c | 669 ++++++++++++++++++++++++++++++++++++++++++++++
 2 files changed, 671 insertions(+)
 create mode 100644 drivers/dma/dmaselftest.c

diff --git a/drivers/dma/dmaengine.h b/drivers/dma/dmaengine.h
index 17f983a..05b5a84 100644
--- a/drivers/dma/dmaengine.h
+++ b/drivers/dma/dmaengine.h
@@ -86,4 +86,6 @@ static inline void dma_set_residue(struct dma_tx_state *state, u32 residue)
 		state->residue = residue;
 }
 
+int dma_selftest_memcpy(struct dma_device *dmadev);
+
 #endif
diff --git a/drivers/dma/dmaselftest.c b/drivers/dma/dmaselftest.c
new file mode 100644
index 0000000..324f7c4
--- /dev/null
+++ b/drivers/dma/dmaselftest.c
@@ -0,0 +1,669 @@
+/*
+ * DMA self test code borrowed from Qualcomm Technologies HIDMA driver
+ *
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <linux/list.h>
+#include <linux/atomic.h>
+#include <linux/wait.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+
+struct test_result {
+	atomic_t counter;
+	wait_queue_head_t wq;
+	struct dma_device *dmadev;
+};
+
+static void dma_selftest_complete(void *arg)
+{
+	struct test_result *result = arg;
+	struct dma_device *dmadev = result->dmadev;
+
+	atomic_inc(&result->counter);
+	wake_up(&result->wq);
+	dev_dbg(dmadev->dev, "self test transfer complete :%d\n",
+		atomic_read(&result->counter));
+}
+
+/*
+ * Perform a transaction to verify the HW works.
+ */
+static int dma_selftest_sg(struct dma_device *dmadev,
+			struct dma_chan *dma_chanptr, u64 size,
+			unsigned long flags)
+{
+	dma_addr_t src_dma, dest_dma, dest_dma_it;
+	u8 *dest_buf;
+	u32 i, j = 0;
+	dma_cookie_t cookie;
+	struct dma_async_tx_descriptor *tx;
+	int err = 0;
+	int ret;
+	struct sg_table sg_table;
+	struct scatterlist	*sg;
+	int nents = 10, count;
+	bool free_channel = 1;
+	u8 *src_buf;
+	int map_count;
+	struct test_result result;
+
+	init_waitqueue_head(&result.wq);
+	atomic_set(&result.counter, 0);
+	result.dmadev = dmadev;
+
+	if (!dma_chanptr)
+		return -ENOMEM;
+
+	if (dmadev->device_alloc_chan_resources(dma_chanptr) < 1)
+		return -ENODEV;
+
+	if (!dma_chanptr->device || !dmadev->dev) {
+		dmadev->device_free_chan_resources(dma_chanptr);
+		return -ENODEV;
+	}
+
+	ret = sg_alloc_table(&sg_table, nents, GFP_KERNEL);
+	if (ret) {
+		err = ret;
+		goto sg_table_alloc_failed;
+	}
+
+	for_each_sg(sg_table.sgl, sg, nents, i) {
+		u64 alloc_sz;
+		void *cpu_addr;
+
+		alloc_sz = round_up(size, nents);
+		do_div(alloc_sz, nents);
+		cpu_addr = kmalloc(alloc_sz, GFP_KERNEL);
+
+		if (!cpu_addr) {
+			err = -ENOMEM;
+			goto sg_buf_alloc_failed;
+		}
+
+		dev_dbg(dmadev->dev, "set sg buf[%d] :%p\n", i, cpu_addr);
+		sg_set_buf(sg, cpu_addr, alloc_sz);
+	}
+
+	dest_buf = kmalloc(round_up(size, nents), GFP_KERNEL);
+	if (!dest_buf) {
+		err = -ENOMEM;
+		goto dst_alloc_failed;
+	}
+	dev_dbg(dmadev->dev, "dest:%p\n", dest_buf);
+
+	/* Fill in src buffer */
+	count = 0;
+	for_each_sg(sg_table.sgl, sg, nents, i) {
+		src_buf = sg_virt(sg);
+		dev_dbg(dmadev->dev,
+			"set src[%d, %d, %p] = %d\n", i, j, src_buf, count);
+
+		for (j = 0; j < sg_dma_len(sg); j++)
+			src_buf[j] = count++;
+	}
+
+	/* dma_map_sg cleans and invalidates the cache in arm64 when
+	 * DMA_TO_DEVICE is selected for src. That's why, we need to do
+	 * the mapping after the data is copied.
+	 */
+	map_count = dma_map_sg(dmadev->dev, sg_table.sgl, nents,
+				DMA_TO_DEVICE);
+	if (!map_count) {
+		err =  -EINVAL;
+		goto src_map_failed;
+	}
+
+	dest_dma = dma_map_single(dmadev->dev, dest_buf,
+				size, DMA_FROM_DEVICE);
+
+	err = dma_mapping_error(dmadev->dev, dest_dma);
+	if (err)
+		goto dest_map_failed;
+
+	/* check scatter gather list contents */
+	for_each_sg(sg_table.sgl, sg, map_count, i)
+		dev_dbg(dmadev->dev,
+			"[%d/%d] src va=%p, iova = %pa len:%d\n",
+			i, map_count, sg_virt(sg), &sg_dma_address(sg),
+			sg_dma_len(sg));
+
+	dest_dma_it = dest_dma;
+	for_each_sg(sg_table.sgl, sg, map_count, i) {
+		src_buf = sg_virt(sg);
+		src_dma = sg_dma_address(sg);
+		dev_dbg(dmadev->dev, "src_dma: %pad dest_dma:%pad\n",
+			&src_dma, &dest_dma_it);
+
+		tx = dmadev->device_prep_dma_memcpy(dma_chanptr, dest_dma_it,
+				src_dma, sg_dma_len(sg), flags);
+		if (!tx) {
+			dev_err(dmadev->dev,
+				"Self-test sg failed, disabling\n");
+			err = -ENODEV;
+			goto prep_memcpy_failed;
+		}
+
+		tx->callback_param = &result;
+		tx->callback = dma_selftest_complete;
+		cookie = tx->tx_submit(tx);
+		dest_dma_it += sg_dma_len(sg);
+	}
+
+	dmadev->device_issue_pending(dma_chanptr);
+
+	/*
+	 * It is assumed that the hardware can move the data within 1s
+	 * and signal the OS of the completion
+	 */
+	ret = wait_event_timeout(result.wq,
+		atomic_read(&result.counter) == (map_count),
+				msecs_to_jiffies(10000));
+
+	if (ret <= 0) {
+		dev_err(dmadev->dev,
+			"Self-test sg copy timed out, disabling\n");
+		err = -ENODEV;
+		goto tx_status;
+	}
+	dev_dbg(dmadev->dev,
+		"Self-test complete signal received\n");
+
+	if (dmadev->device_tx_status(dma_chanptr, cookie, NULL) !=
+				DMA_COMPLETE) {
+		dev_err(dmadev->dev,
+			"Self-test sg status not complete, disabling\n");
+		err = -ENODEV;
+		goto tx_status;
+	}
+
+	dma_sync_single_for_cpu(dmadev->dev, dest_dma, size,
+				DMA_FROM_DEVICE);
+
+	count = 0;
+	for_each_sg(sg_table.sgl, sg, map_count, i) {
+		src_buf = sg_virt(sg);
+		if (memcmp(src_buf, &dest_buf[count], sg_dma_len(sg)) == 0) {
+			count += sg_dma_len(sg);
+			continue;
+		}
+
+		for (j = 0; j < sg_dma_len(sg); j++) {
+			if (src_buf[j] != dest_buf[count]) {
+				dev_dbg(dmadev->dev,
+				"[%d, %d] (%p) src :%x dest (%p):%x cnt:%d\n",
+					i, j, &src_buf[j], src_buf[j],
+					&dest_buf[count], dest_buf[count],
+					count);
+				dev_err(dmadev->dev,
+				 "Self-test copy failed compare, disabling\n");
+				err = -EFAULT;
+				return err;
+				goto compare_failed;
+			}
+			count++;
+		}
+	}
+
+	/*
+	 * do not release the channel
+	 * we want to consume all the channels on self test
+	 */
+	free_channel = 0;
+
+compare_failed:
+tx_status:
+prep_memcpy_failed:
+	dma_unmap_single(dmadev->dev, dest_dma, size,
+			 DMA_FROM_DEVICE);
+dest_map_failed:
+	dma_unmap_sg(dmadev->dev, sg_table.sgl, nents,
+			DMA_TO_DEVICE);
+
+src_map_failed:
+	kfree(dest_buf);
+
+dst_alloc_failed:
+sg_buf_alloc_failed:
+	for_each_sg(sg_table.sgl, sg, nents, i) {
+		if (sg_virt(sg))
+			kfree(sg_virt(sg));
+	}
+	sg_free_table(&sg_table);
+sg_table_alloc_failed:
+	if (free_channel)
+		dmadev->device_free_chan_resources(dma_chanptr);
+
+	return err;
+}
+
+/*
+ * Perform a streaming transaction to verify the HW works.
+ */
+static int dma_selftest_streaming(struct dma_device *dmadev,
+			struct dma_chan *dma_chanptr, u64 size,
+			unsigned long flags)
+{
+	dma_addr_t src_dma, dest_dma;
+	u8 *dest_buf, *src_buf;
+	u32 i;
+	dma_cookie_t cookie;
+	struct dma_async_tx_descriptor *tx;
+	int err = 0;
+	int ret;
+	bool free_channel = 1;
+	struct test_result result;
+
+	init_waitqueue_head(&result.wq);
+	atomic_set(&result.counter, 0);
+	result.dmadev = dmadev;
+
+	if (!dma_chanptr)
+		return -ENOMEM;
+
+	if (dmadev->device_alloc_chan_resources(dma_chanptr) < 1)
+		return -ENODEV;
+
+	if (!dma_chanptr->device || !dmadev->dev) {
+		dmadev->device_free_chan_resources(dma_chanptr);
+		return -ENODEV;
+	}
+
+	src_buf = kmalloc(size, GFP_KERNEL);
+	if (!src_buf) {
+		err = -ENOMEM;
+		goto src_alloc_failed;
+	}
+
+	dest_buf = kmalloc(size, GFP_KERNEL);
+	if (!dest_buf) {
+		err = -ENOMEM;
+		goto dst_alloc_failed;
+	}
+
+	dev_dbg(dmadev->dev, "src: %p dest:%p\n", src_buf, dest_buf);
+
+	/* Fill in src buffer */
+	for (i = 0; i < size; i++)
+		src_buf[i] = (u8)i;
+
+	/* dma_map_single cleans and invalidates the cache in arm64 when
+	 * DMA_TO_DEVICE is selected for src. That's why, we need to do
+	 * the mapping after the data is copied.
+	 */
+	src_dma = dma_map_single(dmadev->dev, src_buf,
+				 size, DMA_TO_DEVICE);
+
+	err = dma_mapping_error(dmadev->dev, src_dma);
+	if (err)
+		goto src_map_failed;
+
+	dest_dma = dma_map_single(dmadev->dev, dest_buf,
+				size, DMA_FROM_DEVICE);
+
+	err = dma_mapping_error(dmadev->dev, dest_dma);
+	if (err)
+		goto dest_map_failed;
+	dev_dbg(dmadev->dev, "src_dma: %pad dest_dma:%pad\n", &src_dma,
+		&dest_dma);
+	tx = dmadev->device_prep_dma_memcpy(dma_chanptr, dest_dma, src_dma,
+					size, flags);
+	if (!tx) {
+		dev_err(dmadev->dev,
+			"Self-test streaming failed, disabling\n");
+		err = -ENODEV;
+		goto prep_memcpy_failed;
+	}
+
+	tx->callback_param = &result;
+	tx->callback = dma_selftest_complete;
+	cookie = tx->tx_submit(tx);
+	dmadev->device_issue_pending(dma_chanptr);
+
+	/*
+	 * It is assumed that the hardware can move the data within 1s
+	 * and signal the OS of the completion
+	 */
+	ret = wait_event_timeout(result.wq,
+				atomic_read(&result.counter) == 1,
+				msecs_to_jiffies(10000));
+
+	if (ret <= 0) {
+		dev_err(dmadev->dev,
+			"Self-test copy timed out, disabling\n");
+		err = -ENODEV;
+		goto tx_status;
+	}
+	dev_dbg(dmadev->dev, "Self-test complete signal received\n");
+
+	if (dmadev->device_tx_status(dma_chanptr, cookie, NULL) !=
+				DMA_COMPLETE) {
+		dev_err(dmadev->dev,
+			"Self-test copy timed out, disabling\n");
+		err = -ENODEV;
+		goto tx_status;
+	}
+
+	dma_sync_single_for_cpu(dmadev->dev, dest_dma, size,
+				DMA_FROM_DEVICE);
+
+	if (memcmp(src_buf, dest_buf, size)) {
+		for (i = 0; i < size/4; i++) {
+			if (((u32 *)src_buf)[i] != ((u32 *)(dest_buf))[i]) {
+				dev_dbg(dmadev->dev,
+					"[%d] src data:%x dest data:%x\n",
+					i, ((u32 *)src_buf)[i],
+					((u32 *)(dest_buf))[i]);
+				break;
+			}
+		}
+		dev_err(dmadev->dev,
+			"Self-test copy failed compare, disabling\n");
+		err = -EFAULT;
+		goto compare_failed;
+	}
+
+	/*
+	 * do not release the channel
+	 * we want to consume all the channels on self test
+	 */
+	free_channel = 0;
+
+compare_failed:
+tx_status:
+prep_memcpy_failed:
+	dma_unmap_single(dmadev->dev, dest_dma, size,
+			 DMA_FROM_DEVICE);
+dest_map_failed:
+	dma_unmap_single(dmadev->dev, src_dma, size,
+			DMA_TO_DEVICE);
+
+src_map_failed:
+	kfree(dest_buf);
+
+dst_alloc_failed:
+	kfree(src_buf);
+
+src_alloc_failed:
+	if (free_channel)
+		dmadev->device_free_chan_resources(dma_chanptr);
+
+	return err;
+}
+
+/*
+ * Perform a coherent transaction to verify the HW works.
+ */
+static int dma_selftest_one_coherent(struct dma_device *dmadev,
+			struct dma_chan *dma_chanptr, u64 size,
+			unsigned long flags)
+{
+	dma_addr_t src_dma, dest_dma;
+	u8 *dest_buf, *src_buf;
+	u32 i;
+	dma_cookie_t cookie;
+	struct dma_async_tx_descriptor *tx;
+	int err = 0;
+	int ret;
+	bool free_channel = true;
+	struct test_result result;
+
+	init_waitqueue_head(&result.wq);
+	atomic_set(&result.counter, 0);
+	result.dmadev = dmadev;
+
+	if (!dma_chanptr)
+		return -ENOMEM;
+
+	if (dmadev->device_alloc_chan_resources(dma_chanptr) < 1)
+		return -ENODEV;
+
+	if (!dma_chanptr->device || !dmadev->dev) {
+		dmadev->device_free_chan_resources(dma_chanptr);
+		return -ENODEV;
+	}
+
+	src_buf = dma_alloc_coherent(dmadev->dev, size,
+				&src_dma, GFP_KERNEL);
+	if (!src_buf) {
+		err = -ENOMEM;
+		goto src_alloc_failed;
+	}
+
+	dest_buf = dma_alloc_coherent(dmadev->dev, size,
+				&dest_dma, GFP_KERNEL);
+	if (!dest_buf) {
+		err = -ENOMEM;
+		goto dst_alloc_failed;
+	}
+
+	dev_dbg(dmadev->dev, "src: %p dest:%p\n", src_buf, dest_buf);
+
+	/* Fill in src buffer */
+	for (i = 0; i < size; i++)
+		src_buf[i] = (u8)i;
+
+	dev_dbg(dmadev->dev, "src_dma: %pad dest_dma:%pad\n", &src_dma,
+		&dest_dma);
+	tx = dmadev->device_prep_dma_memcpy(dma_chanptr, dest_dma, src_dma,
+					size,
+					flags);
+	if (!tx) {
+		dev_err(dmadev->dev,
+			"Self-test coherent failed, disabling\n");
+		err = -ENODEV;
+		goto prep_memcpy_failed;
+	}
+
+	tx->callback_param = &result;
+	tx->callback = dma_selftest_complete;
+	cookie = tx->tx_submit(tx);
+	dmadev->device_issue_pending(dma_chanptr);
+
+	/*
+	 * It is assumed that the hardware can move the data within 1s
+	 * and signal the OS of the completion
+	 */
+	ret = wait_event_timeout(result.wq,
+				atomic_read(&result.counter) == 1,
+				msecs_to_jiffies(10000));
+
+	if (ret <= 0) {
+		dev_err(dmadev->dev,
+			"Self-test copy timed out, disabling\n");
+		err = -ENODEV;
+		goto tx_status;
+	}
+	dev_dbg(dmadev->dev, "Self-test complete signal received\n");
+
+	if (dmadev->device_tx_status(dma_chanptr, cookie, NULL) !=
+				DMA_COMPLETE) {
+		dev_err(dmadev->dev,
+			"Self-test copy timed out, disabling\n");
+		err = -ENODEV;
+		goto tx_status;
+	}
+
+	if (memcmp(src_buf, dest_buf, size)) {
+		for (i = 0; i < size/4; i++) {
+			if (((u32 *)src_buf)[i] != ((u32 *)(dest_buf))[i]) {
+				dev_dbg(dmadev->dev,
+					"[%d] src data:%x dest data:%x\n",
+					i, ((u32 *)src_buf)[i],
+					((u32 *)(dest_buf))[i]);
+				break;
+			}
+		}
+		dev_err(dmadev->dev,
+			"Self-test copy failed compare, disabling\n");
+		err = -EFAULT;
+		goto compare_failed;
+	}
+
+	/*
+	 * do not release the channel
+	 * we want to consume all the channels on self test
+	 */
+	free_channel = 0;
+
+compare_failed:
+tx_status:
+prep_memcpy_failed:
+	dma_free_coherent(dmadev->dev, size, dest_buf, dest_dma);
+
+dst_alloc_failed:
+	dma_free_coherent(dmadev->dev, size, src_buf, src_dma);
+
+src_alloc_failed:
+	if (free_channel)
+		dmadev->device_free_chan_resources(dma_chanptr);
+
+	return err;
+}
+
+static int dma_selftest_all(struct dma_device *dmadev,
+				bool req_coherent, bool req_sg)
+{
+	int rc = -ENODEV, i = 0;
+	struct dma_chan **dmach_ptr = NULL;
+	u32 max_channels = 0;
+	u64 sizes[] = {PAGE_SIZE - 1, PAGE_SIZE, PAGE_SIZE + 1, 2801, 13295};
+	int count = 0;
+	u32 j;
+	u64 size;
+	int failed = 0;
+	struct dma_chan *dmach = NULL;
+
+	list_for_each_entry(dmach, &dmadev->channels,
+			device_node) {
+		max_channels++;
+	}
+
+	dmach_ptr = kcalloc(max_channels, sizeof(*dmach_ptr), GFP_KERNEL);
+	if (!dmach_ptr) {
+		rc = -ENOMEM;
+		goto failed_exit;
+	}
+
+	for (j = 0; j < ARRAY_SIZE(sizes); j++) {
+		size = sizes[j];
+		count = 0;
+		dev_dbg(dmadev->dev, "test start for size:%llx\n", size);
+		list_for_each_entry(dmach, &dmadev->channels,
+				device_node) {
+			dmach_ptr[count] = dmach;
+			if (req_coherent)
+				rc = dma_selftest_one_coherent(dmadev,
+					dmach, size,
+					DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+			else if (req_sg)
+				rc = dma_selftest_sg(dmadev,
+					dmach, size,
+					DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+			else
+				rc = dma_selftest_streaming(dmadev,
+					dmach, size,
+					DMA_PREP_INTERRUPT | DMA_CTRL_ACK);
+			if (rc) {
+				failed = 1;
+				break;
+			}
+			dev_dbg(dmadev->dev,
+				"self test passed for ch:%d\n", count);
+			count++;
+		}
+
+		/*
+		 * free the channels where the test passed
+		 * Channel resources are freed for a test that fails.
+		 */
+		for (i = 0; i < count; i++)
+			dmadev->device_free_chan_resources(dmach_ptr[i]);
+
+		if (failed)
+			break;
+	}
+
+failed_exit:
+	kfree(dmach_ptr);
+
+	return rc;
+}
+
+static int dma_selftest_mapsngle(struct device *dev)
+{
+	u32 buf_size = 256;
+	char *src;
+	int ret = -ENOMEM;
+	dma_addr_t dma_src;
+
+	src = kmalloc(buf_size, GFP_KERNEL);
+	if (!src)
+		return -ENOMEM;
+
+	strcpy(src, "hello world");
+
+	dma_src = dma_map_single(dev, src, buf_size, DMA_TO_DEVICE);
+	dev_dbg(dev, "mapsingle: src:%p src_dma:%pad\n", src, &dma_src);
+
+	ret = dma_mapping_error(dev, dma_src);
+	if (ret) {
+		dev_err(dev, "dma_mapping_error with ret:%d\n", ret);
+		ret = -ENOMEM;
+	} else {
+		if (strcmp(src, "hello world") != 0) {
+			dev_err(dev, "memory content mismatch\n");
+			ret = -EINVAL;
+		} else
+			dev_dbg(dev, "mapsingle:dma_map_single works\n");
+
+		dma_unmap_single(dev, dma_src, buf_size, DMA_TO_DEVICE);
+	}
+	kfree(src);
+	return ret;
+}
+
+/*
+ * Self test all DMA channels.
+ */
+int dma_selftest_memcpy(struct dma_device *dmadev)
+{
+	int rc;
+
+	dma_selftest_mapsngle(dmadev->dev);
+
+	/* streaming test */
+	rc = dma_selftest_all(dmadev, false, false);
+	if (rc)
+		return rc;
+	dev_dbg(dmadev->dev, "streaming self test passed\n");
+
+	/* coherent test */
+	rc = dma_selftest_all(dmadev, true, false);
+	if (rc)
+		return rc;
+
+	dev_dbg(dmadev->dev, "coherent self test passed\n");
+
+	/* scatter gather test */
+	rc = dma_selftest_all(dmadev, false, true);
+	if (rc)
+		return rc;
+
+	dev_dbg(dmadev->dev, "scatter gather self test passed\n");
+	return 0;
+}
+EXPORT_SYMBOL_GPL(dma_selftest_memcpy);
-- 
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project


^ permalink raw reply related	[flat|nested] 41+ messages in thread

* [PATCH V2 3/3] dma: add Qualcomm Technologies HIDMA channel driver
       [not found] <1446444460-21600-1-git-send-email-okaya@codeaurora.org>
  2015-11-02  6:07 ` [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver Sinan Kaya
  2015-11-02  6:07 ` [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions Sinan Kaya
@ 2015-11-02  6:07 ` Sinan Kaya
  2015-11-03 10:10   ` Andy Shevchenko
  2 siblings, 1 reply; 41+ messages in thread
From: Sinan Kaya @ 2015-11-02  6:07 UTC (permalink / raw)
  To: dmaengine, timur, cov, jcm
  Cc: Sinan Kaya, Rob Herring, Pawel Moll, Mark Rutland, Ian Campbell,
	Kumar Gala, Vinod Koul, Dan Williams, devicetree, linux-kernel

This patch adds support for hidma engine. The driver
consists of two logical blocks. The DMA engine interface
and the low-level interface. The hardware only supports
memcpy/memset and this driver only support memcpy
interface. HW and driver doesn't support slave interface.

Signed-off-by: Sinan Kaya <okaya@codeaurora.org>
---
 .../devicetree/bindings/dma/qcom_hidma.txt         |  18 +
 drivers/dma/Kconfig                                |  10 +
 drivers/dma/Makefile                               |   4 +
 drivers/dma/qcom_hidma.c                           | 803 +++++++++++++++++
 drivers/dma/qcom_hidma.h                           |  45 +
 drivers/dma/qcom_hidma_ll.c                        | 972 +++++++++++++++++++++
 6 files changed, 1852 insertions(+)
 create mode 100644 Documentation/devicetree/bindings/dma/qcom_hidma.txt
 create mode 100644 drivers/dma/qcom_hidma.c
 create mode 100644 drivers/dma/qcom_hidma.h
 create mode 100644 drivers/dma/qcom_hidma_ll.c

diff --git a/Documentation/devicetree/bindings/dma/qcom_hidma.txt b/Documentation/devicetree/bindings/dma/qcom_hidma.txt
new file mode 100644
index 0000000..c9fb2d44
--- /dev/null
+++ b/Documentation/devicetree/bindings/dma/qcom_hidma.txt
@@ -0,0 +1,18 @@
+Qualcomm Technologies HIDMA Channel driver
+
+Required properties:
+- compatible: must contain "qcom,hidma"
+- reg: Addresses for the transfer and event channel
+- interrupts: Should contain the event interrupt
+- desc-count: Number of asynchronous requests this channel can handle
+- event-channel: The HW event channel completions will be delivered.
+Example:
+
+	hidma_24: dma-controller@0x5c050000 {
+		compatible = "qcom,hidma-1.0";
+		reg = <0 0x5c050000 0x0 0x1000>,
+		      <0 0x5c0b0000 0x0 0x1000>;
+		interrupts = <0 389 0>;
+		desc-count = <10>;
+		event-channel = <4>;
+	};
diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig
index 4c6f0b5..5f0bb68 100644
--- a/drivers/dma/Kconfig
+++ b/drivers/dma/Kconfig
@@ -512,6 +512,16 @@ config QCOM_HIDMA_MGMT
 	  the guest OS would run QCOM_HIDMA channel driver and the
 	  hypervisor would run the QCOM_HIDMA_MGMT management driver.
 
+config QCOM_HIDMA
+	tristate "Qualcomm Technologies HIDMA Channel support"
+	select DMA_ENGINE
+	help
+	  Enable support for the Qualcomm Technologies HIDMA controller.
+	  The HIDMA controller supports optimized buffer copies
+	  (user to kernel, kernel to kernel, etc.).  It only supports
+	  memcpy interface. The core is not intended for general
+	  purpose slave DMA.
+
 config XILINX_VDMA
 	tristate "Xilinx AXI VDMA Engine"
 	depends on (ARCH_ZYNQ || MICROBLAZE)
diff --git a/drivers/dma/Makefile b/drivers/dma/Makefile
index 3d25ffd..54f418e 100644
--- a/drivers/dma/Makefile
+++ b/drivers/dma/Makefile
@@ -53,6 +53,10 @@ obj-$(CONFIG_PL330_DMA) += pl330.o
 obj-$(CONFIG_PPC_BESTCOMM) += bestcomm/
 obj-$(CONFIG_PXA_DMA) += pxa_dma.o
 obj-$(CONFIG_QCOM_BAM_DMA) += qcom_bam_dma.o
+obj-$(CONFIG_QCOM_HIDMA) +=  qcom_hdma.o
+qcom_hdma-objs        := qcom_hidma_ll.o qcom_hidma.o dmaselftest.o
+
+
 obj-$(CONFIG_QCOM_HIDMA_MGMT) += qcom_hidma_mgmt.o
 obj-$(CONFIG_RENESAS_DMA) += sh/
 obj-$(CONFIG_SIRF_DMA) += sirf-dma.o
diff --git a/drivers/dma/qcom_hidma.c b/drivers/dma/qcom_hidma.c
new file mode 100644
index 0000000..740d0e9
--- /dev/null
+++ b/drivers/dma/qcom_hidma.c
@@ -0,0 +1,803 @@
+/*
+ * Qualcomm Technologies HIDMA DMA engine interface
+ *
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+/*
+ * Copyright (C) Freescale Semicondutor, Inc. 2007, 2008.
+ * Copyright (C) Semihalf 2009
+ * Copyright (C) Ilya Yanok, Emcraft Systems 2010
+ * Copyright (C) Alexander Popov, Promcontroller 2014
+ *
+ * Written by Piotr Ziecik <kosmo@semihalf.com>. Hardware description
+ * (defines, structures and comments) was taken from MPC5121 DMA driver
+ * written by Hongjun Chen <hong-jun.chen@freescale.com>.
+ *
+ * Approved as OSADL project by a majority of OSADL members and funded
+ * by OSADL membership fees in 2009;  for details see www.osadl.org.
+ *
+ * This program is free software; you can redistribute it and/or modify it
+ * under the terms of the GNU General Public License as published by the Free
+ * Software Foundation; either version 2 of the License, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful, but WITHOUT
+ * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
+ * more details.
+ *
+ * The full GNU General Public License is included in this distribution in the
+ * file called COPYING.
+ */
+
+/* Linux Foundation elects GPLv2 license only.
+ */
+
+#include <linux/dmaengine.h>
+#include <linux/dma-mapping.h>
+#include <asm/dma.h>
+#include <linux/err.h>
+#include <linux/init.h>
+#include <linux/interrupt.h>
+#include <linux/list.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/spinlock.h>
+#include <linux/of_dma.h>
+#include <linux/property.h>
+#include <linux/delay.h>
+#include <linux/highmem.h>
+#include <linux/io.h>
+#include <linux/sched.h>
+#include <linux/wait.h>
+#include <linux/acpi.h>
+#include <linux/irq.h>
+#include <linux/atomic.h>
+#include <linux/pm_runtime.h>
+#include <asm/div64.h>
+#include "dmaengine.h"
+#include "qcom_hidma.h"
+
+/* Default idle time is 2 seconds. This parameter can
+ * be overridden by changing the following
+ * /sys/bus/platform/devices/QCOM8061:<xy>/power/autosuspend_delay_ms
+ * during kernel boot.
+ */
+#define AUTOSUSPEND_TIMEOUT		2000
+
+struct hidma_lldev;
+
+struct hidma_dev {
+	int				evridx;
+	u32				nr_descriptors;
+
+	struct hidma_lldev		*lldev;
+	void				__iomem *dev_trca;
+	void				__iomem *dev_evca;
+
+	/* used to protect the pending channel list*/
+	spinlock_t			lock;
+	struct dma_device		ddev;
+};
+
+struct hidma_chan {
+	bool				paused;
+	bool				allocated;
+	char				name[16];
+	u32				dma_sig;
+
+	/*
+	 * active descriptor on this channel
+	 * It is used by the DMA complete notification to
+	 * locate the descriptor that initiated the transfer.
+	 */
+	struct hidma_dev		*dmadev;
+
+	struct dma_chan			chan;
+	struct list_head		free;
+	struct list_head		prepared;
+	struct list_head		active;
+	struct list_head		completed;
+
+	/* Lock for this structure */
+	spinlock_t			lock;
+};
+
+struct hidma_desc {
+	struct dma_async_tx_descriptor	desc;
+	/* link list node for this channel*/
+	struct list_head		node;
+	u32				tre_ch;
+};
+
+static inline
+struct hidma_dev *to_hidma_dev(struct dma_device *dmadev)
+{
+	return container_of(dmadev, struct hidma_dev, ddev);
+}
+
+static inline
+struct hidma_dev *to_hidma_dev_from_lldev(struct hidma_lldev **_lldevp)
+{
+	return container_of(_lldevp, struct hidma_dev, lldev);
+}
+
+static inline
+struct hidma_chan *to_hidma_chan(struct dma_chan *dmach)
+{
+	return container_of(dmach, struct hidma_chan, chan);
+}
+
+static inline struct hidma_desc *
+to_hidma_desc(struct dma_async_tx_descriptor *t)
+{
+	return container_of(t, struct hidma_desc, desc);
+}
+
+static void hidma_free(struct hidma_dev *dmadev)
+{
+	dev_dbg(dmadev->ddev.dev, "free dmadev\n");
+	INIT_LIST_HEAD(&dmadev->ddev.channels);
+}
+
+static unsigned int nr_desc_prm;
+module_param(nr_desc_prm, uint, 0644);
+MODULE_PARM_DESC(nr_desc_prm,
+		 "number of descriptors (default: 0)");
+
+#define MAX_HIDMA_CHANNELS	64
+static int event_channel_idx[MAX_HIDMA_CHANNELS] = {
+	[0 ... (MAX_HIDMA_CHANNELS - 1)] = -1};
+static unsigned int num_event_channel_idx;
+module_param_array_named(event_channel_idx, event_channel_idx, int,
+			&num_event_channel_idx, 0644);
+MODULE_PARM_DESC(event_channel_idx,
+		"event channel index array for the notifications");
+static atomic_t channel_ref_count;
+
+/* process completed descriptors */
+static void hidma_process_completed(struct hidma_dev *mdma)
+{
+	dma_cookie_t last_cookie = 0;
+	struct hidma_chan *mchan;
+	struct hidma_desc *mdesc;
+	struct dma_async_tx_descriptor *desc;
+	unsigned long irqflags;
+	LIST_HEAD(list);
+	struct dma_chan *dmach = NULL;
+
+	list_for_each_entry(dmach, &mdma->ddev.channels,
+			device_node) {
+		mchan = to_hidma_chan(dmach);
+
+		/* Get all completed descriptors */
+		spin_lock_irqsave(&mchan->lock, irqflags);
+		if (!list_empty(&mchan->completed))
+			list_splice_tail_init(&mchan->completed, &list);
+		spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+		if (list_empty(&list))
+			continue;
+
+		/* Execute callbacks and run dependencies */
+		list_for_each_entry(mdesc, &list, node) {
+			desc = &mdesc->desc;
+
+			spin_lock_irqsave(&mchan->lock, irqflags);
+			dma_cookie_complete(desc);
+			spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+			if (desc->callback &&
+				(hidma_ll_status(mdma->lldev, mdesc->tre_ch)
+				== DMA_COMPLETE))
+				desc->callback(desc->callback_param);
+
+			last_cookie = desc->cookie;
+			dma_run_dependencies(desc);
+		}
+
+		/* Free descriptors */
+		spin_lock_irqsave(&mchan->lock, irqflags);
+		list_splice_tail_init(&list, &mchan->free);
+		spin_unlock_irqrestore(&mchan->lock, irqflags);
+	}
+}
+
+/*
+ * Execute all queued DMA descriptors.
+ * This function is called either on the first transfer attempt in tx_submit
+ * or from the callback routine when one transfer is finished. It can only be
+ * called from a single location since both of places check active list to be
+ * empty and will immediately fill the active list while lock is held.
+ *
+ * Following requirements must be met while calling hidma_execute():
+ *	a) mchan->lock is locked,
+ *	b) mchan->active list contains multiple entries.
+ *	c) pm protected
+ */
+static int hidma_execute(struct hidma_chan *mchan)
+{
+	struct hidma_dev *mdma = mchan->dmadev;
+	int rc;
+
+	if (!hidma_ll_isenabled(mdma->lldev))
+		return -ENODEV;
+
+	/* Start the transfer */
+	if (!list_empty(&mchan->active))
+		rc = hidma_ll_start(mdma->lldev);
+
+	return 0;
+}
+
+/*
+ * Called once for each submitted descriptor.
+ * PM is locked once for each descriptor that is currently
+ * in execution.
+ */
+static void hidma_callback(void *data)
+{
+	struct hidma_desc *mdesc = data;
+	struct hidma_chan *mchan = to_hidma_chan(mdesc->desc.chan);
+	unsigned long irqflags;
+	struct dma_device *ddev = mchan->chan.device;
+	struct hidma_dev *dmadev = to_hidma_dev(ddev);
+	bool queued = false;
+
+	dev_dbg(dmadev->ddev.dev, "callback: data:0x%p\n", data);
+
+	spin_lock_irqsave(&mchan->lock, irqflags);
+
+	if (mdesc->node.next) {
+		/* Delete from the active list, add to completed list */
+		list_move_tail(&mdesc->node, &mchan->completed);
+		queued = true;
+	}
+	spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+	hidma_process_completed(dmadev);
+
+	if (queued) {
+		pm_runtime_mark_last_busy(dmadev->ddev.dev);
+		pm_runtime_put_autosuspend(dmadev->ddev.dev);
+	}
+}
+
+static int hidma_chan_init(struct hidma_dev *dmadev, u32 dma_sig)
+{
+	struct hidma_chan *mchan;
+	struct dma_device *ddev;
+
+	mchan = devm_kzalloc(dmadev->ddev.dev, sizeof(*mchan), GFP_KERNEL);
+	if (!mchan)
+		return -ENOMEM;
+
+	ddev = &dmadev->ddev;
+	mchan->dma_sig = dma_sig;
+	mchan->dmadev = dmadev;
+	mchan->chan.device = ddev;
+	dma_cookie_init(&mchan->chan);
+
+	INIT_LIST_HEAD(&mchan->free);
+	INIT_LIST_HEAD(&mchan->prepared);
+	INIT_LIST_HEAD(&mchan->active);
+	INIT_LIST_HEAD(&mchan->completed);
+
+	spin_lock_init(&mchan->lock);
+	list_add_tail(&mchan->chan.device_node, &ddev->channels);
+	dmadev->ddev.chancnt++;
+	return 0;
+}
+
+static void hidma_issue_pending(struct dma_chan *dmach)
+{
+}
+
+static enum dma_status hidma_tx_status(struct dma_chan *dmach,
+					dma_cookie_t cookie,
+					struct dma_tx_state *txstate)
+{
+	enum dma_status ret;
+	unsigned long irqflags;
+	struct hidma_chan *mchan = to_hidma_chan(dmach);
+
+	spin_lock_irqsave(&mchan->lock, irqflags);
+	if (mchan->paused)
+		ret = DMA_PAUSED;
+	else
+		ret = dma_cookie_status(dmach, cookie, txstate);
+	spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+	return ret;
+}
+
+/*
+ * Submit descriptor to hardware.
+ * Lock the PM for each descriptor we are sending.
+ */
+static dma_cookie_t hidma_tx_submit(struct dma_async_tx_descriptor *txd)
+{
+	struct hidma_chan *mchan = to_hidma_chan(txd->chan);
+	struct hidma_dev *dmadev = mchan->dmadev;
+	struct hidma_desc *mdesc;
+	unsigned long irqflags;
+	dma_cookie_t cookie;
+
+	if (!hidma_ll_isenabled(dmadev->lldev))
+		return -ENODEV;
+
+	pm_runtime_get_sync(dmadev->ddev.dev);
+	mdesc = container_of(txd, struct hidma_desc, desc);
+	spin_lock_irqsave(&mchan->lock, irqflags);
+
+	/* Move descriptor to active */
+	list_move_tail(&mdesc->node, &mchan->active);
+
+	/* Update cookie */
+	cookie = dma_cookie_assign(txd);
+
+	hidma_ll_queue_request(dmadev->lldev, mdesc->tre_ch);
+	hidma_execute(mchan);
+
+	spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+	return cookie;
+}
+
+static int hidma_alloc_chan_resources(struct dma_chan *dmach)
+{
+	struct hidma_chan *mchan = to_hidma_chan(dmach);
+	struct hidma_dev *dmadev = mchan->dmadev;
+	int rc = 0;
+	struct hidma_desc *mdesc, *tmp;
+	unsigned long irqflags;
+	LIST_HEAD(descs);
+	u32 i;
+
+	if (mchan->allocated)
+		return 0;
+
+	/* Alloc descriptors for this channel */
+	for (i = 0; i < dmadev->nr_descriptors; i++) {
+		mdesc = kzalloc(sizeof(struct hidma_desc), GFP_KERNEL);
+		if (!mdesc) {
+			dev_err(dmadev->ddev.dev, "Memory allocation error. ");
+			rc = -ENOMEM;
+			break;
+		}
+		dma_async_tx_descriptor_init(&mdesc->desc, dmach);
+		mdesc->desc.flags = DMA_CTRL_ACK;
+		mdesc->desc.tx_submit = hidma_tx_submit;
+
+		rc = hidma_ll_request(dmadev->lldev,
+				mchan->dma_sig, "DMA engine", hidma_callback,
+				mdesc, &mdesc->tre_ch);
+		if (rc != 1) {
+			dev_err(dmach->device->dev,
+				"channel alloc failed at %u\n", i);
+			kfree(mdesc);
+			break;
+		}
+		list_add_tail(&mdesc->node, &descs);
+	}
+
+	if (rc != 1) {
+		/* return the allocated descriptors */
+		list_for_each_entry_safe(mdesc, tmp, &descs, node) {
+			hidma_ll_free(dmadev->lldev, mdesc->tre_ch);
+			kfree(mdesc);
+		}
+		return rc;
+	}
+
+	spin_lock_irqsave(&mchan->lock, irqflags);
+	list_splice_tail_init(&descs, &mchan->free);
+	mchan->allocated = true;
+	spin_unlock_irqrestore(&mchan->lock, irqflags);
+	dev_dbg(dmadev->ddev.dev,
+		"allocated channel for %u\n", mchan->dma_sig);
+	return rc;
+}
+
+static void hidma_free_chan_resources(struct dma_chan *dmach)
+{
+	struct hidma_chan *mchan = to_hidma_chan(dmach);
+	struct hidma_dev *mdma = mchan->dmadev;
+	struct hidma_desc *mdesc, *tmp;
+	unsigned long irqflags;
+	LIST_HEAD(descs);
+
+	if (!list_empty(&mchan->prepared) ||
+		!list_empty(&mchan->active) ||
+		!list_empty(&mchan->completed)) {
+		/* We have unfinished requests waiting.
+		 * Terminate the request from the hardware.
+		 */
+		hidma_cleanup_pending_tre(mdma->lldev, 0x77, 0x77);
+
+		/* Give enough time for completions to be called. */
+		msleep(100);
+	}
+
+	spin_lock_irqsave(&mchan->lock, irqflags);
+	/* Channel must be idle */
+	WARN_ON(!list_empty(&mchan->prepared));
+	WARN_ON(!list_empty(&mchan->active));
+	WARN_ON(!list_empty(&mchan->completed));
+
+	/* Move data */
+	list_splice_tail_init(&mchan->free, &descs);
+
+	/* Free descriptors */
+	list_for_each_entry_safe(mdesc, tmp, &descs, node) {
+		hidma_ll_free(mdma->lldev, mdesc->tre_ch);
+		list_del(&mdesc->node);
+		kfree(mdesc);
+	}
+
+	mchan->allocated = 0;
+	spin_unlock_irqrestore(&mchan->lock, irqflags);
+	dev_dbg(mdma->ddev.dev, "freed channel for %u\n", mchan->dma_sig);
+}
+
+
+static struct dma_async_tx_descriptor *
+hidma_prep_dma_memcpy(struct dma_chan *dmach, dma_addr_t dma_dest,
+			dma_addr_t dma_src, size_t len, unsigned long flags)
+{
+	struct hidma_chan *mchan = to_hidma_chan(dmach);
+	struct hidma_desc *mdesc = NULL;
+	struct hidma_dev *mdma = mchan->dmadev;
+	unsigned long irqflags;
+
+	dev_dbg(mdma->ddev.dev,
+		"memcpy: chan:%p dest:%pad src:%pad len:%zu\n", mchan,
+		&dma_dest, &dma_src, len);
+
+	/* Get free descriptor */
+	spin_lock_irqsave(&mchan->lock, irqflags);
+	if (!list_empty(&mchan->free)) {
+		mdesc = list_first_entry(&mchan->free, struct hidma_desc,
+					node);
+		list_del(&mdesc->node);
+	}
+	spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+	if (!mdesc)
+		return NULL;
+
+	hidma_ll_set_transfer_params(mdma->lldev, mdesc->tre_ch,
+			dma_src, dma_dest, len, flags);
+
+	/* Place descriptor in prepared list */
+	spin_lock_irqsave(&mchan->lock, irqflags);
+	list_add_tail(&mdesc->node, &mchan->prepared);
+	spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+	return &mdesc->desc;
+}
+
+static int hidma_terminate_all(struct dma_chan *chan)
+{
+	struct hidma_dev *dmadev;
+	LIST_HEAD(head);
+	unsigned long irqflags;
+	LIST_HEAD(list);
+	struct hidma_desc *tmp, *mdesc = NULL;
+	int rc = 0;
+	struct hidma_chan *mchan;
+
+	mchan = to_hidma_chan(chan);
+	dmadev = to_hidma_dev(mchan->chan.device);
+	dev_dbg(dmadev->ddev.dev, "terminateall: chan:0x%p\n", mchan);
+
+	pm_runtime_get_sync(dmadev->ddev.dev);
+	/* give completed requests a chance to finish */
+	hidma_process_completed(dmadev);
+
+	spin_lock_irqsave(&mchan->lock, irqflags);
+	list_splice_init(&mchan->active, &list);
+	list_splice_init(&mchan->prepared, &list);
+	list_splice_init(&mchan->completed, &list);
+	spin_unlock_irqrestore(&mchan->lock, irqflags);
+
+	/* this suspends the existing transfer */
+	rc = hidma_ll_pause(dmadev->lldev);
+	if (rc) {
+		dev_err(dmadev->ddev.dev, "channel did not pause\n");
+		goto out;
+	}
+
+	/* return all user requests */
+	list_for_each_entry_safe(mdesc, tmp, &list, node) {
+		struct dma_async_tx_descriptor	*txd = &mdesc->desc;
+		dma_async_tx_callback callback = mdesc->desc.callback;
+		void *param = mdesc->desc.callback_param;
+		enum dma_status status;
+
+		dma_descriptor_unmap(txd);
+
+		status = hidma_ll_status(dmadev->lldev, mdesc->tre_ch);
+		/*
+		 * The API requires that no submissions are done from a
+		 * callback, so we don't need to drop the lock here
+		 */
+		if (callback && (status == DMA_COMPLETE))
+			callback(param);
+
+		dma_run_dependencies(txd);
+
+		/* move myself to free_list */
+		list_move(&mdesc->node, &mchan->free);
+	}
+
+	/* reinitialize the hardware */
+	rc = hidma_ll_setup(dmadev->lldev);
+
+out:
+	pm_runtime_mark_last_busy(dmadev->ddev.dev);
+	pm_runtime_put_autosuspend(dmadev->ddev.dev);
+	return rc;
+}
+
+static int hidma_pause(struct dma_chan *chan)
+{
+	struct hidma_chan *mchan;
+	struct hidma_dev *dmadev;
+
+	mchan = to_hidma_chan(chan);
+	dmadev = to_hidma_dev(mchan->chan.device);
+	dev_dbg(dmadev->ddev.dev, "pause: chan:0x%p\n", mchan);
+
+	pm_runtime_get_sync(dmadev->ddev.dev);
+	if (!mchan->paused) {
+		if (hidma_ll_pause(dmadev->lldev))
+			dev_warn(dmadev->ddev.dev, "channel did not stop\n");
+		mchan->paused = true;
+	}
+	pm_runtime_mark_last_busy(dmadev->ddev.dev);
+	pm_runtime_put_autosuspend(dmadev->ddev.dev);
+	return 0;
+}
+
+static int hidma_resume(struct dma_chan *chan)
+{
+	struct hidma_chan *mchan;
+	struct hidma_dev *dmadev;
+	int rc = 0;
+
+	mchan = to_hidma_chan(chan);
+	dmadev = to_hidma_dev(mchan->chan.device);
+	dev_dbg(dmadev->ddev.dev, "resume: chan:0x%p\n", mchan);
+
+	pm_runtime_get_sync(dmadev->ddev.dev);
+	if (mchan->paused) {
+		rc = hidma_ll_resume(dmadev->lldev);
+		if (!rc)
+			mchan->paused = false;
+		else
+			dev_err(dmadev->ddev.dev,
+					"failed to resume the channel");
+	}
+	pm_runtime_mark_last_busy(dmadev->ddev.dev);
+	pm_runtime_put_autosuspend(dmadev->ddev.dev);
+	return rc;
+}
+
+static irqreturn_t hidma_chirq_handler(int chirq, void *arg)
+{
+	struct hidma_lldev **lldev_ptr = arg;
+	irqreturn_t ret;
+	struct hidma_dev *dmadev = to_hidma_dev_from_lldev(lldev_ptr);
+
+	pm_runtime_get_sync(dmadev->ddev.dev);
+	ret = hidma_ll_inthandler(chirq, *lldev_ptr);
+	pm_runtime_mark_last_busy(dmadev->ddev.dev);
+	pm_runtime_put_autosuspend(dmadev->ddev.dev);
+	return ret;
+}
+
+static int hidma_probe(struct platform_device *pdev)
+{
+	struct hidma_dev *dmadev;
+	int rc = 0;
+	struct resource *trca_resource;
+	struct resource *evca_resource;
+	int chirq;
+	int current_channel_index = atomic_read(&channel_ref_count);
+
+	pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT);
+	pm_runtime_use_autosuspend(&pdev->dev);
+	pm_runtime_set_active(&pdev->dev);
+	pm_runtime_enable(&pdev->dev);
+
+	trca_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+	if (!trca_resource) {
+		rc = -ENODEV;
+		goto bailout;
+	}
+
+	evca_resource = platform_get_resource(pdev, IORESOURCE_MEM, 1);
+	if (!evca_resource) {
+		rc = -ENODEV;
+		goto bailout;
+	}
+
+	/* This driver only handles the channel IRQs.
+	 * Common IRQ is handled by the management driver.
+	 */
+	chirq = platform_get_irq(pdev, 0);
+	if (chirq < 0) {
+		rc = -ENODEV;
+		goto bailout;
+	}
+
+	dmadev = devm_kzalloc(&pdev->dev, sizeof(*dmadev), GFP_KERNEL);
+	if (!dmadev) {
+		rc = -ENOMEM;
+		goto bailout;
+	}
+
+	INIT_LIST_HEAD(&dmadev->ddev.channels);
+	spin_lock_init(&dmadev->lock);
+	dmadev->ddev.dev = &pdev->dev;
+	pm_runtime_get_sync(dmadev->ddev.dev);
+
+	dma_cap_set(DMA_MEMCPY, dmadev->ddev.cap_mask);
+	if (WARN_ON(!pdev->dev.dma_mask)) {
+		rc = -ENXIO;
+		goto dmafree;
+	}
+
+	dmadev->dev_evca = devm_ioremap_resource(&pdev->dev,
+						evca_resource);
+	if (IS_ERR(dmadev->dev_evca)) {
+		rc = -ENOMEM;
+		goto dmafree;
+	}
+
+	dmadev->dev_trca = devm_ioremap_resource(&pdev->dev,
+						trca_resource);
+	if (IS_ERR(dmadev->dev_trca)) {
+		rc = -ENOMEM;
+		goto dmafree;
+	}
+	dmadev->ddev.device_prep_dma_memcpy = hidma_prep_dma_memcpy;
+	dmadev->ddev.device_alloc_chan_resources =
+		hidma_alloc_chan_resources;
+	dmadev->ddev.device_free_chan_resources = hidma_free_chan_resources;
+	dmadev->ddev.device_tx_status = hidma_tx_status;
+	dmadev->ddev.device_issue_pending = hidma_issue_pending;
+	dmadev->ddev.device_pause = hidma_pause;
+	dmadev->ddev.device_resume = hidma_resume;
+	dmadev->ddev.device_terminate_all = hidma_terminate_all;
+	dmadev->ddev.copy_align = 8;
+
+	device_property_read_u32(&pdev->dev, "desc-count",
+				&dmadev->nr_descriptors);
+
+	if (!dmadev->nr_descriptors && nr_desc_prm)
+		dmadev->nr_descriptors = nr_desc_prm;
+
+	if (!dmadev->nr_descriptors)
+		goto dmafree;
+
+	if (current_channel_index > MAX_HIDMA_CHANNELS)
+		goto dmafree;
+
+	dmadev->evridx = -1;
+	device_property_read_u32(&pdev->dev, "event-channel", &dmadev->evridx);
+
+	/* kernel command line override for the guest machine */
+	if (event_channel_idx[current_channel_index] != -1)
+		dmadev->evridx = event_channel_idx[current_channel_index];
+
+	if (dmadev->evridx == -1)
+		goto dmafree;
+
+	/* Set DMA mask to 64 bits. */
+	rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
+	if (rc) {
+		dev_warn(&pdev->dev, "unable to set coherent mask to 64");
+		rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
+	}
+	if (rc)
+		goto dmafree;
+
+	dmadev->lldev = hidma_ll_init(dmadev->ddev.dev,
+				dmadev->nr_descriptors, dmadev->dev_trca,
+				dmadev->dev_evca, dmadev->evridx);
+	if (!dmadev->lldev) {
+		rc = -EPROBE_DEFER;
+		goto dmafree;
+	}
+
+	rc = devm_request_irq(&pdev->dev, chirq, hidma_chirq_handler, 0,
+			      "qcom-hidma", &dmadev->lldev);
+	if (rc)
+		goto uninit;
+
+	INIT_LIST_HEAD(&dmadev->ddev.channels);
+	rc = hidma_chan_init(dmadev, 0);
+	if (rc)
+		goto uninit;
+
+	rc = dma_selftest_memcpy(&dmadev->ddev);
+	if (rc)
+		goto uninit;
+
+	rc = dma_async_device_register(&dmadev->ddev);
+	if (rc)
+		goto uninit;
+
+	dev_info(&pdev->dev, "HI-DMA engine driver registration complete\n");
+	platform_set_drvdata(pdev, dmadev);
+	pm_runtime_mark_last_busy(dmadev->ddev.dev);
+	pm_runtime_put_autosuspend(dmadev->ddev.dev);
+	atomic_inc(&channel_ref_count);
+	return 0;
+
+uninit:
+	hidma_ll_uninit(dmadev->lldev);
+dmafree:
+	if (dmadev)
+		hidma_free(dmadev);
+bailout:
+	pm_runtime_disable(&pdev->dev);
+	pm_runtime_put_sync_suspend(&pdev->dev);
+	return rc;
+}
+
+static int hidma_remove(struct platform_device *pdev)
+{
+	struct hidma_dev *dmadev = platform_get_drvdata(pdev);
+
+	dev_dbg(&pdev->dev, "removing\n");
+	pm_runtime_get_sync(dmadev->ddev.dev);
+
+	dma_async_device_unregister(&dmadev->ddev);
+	hidma_ll_uninit(dmadev->lldev);
+	hidma_free(dmadev);
+
+	dev_info(&pdev->dev, "HI-DMA engine removed\n");
+	pm_runtime_put_sync_suspend(&pdev->dev);
+	pm_runtime_disable(&pdev->dev);
+
+	return 0;
+}
+
+#if IS_ENABLED(CONFIG_ACPI)
+static const struct acpi_device_id hidma_acpi_ids[] = {
+	{"QCOM8061"},
+	{},
+};
+#endif
+
+static const struct of_device_id hidma_match[] = {
+	{ .compatible = "qcom,hidma-1.0", },
+	{},
+};
+MODULE_DEVICE_TABLE(of, hidma_match);
+
+static struct platform_driver hidma_driver = {
+	.probe = hidma_probe,
+	.remove = hidma_remove,
+	.driver = {
+		.name = "hidma",
+		.of_match_table = hidma_match,
+		.acpi_match_table = ACPI_PTR(hidma_acpi_ids),
+	},
+};
+module_platform_driver(hidma_driver);
+MODULE_LICENSE("GPL v2");
diff --git a/drivers/dma/qcom_hidma.h b/drivers/dma/qcom_hidma.h
new file mode 100644
index 0000000..d671b39
--- /dev/null
+++ b/drivers/dma/qcom_hidma.h
@@ -0,0 +1,45 @@
+/*
+ * Qualcomm Technologies HIDMA data structures
+ *
+ * Copyright (c) 2014, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#ifndef QCOM_HIDMA_H
+#define QCOM_HIDMA_H
+
+struct hidma_lldev;
+struct hidma_llchan;
+struct seq_file;
+struct hidma_lldev;
+
+int hidma_ll_request(struct hidma_lldev *llhndl, u32 dev_id,
+			const char *dev_name,
+			void (*callback)(void *data), void *data, u32 *tre_ch);
+
+void hidma_ll_free(struct hidma_lldev *llhndl, u32 tre_ch);
+enum dma_status hidma_ll_status(struct hidma_lldev *llhndl, u32 tre_ch);
+bool hidma_ll_isenabled(struct hidma_lldev *llhndl);
+int hidma_ll_queue_request(struct hidma_lldev *llhndl, u32 tre_ch);
+int hidma_ll_start(struct hidma_lldev *llhndl);
+int hidma_ll_pause(struct hidma_lldev *llhndl);
+int hidma_ll_resume(struct hidma_lldev *llhndl);
+void hidma_ll_set_transfer_params(struct hidma_lldev *llhndl, u32 tre_ch,
+	dma_addr_t src, dma_addr_t dest, u32 len, u32 flags);
+int hidma_ll_setup(struct hidma_lldev *lldev);
+struct hidma_lldev *hidma_ll_init(struct device *dev, u32 max_channels,
+			void __iomem *trca, void __iomem *evca,
+			u8 evridx);
+int hidma_ll_uninit(struct hidma_lldev *llhndl);
+irqreturn_t hidma_ll_inthandler(int irq, void *arg);
+void hidma_cleanup_pending_tre(struct hidma_lldev *llhndl, u8 err_info,
+				u8 err_code);
+#endif
diff --git a/drivers/dma/qcom_hidma_ll.c b/drivers/dma/qcom_hidma_ll.c
new file mode 100644
index 0000000..1e8b4aa
--- /dev/null
+++ b/drivers/dma/qcom_hidma_ll.c
@@ -0,0 +1,972 @@
+/*
+ * Qualcomm Technologies HIDMA DMA engine low level code
+ *
+ * Copyright (c) 2015, The Linux Foundation. All rights reserved.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License version 2 and
+ * only version 2 as published by the Free Software Foundation.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+#include <linux/dmaengine.h>
+#include <linux/slab.h>
+#include <linux/interrupt.h>
+#include <linux/mm.h>
+#include <linux/highmem.h>
+#include <linux/dma-mapping.h>
+#include <linux/delay.h>
+#include <linux/atomic.h>
+#include <linux/iopoll.h>
+#include "qcom_hidma.h"
+
+#define TRE_SIZE			32 /* each TRE is 32 bytes  */
+#define EVRE_SIZE			16 /* each EVRE is 16 bytes */
+
+#define TRCA_CTRLSTS_OFFSET		0x0
+#define TRCA_RING_LOW_OFFSET		0x8
+#define TRCA_RING_HIGH_OFFSET		0xC
+#define TRCA_RING_LEN_OFFSET		0x10
+#define TRCA_READ_PTR_OFFSET		0x18
+#define TRCA_WRITE_PTR_OFFSET		0x20
+#define TRCA_DOORBELL_OFFSET		0x400
+
+#define EVCA_CTRLSTS_OFFSET		0x0
+#define EVCA_INTCTRL_OFFSET		0x4
+#define EVCA_RING_LOW_OFFSET		0x8
+#define EVCA_RING_HIGH_OFFSET		0xC
+#define EVCA_RING_LEN_OFFSET		0x10
+#define EVCA_READ_PTR_OFFSET		0x18
+#define EVCA_WRITE_PTR_OFFSET		0x20
+#define EVCA_DOORBELL_OFFSET		0x400
+
+#define EVCA_IRQ_STAT_OFFSET		0x100
+#define EVCA_IRQ_CLR_OFFSET		0x108
+#define EVCA_IRQ_EN_OFFSET		0x110
+
+#define TRE_CFG_IDX			0
+#define TRE_LEN_IDX			1
+#define TRE_SRC_LOW_IDX		2
+#define TRE_SRC_HI_IDX			3
+#define TRE_DEST_LOW_IDX		4
+#define TRE_DEST_HI_IDX		5
+
+#define EVRE_CFG_IDX			0
+#define EVRE_LEN_IDX			1
+#define EVRE_DEST_LOW_IDX		2
+#define EVRE_DEST_HI_IDX		3
+
+#define EVRE_ERRINFO_BIT_POS		24
+#define EVRE_CODE_BIT_POS		28
+
+#define EVRE_ERRINFO_MASK		0xF
+#define EVRE_CODE_MASK			0xF
+
+#define CH_CONTROL_MASK		0xFF
+#define CH_STATE_MASK			0xFF
+#define CH_STATE_BIT_POS		0x8
+
+#define MAKE64(high, low) (((u64)(high) << 32) | (low))
+
+#define IRQ_EV_CH_EOB_IRQ_BIT_POS	0
+#define IRQ_EV_CH_WR_RESP_BIT_POS	1
+#define IRQ_TR_CH_TRE_RD_RSP_ER_BIT_POS 9
+#define IRQ_TR_CH_DATA_RD_ER_BIT_POS	10
+#define IRQ_TR_CH_DATA_WR_ER_BIT_POS	11
+#define IRQ_TR_CH_INVALID_TRE_BIT_POS	14
+
+#define	ENABLE_IRQS (BIT(IRQ_EV_CH_EOB_IRQ_BIT_POS) | \
+		BIT(IRQ_EV_CH_WR_RESP_BIT_POS) | \
+		BIT(IRQ_TR_CH_TRE_RD_RSP_ER_BIT_POS) |	 \
+		BIT(IRQ_TR_CH_DATA_RD_ER_BIT_POS) |		 \
+		BIT(IRQ_TR_CH_DATA_WR_ER_BIT_POS) |		 \
+		BIT(IRQ_TR_CH_INVALID_TRE_BIT_POS))
+
+enum ch_command {
+	CH_DISABLE = 0,
+	CH_ENABLE = 1,
+	CH_SUSPEND = 2,
+	CH_RESET = 9,
+};
+
+enum ch_state {
+	CH_DISABLED = 0,
+	CH_ENABLED = 1,
+	CH_RUNNING = 2,
+	CH_SUSPENDED = 3,
+	CH_STOPPED = 4,
+	CH_ERROR = 5,
+	CH_IN_RESET = 9,
+};
+
+enum tre_type {
+	TRE_MEMCPY = 3,
+	TRE_MEMSET = 4,
+};
+
+enum evre_type {
+	EVRE_DMA_COMPLETE = 0x23,
+	EVRE_IMM_DATA = 0x24,
+};
+
+enum err_code {
+	EVRE_STATUS_COMPLETE = 1,
+	EVRE_STATUS_ERROR = 4,
+};
+
+struct hidma_tx_status {
+	u8 err_info;			/* error record in this transfer    */
+	u8 err_code;			/* completion code		    */
+};
+
+struct hidma_lldev {
+	bool initialized;		/* initialized flag               */
+	u8 trch_state;			/* trch_state of the device	  */
+	u8 evch_state;			/* evch_state of the device	  */
+	u8 evridx;			/* event channel to notify	  */
+	u32 nr_tres;			/* max number of configs          */
+	spinlock_t lock;		/* reentrancy                     */
+	struct hidma_tre *trepool;	/* trepool of user configs */
+	struct device *dev;		/* device			  */
+	void __iomem *trca;		/* Transfer Channel address       */
+	void __iomem *evca;		/* Event Channel address          */
+	struct hidma_tre
+		**pending_tre_list;	/* Pointers to pending TREs	  */
+	struct hidma_tx_status
+		*tx_status_list;	/* Pointers to pending TREs status*/
+	s32 pending_tre_count;		/* Number of TREs pending	  */
+
+	void *tre_ring;		/* TRE ring			  */
+	dma_addr_t tre_ring_handle;	/* TRE ring to be shared with HW  */
+	u32 tre_ring_size;		/* Byte size of the ring	  */
+	u32 tre_processed_off;		/* last processed TRE		   */
+
+	void *evre_ring;		/* EVRE ring			   */
+	dma_addr_t evre_ring_handle;	/* EVRE ring to be shared with HW  */
+	u32 evre_ring_size;		/* Byte size of the ring	  */
+	u32 evre_processed_off;	/* last processed EVRE		   */
+
+	u32 tre_write_offset;           /* TRE write location              */
+};
+
+struct hidma_tre {
+	atomic_t allocated;		/* if this channel is allocated	    */
+	bool queued;			/* flag whether this is pending     */
+	u16 status;			/* status			    */
+	u32 chidx;			/* index of the tre	    */
+	u32 dma_sig;			/* signature of the tre	    */
+	const char *dev_name;		/* name of the device		    */
+	void (*callback)(void *data);	/* requester callback		    */
+	void *data;			/* Data associated with this channel*/
+	struct hidma_lldev *lldev;	/* lldma device pointer		    */
+	u32 tre_local[TRE_SIZE / sizeof(u32) + 1]; /* TRE local copy        */
+	struct tasklet_struct task;	/* task delivering notifications    */
+	u32 tre_index;			/* the offset where this was written*/
+	u32 int_flags;			/* interrupt flags*/
+};
+
+void hidma_ll_free(struct hidma_lldev *lldev, u32 tre_ch)
+{
+	struct hidma_tre *tre;
+
+	if (tre_ch >= lldev->nr_tres) {
+		dev_err(lldev->dev, "invalid TRE number in free:%d", tre_ch);
+		return;
+	}
+
+	tre = &lldev->trepool[tre_ch];
+	if (atomic_read(&tre->allocated) != true) {
+		dev_err(lldev->dev, "trying to free an unused TRE:%d",
+			tre_ch);
+		return;
+	}
+
+	atomic_set(&tre->allocated, 0);
+	dev_dbg(lldev->dev, "free_dma: allocated:%d tre_ch:%d\n",
+		atomic_read(&tre->allocated), tre_ch);
+}
+
+int hidma_ll_request(struct hidma_lldev *lldev, u32 dma_sig,
+			const char *dev_name,
+			void (*callback)(void *data), void *data, u32 *tre_ch)
+{
+	u32 i;
+	struct hidma_tre *tre = NULL;
+	u32 *tre_local;
+
+	if (!tre_ch || !lldev)
+		return -EINVAL;
+
+	/* need to have at least one empty spot in the queue */
+	for (i = 0; i < lldev->nr_tres - 1; i++) {
+		if (atomic_add_unless(&lldev->trepool[i].allocated, 1, 1))
+			break;
+	}
+
+	if (i == (lldev->nr_tres - 1))
+		return -ENOMEM;
+
+	tre = &lldev->trepool[i];
+	tre->dma_sig = dma_sig;
+	tre->dev_name = dev_name;
+	tre->callback = callback;
+	tre->data = data;
+	tre->chidx = i;
+	tre->status = 0;
+	tre->queued = 0;
+	lldev->tx_status_list[i].err_code = 0;
+	tre->lldev = lldev;
+	tre_local = &tre->tre_local[0];
+	tre_local[TRE_CFG_IDX] = TRE_MEMCPY;
+	tre_local[TRE_CFG_IDX] |= ((lldev->evridx & 0xFF) << 8);
+	tre_local[TRE_CFG_IDX] |= BIT(16);	/* set IEOB */
+	*tre_ch = i;
+	if (callback)
+		callback(data);
+	return 1;
+}
+
+/*
+ * Multiple TREs may be queued and waiting in the
+ * pending queue.
+ */
+static void hidma_ll_tre_complete(unsigned long arg)
+{
+	struct hidma_tre *tre = (struct hidma_tre *)arg;
+
+	/* call the user if it has been read by the hardware*/
+	if (tre->callback)
+		tre->callback(tre->data);
+}
+
+/*
+ * Called to handle the interrupt for the channel.
+ * Return a positive number if TRE or EVRE were consumed on this run.
+ * Return a positive number if there are pending TREs or EVREs.
+ * Return 0 if there is nothing to consume or no pending TREs/EVREs found.
+ */
+static int hidma_handle_tre_completion(struct hidma_lldev *lldev)
+{
+	struct hidma_tre *tre;
+	u32 evre_write_off;
+	u32 evre_ring_size = lldev->evre_ring_size;
+	u32 tre_ring_size = lldev->tre_ring_size;
+	u32 num_completed = 0, tre_iterator, evre_iterator;
+	unsigned long flags;
+
+	evre_write_off = readl_relaxed(lldev->evca + EVCA_WRITE_PTR_OFFSET);
+	tre_iterator = lldev->tre_processed_off;
+	evre_iterator = lldev->evre_processed_off;
+
+	if ((evre_write_off > evre_ring_size) ||
+		((evre_write_off % EVRE_SIZE) != 0)) {
+		dev_err(lldev->dev, "HW reports invalid EVRE write offset\n");
+		return 0;
+	}
+
+	/* By the time control reaches here the number of EVREs and TREs
+	 * may not match. Only consume the ones that hardware told us.
+	 */
+	while ((evre_iterator != evre_write_off)) {
+		u32 *current_evre = lldev->evre_ring + evre_iterator;
+		u32 cfg;
+		u8 err_info;
+
+		spin_lock_irqsave(&lldev->lock, flags);
+		tre = lldev->pending_tre_list[tre_iterator / TRE_SIZE];
+		if (!tre) {
+			spin_unlock_irqrestore(&lldev->lock, flags);
+			dev_warn(lldev->dev,
+				"tre_index [%d] and tre out of sync\n",
+				tre_iterator / TRE_SIZE);
+			tre_iterator += TRE_SIZE;
+			if (tre_iterator >= tre_ring_size)
+				tre_iterator -= tre_ring_size;
+			evre_iterator += EVRE_SIZE;
+			if (evre_iterator >= evre_ring_size)
+				evre_iterator -= evre_ring_size;
+
+			continue;
+		}
+		lldev->pending_tre_list[tre->tre_index] = NULL;
+
+		/* Keep track of pending TREs that SW is expecting to receive
+		 * from HW. We got one now. Decrement our counter.
+		 */
+		lldev->pending_tre_count--;
+		if (lldev->pending_tre_count < 0) {
+			dev_warn(lldev->dev,
+				"tre count mismatch on completion");
+			lldev->pending_tre_count = 0;
+		}
+
+		spin_unlock_irqrestore(&lldev->lock, flags);
+
+		cfg = current_evre[EVRE_CFG_IDX];
+		err_info = (cfg >> EVRE_ERRINFO_BIT_POS);
+		err_info = err_info & EVRE_ERRINFO_MASK;
+		lldev->tx_status_list[tre->chidx].err_info = err_info;
+		lldev->tx_status_list[tre->chidx].err_code =
+			(cfg >> EVRE_CODE_BIT_POS) & EVRE_CODE_MASK;
+		tre->queued = 0;
+
+		tasklet_schedule(&tre->task);
+
+		tre_iterator += TRE_SIZE;
+		if (tre_iterator >= tre_ring_size)
+			tre_iterator -= tre_ring_size;
+		evre_iterator += EVRE_SIZE;
+		if (evre_iterator >= evre_ring_size)
+			evre_iterator -= evre_ring_size;
+
+		/* Read the new event descriptor written by the HW.
+		 * As we are processing the delivered events, other events
+		 * get queued to the SW for processing.
+		 */
+		evre_write_off =
+			readl_relaxed(lldev->evca + EVCA_WRITE_PTR_OFFSET);
+		num_completed++;
+	}
+
+	if (num_completed) {
+		u32 evre_read_off = (lldev->evre_processed_off +
+				EVRE_SIZE * num_completed);
+		u32 tre_read_off = (lldev->tre_processed_off +
+				TRE_SIZE * num_completed);
+
+		evre_read_off = evre_read_off % evre_ring_size;
+		tre_read_off = tre_read_off % tre_ring_size;
+
+		writel(evre_read_off, lldev->evca + EVCA_DOORBELL_OFFSET);
+
+		/* record the last processed tre offset */
+		lldev->tre_processed_off = tre_read_off;
+		lldev->evre_processed_off = evre_read_off;
+	}
+
+	return num_completed;
+}
+
+void hidma_cleanup_pending_tre(struct hidma_lldev *lldev, u8 err_info,
+				u8 err_code)
+{
+	u32 tre_iterator;
+	struct hidma_tre *tre;
+	u32 tre_ring_size = lldev->tre_ring_size;
+	int num_completed = 0;
+	u32 tre_read_off;
+	unsigned long flags;
+
+	tre_iterator = lldev->tre_processed_off;
+	while (lldev->pending_tre_count) {
+		int tre_index = tre_iterator / TRE_SIZE;
+
+		spin_lock_irqsave(&lldev->lock, flags);
+		tre = lldev->pending_tre_list[tre_index];
+		if (!tre) {
+			spin_unlock_irqrestore(&lldev->lock, flags);
+			tre_iterator += TRE_SIZE;
+			if (tre_iterator >= tre_ring_size)
+				tre_iterator -= tre_ring_size;
+			continue;
+		}
+		lldev->pending_tre_list[tre_index] = NULL;
+		lldev->pending_tre_count--;
+		if (lldev->pending_tre_count < 0) {
+			dev_warn(lldev->dev,
+				"tre count mismatch on completion");
+			lldev->pending_tre_count = 0;
+		}
+		spin_unlock_irqrestore(&lldev->lock, flags);
+
+		lldev->tx_status_list[tre->chidx].err_info = err_info;
+		lldev->tx_status_list[tre->chidx].err_code = err_code;
+		tre->queued = 0;
+
+		tasklet_schedule(&tre->task);
+
+		tre_iterator += TRE_SIZE;
+		if (tre_iterator >= tre_ring_size)
+			tre_iterator -= tre_ring_size;
+
+		num_completed++;
+	}
+	tre_read_off = (lldev->tre_processed_off +
+			TRE_SIZE * num_completed);
+
+	tre_read_off = tre_read_off % tre_ring_size;
+
+	/* record the last processed tre offset */
+	lldev->tre_processed_off = tre_read_off;
+}
+
+static int hidma_ll_reset(struct hidma_lldev *lldev)
+{
+	u32 val;
+	int ret;
+
+	val = readl_relaxed(lldev->trca + TRCA_CTRLSTS_OFFSET);
+	val = val & ~(CH_CONTROL_MASK << 16);
+	val = val | (CH_RESET << 16);
+	writel(val, lldev->trca + TRCA_CTRLSTS_OFFSET);
+
+	/* Delay 10ms after reset to allow DMA logic to quiesce.
+	 * Do a polled read up to 1ms and 10ms maximum.
+	 */
+	ret = readl_poll_timeout(lldev->trca + TRCA_CTRLSTS_OFFSET, val,
+		(((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_DISABLED),
+		1000, 10000);
+	if (ret) {
+		dev_err(lldev->dev,
+			"transfer channel did not reset\n");
+		return ret;
+	}
+
+	val = readl_relaxed(lldev->evca + EVCA_CTRLSTS_OFFSET);
+	val = val & ~(CH_CONTROL_MASK << 16);
+	val = val | (CH_RESET << 16);
+	writel(val, lldev->evca + EVCA_CTRLSTS_OFFSET);
+
+	/* Delay 10ms after reset to allow DMA logic to quiesce.
+	 * Do a polled read up to 1ms and 10ms maximum.
+	 */
+	ret = readl_poll_timeout(lldev->evca + EVCA_CTRLSTS_OFFSET, val,
+		(((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_DISABLED),
+		1000, 10000);
+	if (ret)
+		return ret;
+
+	lldev->trch_state = CH_DISABLED;
+	lldev->evch_state = CH_DISABLED;
+	return 0;
+}
+
+static void hidma_ll_enable_irq(struct hidma_lldev *lldev, u32 irq_bits)
+{
+	writel(irq_bits, lldev->evca + EVCA_IRQ_EN_OFFSET);
+	dev_dbg(lldev->dev, "enableirq\n");
+}
+
+/*
+ * The interrupt handler for HIDMA will try to consume as many pending
+ * EVRE from the event queue as possible. Each EVRE has an associated
+ * TRE that holds the user interface parameters. EVRE reports the
+ * result of the transaction. Hardware guarantees ordering between EVREs
+ * and TREs. We use last processed offset to figure out which TRE is
+ * associated with which EVRE. If two TREs are consumed by HW, the EVREs
+ * are in order in the event ring.
+ * This handler will do a one pass for consuming EVREs. Other EVREs may
+ * be delivered while we are working. It will try to consume incoming
+ * EVREs one more time and return.
+ * For unprocessed EVREs, hardware will trigger another interrupt until
+ * all the interrupt bits are cleared.
+ *
+ * Hardware guarantees that by the time interrupt is observed, all data
+ * transactions in flight are delivered to their respective places and
+ * are visible to the CPU.
+ *
+ * On demand paging for IOMMU is only supported for PCIe via PRI
+ * (Page Request Interface) not for HIDMA. All other hardware instances
+ * including HIDMA work on pinned DMA addresses.
+ *
+ */
+static void hidma_ll_int_handler_internal(struct hidma_lldev *lldev)
+{
+	u32 status;
+	u32 enable;
+	u32 cause;
+	int repeat = 2;
+	unsigned long timeout;
+
+	status = readl_relaxed(lldev->evca + EVCA_IRQ_STAT_OFFSET);
+	enable = readl_relaxed(lldev->evca + EVCA_IRQ_EN_OFFSET);
+	cause = status & enable;
+
+	if ((cause & (BIT(IRQ_TR_CH_INVALID_TRE_BIT_POS))) ||
+			(cause & BIT(IRQ_TR_CH_TRE_RD_RSP_ER_BIT_POS)) ||
+			(cause & BIT(IRQ_EV_CH_WR_RESP_BIT_POS)) ||
+			(cause & BIT(IRQ_TR_CH_DATA_RD_ER_BIT_POS)) ||
+			(cause & BIT(IRQ_TR_CH_DATA_WR_ER_BIT_POS))) {
+		u8 err_code = EVRE_STATUS_ERROR;
+		u8 err_info = 0xFF;
+
+		/* Clear out pending interrupts */
+		writel(cause, lldev->evca + EVCA_IRQ_CLR_OFFSET);
+
+		dev_err(lldev->dev,
+			"error 0x%x, resetting...\n", cause);
+
+		hidma_cleanup_pending_tre(lldev, err_info, err_code);
+
+		/* reset the channel for recovery */
+		if (hidma_ll_setup(lldev)) {
+			dev_err(lldev->dev,
+				"channel reinitialize failed after error\n");
+			return;
+		}
+		hidma_ll_enable_irq(lldev, ENABLE_IRQS);
+		return;
+	}
+
+	/* Try to consume as many EVREs as possible.
+	 * skip this loop if the interrupt is spurious.
+	 */
+	while (cause && repeat) {
+		unsigned long start = jiffies;
+
+		/* This timeout should be sufficent for core to finish */
+		timeout = start + msecs_to_jiffies(500);
+
+		while (lldev->pending_tre_count) {
+			hidma_handle_tre_completion(lldev);
+			if (time_is_before_jiffies(timeout)) {
+				dev_warn(lldev->dev,
+					"ISR timeout %lx-%lx from %lx [%d]\n",
+					jiffies, timeout, start,
+					lldev->pending_tre_count);
+				break;
+			}
+		}
+
+		/* We consumed TREs or there are pending TREs or EVREs. */
+		writel_relaxed(cause, lldev->evca + EVCA_IRQ_CLR_OFFSET);
+
+		/* Another interrupt might have arrived while we are
+		 * processing this one. Read the new cause.
+		 */
+		status = readl_relaxed(lldev->evca + EVCA_IRQ_STAT_OFFSET);
+		enable = readl_relaxed(lldev->evca + EVCA_IRQ_EN_OFFSET);
+		cause = status & enable;
+
+		repeat--;
+	}
+}
+
+
+static int hidma_ll_enable(struct hidma_lldev *lldev)
+{
+	u32 val;
+	int ret;
+
+	val = readl_relaxed(lldev->evca + EVCA_CTRLSTS_OFFSET);
+	val &= ~(CH_CONTROL_MASK << 16);
+	val |= (CH_ENABLE << 16);
+	writel(val, lldev->evca + EVCA_CTRLSTS_OFFSET);
+
+	ret = readl_poll_timeout(lldev->evca + EVCA_CTRLSTS_OFFSET, val,
+		((((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_ENABLED) ||
+		(((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_RUNNING)),
+		1000, 10000);
+	if (ret) {
+		dev_err(lldev->dev,
+			"event channel did not get enabled\n");
+		return ret;
+	}
+
+	val = readl_relaxed(lldev->trca + TRCA_CTRLSTS_OFFSET);
+	val = val & ~(CH_CONTROL_MASK << 16);
+	val = val | (CH_ENABLE << 16);
+	writel(val, lldev->trca + TRCA_CTRLSTS_OFFSET);
+
+	ret = readl_poll_timeout(lldev->trca + TRCA_CTRLSTS_OFFSET, val,
+		((((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_ENABLED) ||
+		(((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_RUNNING)),
+		1000, 10000);
+	if (ret) {
+		dev_err(lldev->dev,
+			"transfer channel did not get enabled\n");
+		return ret;
+	}
+
+	lldev->trch_state = CH_ENABLED;
+	lldev->evch_state = CH_ENABLED;
+
+	return 0;
+}
+
+int hidma_ll_resume(struct hidma_lldev *lldev)
+{
+	return hidma_ll_enable(lldev);
+}
+
+static int hidma_ll_hw_start(struct hidma_lldev *lldev)
+{
+	int rc = 0;
+	unsigned long irqflags;
+
+	spin_lock_irqsave(&lldev->lock, irqflags);
+	writel(lldev->tre_write_offset, lldev->trca + TRCA_DOORBELL_OFFSET);
+	spin_unlock_irqrestore(&lldev->lock, irqflags);
+
+	return rc;
+}
+
+bool hidma_ll_isenabled(struct hidma_lldev *lldev)
+{
+	u32 val;
+
+	val = readl_relaxed(lldev->trca + TRCA_CTRLSTS_OFFSET);
+	lldev->trch_state = (val >> CH_STATE_BIT_POS) & CH_STATE_MASK;
+	val = readl_relaxed(lldev->evca + EVCA_CTRLSTS_OFFSET);
+	lldev->evch_state = (val >> CH_STATE_BIT_POS) & CH_STATE_MASK;
+
+	/* both channels have to be enabled before calling this function*/
+	if (((lldev->trch_state == CH_ENABLED) ||
+		(lldev->trch_state == CH_RUNNING)) &&
+		((lldev->evch_state == CH_ENABLED) ||
+			(lldev->evch_state == CH_RUNNING)))
+		return true;
+
+	dev_dbg(lldev->dev, "channels are not enabled or are in error state");
+	return false;
+}
+
+int hidma_ll_queue_request(struct hidma_lldev *lldev, u32 tre_ch)
+{
+	struct hidma_tre *tre;
+	int rc = 0;
+	unsigned long flags;
+
+	tre = &lldev->trepool[tre_ch];
+
+	/* copy the TRE into its location in the TRE ring */
+	spin_lock_irqsave(&lldev->lock, flags);
+	tre->tre_index = lldev->tre_write_offset / TRE_SIZE;
+	lldev->pending_tre_list[tre->tre_index] = tre;
+	memcpy(lldev->tre_ring + lldev->tre_write_offset, &tre->tre_local[0],
+		TRE_SIZE);
+	lldev->tx_status_list[tre->chidx].err_code = 0;
+	lldev->tx_status_list[tre->chidx].err_info = 0;
+	tre->queued = 1;
+	lldev->pending_tre_count++;
+	lldev->tre_write_offset = (lldev->tre_write_offset + TRE_SIZE)
+				% lldev->tre_ring_size;
+	spin_unlock_irqrestore(&lldev->lock, flags);
+	return rc;
+}
+
+int hidma_ll_start(struct hidma_lldev *lldev)
+{
+	return hidma_ll_hw_start(lldev);
+}
+
+/*
+ * Note that even though we stop this channel
+ * if there is a pending transaction in flight
+ * it will complete and follow the callback.
+ * This request will prevent further requests
+ * to be made.
+ */
+int hidma_ll_pause(struct hidma_lldev *lldev)
+{
+	u32 val;
+	int ret;
+
+	val = readl_relaxed(lldev->evca + EVCA_CTRLSTS_OFFSET);
+	lldev->evch_state = (val >> CH_STATE_BIT_POS) & CH_STATE_MASK;
+	val = readl_relaxed(lldev->trca + TRCA_CTRLSTS_OFFSET);
+	lldev->trch_state = (val >> CH_STATE_BIT_POS) & CH_STATE_MASK;
+
+	/* already suspended by this OS */
+	if ((lldev->trch_state == CH_SUSPENDED) ||
+		(lldev->evch_state == CH_SUSPENDED))
+		return 0;
+
+	/* already stopped by the manager */
+	if ((lldev->trch_state == CH_STOPPED) ||
+		(lldev->evch_state == CH_STOPPED))
+		return 0;
+
+	val = readl_relaxed(lldev->trca + TRCA_CTRLSTS_OFFSET);
+	val = val & ~(CH_CONTROL_MASK << 16);
+	val = val | (CH_SUSPEND << 16);
+	writel(val, lldev->trca + TRCA_CTRLSTS_OFFSET);
+
+	/* Start the wait right after the suspend is confirmed.
+	 * Do a polled read up to 1ms and 10ms maximum.
+	 */
+	ret = readl_poll_timeout(lldev->trca + TRCA_CTRLSTS_OFFSET, val,
+		(((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_SUSPENDED),
+		1000, 10000);
+	if (ret)
+		return ret;
+
+	val = readl_relaxed(lldev->evca + EVCA_CTRLSTS_OFFSET);
+	val = val & ~(CH_CONTROL_MASK << 16);
+	val = val | (CH_SUSPEND << 16);
+	writel(val, lldev->evca + EVCA_CTRLSTS_OFFSET);
+
+	/* Start the wait right after the suspend is confirmed
+	 * Delay up to 10ms after reset to allow DMA logic to quiesce.
+	 */
+	ret = readl_poll_timeout(lldev->evca + EVCA_CTRLSTS_OFFSET, val,
+		(((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_SUSPENDED),
+		1000, 10000);
+	if (ret)
+		return ret;
+
+	lldev->trch_state = CH_SUSPENDED;
+	lldev->evch_state = CH_SUSPENDED;
+	dev_dbg(lldev->dev, "stop\n");
+
+	return 0;
+}
+
+void hidma_ll_set_transfer_params(struct hidma_lldev *lldev, u32 tre_ch,
+	dma_addr_t src, dma_addr_t dest, u32 len, u32 flags)
+{
+	struct hidma_tre *tre;
+	u32 *tre_local;
+
+	if (tre_ch >= lldev->nr_tres) {
+		dev_err(lldev->dev,
+			"invalid TRE number in transfer params:%d", tre_ch);
+		return;
+	}
+
+	tre = &lldev->trepool[tre_ch];
+	if (atomic_read(&tre->allocated) != true) {
+		dev_err(lldev->dev,
+			"trying to set params on an unused TRE:%d", tre_ch);
+		return;
+	}
+
+	tre_local = &tre->tre_local[0];
+	tre_local[TRE_LEN_IDX] = len;
+	tre_local[TRE_SRC_LOW_IDX] = lower_32_bits(src);
+	tre_local[TRE_SRC_HI_IDX] = upper_32_bits(src);
+	tre_local[TRE_DEST_LOW_IDX] = lower_32_bits(dest);
+	tre_local[TRE_DEST_HI_IDX] = upper_32_bits(dest);
+	tre->int_flags = flags;
+
+	dev_dbg(lldev->dev, "transferparams: tre_ch:%d %pap->%pap len:%u\n",
+		tre_ch, &src, &dest, len);
+}
+
+/* Called during initialization and after an error condition
+ * to restore hardware state.
+ */
+int hidma_ll_setup(struct hidma_lldev *lldev)
+{
+	int rc;
+	u64 addr;
+	u32 val;
+	u32 nr_tres = lldev->nr_tres;
+
+	lldev->pending_tre_count = 0;
+	lldev->tre_processed_off = 0;
+	lldev->evre_processed_off = 0;
+	lldev->tre_write_offset = 0;
+
+	/* disable interrupts */
+	hidma_ll_enable_irq(lldev, 0);
+
+	/* clear all pending interrupts */
+	val = readl_relaxed(lldev->evca + EVCA_IRQ_STAT_OFFSET);
+	writel_relaxed(val, lldev->evca + EVCA_IRQ_CLR_OFFSET);
+
+	rc = hidma_ll_reset(lldev);
+	if (rc)
+		return rc;
+
+	/* Clear all pending interrupts again.
+	 * Otherwise, we observe reset complete interrupts.
+	 */
+	val = readl_relaxed(lldev->evca + EVCA_IRQ_STAT_OFFSET);
+	writel_relaxed(val, lldev->evca + EVCA_IRQ_CLR_OFFSET);
+
+	/* disable interrupts again after reset */
+	hidma_ll_enable_irq(lldev, 0);
+
+	addr = lldev->tre_ring_handle;
+	writel_relaxed(lower_32_bits(addr),
+			lldev->trca + TRCA_RING_LOW_OFFSET);
+	writel_relaxed(upper_32_bits(addr),
+			lldev->trca + TRCA_RING_HIGH_OFFSET);
+	writel_relaxed(lldev->tre_ring_size,
+			lldev->trca + TRCA_RING_LEN_OFFSET);
+
+	addr = lldev->evre_ring_handle;
+	writel_relaxed(lower_32_bits(addr),
+			lldev->evca + EVCA_RING_LOW_OFFSET);
+	writel_relaxed(upper_32_bits(addr),
+			lldev->evca + EVCA_RING_HIGH_OFFSET);
+	writel_relaxed(EVRE_SIZE * nr_tres,
+			lldev->evca + EVCA_RING_LEN_OFFSET);
+
+	/* support IRQ only for now */
+	val = readl_relaxed(lldev->evca + EVCA_INTCTRL_OFFSET);
+	val = val & ~(0xF);
+	val = val | 0x1;
+	writel_relaxed(val, lldev->evca + EVCA_INTCTRL_OFFSET);
+
+	/* clear all pending interrupts and enable them*/
+	writel_relaxed(ENABLE_IRQS, lldev->evca + EVCA_IRQ_CLR_OFFSET);
+	hidma_ll_enable_irq(lldev, ENABLE_IRQS);
+
+	rc = hidma_ll_enable(lldev);
+	if (rc)
+		return rc;
+
+	return rc;
+}
+
+struct hidma_lldev *hidma_ll_init(struct device *dev, u32 nr_tres,
+			void __iomem *trca, void __iomem *evca,
+			u8 evridx)
+{
+	u32 required_bytes;
+	struct hidma_lldev *lldev;
+	int rc;
+	u32 i;
+
+	if (!trca || !evca || !dev || !nr_tres)
+		return NULL;
+
+	/* need at least four TREs */
+	if (nr_tres < 4)
+		return NULL;
+
+	/* need an extra space */
+	nr_tres += 1;
+
+	lldev = devm_kzalloc(dev, sizeof(struct hidma_lldev), GFP_KERNEL);
+	if (!lldev)
+		return NULL;
+
+	lldev->evca = evca;
+	lldev->trca = trca;
+	lldev->dev = dev;
+	required_bytes = sizeof(struct hidma_tre) * nr_tres;
+	lldev->trepool = devm_kzalloc(lldev->dev, required_bytes, GFP_KERNEL);
+	if (!lldev->trepool)
+		return NULL;
+
+	required_bytes = sizeof(lldev->pending_tre_list[0]) * nr_tres;
+	lldev->pending_tre_list = devm_kzalloc(dev, required_bytes,
+					GFP_KERNEL);
+	if (!lldev->pending_tre_list)
+		return NULL;
+
+	required_bytes = sizeof(lldev->tx_status_list[0]) * nr_tres;
+	lldev->tx_status_list = devm_kzalloc(dev, required_bytes, GFP_KERNEL);
+	if (!lldev->tx_status_list)
+		return NULL;
+
+	lldev->tre_ring = dmam_alloc_coherent(dev, (TRE_SIZE + 1) * nr_tres,
+					&lldev->tre_ring_handle, GFP_KERNEL);
+	if (!lldev->tre_ring)
+		return NULL;
+
+	memset(lldev->tre_ring, 0, (TRE_SIZE + 1) * nr_tres);
+	lldev->tre_ring_size = TRE_SIZE * nr_tres;
+	lldev->nr_tres = nr_tres;
+
+	/* the TRE ring has to be TRE_SIZE aligned */
+	if (!IS_ALIGNED(lldev->tre_ring_handle, TRE_SIZE)) {
+		u8  tre_ring_shift;
+
+		tre_ring_shift = lldev->tre_ring_handle % TRE_SIZE;
+		tre_ring_shift = TRE_SIZE - tre_ring_shift;
+		lldev->tre_ring_handle += tre_ring_shift;
+		lldev->tre_ring += tre_ring_shift;
+	}
+
+	lldev->evre_ring = dmam_alloc_coherent(dev, (EVRE_SIZE + 1) * nr_tres,
+					&lldev->evre_ring_handle, GFP_KERNEL);
+	if (!lldev->evre_ring)
+		return NULL;
+
+	memset(lldev->evre_ring, 0, (EVRE_SIZE + 1) * nr_tres);
+	lldev->evre_ring_size = EVRE_SIZE * nr_tres;
+
+	/* the EVRE ring has to be EVRE_SIZE aligned */
+	if (!IS_ALIGNED(lldev->evre_ring_handle, EVRE_SIZE)) {
+		u8  evre_ring_shift;
+
+		evre_ring_shift = lldev->evre_ring_handle % EVRE_SIZE;
+		evre_ring_shift = EVRE_SIZE - evre_ring_shift;
+		lldev->evre_ring_handle += evre_ring_shift;
+		lldev->evre_ring += evre_ring_shift;
+	}
+	lldev->nr_tres = nr_tres;
+	lldev->evridx = evridx;
+
+	rc = hidma_ll_setup(lldev);
+	if (rc)
+		return NULL;
+
+	spin_lock_init(&lldev->lock);
+	for (i = 0; i < nr_tres; i++)
+		tasklet_init(&lldev->trepool[i].task, hidma_ll_tre_complete,
+				(unsigned long)&lldev->trepool[i]);
+	lldev->initialized = 1;
+	hidma_ll_enable_irq(lldev, ENABLE_IRQS);
+	return lldev;
+}
+
+int hidma_ll_uninit(struct hidma_lldev *lldev)
+{
+	int rc = 0;
+	u32 val;
+
+	if (!lldev)
+		return -ENODEV;
+
+	if (lldev->initialized) {
+		u32 required_bytes;
+		u32 i;
+
+		lldev->initialized = 0;
+
+		required_bytes = sizeof(struct hidma_tre) * lldev->nr_tres;
+		for (i = 0; i < lldev->nr_tres; i++)
+			tasklet_kill(&lldev->trepool[i].task);
+		memset(lldev->trepool, 0, required_bytes);
+		lldev->trepool = NULL;
+		lldev->pending_tre_count = 0;
+		lldev->tre_write_offset = 0;
+
+		rc = hidma_ll_reset(lldev);
+
+		/* Clear all pending interrupts again.
+		 * Otherwise, we observe reset complete interrupts.
+		 */
+		val = readl_relaxed(lldev->evca + EVCA_IRQ_STAT_OFFSET);
+		writel_relaxed(val, lldev->evca + EVCA_IRQ_CLR_OFFSET);
+		hidma_ll_enable_irq(lldev, 0);
+	}
+	return rc;
+}
+
+irqreturn_t hidma_ll_inthandler(int chirq, void *arg)
+{
+	struct hidma_lldev *lldev = arg;
+
+	hidma_ll_int_handler_internal(lldev);
+	return IRQ_HANDLED;
+}
+
+enum dma_status hidma_ll_status(struct hidma_lldev *lldev, u32 tre_ch)
+{
+	enum dma_status ret = DMA_ERROR;
+	unsigned long flags;
+	u8 err_code;
+
+	spin_lock_irqsave(&lldev->lock, flags);
+	err_code = lldev->tx_status_list[tre_ch].err_code;
+
+	if (err_code & EVRE_STATUS_COMPLETE)
+		ret = DMA_COMPLETE;
+	else if (err_code & EVRE_STATUS_ERROR)
+		ret = DMA_ERROR;
+	else
+		ret = DMA_IN_PROGRESS;
+	spin_unlock_irqrestore(&lldev->lock, flags);
+
+	return ret;
+}
-- 
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project


^ permalink raw reply related	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02  6:07 ` [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver Sinan Kaya
@ 2015-11-02 15:57   ` Rob Herring
  2015-11-02 16:20     ` Sinan Kaya
  2015-11-03  5:18     ` Sinan Kaya
  2015-11-03 10:22   ` Andy Shevchenko
  1 sibling, 2 replies; 41+ messages in thread
From: Rob Herring @ 2015-11-02 15:57 UTC (permalink / raw)
  To: Sinan Kaya
  Cc: dmaengine, Timur Tabi, Christopher Covington, jcm, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel

On Mon, Nov 2, 2015 at 12:07 AM, Sinan Kaya <okaya@codeaurora.org> wrote:
> The Qualcomm Technologies HIDMA device has been designed
> to support virtualization technology. The driver has been
> divided into two to follow the hardware design. The management
> driver is executed in hypervisor context and is the main
> management entity for all channels provided by the device.
> The channel driver is executed in the hypervisor/guest OS
> context.
>
> All channel devices get probed in the hypervisor
> context during power up. They show up as DMA engine
> channels. Then, before starting the virtualization; each
> channel device is unbound from the hypervisor by VFIO
> and assigned to the guest machine for control.
>
> This management driver will be used by the system
> admin to monitor/reset the execution state of the DMA
> channels. This will be the management interface.
>
> Signed-off-by: Sinan Kaya <okaya@codeaurora.org>
> ---
>  .../devicetree/bindings/dma/qcom_hidma_mgmt.txt    |  56 ++
>  drivers/dma/Kconfig                                |  11 +
>  drivers/dma/Makefile                               |   1 +
>  drivers/dma/qcom_hidma_mgmt.c                      | 747 +++++++++++++++++++++
>  4 files changed, 815 insertions(+)
>  create mode 100644 Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt
>  create mode 100644 drivers/dma/qcom_hidma_mgmt.c
>
> diff --git a/Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt b/Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt
> new file mode 100644
> index 0000000..514d37d
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt
> @@ -0,0 +1,56 @@
> +Qualcomm Technologies HIDMA Management interface
> +
> +The Qualcomm Technologies HIDMA device has been designed
> +to support virtualization technology. The driver has been
> +divided into two to follow the hardware design. The management
> +driver is executed in hypervisor context and is the main
> +management entity for all channels provided by the device.
> +The channel driver is executed in the hypervisor/guest OS
> +context.

This doesn't really explain what the block is.

> +All channel devices get probed in the hypervisor
> +context during power up. They show up as DMA engine
> +DMA channels. Then, before starting the virtualization; each
> +channel device is unbound from the hypervisor by VFIO
> +and assign to the guest machine for control.
> +
> +This management driver will  be used by the system
> +admin to monitor/reset the execution state of the DMA
> +channels. This will be the management interface.
> +
> +
> +Required properties:
> +- compatible: must contain "qcom,hidma-mgmt"

Please make this more specific. It doesn't match your example either.
Unless "1.0" type versioning is tightly controlled and defined,
version numbers are usually not a good practice.

> +- reg: Address range for DMA device
> +- dma-channels: Number of channels supported by this DMA controller.
> +- max-write-burst-bytes: Maximum write burst in bytes. A memcpy requested is
> +  fragmented to multiples of this amount.
> +- max-read-burst-bytes: Maximum read burst in bytes. A memcpy request is
> +  fragmented to multiples of this amount.
> +- max-write-transactions: Maximum write transactions to perform in a burst
> +- max-read-transactions: Maximum read transactions to perform in a burst

This would be a function of burst-bytes and bus width. Are you sure
you don't me number of outstanding transactions which is a common
parameter for AXI bus peripherals.

> +- channel-reset-timeout: Channel reset timeout for this SOC.

Please add units to property name.

> +- channel-priority: Priority of the channel.
> +  Each dma channel share the same HW bandwidth with other dma channels.
> +  If two requests reach to the HW at the same time from a low priority and
> +  high priority channel, high priority channel will claim the bus.
> +  0=low priority, 1=high priority
> +- channel-weight: Round robin weight of the channel
> +  Since there are only two priority levels supported, scheduling among
> +  the equal priority channels is done via weights.
> +
> +Example:
> +
> +       hidma-mgmt@f9984000 = {
> +               compatible = "qcom,hidma-mgmt-1.0";
> +               reg = <0xf9984000 0x15000>;
> +               dma-channels = 6;
> +               max-write-burst-bytes = 1024;
> +               max-read-burst-bytes = 1024;
> +               max-write-transactions = 31;
> +               max-read-transactions = 31;
> +               channel-reset-timeout = 0x500;
> +               channel-priority = < 1 1 0 0 0 0>;
> +               channel-weight = < 1 13 10 3 4 5>;
> +       };
> +

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02 15:57   ` Rob Herring
@ 2015-11-02 16:20     ` Sinan Kaya
  2015-11-02 17:26       ` Timur Tabi
  2015-11-03  5:18     ` Sinan Kaya
  1 sibling, 1 reply; 41+ messages in thread
From: Sinan Kaya @ 2015-11-02 16:20 UTC (permalink / raw)
  To: Rob Herring
  Cc: dmaengine, Timur Tabi, Christopher Covington, jcm, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel



On 11/2/2015 10:57 AM, Rob Herring wrote:
> Please make this more specific. It doesn't match your example either.
> Unless "1.0" type versioning is tightly controlled and defined,
> version numbers are usually not a good practice.
Is there a good example I can look or a wiki about the device-tree 
naming conventions?

I'm more of an ACPI person than DTS.

-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02 16:20     ` Sinan Kaya
@ 2015-11-02 17:26       ` Timur Tabi
  2015-11-02 17:42         ` Rob Herring
  0 siblings, 1 reply; 41+ messages in thread
From: Timur Tabi @ 2015-11-02 17:26 UTC (permalink / raw)
  To: Sinan Kaya, Rob Herring
  Cc: dmaengine, Christopher Covington, jcm, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams, devicetree,
	linux-kernel

On 11/02/2015 10:20 AM, Sinan Kaya wrote:
>>
> Is there a good example I can look or a wiki about the device-tree
> naming conventions?
>
> I'm more of an ACPI person than DTS.

I think Rob is talking about something like this:

	compatible="qcom,hidma-mgmt-1.0", "qcom,hidma-mgmt"

This specifies that this is the v1.0 of the HIDMA management engine (or, 
the management engine for the 1.0 HIDMA device).  That way, if in the 
future there's a v1.1, you can do this:

	compatible="qcom,hidma-mgmt-1.1", "qcom,hidma-mgmt"

The driver will probe only on ""qcom,hidma-mgmt", but in the probe 
function, it can query the version number and act accordingly.

Alternatively, the driver can probe on both:

static const struct of_device_id hidma_match[] = {
	{ .compatible = "qcom,hidma-mgmt-1.0", &v10_struct},
	{ .compatible = "qcom,hidma-mgmt-1.1", &v11_struct},
	{},
};
MODULE_DEVICE_TABLE(of, hidma_match);

And then the probe function will automatically get a pointer to either 
v10_struct or v11_struct.
	
-- 
Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the
Code Aurora Forum, a Linux Foundation Collaborative Project.

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02 17:26       ` Timur Tabi
@ 2015-11-02 17:42         ` Rob Herring
  2015-11-02 17:48           ` Timur Tabi
  2015-11-02 18:49           ` Sinan Kaya
  0 siblings, 2 replies; 41+ messages in thread
From: Rob Herring @ 2015-11-02 17:42 UTC (permalink / raw)
  To: Timur Tabi
  Cc: Sinan Kaya, dmaengine, Christopher Covington, jcm, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel

On Mon, Nov 2, 2015 at 11:26 AM, Timur Tabi <timur@codeaurora.org> wrote:
> On 11/02/2015 10:20 AM, Sinan Kaya wrote:
>>>
>>>
>> Is there a good example I can look or a wiki about the device-tree
>> naming conventions?

There are many examples. Generally, it is the form of:

<vendor>,<chip/soc>-<block name>

>>
>> I'm more of an ACPI person than DTS.
>
>
> I think Rob is talking about something like this:
>
>         compatible="qcom,hidma-mgmt-1.0", "qcom,hidma-mgmt"
>
> This specifies that this is the v1.0 of the HIDMA management engine (or, the
> management engine for the 1.0 HIDMA device).  That way, if in the future
> there's a v1.1, you can do this:
>
>         compatible="qcom,hidma-mgmt-1.1", "qcom,hidma-mgmt"

Except I was suggesting not using 1.0 or 1.1. There is one main
exception and that is Xilinx blocks, but they are releasing versions
of blocks to customers. If "1.0" is not a well defined number, then
don't use that. I'd be surprised if any SOC vendor had such well
defined process around versioning of their IP blocks such that they
are well documented and guaranteed such that every change will change
the version.

Rob

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02 17:42         ` Rob Herring
@ 2015-11-02 17:48           ` Timur Tabi
  2015-11-02 18:25             ` Rob Herring
  2015-11-02 18:49           ` Sinan Kaya
  1 sibling, 1 reply; 41+ messages in thread
From: Timur Tabi @ 2015-11-02 17:48 UTC (permalink / raw)
  To: Rob Herring
  Cc: Sinan Kaya, dmaengine, Christopher Covington, jcm, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel

On 11/02/2015 11:42 AM, Rob Herring wrote:
> On Mon, Nov 2, 2015 at 11:26 AM, Timur Tabi<timur@codeaurora.org>  wrote:
>> >On 11/02/2015 10:20 AM, Sinan Kaya wrote:
>>>> >>>
>>>> >>>
>>> >>Is there a good example I can look or a wiki about the device-tree
>>> >>naming conventions?
> There are many examples. Generally, it is the form of:
>
> <vendor>,<chip/soc>-<block name>
>

The problem is that we don't actually have an official name for this 
chip yet.


-- 
Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the
Code Aurora Forum, a Linux Foundation Collaborative Project.

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02 17:48           ` Timur Tabi
@ 2015-11-02 18:25             ` Rob Herring
  2015-11-02 18:30               ` Timur Tabi
  0 siblings, 1 reply; 41+ messages in thread
From: Rob Herring @ 2015-11-02 18:25 UTC (permalink / raw)
  To: Timur Tabi
  Cc: Sinan Kaya, dmaengine, Christopher Covington, jcm, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel

On Mon, Nov 2, 2015 at 11:48 AM, Timur Tabi <timur@codeaurora.org> wrote:
> On 11/02/2015 11:42 AM, Rob Herring wrote:
>>
>> On Mon, Nov 2, 2015 at 11:26 AM, Timur Tabi<timur@codeaurora.org>  wrote:
>>>
>>> >On 11/02/2015 10:20 AM, Sinan Kaya wrote:
>>>>>
>>>>> >>>
>>>>> >>>
>>>>
>>>> >>Is there a good example I can look or a wiki about the device-tree
>>>> >>naming conventions?
>>
>> There are many examples. Generally, it is the form of:
>>
>> <vendor>,<chip/soc>-<block name>
>>
>
> The problem is that we don't actually have an official name for this chip
> yet.

Then document it with "<chip>" and fill that in later. Just don't make
up version numbers.

Rob

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02 18:25             ` Rob Herring
@ 2015-11-02 18:30               ` Timur Tabi
  2015-11-05 14:31                 ` Rob Herring
  0 siblings, 1 reply; 41+ messages in thread
From: Timur Tabi @ 2015-11-02 18:30 UTC (permalink / raw)
  To: Rob Herring
  Cc: Sinan Kaya, dmaengine, Christopher Covington, jcm, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel

On 11/02/2015 12:25 PM, Rob Herring wrote:
> Then document it with "<chip>" and fill that in later. Just don't make
> up version numbers.

I don't think you understand.  We literally have no name for our chip. 
The closest is what I used on the pin control driver, "qdf2xxx", which 
really doesn't say anything.

	"qcom,qdf2xxx-hidma-mgmt"

doesn't sound right.

As for version numbers, sorry, I don't know what I was thinking.  I 
wrote plenty of device trees the way you suggest, I just forgot about them.

-- 
Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the
Code Aurora Forum, a Linux Foundation Collaborative Project.

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02 17:42         ` Rob Herring
  2015-11-02 17:48           ` Timur Tabi
@ 2015-11-02 18:49           ` Sinan Kaya
  2015-11-02 22:00             ` Arnd Bergmann
  1 sibling, 1 reply; 41+ messages in thread
From: Sinan Kaya @ 2015-11-02 18:49 UTC (permalink / raw)
  To: Rob Herring, Timur Tabi
  Cc: dmaengine, Christopher Covington, jcm, Pawel Moll, Mark Rutland,
	Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams, devicetree,
	linux-kernel



On 11/2/2015 12:42 PM, Rob Herring wrote:
> Except I was suggesting not using 1.0 or 1.1. There is one main
> exception and that is Xilinx blocks, but they are releasing versions
> of blocks to customers. If "1.0" is not a well defined number, then
> don't use that. I'd be surprised if any SOC vendor had such well
> defined process around versioning of their IP blocks such that they
> are well documented and guaranteed such that every change will change
> the version.

Here is one.

I have two versions of the same IP. The first version in one chip has 
sw_version register that returns 1.0. The second version which has more 
capabilities has 1.1 in it.

Is it OK to use?

compatible="qcom,hidma-mgmt-1.0", "qcom,hidma-mgmt"

for now and

compatible="qcom,hidma-mgmt-1.1", "qcom,hidma-mgmt"

later for the second chip? 1.1 is backwards compatible with 1.0 BTW.

Since the same IP goes into multiple chips, why would you list the chip 
name here and submit patches multiple times for each single chip.

or to follow what Timur did, I can do this.

"qcom,qdf2xxx-hidma-mgmt-1.0"

qdf2xxx would become the chip family.

-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02 18:49           ` Sinan Kaya
@ 2015-11-02 22:00             ` Arnd Bergmann
  0 siblings, 0 replies; 41+ messages in thread
From: Arnd Bergmann @ 2015-11-02 22:00 UTC (permalink / raw)
  To: Sinan Kaya
  Cc: Rob Herring, Timur Tabi, dmaengine, Christopher Covington, jcm,
	Pawel Moll, Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul,
	Dan Williams, devicetree, linux-kernel

On Monday 02 November 2015 13:49:35 Sinan Kaya wrote:
> On 11/2/2015 12:42 PM, Rob Herring wrote:
> > Except I was suggesting not using 1.0 or 1.1. There is one main
> > exception and that is Xilinx blocks, but they are releasing versions
> > of blocks to customers. If "1.0" is not a well defined number, then
> > don't use that. I'd be surprised if any SOC vendor had such well
> > defined process around versioning of their IP blocks such that they
> > are well documented and guaranteed such that every change will change
> > the version.
> 
> Here is one.
> 
> I have two versions of the same IP. The first version in one chip has 
> sw_version register that returns 1.0. The second version which has more 
> capabilities has 1.1 in it.
> 
> Is it OK to use?
> 
> compatible="qcom,hidma-mgmt-1.0", "qcom,hidma-mgmt"
> 
> for now and
> 
> compatible="qcom,hidma-mgmt-1.1", "qcom,hidma-mgmt"
> 
> later for the second chip? 1.1 is backwards compatible with 1.0 BTW.

I think this is fine. As they are backwards compatible, I would even make the
latter one

compatible = "qcom,hidma-mgmt-1.1", "qcom,hidma-mgmt-1.0", "qcom,hidma-mgmt";

> Since the same IP goes into multiple chips, why would you list the chip 
> name here and submit patches multiple times for each single chip.
> 
> or to follow what Timur did, I can do this.
> 
> "qcom,qdf2xxx-hidma-mgmt-1.0"
> 
> qdf2xxx would become the chip family.

We really don't want wildcards in here, but want to use the most specific
name you have for it, so we can add quirks to the driver later if it
turns out that they are not fully compatible after all.

	Arnd

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-02  6:07 ` [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions Sinan Kaya
@ 2015-11-03  4:15   ` Vinod Koul
  2015-11-03  4:18     ` Sinan Kaya
  0 siblings, 1 reply; 41+ messages in thread
From: Vinod Koul @ 2015-11-03  4:15 UTC (permalink / raw)
  To: Sinan Kaya; +Cc: dmaengine, timur, cov, jcm, Dan Williams, linux-kernel

On Mon, Nov 02, 2015 at 01:07:38AM -0500, Sinan Kaya wrote:
> This patch adds supporting utility functions
> for selftest. The intention is to share the self
> test code between different drivers.
> 
> Supported test cases include:
> 1. dma_map_single
> 2. streaming DMA
> 3. coherent DMA
> 4. scatter-gather DMA

This seems quite similar to dmatest, any reason why you cannot use/enhance
that?

-- 
~Vinod

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03  4:15   ` Vinod Koul
@ 2015-11-03  4:18     ` Sinan Kaya
  2015-11-03  6:30       ` Vinod Koul
  2015-11-03 14:31       ` Timur Tabi
  0 siblings, 2 replies; 41+ messages in thread
From: Sinan Kaya @ 2015-11-03  4:18 UTC (permalink / raw)
  To: Vinod Koul; +Cc: dmaengine, timur, cov, jcm, Dan Williams, linux-kernel



On 11/2/2015 11:15 PM, Vinod Koul wrote:
> On Mon, Nov 02, 2015 at 01:07:38AM -0500, Sinan Kaya wrote:
>> This patch adds supporting utility functions
>> for selftest. The intention is to share the self
>> test code between different drivers.
>>
>> Supported test cases include:
>> 1. dma_map_single
>> 2. streaming DMA
>> 3. coherent DMA
>> 4. scatter-gather DMA
>
> This seems quite similar to dmatest, any reason why you cannot use/enhance
> that?
>
Dmatest is a standalone kernel module intended for stress testing DMA 
engines from userspace with N number of threads and M size combinations etc.

This one; on the other hand, is selftest to verify hardware is working 
as expected during power up.

Almost all DMA engine drivers come with some sort of selftest code 
called from probe. I followed the same design pattern.

I think the goal is to remove the duplicate self test code in all 
drivers over time.


-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02 15:57   ` Rob Herring
  2015-11-02 16:20     ` Sinan Kaya
@ 2015-11-03  5:18     ` Sinan Kaya
  1 sibling, 0 replies; 41+ messages in thread
From: Sinan Kaya @ 2015-11-03  5:18 UTC (permalink / raw)
  To: Rob Herring
  Cc: dmaengine, Timur Tabi, Christopher Covington, jcm, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel



On 11/2/2015 10:57 AM, Rob Herring wrote:
> On Mon, Nov 2, 2015 at 12:07 AM, Sinan Kaya <okaya@codeaurora.org> wrote:
>> The Qualcomm Technologies HIDMA device has been designed
>> to support virtualization technology. The driver has been
>> divided into two to follow the hardware design. The management
>> driver is executed in hypervisor context and is the main
>> management entity for all channels provided by the device.
>> The channel driver is executed in the hypervisor/guest OS
>> context.
>>
>> All channel devices get probed in the hypervisor
>> context during power up. They show up as DMA engine
>> channels. Then, before starting the virtualization; each
>> channel device is unbound from the hypervisor by VFIO
>> and assigned to the guest machine for control.
>>
>> This management driver will be used by the system
>> admin to monitor/reset the execution state of the DMA
>> channels. This will be the management interface.
>>
>> Signed-off-by: Sinan Kaya <okaya@codeaurora.org>
>> ---
>>   .../devicetree/bindings/dma/qcom_hidma_mgmt.txt    |  56 ++
>>   drivers/dma/Kconfig                                |  11 +
>>   drivers/dma/Makefile                               |   1 +
>>   drivers/dma/qcom_hidma_mgmt.c                      | 747 +++++++++++++++++++++
>>   4 files changed, 815 insertions(+)
>>   create mode 100644 Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt
>>   create mode 100644 drivers/dma/qcom_hidma_mgmt.c
>>
>> diff --git a/Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt b/Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt
>> new file mode 100644
>> index 0000000..514d37d
>> --- /dev/null
>> +++ b/Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt
>> @@ -0,0 +1,56 @@
>> +Qualcomm Technologies HIDMA Management interface
>> +
>> +The Qualcomm Technologies HIDMA device has been designed
>> +to support virtualization technology. The driver has been
>> +divided into two to follow the hardware design. The management
>> +driver is executed in hypervisor context and is the main
>> +management entity for all channels provided by the device.

Let me try one more time. Hope this helps.

Each HIDMA HW consists of multiple channels. These channels share
some set of common parameters. These parameters are initialized
by the management driver during power up. Same management driver is used 
for monitoring the execution of the channels. Management driver
can change the performance behaviors dynamically such as bandwidth
allocation and prioritization.

>
> This doesn't really explain what the block is.
>
>> +All channel devices get probed in the hypervisor
>> +context during power up. They show up as DMA engine
>> +DMA channels. Then, before starting the virtualization; each
>> +channel device is unbound from the hypervisor by VFIO
>> +and assign to the guest machine for control.
>> +
>> +This management driver will  be used by the system
>> +admin to monitor/reset the execution state of the DMA
>> +channels. This will be the management interface.
>> +
>> +
>> +Required properties:
>> +- compatible: must contain "qcom,hidma-mgmt"
>
> Please make this more specific. It doesn't match your example either.
> Unless "1.0" type versioning is tightly controlled and defined,
> version numbers are usually not a good practice.
>
>> +- reg: Address range for DMA device
>> +- dma-channels: Number of channels supported by this DMA controller.
>> +- max-write-burst-bytes: Maximum write burst in bytes. A memcpy requested is
>> +  fragmented to multiples of this amount.
>> +- max-read-burst-bytes: Maximum read burst in bytes. A memcpy request is
>> +  fragmented to multiples of this amount.
>> +- max-write-transactions: Maximum write transactions to perform in a burst
>> +- max-read-transactions: Maximum read transactions to perform in a burst
>
> This would be a function of burst-bytes and bus width. Are you sure
> you don't me number of outstanding transactions which is a common
> parameter for AXI bus peripherals.
>

These are all the available HW knobs for performance at this moment. 
max-write-transactions corresponds to the max number of outstanding 
transactions.

>> +- channel-reset-timeout: Channel reset timeout for this SOC.
>
> Please add units to property name.

ok, changed to channel-reset-timeout-cycles

>
>> +- channel-priority: Priority of the channel.
>> +  Each dma channel share the same HW bandwidth with other dma channels.
>> +  If two requests reach to the HW at the same time from a low priority and
>> +  high priority channel, high priority channel will claim the bus.
>> +  0=low priority, 1=high priority
>> +- channel-weight: Round robin weight of the channel
>> +  Since there are only two priority levels supported, scheduling among
>> +  the equal priority channels is done via weights.
>> +
>> +Example:
>> +
>> +       hidma-mgmt@f9984000 = {
>> +               compatible = "qcom,hidma-mgmt-1.0";
>> +               reg = <0xf9984000 0x15000>;
>> +               dma-channels = 6;
>> +               max-write-burst-bytes = 1024;
>> +               max-read-burst-bytes = 1024;
>> +               max-write-transactions = 31;
>> +               max-read-transactions = 31;
>> +               channel-reset-timeout = 0x500;
>> +               channel-priority = < 1 1 0 0 0 0>;
>> +               channel-weight = < 1 13 10 3 4 5>;
>> +       };
>> +

-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03  4:18     ` Sinan Kaya
@ 2015-11-03  6:30       ` Vinod Koul
  2015-11-03  7:44         ` Dan Williams
  2015-11-03 14:31       ` Timur Tabi
  1 sibling, 1 reply; 41+ messages in thread
From: Vinod Koul @ 2015-11-03  6:30 UTC (permalink / raw)
  To: Sinan Kaya; +Cc: dmaengine, timur, cov, jcm, Dan Williams, linux-kernel

On Mon, Nov 02, 2015 at 11:18:37PM -0500, Sinan Kaya wrote:
> 
> 
> On 11/2/2015 11:15 PM, Vinod Koul wrote:
> >On Mon, Nov 02, 2015 at 01:07:38AM -0500, Sinan Kaya wrote:
> >>This patch adds supporting utility functions
> >>for selftest. The intention is to share the self
> >>test code between different drivers.
> >>
> >>Supported test cases include:
> >>1. dma_map_single
> >>2. streaming DMA
> >>3. coherent DMA
> >>4. scatter-gather DMA
> >
> >This seems quite similar to dmatest, any reason why you cannot use/enhance
> >that?
> >
> Dmatest is a standalone kernel module intended for stress testing
> DMA engines from userspace with N number of threads and M size
> combinations etc.
> 
> This one; on the other hand, is selftest to verify hardware is
> working as expected during power up.
> 
> Almost all DMA engine drivers come with some sort of selftest code
> called from probe. I followed the same design pattern.

which ones ?

> 
> I think the goal is to remove the duplicate self test code in all
> drivers over time.

and what prevents us from having common selftest plus dmatest code. Most of
the code here to do selftest is _same_ dmaengine routine code used in
dmatest

We can have common code which is used for dmatest as well as selftest. I do
not want to see same code duplicated..

-- 
~Vinod

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03  6:30       ` Vinod Koul
@ 2015-11-03  7:44         ` Dan Williams
  2015-11-03  8:22           ` Andy Shevchenko
                             ` (2 more replies)
  0 siblings, 3 replies; 41+ messages in thread
From: Dan Williams @ 2015-11-03  7:44 UTC (permalink / raw)
  To: Vinod Koul; +Cc: Sinan Kaya, dmaengine, timur, cov, jcm, linux-kernel

On Mon, Nov 2, 2015 at 10:30 PM, Vinod Koul <vinod.koul@intel.com> wrote:
> On Mon, Nov 02, 2015 at 11:18:37PM -0500, Sinan Kaya wrote:
>>
>>
>> On 11/2/2015 11:15 PM, Vinod Koul wrote:
>> >On Mon, Nov 02, 2015 at 01:07:38AM -0500, Sinan Kaya wrote:
>> >>This patch adds supporting utility functions
>> >>for selftest. The intention is to share the self
>> >>test code between different drivers.
>> >>
>> >>Supported test cases include:
>> >>1. dma_map_single
>> >>2. streaming DMA
>> >>3. coherent DMA
>> >>4. scatter-gather DMA
>> >
>> >This seems quite similar to dmatest, any reason why you cannot use/enhance
>> >that?
>> >
>> Dmatest is a standalone kernel module intended for stress testing
>> DMA engines from userspace with N number of threads and M size
>> combinations etc.
>>
>> This one; on the other hand, is selftest to verify hardware is
>> working as expected during power up.
>>
>> Almost all DMA engine drivers come with some sort of selftest code
>> called from probe. I followed the same design pattern.
>
> which ones ?
>
>>
>> I think the goal is to remove the duplicate self test code in all
>> drivers over time.
>
> and what prevents us from having common selftest plus dmatest code. Most of
> the code here to do selftest is _same_ dmaengine routine code used in
> dmatest
>
> We can have common code which is used for dmatest as well as selftest. I do
> not want to see same code duplicated..

Originally ioatdma and iop-adma had local self tests before Haavard
created dmatest.  I agree having the drivers also do a test each boot
is redundant, but then again dmatest is not automatic and I saw the
local self test catch an interrupt setup regression.

Maybe you could arrange for drivers to do a quick autorun through
dmatest on load if dmatest is enabled, but otherwise load without
testing?  Just my 2 cents from a dmaengine spectator.

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03  7:44         ` Dan Williams
@ 2015-11-03  8:22           ` Andy Shevchenko
  2015-11-03 16:08             ` Vinod Koul
  2015-11-03 15:51           ` Sinan Kaya
  2015-11-03 16:06           ` Vinod Koul
  2 siblings, 1 reply; 41+ messages in thread
From: Andy Shevchenko @ 2015-11-03  8:22 UTC (permalink / raw)
  To: Dan Williams
  Cc: Vinod Koul, Sinan Kaya, dmaengine, timur, cov, jcm, linux-kernel

On Tue, Nov 3, 2015 at 9:44 AM, Dan Williams <dan.j.williams@intel.com> wrote:
> On Mon, Nov 2, 2015 at 10:30 PM, Vinod Koul <vinod.koul@intel.com> wrote:
>> On Mon, Nov 02, 2015 at 11:18:37PM -0500, Sinan Kaya wrote:
>>> On 11/2/2015 11:15 PM, Vinod Koul wrote:
>>> >On Mon, Nov 02, 2015 at 01:07:38AM -0500, Sinan Kaya wrote:


>>> This one; on the other hand, is selftest to verify hardware is
>>> working as expected during power up.

I prefer to have such nice case by run time parameter (let's say
common to all DMA Engine drivers)

>> We can have common code which is used for dmatest as well as selftest. I do
>> not want to see same code duplicated..

First thought was to merge this to dmatest, however, some DMA
controllers doesn't have a memcpy capability.

How would we test them?

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 3/3] dma: add Qualcomm Technologies HIDMA channel driver
  2015-11-02  6:07 ` [PATCH V2 3/3] dma: add Qualcomm Technologies HIDMA channel driver Sinan Kaya
@ 2015-11-03 10:10   ` Andy Shevchenko
  2015-11-04  0:07     ` Sinan Kaya
  0 siblings, 1 reply; 41+ messages in thread
From: Andy Shevchenko @ 2015-11-03 10:10 UTC (permalink / raw)
  To: Sinan Kaya
  Cc: dmaengine, timur, cov, jcm, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel

On Mon, Nov 2, 2015 at 8:07 AM, Sinan Kaya <okaya@codeaurora.org> wrote:
> This patch adds support for hidma engine. The driver
> consists of two logical blocks. The DMA engine interface
> and the low-level interface. The hardware only supports
> memcpy/memset and this driver only support memcpy
> interface. HW and driver doesn't support slave interface.

> +/* Linux Foundation elects GPLv2 license only.
> + */

One line?

> +#include <linux/dmaengine.h>
> +#include <linux/dma-mapping.h>
> +#include <asm/dma.h>

Do you need this one explicitly?

> +#include <linux/err.h>
> +#include <linux/init.h>
> +#include <linux/interrupt.h>
> +#include <linux/list.h>
> +#include <linux/module.h>
> +#include <linux/platform_device.h>
> +#include <linux/slab.h>
> +#include <linux/spinlock.h>
> +#include <linux/of_dma.h>
> +#include <linux/property.h>
> +#include <linux/delay.h>
> +#include <linux/highmem.h>
> +#include <linux/io.h>
> +#include <linux/sched.h>
> +#include <linux/wait.h>
> +#include <linux/acpi.h>
> +#include <linux/irq.h>
> +#include <linux/atomic.h>
> +#include <linux/pm_runtime.h>

+ empty line?

> +#include <asm/div64.h>

+ empty line?

> +#include "dmaengine.h"
> +#include "qcom_hidma.h"
> +
> +/* Default idle time is 2 seconds. This parameter can
> + * be overridden by changing the following
> + * /sys/bus/platform/devices/QCOM8061:<xy>/power/autosuspend_delay_ms
> + * during kernel boot.
> + */

Block comments usually like
/*
 * text
 */

> +#define AUTOSUSPEND_TIMEOUT            2000
> +
> +struct hidma_lldev;
> +
> +struct hidma_dev {
> +       int                             evridx;
> +       u32                             nr_descriptors;
> +
> +       struct hidma_lldev              *lldev;
> +       void                            __iomem *dev_trca;
> +       void                            __iomem *dev_evca;
> +
> +       /* used to protect the pending channel list*/
> +       spinlock_t                      lock;
> +       struct dma_device               ddev;
> +};
> +
> +struct hidma_chan {
> +       bool                            paused;
> +       bool                            allocated;
> +       char                            name[16];

So, do you need specific name? There is already one in struct dma_chan.

> +       u32                             dma_sig;
> +
> +       /*
> +        * active descriptor on this channel
> +        * It is used by the DMA complete notification to
> +        * locate the descriptor that initiated the transfer.
> +        */
> +       struct hidma_dev                *dmadev;
> +
> +       struct dma_chan                 chan;
> +       struct list_head                free;
> +       struct list_head                prepared;
> +       struct list_head                active;
> +       struct list_head                completed;
> +
> +       /* Lock for this structure */
> +       spinlock_t                      lock;
> +};
> +
> +struct hidma_desc {
> +       struct dma_async_tx_descriptor  desc;
> +       /* link list node for this channel*/
> +       struct list_head                node;
> +       u32                             tre_ch;
> +};
> +
> +static inline
> +struct hidma_dev *to_hidma_dev(struct dma_device *dmadev)
> +{
> +       return container_of(dmadev, struct hidma_dev, ddev);
> +}
> +
> +static inline
> +struct hidma_dev *to_hidma_dev_from_lldev(struct hidma_lldev **_lldevp)
> +{
> +       return container_of(_lldevp, struct hidma_dev, lldev);
> +}
> +
> +static inline
> +struct hidma_chan *to_hidma_chan(struct dma_chan *dmach)
> +{
> +       return container_of(dmach, struct hidma_chan, chan);
> +}
> +
> +static inline struct hidma_desc *
> +to_hidma_desc(struct dma_async_tx_descriptor *t)
> +{
> +       return container_of(t, struct hidma_desc, desc);
> +}
> +
> +static void hidma_free(struct hidma_dev *dmadev)
> +{
> +       dev_dbg(dmadev->ddev.dev, "free dmadev\n");
> +       INIT_LIST_HEAD(&dmadev->ddev.channels);
> +}
> +
> +static unsigned int nr_desc_prm;
> +module_param(nr_desc_prm, uint, 0644);
> +MODULE_PARM_DESC(nr_desc_prm,
> +                "number of descriptors (default: 0)");
> +
> +#define MAX_HIDMA_CHANNELS     64
> +static int event_channel_idx[MAX_HIDMA_CHANNELS] = {
> +       [0 ... (MAX_HIDMA_CHANNELS - 1)] = -1};
> +static unsigned int num_event_channel_idx;
> +module_param_array_named(event_channel_idx, event_channel_idx, int,
> +                       &num_event_channel_idx, 0644);
> +MODULE_PARM_DESC(event_channel_idx,
> +               "event channel index array for the notifications");
> +static atomic_t channel_ref_count;
> +
> +/* process completed descriptors */
> +static void hidma_process_completed(struct hidma_dev *mdma)
> +{
> +       dma_cookie_t last_cookie = 0;
> +       struct hidma_chan *mchan;
> +       struct hidma_desc *mdesc;
> +       struct dma_async_tx_descriptor *desc;
> +       unsigned long irqflags;
> +       LIST_HEAD(list);
> +       struct dma_chan *dmach = NULL;
> +
> +       list_for_each_entry(dmach, &mdma->ddev.channels,
> +                       device_node) {
> +               mchan = to_hidma_chan(dmach);
> +
> +               /* Get all completed descriptors */
> +               spin_lock_irqsave(&mchan->lock, irqflags);
> +               if (!list_empty(&mchan->completed))
> +                       list_splice_tail_init(&mchan->completed, &list);
> +               spin_unlock_irqrestore(&mchan->lock, irqflags);
> +
> +               if (list_empty(&list))
> +                       continue;

Redundant check. It's done in both list_for_each_entry() and
list_splice_tail_init().

> +
> +               /* Execute callbacks and run dependencies */
> +               list_for_each_entry(mdesc, &list, node) {
> +                       desc = &mdesc->desc;
> +
> +                       spin_lock_irqsave(&mchan->lock, irqflags);
> +                       dma_cookie_complete(desc);
> +                       spin_unlock_irqrestore(&mchan->lock, irqflags);
> +
> +                       if (desc->callback &&
> +                               (hidma_ll_status(mdma->lldev, mdesc->tre_ch)
> +                               == DMA_COMPLETE))
> +                               desc->callback(desc->callback_param);
> +
> +                       last_cookie = desc->cookie;
> +                       dma_run_dependencies(desc);
> +               }
> +
> +               /* Free descriptors */
> +               spin_lock_irqsave(&mchan->lock, irqflags);
> +               list_splice_tail_init(&list, &mchan->free);
> +               spin_unlock_irqrestore(&mchan->lock, irqflags);
> +       }
> +}
> +
> +/*
> + * Execute all queued DMA descriptors.
> + * This function is called either on the first transfer attempt in tx_submit
> + * or from the callback routine when one transfer is finished. It can only be
> + * called from a single location since both of places check active list to be
> + * empty and will immediately fill the active list while lock is held.
> + *
> + * Following requirements must be met while calling hidma_execute():
> + *     a) mchan->lock is locked,
> + *     b) mchan->active list contains multiple entries.
> + *     c) pm protected
> + */
> +static int hidma_execute(struct hidma_chan *mchan)
> +{
> +       struct hidma_dev *mdma = mchan->dmadev;
> +       int rc;
> +
> +       if (!hidma_ll_isenabled(mdma->lldev))
> +               return -ENODEV;
> +
> +       /* Start the transfer */
> +       if (!list_empty(&mchan->active))
> +               rc = hidma_ll_start(mdma->lldev);
> +
> +       return 0;
> +}
> +
> +/*
> + * Called once for each submitted descriptor.
> + * PM is locked once for each descriptor that is currently
> + * in execution.
> + */
> +static void hidma_callback(void *data)
> +{
> +       struct hidma_desc *mdesc = data;
> +       struct hidma_chan *mchan = to_hidma_chan(mdesc->desc.chan);
> +       unsigned long irqflags;
> +       struct dma_device *ddev = mchan->chan.device;
> +       struct hidma_dev *dmadev = to_hidma_dev(ddev);
> +       bool queued = false;
> +
> +       dev_dbg(dmadev->ddev.dev, "callback: data:0x%p\n", data);
> +
> +       spin_lock_irqsave(&mchan->lock, irqflags);
> +
> +       if (mdesc->node.next) {
> +               /* Delete from the active list, add to completed list */
> +               list_move_tail(&mdesc->node, &mchan->completed);
> +               queued = true;
> +       }
> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
> +
> +       hidma_process_completed(dmadev);
> +
> +       if (queued) {
> +               pm_runtime_mark_last_busy(dmadev->ddev.dev);
> +               pm_runtime_put_autosuspend(dmadev->ddev.dev);
> +       }
> +}
> +
> +static int hidma_chan_init(struct hidma_dev *dmadev, u32 dma_sig)
> +{
> +       struct hidma_chan *mchan;
> +       struct dma_device *ddev;
> +
> +       mchan = devm_kzalloc(dmadev->ddev.dev, sizeof(*mchan), GFP_KERNEL);
> +       if (!mchan)
> +               return -ENOMEM;
> +
> +       ddev = &dmadev->ddev;
> +       mchan->dma_sig = dma_sig;
> +       mchan->dmadev = dmadev;
> +       mchan->chan.device = ddev;
> +       dma_cookie_init(&mchan->chan);
> +
> +       INIT_LIST_HEAD(&mchan->free);
> +       INIT_LIST_HEAD(&mchan->prepared);
> +       INIT_LIST_HEAD(&mchan->active);
> +       INIT_LIST_HEAD(&mchan->completed);
> +
> +       spin_lock_init(&mchan->lock);
> +       list_add_tail(&mchan->chan.device_node, &ddev->channels);
> +       dmadev->ddev.chancnt++;
> +       return 0;
> +}
> +
> +static void hidma_issue_pending(struct dma_chan *dmach)
> +{

Wrong. It should actually start the transfer. tx_submit() just puts
the descriptor to a queue.

> +}
> +
> +static enum dma_status hidma_tx_status(struct dma_chan *dmach,
> +                                       dma_cookie_t cookie,
> +                                       struct dma_tx_state *txstate)
> +{
> +       enum dma_status ret;
> +       unsigned long irqflags;
> +       struct hidma_chan *mchan = to_hidma_chan(dmach);
> +
> +       spin_lock_irqsave(&mchan->lock, irqflags);

So, what are you protecting here? paused member, right?

> +       if (mchan->paused)
> +               ret = DMA_PAUSED;
> +       else
> +               ret = dma_cookie_status(dmach, cookie, txstate);

This one has no need to be under spin lock.

> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
> +
> +       return ret;
> +}
> +
> +/*
> + * Submit descriptor to hardware.
> + * Lock the PM for each descriptor we are sending.
> + */
> +static dma_cookie_t hidma_tx_submit(struct dma_async_tx_descriptor *txd)
> +{
> +       struct hidma_chan *mchan = to_hidma_chan(txd->chan);
> +       struct hidma_dev *dmadev = mchan->dmadev;
> +       struct hidma_desc *mdesc;
> +       unsigned long irqflags;
> +       dma_cookie_t cookie;
> +
> +       if (!hidma_ll_isenabled(dmadev->lldev))
> +               return -ENODEV;
> +
> +       pm_runtime_get_sync(dmadev->ddev.dev);

No point to do it here. It should be done on the function that
actually starts the transfer (see issue pending).

> +       mdesc = container_of(txd, struct hidma_desc, desc);
> +       spin_lock_irqsave(&mchan->lock, irqflags);
> +
> +       /* Move descriptor to active */
> +       list_move_tail(&mdesc->node, &mchan->active);
> +
> +       /* Update cookie */
> +       cookie = dma_cookie_assign(txd);
> +
> +       hidma_ll_queue_request(dmadev->lldev, mdesc->tre_ch);
> +       hidma_execute(mchan);
> +
> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
> +
> +       return cookie;
> +}
> +
> +static int hidma_alloc_chan_resources(struct dma_chan *dmach)
> +{
> +       struct hidma_chan *mchan = to_hidma_chan(dmach);
> +       struct hidma_dev *dmadev = mchan->dmadev;
> +       int rc = 0;
> +       struct hidma_desc *mdesc, *tmp;
> +       unsigned long irqflags;
> +       LIST_HEAD(descs);
> +       u32 i;
> +
> +       if (mchan->allocated)
> +               return 0;
> +
> +       /* Alloc descriptors for this channel */
> +       for (i = 0; i < dmadev->nr_descriptors; i++) {
> +               mdesc = kzalloc(sizeof(struct hidma_desc), GFP_KERNEL);
> +               if (!mdesc) {
> +                       dev_err(dmadev->ddev.dev, "Memory allocation error. ");
> +                       rc = -ENOMEM;
> +                       break;
> +               }
> +               dma_async_tx_descriptor_init(&mdesc->desc, dmach);
> +               mdesc->desc.flags = DMA_CTRL_ACK;
> +               mdesc->desc.tx_submit = hidma_tx_submit;
> +
> +               rc = hidma_ll_request(dmadev->lldev,
> +                               mchan->dma_sig, "DMA engine", hidma_callback,
> +                               mdesc, &mdesc->tre_ch);
> +               if (rc != 1) {

if (rc < 1) {

> +                       dev_err(dmach->device->dev,
> +                               "channel alloc failed at %u\n", i);

> +                       kfree(mdesc);
> +                       break;
> +               }
> +               list_add_tail(&mdesc->node, &descs);
> +       }
> +
> +       if (rc != 1) {

if (rc < 1)

> +               /* return the allocated descriptors */
> +               list_for_each_entry_safe(mdesc, tmp, &descs, node) {
> +                       hidma_ll_free(dmadev->lldev, mdesc->tre_ch);
> +                       kfree(mdesc);
> +               }
> +               return rc;
> +       }
> +
> +       spin_lock_irqsave(&mchan->lock, irqflags);
> +       list_splice_tail_init(&descs, &mchan->free);
> +       mchan->allocated = true;
> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
> +       dev_dbg(dmadev->ddev.dev,
> +               "allocated channel for %u\n", mchan->dma_sig);
> +       return rc;
> +}
> +
> +static void hidma_free_chan_resources(struct dma_chan *dmach)
> +{
> +       struct hidma_chan *mchan = to_hidma_chan(dmach);
> +       struct hidma_dev *mdma = mchan->dmadev;
> +       struct hidma_desc *mdesc, *tmp;
> +       unsigned long irqflags;
> +       LIST_HEAD(descs);
> +
> +       if (!list_empty(&mchan->prepared) ||
> +               !list_empty(&mchan->active) ||
> +               !list_empty(&mchan->completed)) {
> +               /* We have unfinished requests waiting.
> +                * Terminate the request from the hardware.
> +                */
> +               hidma_cleanup_pending_tre(mdma->lldev, 0x77, 0x77);

0x77 is magic.

> +
> +               /* Give enough time for completions to be called. */
> +               msleep(100);
> +       }
> +
> +       spin_lock_irqsave(&mchan->lock, irqflags);
> +       /* Channel must be idle */
> +       WARN_ON(!list_empty(&mchan->prepared));
> +       WARN_ON(!list_empty(&mchan->active));
> +       WARN_ON(!list_empty(&mchan->completed));
> +
> +       /* Move data */
> +       list_splice_tail_init(&mchan->free, &descs);
> +
> +       /* Free descriptors */
> +       list_for_each_entry_safe(mdesc, tmp, &descs, node) {
> +               hidma_ll_free(mdma->lldev, mdesc->tre_ch);
> +               list_del(&mdesc->node);
> +               kfree(mdesc);
> +       }
> +
> +       mchan->allocated = 0;
> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
> +       dev_dbg(mdma->ddev.dev, "freed channel for %u\n", mchan->dma_sig);
> +}
> +
> +
> +static struct dma_async_tx_descriptor *
> +hidma_prep_dma_memcpy(struct dma_chan *dmach, dma_addr_t dma_dest,
> +                       dma_addr_t dma_src, size_t len, unsigned long flags)
> +{
> +       struct hidma_chan *mchan = to_hidma_chan(dmach);
> +       struct hidma_desc *mdesc = NULL;
> +       struct hidma_dev *mdma = mchan->dmadev;
> +       unsigned long irqflags;
> +
> +       dev_dbg(mdma->ddev.dev,
> +               "memcpy: chan:%p dest:%pad src:%pad len:%zu\n", mchan,
> +               &dma_dest, &dma_src, len);
> +
> +       /* Get free descriptor */
> +       spin_lock_irqsave(&mchan->lock, irqflags);
> +       if (!list_empty(&mchan->free)) {
> +               mdesc = list_first_entry(&mchan->free, struct hidma_desc,
> +                                       node);
> +               list_del(&mdesc->node);
> +       }
> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
> +
> +       if (!mdesc)
> +               return NULL;
> +
> +       hidma_ll_set_transfer_params(mdma->lldev, mdesc->tre_ch,
> +                       dma_src, dma_dest, len, flags);
> +
> +       /* Place descriptor in prepared list */
> +       spin_lock_irqsave(&mchan->lock, irqflags);
> +       list_add_tail(&mdesc->node, &mchan->prepared);
> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
> +
> +       return &mdesc->desc;
> +}
> +
> +static int hidma_terminate_all(struct dma_chan *chan)
> +{
> +       struct hidma_dev *dmadev;
> +       LIST_HEAD(head);
> +       unsigned long irqflags;
> +       LIST_HEAD(list);
> +       struct hidma_desc *tmp, *mdesc = NULL;
> +       int rc = 0;

Useless assignment.

> +       struct hidma_chan *mchan;
> +
> +       mchan = to_hidma_chan(chan);
> +       dmadev = to_hidma_dev(mchan->chan.device);
> +       dev_dbg(dmadev->ddev.dev, "terminateall: chan:0x%p\n", mchan);
> +
> +       pm_runtime_get_sync(dmadev->ddev.dev);
> +       /* give completed requests a chance to finish */
> +       hidma_process_completed(dmadev);
> +
> +       spin_lock_irqsave(&mchan->lock, irqflags);
> +       list_splice_init(&mchan->active, &list);
> +       list_splice_init(&mchan->prepared, &list);
> +       list_splice_init(&mchan->completed, &list);
> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
> +
> +       /* this suspends the existing transfer */
> +       rc = hidma_ll_pause(dmadev->lldev);
> +       if (rc) {
> +               dev_err(dmadev->ddev.dev, "channel did not pause\n");
> +               goto out;
> +       }
> +
> +       /* return all user requests */
> +       list_for_each_entry_safe(mdesc, tmp, &list, node) {
> +               struct dma_async_tx_descriptor  *txd = &mdesc->desc;
> +               dma_async_tx_callback callback = mdesc->desc.callback;
> +               void *param = mdesc->desc.callback_param;
> +               enum dma_status status;
> +
> +               dma_descriptor_unmap(txd);
> +
> +               status = hidma_ll_status(dmadev->lldev, mdesc->tre_ch);
> +               /*
> +                * The API requires that no submissions are done from a
> +                * callback, so we don't need to drop the lock here
> +                */
> +               if (callback && (status == DMA_COMPLETE))
> +                       callback(param);
> +
> +               dma_run_dependencies(txd);
> +
> +               /* move myself to free_list */
> +               list_move(&mdesc->node, &mchan->free);
> +       }
> +
> +       /* reinitialize the hardware */
> +       rc = hidma_ll_setup(dmadev->lldev);
> +
> +out:
> +       pm_runtime_mark_last_busy(dmadev->ddev.dev);
> +       pm_runtime_put_autosuspend(dmadev->ddev.dev);
> +       return rc;
> +}
> +
> +static int hidma_pause(struct dma_chan *chan)
> +{
> +       struct hidma_chan *mchan;
> +       struct hidma_dev *dmadev;
> +
> +       mchan = to_hidma_chan(chan);
> +       dmadev = to_hidma_dev(mchan->chan.device);
> +       dev_dbg(dmadev->ddev.dev, "pause: chan:0x%p\n", mchan);
> +
> +       pm_runtime_get_sync(dmadev->ddev.dev);

Why it's here? Here is nothing to do with the device, move it to _pause().

> +       if (!mchan->paused) {
> +               if (hidma_ll_pause(dmadev->lldev))
> +                       dev_warn(dmadev->ddev.dev, "channel did not stop\n");
> +               mchan->paused = true;
> +       }
> +       pm_runtime_mark_last_busy(dmadev->ddev.dev);
> +       pm_runtime_put_autosuspend(dmadev->ddev.dev);
> +       return 0;
> +}
> +
> +static int hidma_resume(struct dma_chan *chan)
> +{
> +       struct hidma_chan *mchan;
> +       struct hidma_dev *dmadev;
> +       int rc = 0;
> +
> +       mchan = to_hidma_chan(chan);
> +       dmadev = to_hidma_dev(mchan->chan.device);
> +       dev_dbg(dmadev->ddev.dev, "resume: chan:0x%p\n", mchan);
> +
> +       pm_runtime_get_sync(dmadev->ddev.dev);

Ditto.

> +       if (mchan->paused) {
> +               rc = hidma_ll_resume(dmadev->lldev);
> +               if (!rc)
> +                       mchan->paused = false;
> +               else
> +                       dev_err(dmadev->ddev.dev,
> +                                       "failed to resume the channel");
> +       }
> +       pm_runtime_mark_last_busy(dmadev->ddev.dev);
> +       pm_runtime_put_autosuspend(dmadev->ddev.dev);
> +       return rc;
> +}
> +
> +static irqreturn_t hidma_chirq_handler(int chirq, void *arg)
> +{
> +       struct hidma_lldev **lldev_ptr = arg;
> +       irqreturn_t ret;
> +       struct hidma_dev *dmadev = to_hidma_dev_from_lldev(lldev_ptr);
> +
> +       pm_runtime_get_sync(dmadev->ddev.dev);

Hmm... Do you have shared IRQ line or wakeup able one?
Otherwise I can't see ways how device can generate interrupts.
If there is a case other than described, put comment why it might happen.

> +       ret = hidma_ll_inthandler(chirq, *lldev_ptr);
> +       pm_runtime_mark_last_busy(dmadev->ddev.dev);
> +       pm_runtime_put_autosuspend(dmadev->ddev.dev);
> +       return ret;
> +}
> +
> +static int hidma_probe(struct platform_device *pdev)
> +{
> +       struct hidma_dev *dmadev;
> +       int rc = 0;
> +       struct resource *trca_resource;
> +       struct resource *evca_resource;
> +       int chirq;
> +       int current_channel_index = atomic_read(&channel_ref_count);
> +
> +       pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT);
> +       pm_runtime_use_autosuspend(&pdev->dev);
> +       pm_runtime_set_active(&pdev->dev);
> +       pm_runtime_enable(&pdev->dev);
> +
> +       trca_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       if (!trca_resource) {
> +               rc = -ENODEV;
> +               goto bailout;
> +       }
> +
> +       evca_resource = platform_get_resource(pdev, IORESOURCE_MEM, 1);
> +       if (!evca_resource) {
> +               rc = -ENODEV;
> +               goto bailout;
> +       }


Consolidate these with devm_ioremap_resource();

> +
> +       /* This driver only handles the channel IRQs.
> +        * Common IRQ is handled by the management driver.
> +        */
> +       chirq = platform_get_irq(pdev, 0);
> +       if (chirq < 0) {
> +               rc = -ENODEV;
> +               goto bailout;
> +       }
> +
> +       dmadev = devm_kzalloc(&pdev->dev, sizeof(*dmadev), GFP_KERNEL);
> +       if (!dmadev) {
> +               rc = -ENOMEM;
> +               goto bailout;
> +       }
> +
> +       INIT_LIST_HEAD(&dmadev->ddev.channels);
> +       spin_lock_init(&dmadev->lock);
> +       dmadev->ddev.dev = &pdev->dev;
> +       pm_runtime_get_sync(dmadev->ddev.dev);
> +
> +       dma_cap_set(DMA_MEMCPY, dmadev->ddev.cap_mask);
> +       if (WARN_ON(!pdev->dev.dma_mask)) {
> +               rc = -ENXIO;
> +               goto dmafree;
> +       }
> +
> +       dmadev->dev_evca = devm_ioremap_resource(&pdev->dev,
> +                                               evca_resource);
> +       if (IS_ERR(dmadev->dev_evca)) {
> +               rc = -ENOMEM;
> +               goto dmafree;
> +       }
> +
> +       dmadev->dev_trca = devm_ioremap_resource(&pdev->dev,
> +                                               trca_resource);
> +       if (IS_ERR(dmadev->dev_trca)) {
> +               rc = -ENOMEM;
> +               goto dmafree;
> +       }
> +       dmadev->ddev.device_prep_dma_memcpy = hidma_prep_dma_memcpy;
> +       dmadev->ddev.device_alloc_chan_resources =
> +               hidma_alloc_chan_resources;
> +       dmadev->ddev.device_free_chan_resources = hidma_free_chan_resources;
> +       dmadev->ddev.device_tx_status = hidma_tx_status;
> +       dmadev->ddev.device_issue_pending = hidma_issue_pending;
> +       dmadev->ddev.device_pause = hidma_pause;
> +       dmadev->ddev.device_resume = hidma_resume;
> +       dmadev->ddev.device_terminate_all = hidma_terminate_all;
> +       dmadev->ddev.copy_align = 8;
> +
> +       device_property_read_u32(&pdev->dev, "desc-count",
> +                               &dmadev->nr_descriptors);
> +
> +       if (!dmadev->nr_descriptors && nr_desc_prm)
> +               dmadev->nr_descriptors = nr_desc_prm;
> +
> +       if (!dmadev->nr_descriptors)
> +               goto dmafree;
> +
> +       if (current_channel_index > MAX_HIDMA_CHANNELS)
> +               goto dmafree;
> +
> +       dmadev->evridx = -1;
> +       device_property_read_u32(&pdev->dev, "event-channel", &dmadev->evridx);
> +
> +       /* kernel command line override for the guest machine */
> +       if (event_channel_idx[current_channel_index] != -1)
> +               dmadev->evridx = event_channel_idx[current_channel_index];
> +
> +       if (dmadev->evridx == -1)
> +               goto dmafree;
> +
> +       /* Set DMA mask to 64 bits. */
> +       rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
> +       if (rc) {
> +               dev_warn(&pdev->dev, "unable to set coherent mask to 64");
> +               rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
> +       }
> +       if (rc)
> +               goto dmafree;
> +
> +       dmadev->lldev = hidma_ll_init(dmadev->ddev.dev,
> +                               dmadev->nr_descriptors, dmadev->dev_trca,
> +                               dmadev->dev_evca, dmadev->evridx);
> +       if (!dmadev->lldev) {
> +               rc = -EPROBE_DEFER;
> +               goto dmafree;
> +       }
> +
> +       rc = devm_request_irq(&pdev->dev, chirq, hidma_chirq_handler, 0,
> +                             "qcom-hidma", &dmadev->lldev);

Better to use request_irq().

> +       if (rc)
> +               goto uninit;
> +
> +       INIT_LIST_HEAD(&dmadev->ddev.channels);
> +       rc = hidma_chan_init(dmadev, 0);
> +       if (rc)
> +               goto uninit;
> +
> +       rc = dma_selftest_memcpy(&dmadev->ddev);
> +       if (rc)
> +               goto uninit;
> +
> +       rc = dma_async_device_register(&dmadev->ddev);
> +       if (rc)
> +               goto uninit;
> +
> +       dev_info(&pdev->dev, "HI-DMA engine driver registration complete\n");
> +       platform_set_drvdata(pdev, dmadev);
> +       pm_runtime_mark_last_busy(dmadev->ddev.dev);
> +       pm_runtime_put_autosuspend(dmadev->ddev.dev);
> +       atomic_inc(&channel_ref_count);
> +       return 0;
> +
> +uninit:
> +       hidma_ll_uninit(dmadev->lldev);
> +dmafree:
> +       if (dmadev)
> +               hidma_free(dmadev);
> +bailout:
> +       pm_runtime_disable(&pdev->dev);
> +       pm_runtime_put_sync_suspend(&pdev->dev);
> +       return rc;
> +}
> +
> +static int hidma_remove(struct platform_device *pdev)
> +{
> +       struct hidma_dev *dmadev = platform_get_drvdata(pdev);
> +
> +       dev_dbg(&pdev->dev, "removing\n");
> +       pm_runtime_get_sync(dmadev->ddev.dev);
> +
> +       dma_async_device_unregister(&dmadev->ddev);
> +       hidma_ll_uninit(dmadev->lldev);
> +       hidma_free(dmadev);
> +
> +       dev_info(&pdev->dev, "HI-DMA engine removed\n");
> +       pm_runtime_put_sync_suspend(&pdev->dev);
> +       pm_runtime_disable(&pdev->dev);
> +
> +       return 0;
> +}
> +
> +#if IS_ENABLED(CONFIG_ACPI)
> +static const struct acpi_device_id hidma_acpi_ids[] = {
> +       {"QCOM8061"},
> +       {},
> +};
> +#endif
> +
> +static const struct of_device_id hidma_match[] = {
> +       { .compatible = "qcom,hidma-1.0", },
> +       {},
> +};
> +MODULE_DEVICE_TABLE(of, hidma_match);
> +
> +static struct platform_driver hidma_driver = {
> +       .probe = hidma_probe,
> +       .remove = hidma_remove,
> +       .driver = {
> +               .name = "hidma",
> +               .of_match_table = hidma_match,
> +               .acpi_match_table = ACPI_PTR(hidma_acpi_ids),
> +       },
> +};
> +module_platform_driver(hidma_driver);
> +MODULE_LICENSE("GPL v2");
> diff --git a/drivers/dma/qcom_hidma.h b/drivers/dma/qcom_hidma.h
> new file mode 100644
> index 0000000..d671b39
> --- /dev/null
> +++ b/drivers/dma/qcom_hidma.h
> @@ -0,0 +1,45 @@
> +/*
> + * Qualcomm Technologies HIDMA data structures
> + *
> + * Copyright (c) 2014, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#ifndef QCOM_HIDMA_H
> +#define QCOM_HIDMA_H
> +
> +struct hidma_lldev;
> +struct hidma_llchan;
> +struct seq_file;
> +struct hidma_lldev;
> +
> +int hidma_ll_request(struct hidma_lldev *llhndl, u32 dev_id,
> +                       const char *dev_name,
> +                       void (*callback)(void *data), void *data, u32 *tre_ch);
> +
> +void hidma_ll_free(struct hidma_lldev *llhndl, u32 tre_ch);
> +enum dma_status hidma_ll_status(struct hidma_lldev *llhndl, u32 tre_ch);
> +bool hidma_ll_isenabled(struct hidma_lldev *llhndl);
> +int hidma_ll_queue_request(struct hidma_lldev *llhndl, u32 tre_ch);
> +int hidma_ll_start(struct hidma_lldev *llhndl);
> +int hidma_ll_pause(struct hidma_lldev *llhndl);
> +int hidma_ll_resume(struct hidma_lldev *llhndl);
> +void hidma_ll_set_transfer_params(struct hidma_lldev *llhndl, u32 tre_ch,
> +       dma_addr_t src, dma_addr_t dest, u32 len, u32 flags);
> +int hidma_ll_setup(struct hidma_lldev *lldev);
> +struct hidma_lldev *hidma_ll_init(struct device *dev, u32 max_channels,
> +                       void __iomem *trca, void __iomem *evca,
> +                       u8 evridx);
> +int hidma_ll_uninit(struct hidma_lldev *llhndl);
> +irqreturn_t hidma_ll_inthandler(int irq, void *arg);
> +void hidma_cleanup_pending_tre(struct hidma_lldev *llhndl, u8 err_info,
> +                               u8 err_code);
> +#endif
> diff --git a/drivers/dma/qcom_hidma_ll.c b/drivers/dma/qcom_hidma_ll.c
> new file mode 100644
> index 0000000..1e8b4aa
> --- /dev/null
> +++ b/drivers/dma/qcom_hidma_ll.c
> @@ -0,0 +1,972 @@
> +/*
> + * Qualcomm Technologies HIDMA DMA engine low level code
> + *
> + * Copyright (c) 2015, The Linux Foundation. All rights reserved.
> + *
> + * This program is free software; you can redistribute it and/or modify
> + * it under the terms of the GNU General Public License version 2 and
> + * only version 2 as published by the Free Software Foundation.
> + *
> + * This program is distributed in the hope that it will be useful,
> + * but WITHOUT ANY WARRANTY; without even the implied warranty of
> + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
> + * GNU General Public License for more details.
> + */
> +
> +#include <linux/dmaengine.h>
> +#include <linux/slab.h>
> +#include <linux/interrupt.h>
> +#include <linux/mm.h>
> +#include <linux/highmem.h>
> +#include <linux/dma-mapping.h>
> +#include <linux/delay.h>
> +#include <linux/atomic.h>
> +#include <linux/iopoll.h>
> +#include "qcom_hidma.h"
> +
> +#define TRE_SIZE                       32 /* each TRE is 32 bytes  */
> +#define EVRE_SIZE                      16 /* each EVRE is 16 bytes */
> +
> +#define TRCA_CTRLSTS_OFFSET            0x0
> +#define TRCA_RING_LOW_OFFSET           0x8
> +#define TRCA_RING_HIGH_OFFSET          0xC
> +#define TRCA_RING_LEN_OFFSET           0x10
> +#define TRCA_READ_PTR_OFFSET           0x18
> +#define TRCA_WRITE_PTR_OFFSET          0x20
> +#define TRCA_DOORBELL_OFFSET           0x400
> +
> +#define EVCA_CTRLSTS_OFFSET            0x0
> +#define EVCA_INTCTRL_OFFSET            0x4
> +#define EVCA_RING_LOW_OFFSET           0x8
> +#define EVCA_RING_HIGH_OFFSET          0xC
> +#define EVCA_RING_LEN_OFFSET           0x10
> +#define EVCA_READ_PTR_OFFSET           0x18
> +#define EVCA_WRITE_PTR_OFFSET          0x20
> +#define EVCA_DOORBELL_OFFSET           0x400
> +
> +#define EVCA_IRQ_STAT_OFFSET           0x100
> +#define EVCA_IRQ_CLR_OFFSET            0x108
> +#define EVCA_IRQ_EN_OFFSET             0x110
> +
> +#define TRE_CFG_IDX                    0
> +#define TRE_LEN_IDX                    1
> +#define TRE_SRC_LOW_IDX                2
> +#define TRE_SRC_HI_IDX                 3
> +#define TRE_DEST_LOW_IDX               4
> +#define TRE_DEST_HI_IDX                5
> +
> +#define EVRE_CFG_IDX                   0
> +#define EVRE_LEN_IDX                   1
> +#define EVRE_DEST_LOW_IDX              2
> +#define EVRE_DEST_HI_IDX               3
> +
> +#define EVRE_ERRINFO_BIT_POS           24
> +#define EVRE_CODE_BIT_POS              28
> +
> +#define EVRE_ERRINFO_MASK              0xF
> +#define EVRE_CODE_MASK                 0xF
> +
> +#define CH_CONTROL_MASK                0xFF
> +#define CH_STATE_MASK                  0xFF
> +#define CH_STATE_BIT_POS               0x8
> +
> +#define MAKE64(high, low) (((u64)(high) << 32) | (low))
> +
> +#define IRQ_EV_CH_EOB_IRQ_BIT_POS      0
> +#define IRQ_EV_CH_WR_RESP_BIT_POS      1
> +#define IRQ_TR_CH_TRE_RD_RSP_ER_BIT_POS 9
> +#define IRQ_TR_CH_DATA_RD_ER_BIT_POS   10
> +#define IRQ_TR_CH_DATA_WR_ER_BIT_POS   11
> +#define IRQ_TR_CH_INVALID_TRE_BIT_POS  14
> +
> +#define        ENABLE_IRQS (BIT(IRQ_EV_CH_EOB_IRQ_BIT_POS) | \
> +               BIT(IRQ_EV_CH_WR_RESP_BIT_POS) | \
> +               BIT(IRQ_TR_CH_TRE_RD_RSP_ER_BIT_POS) |   \
> +               BIT(IRQ_TR_CH_DATA_RD_ER_BIT_POS) |              \
> +               BIT(IRQ_TR_CH_DATA_WR_ER_BIT_POS) |              \
> +               BIT(IRQ_TR_CH_INVALID_TRE_BIT_POS))
> +
> +enum ch_command {
> +       CH_DISABLE = 0,
> +       CH_ENABLE = 1,
> +       CH_SUSPEND = 2,
> +       CH_RESET = 9,
> +};
> +
> +enum ch_state {
> +       CH_DISABLED = 0,
> +       CH_ENABLED = 1,
> +       CH_RUNNING = 2,
> +       CH_SUSPENDED = 3,
> +       CH_STOPPED = 4,
> +       CH_ERROR = 5,
> +       CH_IN_RESET = 9,
> +};
> +
> +enum tre_type {
> +       TRE_MEMCPY = 3,
> +       TRE_MEMSET = 4,
> +};
> +
> +enum evre_type {
> +       EVRE_DMA_COMPLETE = 0x23,
> +       EVRE_IMM_DATA = 0x24,
> +};
> +
> +enum err_code {
> +       EVRE_STATUS_COMPLETE = 1,
> +       EVRE_STATUS_ERROR = 4,
> +};
> +
> +struct hidma_tx_status {
> +       u8 err_info;                    /* error record in this transfer    */
> +       u8 err_code;                    /* completion code                  */
> +};
> +
> +struct hidma_lldev {
> +       bool initialized;               /* initialized flag               */
> +       u8 trch_state;                  /* trch_state of the device       */
> +       u8 evch_state;                  /* evch_state of the device       */
> +       u8 evridx;                      /* event channel to notify        */
> +       u32 nr_tres;                    /* max number of configs          */
> +       spinlock_t lock;                /* reentrancy                     */
> +       struct hidma_tre *trepool;      /* trepool of user configs */
> +       struct device *dev;             /* device                         */
> +       void __iomem *trca;             /* Transfer Channel address       */
> +       void __iomem *evca;             /* Event Channel address          */
> +       struct hidma_tre
> +               **pending_tre_list;     /* Pointers to pending TREs       */
> +       struct hidma_tx_status
> +               *tx_status_list;        /* Pointers to pending TREs status*/
> +       s32 pending_tre_count;          /* Number of TREs pending         */
> +
> +       void *tre_ring;         /* TRE ring                       */
> +       dma_addr_t tre_ring_handle;     /* TRE ring to be shared with HW  */
> +       u32 tre_ring_size;              /* Byte size of the ring          */
> +       u32 tre_processed_off;          /* last processed TRE              */
> +
> +       void *evre_ring;                /* EVRE ring                       */
> +       dma_addr_t evre_ring_handle;    /* EVRE ring to be shared with HW  */
> +       u32 evre_ring_size;             /* Byte size of the ring          */
> +       u32 evre_processed_off; /* last processed EVRE             */
> +
> +       u32 tre_write_offset;           /* TRE write location              */
> +};
> +
> +struct hidma_tre {
> +       atomic_t allocated;             /* if this channel is allocated     */
> +       bool queued;                    /* flag whether this is pending     */
> +       u16 status;                     /* status                           */
> +       u32 chidx;                      /* index of the tre         */
> +       u32 dma_sig;                    /* signature of the tre     */
> +       const char *dev_name;           /* name of the device               */
> +       void (*callback)(void *data);   /* requester callback               */
> +       void *data;                     /* Data associated with this channel*/
> +       struct hidma_lldev *lldev;      /* lldma device pointer             */
> +       u32 tre_local[TRE_SIZE / sizeof(u32) + 1]; /* TRE local copy        */
> +       struct tasklet_struct task;     /* task delivering notifications    */
> +       u32 tre_index;                  /* the offset where this was written*/
> +       u32 int_flags;                  /* interrupt flags*/
> +};
> +
> +void hidma_ll_free(struct hidma_lldev *lldev, u32 tre_ch)
> +{
> +       struct hidma_tre *tre;
> +
> +       if (tre_ch >= lldev->nr_tres) {
> +               dev_err(lldev->dev, "invalid TRE number in free:%d", tre_ch);
> +               return;
> +       }
> +
> +       tre = &lldev->trepool[tre_ch];
> +       if (atomic_read(&tre->allocated) != true) {
> +               dev_err(lldev->dev, "trying to free an unused TRE:%d",
> +                       tre_ch);
> +               return;
> +       }
> +
> +       atomic_set(&tre->allocated, 0);
> +       dev_dbg(lldev->dev, "free_dma: allocated:%d tre_ch:%d\n",
> +               atomic_read(&tre->allocated), tre_ch);
> +}
> +
> +int hidma_ll_request(struct hidma_lldev *lldev, u32 dma_sig,
> +                       const char *dev_name,
> +                       void (*callback)(void *data), void *data, u32 *tre_ch)
> +{
> +       u32 i;
> +       struct hidma_tre *tre = NULL;
> +       u32 *tre_local;
> +
> +       if (!tre_ch || !lldev)
> +               return -EINVAL;
> +
> +       /* need to have at least one empty spot in the queue */
> +       for (i = 0; i < lldev->nr_tres - 1; i++) {
> +               if (atomic_add_unless(&lldev->trepool[i].allocated, 1, 1))
> +                       break;
> +       }
> +
> +       if (i == (lldev->nr_tres - 1))
> +               return -ENOMEM;
> +
> +       tre = &lldev->trepool[i];
> +       tre->dma_sig = dma_sig;
> +       tre->dev_name = dev_name;
> +       tre->callback = callback;
> +       tre->data = data;
> +       tre->chidx = i;
> +       tre->status = 0;
> +       tre->queued = 0;
> +       lldev->tx_status_list[i].err_code = 0;
> +       tre->lldev = lldev;
> +       tre_local = &tre->tre_local[0];
> +       tre_local[TRE_CFG_IDX] = TRE_MEMCPY;
> +       tre_local[TRE_CFG_IDX] |= ((lldev->evridx & 0xFF) << 8);
> +       tre_local[TRE_CFG_IDX] |= BIT(16);      /* set IEOB */
> +       *tre_ch = i;
> +       if (callback)
> +               callback(data);
> +       return 1;
> +}
> +
> +/*
> + * Multiple TREs may be queued and waiting in the
> + * pending queue.
> + */
> +static void hidma_ll_tre_complete(unsigned long arg)
> +{
> +       struct hidma_tre *tre = (struct hidma_tre *)arg;
> +
> +       /* call the user if it has been read by the hardware*/
> +       if (tre->callback)
> +               tre->callback(tre->data);
> +}
> +
> +/*
> + * Called to handle the interrupt for the channel.
> + * Return a positive number if TRE or EVRE were consumed on this run.
> + * Return a positive number if there are pending TREs or EVREs.
> + * Return 0 if there is nothing to consume or no pending TREs/EVREs found.
> + */
> +static int hidma_handle_tre_completion(struct hidma_lldev *lldev)
> +{
> +       struct hidma_tre *tre;
> +       u32 evre_write_off;
> +       u32 evre_ring_size = lldev->evre_ring_size;
> +       u32 tre_ring_size = lldev->tre_ring_size;
> +       u32 num_completed = 0, tre_iterator, evre_iterator;
> +       unsigned long flags;
> +
> +       evre_write_off = readl_relaxed(lldev->evca + EVCA_WRITE_PTR_OFFSET);
> +       tre_iterator = lldev->tre_processed_off;
> +       evre_iterator = lldev->evre_processed_off;
> +
> +       if ((evre_write_off > evre_ring_size) ||
> +               ((evre_write_off % EVRE_SIZE) != 0)) {
> +               dev_err(lldev->dev, "HW reports invalid EVRE write offset\n");
> +               return 0;
> +       }
> +
> +       /* By the time control reaches here the number of EVREs and TREs
> +        * may not match. Only consume the ones that hardware told us.
> +        */
> +       while ((evre_iterator != evre_write_off)) {
> +               u32 *current_evre = lldev->evre_ring + evre_iterator;
> +               u32 cfg;
> +               u8 err_info;
> +
> +               spin_lock_irqsave(&lldev->lock, flags);
> +               tre = lldev->pending_tre_list[tre_iterator / TRE_SIZE];
> +               if (!tre) {
> +                       spin_unlock_irqrestore(&lldev->lock, flags);
> +                       dev_warn(lldev->dev,
> +                               "tre_index [%d] and tre out of sync\n",
> +                               tre_iterator / TRE_SIZE);
> +                       tre_iterator += TRE_SIZE;
> +                       if (tre_iterator >= tre_ring_size)
> +                               tre_iterator -= tre_ring_size;
> +                       evre_iterator += EVRE_SIZE;
> +                       if (evre_iterator >= evre_ring_size)
> +                               evre_iterator -= evre_ring_size;
> +
> +                       continue;
> +               }
> +               lldev->pending_tre_list[tre->tre_index] = NULL;
> +
> +               /* Keep track of pending TREs that SW is expecting to receive
> +                * from HW. We got one now. Decrement our counter.
> +                */
> +               lldev->pending_tre_count--;
> +               if (lldev->pending_tre_count < 0) {
> +                       dev_warn(lldev->dev,
> +                               "tre count mismatch on completion");
> +                       lldev->pending_tre_count = 0;
> +               }
> +
> +               spin_unlock_irqrestore(&lldev->lock, flags);
> +
> +               cfg = current_evre[EVRE_CFG_IDX];
> +               err_info = (cfg >> EVRE_ERRINFO_BIT_POS);
> +               err_info = err_info & EVRE_ERRINFO_MASK;
> +               lldev->tx_status_list[tre->chidx].err_info = err_info;
> +               lldev->tx_status_list[tre->chidx].err_code =
> +                       (cfg >> EVRE_CODE_BIT_POS) & EVRE_CODE_MASK;
> +               tre->queued = 0;
> +
> +               tasklet_schedule(&tre->task);
> +
> +               tre_iterator += TRE_SIZE;
> +               if (tre_iterator >= tre_ring_size)
> +                       tre_iterator -= tre_ring_size;
> +               evre_iterator += EVRE_SIZE;
> +               if (evre_iterator >= evre_ring_size)
> +                       evre_iterator -= evre_ring_size;
> +
> +               /* Read the new event descriptor written by the HW.
> +                * As we are processing the delivered events, other events
> +                * get queued to the SW for processing.
> +                */
> +               evre_write_off =
> +                       readl_relaxed(lldev->evca + EVCA_WRITE_PTR_OFFSET);
> +               num_completed++;
> +       }
> +
> +       if (num_completed) {
> +               u32 evre_read_off = (lldev->evre_processed_off +
> +                               EVRE_SIZE * num_completed);
> +               u32 tre_read_off = (lldev->tre_processed_off +
> +                               TRE_SIZE * num_completed);
> +
> +               evre_read_off = evre_read_off % evre_ring_size;
> +               tre_read_off = tre_read_off % tre_ring_size;
> +
> +               writel(evre_read_off, lldev->evca + EVCA_DOORBELL_OFFSET);
> +
> +               /* record the last processed tre offset */
> +               lldev->tre_processed_off = tre_read_off;
> +               lldev->evre_processed_off = evre_read_off;
> +       }
> +
> +       return num_completed;
> +}
> +
> +void hidma_cleanup_pending_tre(struct hidma_lldev *lldev, u8 err_info,
> +                               u8 err_code)
> +{
> +       u32 tre_iterator;
> +       struct hidma_tre *tre;
> +       u32 tre_ring_size = lldev->tre_ring_size;
> +       int num_completed = 0;
> +       u32 tre_read_off;
> +       unsigned long flags;
> +
> +       tre_iterator = lldev->tre_processed_off;
> +       while (lldev->pending_tre_count) {
> +               int tre_index = tre_iterator / TRE_SIZE;
> +
> +               spin_lock_irqsave(&lldev->lock, flags);
> +               tre = lldev->pending_tre_list[tre_index];
> +               if (!tre) {
> +                       spin_unlock_irqrestore(&lldev->lock, flags);
> +                       tre_iterator += TRE_SIZE;
> +                       if (tre_iterator >= tre_ring_size)
> +                               tre_iterator -= tre_ring_size;
> +                       continue;
> +               }
> +               lldev->pending_tre_list[tre_index] = NULL;
> +               lldev->pending_tre_count--;
> +               if (lldev->pending_tre_count < 0) {
> +                       dev_warn(lldev->dev,
> +                               "tre count mismatch on completion");
> +                       lldev->pending_tre_count = 0;
> +               }
> +               spin_unlock_irqrestore(&lldev->lock, flags);
> +
> +               lldev->tx_status_list[tre->chidx].err_info = err_info;
> +               lldev->tx_status_list[tre->chidx].err_code = err_code;
> +               tre->queued = 0;
> +
> +               tasklet_schedule(&tre->task);
> +
> +               tre_iterator += TRE_SIZE;
> +               if (tre_iterator >= tre_ring_size)
> +                       tre_iterator -= tre_ring_size;
> +
> +               num_completed++;
> +       }
> +       tre_read_off = (lldev->tre_processed_off +
> +                       TRE_SIZE * num_completed);
> +
> +       tre_read_off = tre_read_off % tre_ring_size;
> +
> +       /* record the last processed tre offset */
> +       lldev->tre_processed_off = tre_read_off;
> +}
> +
> +static int hidma_ll_reset(struct hidma_lldev *lldev)
> +{
> +       u32 val;
> +       int ret;
> +
> +       val = readl_relaxed(lldev->trca + TRCA_CTRLSTS_OFFSET);
> +       val = val & ~(CH_CONTROL_MASK << 16);
> +       val = val | (CH_RESET << 16);
> +       writel(val, lldev->trca + TRCA_CTRLSTS_OFFSET);
> +
> +       /* Delay 10ms after reset to allow DMA logic to quiesce.
> +        * Do a polled read up to 1ms and 10ms maximum.
> +        */
> +       ret = readl_poll_timeout(lldev->trca + TRCA_CTRLSTS_OFFSET, val,
> +               (((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_DISABLED),
> +               1000, 10000);
> +       if (ret) {
> +               dev_err(lldev->dev,
> +                       "transfer channel did not reset\n");
> +               return ret;
> +       }
> +
> +       val = readl_relaxed(lldev->evca + EVCA_CTRLSTS_OFFSET);
> +       val = val & ~(CH_CONTROL_MASK << 16);
> +       val = val | (CH_RESET << 16);
> +       writel(val, lldev->evca + EVCA_CTRLSTS_OFFSET);
> +
> +       /* Delay 10ms after reset to allow DMA logic to quiesce.
> +        * Do a polled read up to 1ms and 10ms maximum.
> +        */
> +       ret = readl_poll_timeout(lldev->evca + EVCA_CTRLSTS_OFFSET, val,
> +               (((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_DISABLED),
> +               1000, 10000);
> +       if (ret)
> +               return ret;
> +
> +       lldev->trch_state = CH_DISABLED;
> +       lldev->evch_state = CH_DISABLED;
> +       return 0;
> +}
> +
> +static void hidma_ll_enable_irq(struct hidma_lldev *lldev, u32 irq_bits)
> +{
> +       writel(irq_bits, lldev->evca + EVCA_IRQ_EN_OFFSET);
> +       dev_dbg(lldev->dev, "enableirq\n");
> +}
> +
> +/*
> + * The interrupt handler for HIDMA will try to consume as many pending
> + * EVRE from the event queue as possible. Each EVRE has an associated
> + * TRE that holds the user interface parameters. EVRE reports the
> + * result of the transaction. Hardware guarantees ordering between EVREs
> + * and TREs. We use last processed offset to figure out which TRE is
> + * associated with which EVRE. If two TREs are consumed by HW, the EVREs
> + * are in order in the event ring.
> + * This handler will do a one pass for consuming EVREs. Other EVREs may
> + * be delivered while we are working. It will try to consume incoming
> + * EVREs one more time and return.
> + * For unprocessed EVREs, hardware will trigger another interrupt until
> + * all the interrupt bits are cleared.
> + *
> + * Hardware guarantees that by the time interrupt is observed, all data
> + * transactions in flight are delivered to their respective places and
> + * are visible to the CPU.
> + *
> + * On demand paging for IOMMU is only supported for PCIe via PRI
> + * (Page Request Interface) not for HIDMA. All other hardware instances
> + * including HIDMA work on pinned DMA addresses.
> + *
> + */
> +static void hidma_ll_int_handler_internal(struct hidma_lldev *lldev)
> +{
> +       u32 status;
> +       u32 enable;
> +       u32 cause;
> +       int repeat = 2;
> +       unsigned long timeout;
> +
> +       status = readl_relaxed(lldev->evca + EVCA_IRQ_STAT_OFFSET);
> +       enable = readl_relaxed(lldev->evca + EVCA_IRQ_EN_OFFSET);
> +       cause = status & enable;
> +
> +       if ((cause & (BIT(IRQ_TR_CH_INVALID_TRE_BIT_POS))) ||
> +                       (cause & BIT(IRQ_TR_CH_TRE_RD_RSP_ER_BIT_POS)) ||
> +                       (cause & BIT(IRQ_EV_CH_WR_RESP_BIT_POS)) ||
> +                       (cause & BIT(IRQ_TR_CH_DATA_RD_ER_BIT_POS)) ||
> +                       (cause & BIT(IRQ_TR_CH_DATA_WR_ER_BIT_POS))) {
> +               u8 err_code = EVRE_STATUS_ERROR;
> +               u8 err_info = 0xFF;
> +
> +               /* Clear out pending interrupts */
> +               writel(cause, lldev->evca + EVCA_IRQ_CLR_OFFSET);
> +
> +               dev_err(lldev->dev,
> +                       "error 0x%x, resetting...\n", cause);
> +
> +               hidma_cleanup_pending_tre(lldev, err_info, err_code);
> +
> +               /* reset the channel for recovery */
> +               if (hidma_ll_setup(lldev)) {
> +                       dev_err(lldev->dev,
> +                               "channel reinitialize failed after error\n");
> +                       return;
> +               }
> +               hidma_ll_enable_irq(lldev, ENABLE_IRQS);
> +               return;
> +       }
> +
> +       /* Try to consume as many EVREs as possible.
> +        * skip this loop if the interrupt is spurious.
> +        */
> +       while (cause && repeat) {
> +               unsigned long start = jiffies;
> +
> +               /* This timeout should be sufficent for core to finish */
> +               timeout = start + msecs_to_jiffies(500);
> +
> +               while (lldev->pending_tre_count) {
> +                       hidma_handle_tre_completion(lldev);
> +                       if (time_is_before_jiffies(timeout)) {
> +                               dev_warn(lldev->dev,
> +                                       "ISR timeout %lx-%lx from %lx [%d]\n",
> +                                       jiffies, timeout, start,
> +                                       lldev->pending_tre_count);
> +                               break;
> +                       }
> +               }
> +
> +               /* We consumed TREs or there are pending TREs or EVREs. */
> +               writel_relaxed(cause, lldev->evca + EVCA_IRQ_CLR_OFFSET);
> +
> +               /* Another interrupt might have arrived while we are
> +                * processing this one. Read the new cause.
> +                */
> +               status = readl_relaxed(lldev->evca + EVCA_IRQ_STAT_OFFSET);
> +               enable = readl_relaxed(lldev->evca + EVCA_IRQ_EN_OFFSET);
> +               cause = status & enable;
> +
> +               repeat--;
> +       }
> +}
> +
> +
> +static int hidma_ll_enable(struct hidma_lldev *lldev)
> +{
> +       u32 val;
> +       int ret;
> +
> +       val = readl_relaxed(lldev->evca + EVCA_CTRLSTS_OFFSET);
> +       val &= ~(CH_CONTROL_MASK << 16);
> +       val |= (CH_ENABLE << 16);
> +       writel(val, lldev->evca + EVCA_CTRLSTS_OFFSET);
> +
> +       ret = readl_poll_timeout(lldev->evca + EVCA_CTRLSTS_OFFSET, val,
> +               ((((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_ENABLED) ||
> +               (((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_RUNNING)),
> +               1000, 10000);
> +       if (ret) {
> +               dev_err(lldev->dev,
> +                       "event channel did not get enabled\n");
> +               return ret;
> +       }
> +
> +       val = readl_relaxed(lldev->trca + TRCA_CTRLSTS_OFFSET);
> +       val = val & ~(CH_CONTROL_MASK << 16);
> +       val = val | (CH_ENABLE << 16);
> +       writel(val, lldev->trca + TRCA_CTRLSTS_OFFSET);
> +
> +       ret = readl_poll_timeout(lldev->trca + TRCA_CTRLSTS_OFFSET, val,
> +               ((((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_ENABLED) ||
> +               (((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_RUNNING)),
> +               1000, 10000);
> +       if (ret) {
> +               dev_err(lldev->dev,
> +                       "transfer channel did not get enabled\n");
> +               return ret;
> +       }
> +
> +       lldev->trch_state = CH_ENABLED;
> +       lldev->evch_state = CH_ENABLED;
> +
> +       return 0;
> +}
> +
> +int hidma_ll_resume(struct hidma_lldev *lldev)
> +{
> +       return hidma_ll_enable(lldev);
> +}
> +
> +static int hidma_ll_hw_start(struct hidma_lldev *lldev)
> +{
> +       int rc = 0;
> +       unsigned long irqflags;
> +
> +       spin_lock_irqsave(&lldev->lock, irqflags);
> +       writel(lldev->tre_write_offset, lldev->trca + TRCA_DOORBELL_OFFSET);
> +       spin_unlock_irqrestore(&lldev->lock, irqflags);
> +
> +       return rc;
> +}
> +
> +bool hidma_ll_isenabled(struct hidma_lldev *lldev)
> +{
> +       u32 val;
> +
> +       val = readl_relaxed(lldev->trca + TRCA_CTRLSTS_OFFSET);
> +       lldev->trch_state = (val >> CH_STATE_BIT_POS) & CH_STATE_MASK;
> +       val = readl_relaxed(lldev->evca + EVCA_CTRLSTS_OFFSET);
> +       lldev->evch_state = (val >> CH_STATE_BIT_POS) & CH_STATE_MASK;
> +
> +       /* both channels have to be enabled before calling this function*/
> +       if (((lldev->trch_state == CH_ENABLED) ||
> +               (lldev->trch_state == CH_RUNNING)) &&
> +               ((lldev->evch_state == CH_ENABLED) ||
> +                       (lldev->evch_state == CH_RUNNING)))
> +               return true;
> +
> +       dev_dbg(lldev->dev, "channels are not enabled or are in error state");
> +       return false;
> +}
> +
> +int hidma_ll_queue_request(struct hidma_lldev *lldev, u32 tre_ch)
> +{
> +       struct hidma_tre *tre;
> +       int rc = 0;
> +       unsigned long flags;
> +
> +       tre = &lldev->trepool[tre_ch];
> +
> +       /* copy the TRE into its location in the TRE ring */
> +       spin_lock_irqsave(&lldev->lock, flags);
> +       tre->tre_index = lldev->tre_write_offset / TRE_SIZE;
> +       lldev->pending_tre_list[tre->tre_index] = tre;
> +       memcpy(lldev->tre_ring + lldev->tre_write_offset, &tre->tre_local[0],
> +               TRE_SIZE);
> +       lldev->tx_status_list[tre->chidx].err_code = 0;
> +       lldev->tx_status_list[tre->chidx].err_info = 0;
> +       tre->queued = 1;
> +       lldev->pending_tre_count++;
> +       lldev->tre_write_offset = (lldev->tre_write_offset + TRE_SIZE)
> +                               % lldev->tre_ring_size;
> +       spin_unlock_irqrestore(&lldev->lock, flags);
> +       return rc;
> +}
> +
> +int hidma_ll_start(struct hidma_lldev *lldev)
> +{
> +       return hidma_ll_hw_start(lldev);
> +}
> +
> +/*
> + * Note that even though we stop this channel
> + * if there is a pending transaction in flight
> + * it will complete and follow the callback.
> + * This request will prevent further requests
> + * to be made.
> + */
> +int hidma_ll_pause(struct hidma_lldev *lldev)
> +{
> +       u32 val;
> +       int ret;
> +
> +       val = readl_relaxed(lldev->evca + EVCA_CTRLSTS_OFFSET);
> +       lldev->evch_state = (val >> CH_STATE_BIT_POS) & CH_STATE_MASK;
> +       val = readl_relaxed(lldev->trca + TRCA_CTRLSTS_OFFSET);
> +       lldev->trch_state = (val >> CH_STATE_BIT_POS) & CH_STATE_MASK;
> +
> +       /* already suspended by this OS */
> +       if ((lldev->trch_state == CH_SUSPENDED) ||
> +               (lldev->evch_state == CH_SUSPENDED))
> +               return 0;
> +
> +       /* already stopped by the manager */
> +       if ((lldev->trch_state == CH_STOPPED) ||
> +               (lldev->evch_state == CH_STOPPED))
> +               return 0;
> +
> +       val = readl_relaxed(lldev->trca + TRCA_CTRLSTS_OFFSET);
> +       val = val & ~(CH_CONTROL_MASK << 16);
> +       val = val | (CH_SUSPEND << 16);
> +       writel(val, lldev->trca + TRCA_CTRLSTS_OFFSET);
> +
> +       /* Start the wait right after the suspend is confirmed.
> +        * Do a polled read up to 1ms and 10ms maximum.
> +        */
> +       ret = readl_poll_timeout(lldev->trca + TRCA_CTRLSTS_OFFSET, val,
> +               (((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_SUSPENDED),
> +               1000, 10000);
> +       if (ret)
> +               return ret;
> +
> +       val = readl_relaxed(lldev->evca + EVCA_CTRLSTS_OFFSET);
> +       val = val & ~(CH_CONTROL_MASK << 16);
> +       val = val | (CH_SUSPEND << 16);
> +       writel(val, lldev->evca + EVCA_CTRLSTS_OFFSET);
> +
> +       /* Start the wait right after the suspend is confirmed
> +        * Delay up to 10ms after reset to allow DMA logic to quiesce.
> +        */
> +       ret = readl_poll_timeout(lldev->evca + EVCA_CTRLSTS_OFFSET, val,
> +               (((val >> CH_STATE_BIT_POS) & CH_STATE_MASK) == CH_SUSPENDED),
> +               1000, 10000);
> +       if (ret)
> +               return ret;
> +
> +       lldev->trch_state = CH_SUSPENDED;
> +       lldev->evch_state = CH_SUSPENDED;
> +       dev_dbg(lldev->dev, "stop\n");
> +
> +       return 0;
> +}
> +
> +void hidma_ll_set_transfer_params(struct hidma_lldev *lldev, u32 tre_ch,
> +       dma_addr_t src, dma_addr_t dest, u32 len, u32 flags)
> +{
> +       struct hidma_tre *tre;
> +       u32 *tre_local;
> +
> +       if (tre_ch >= lldev->nr_tres) {
> +               dev_err(lldev->dev,
> +                       "invalid TRE number in transfer params:%d", tre_ch);
> +               return;
> +       }
> +
> +       tre = &lldev->trepool[tre_ch];
> +       if (atomic_read(&tre->allocated) != true) {
> +               dev_err(lldev->dev,
> +                       "trying to set params on an unused TRE:%d", tre_ch);
> +               return;
> +       }
> +
> +       tre_local = &tre->tre_local[0];
> +       tre_local[TRE_LEN_IDX] = len;
> +       tre_local[TRE_SRC_LOW_IDX] = lower_32_bits(src);
> +       tre_local[TRE_SRC_HI_IDX] = upper_32_bits(src);
> +       tre_local[TRE_DEST_LOW_IDX] = lower_32_bits(dest);
> +       tre_local[TRE_DEST_HI_IDX] = upper_32_bits(dest);
> +       tre->int_flags = flags;
> +
> +       dev_dbg(lldev->dev, "transferparams: tre_ch:%d %pap->%pap len:%u\n",
> +               tre_ch, &src, &dest, len);
> +}
> +
> +/* Called during initialization and after an error condition
> + * to restore hardware state.
> + */
> +int hidma_ll_setup(struct hidma_lldev *lldev)
> +{
> +       int rc;
> +       u64 addr;
> +       u32 val;
> +       u32 nr_tres = lldev->nr_tres;
> +
> +       lldev->pending_tre_count = 0;
> +       lldev->tre_processed_off = 0;
> +       lldev->evre_processed_off = 0;
> +       lldev->tre_write_offset = 0;
> +
> +       /* disable interrupts */
> +       hidma_ll_enable_irq(lldev, 0);
> +
> +       /* clear all pending interrupts */
> +       val = readl_relaxed(lldev->evca + EVCA_IRQ_STAT_OFFSET);
> +       writel_relaxed(val, lldev->evca + EVCA_IRQ_CLR_OFFSET);
> +
> +       rc = hidma_ll_reset(lldev);
> +       if (rc)
> +               return rc;
> +
> +       /* Clear all pending interrupts again.
> +        * Otherwise, we observe reset complete interrupts.
> +        */
> +       val = readl_relaxed(lldev->evca + EVCA_IRQ_STAT_OFFSET);
> +       writel_relaxed(val, lldev->evca + EVCA_IRQ_CLR_OFFSET);
> +
> +       /* disable interrupts again after reset */
> +       hidma_ll_enable_irq(lldev, 0);
> +
> +       addr = lldev->tre_ring_handle;
> +       writel_relaxed(lower_32_bits(addr),
> +                       lldev->trca + TRCA_RING_LOW_OFFSET);
> +       writel_relaxed(upper_32_bits(addr),
> +                       lldev->trca + TRCA_RING_HIGH_OFFSET);
> +       writel_relaxed(lldev->tre_ring_size,
> +                       lldev->trca + TRCA_RING_LEN_OFFSET);
> +
> +       addr = lldev->evre_ring_handle;
> +       writel_relaxed(lower_32_bits(addr),
> +                       lldev->evca + EVCA_RING_LOW_OFFSET);
> +       writel_relaxed(upper_32_bits(addr),
> +                       lldev->evca + EVCA_RING_HIGH_OFFSET);
> +       writel_relaxed(EVRE_SIZE * nr_tres,
> +                       lldev->evca + EVCA_RING_LEN_OFFSET);
> +
> +       /* support IRQ only for now */
> +       val = readl_relaxed(lldev->evca + EVCA_INTCTRL_OFFSET);
> +       val = val & ~(0xF);
> +       val = val | 0x1;
> +       writel_relaxed(val, lldev->evca + EVCA_INTCTRL_OFFSET);
> +
> +       /* clear all pending interrupts and enable them*/
> +       writel_relaxed(ENABLE_IRQS, lldev->evca + EVCA_IRQ_CLR_OFFSET);
> +       hidma_ll_enable_irq(lldev, ENABLE_IRQS);
> +
> +       rc = hidma_ll_enable(lldev);
> +       if (rc)
> +               return rc;
> +
> +       return rc;
> +}
> +
> +struct hidma_lldev *hidma_ll_init(struct device *dev, u32 nr_tres,
> +                       void __iomem *trca, void __iomem *evca,
> +                       u8 evridx)
> +{
> +       u32 required_bytes;
> +       struct hidma_lldev *lldev;
> +       int rc;
> +       u32 i;
> +
> +       if (!trca || !evca || !dev || !nr_tres)
> +               return NULL;
> +
> +       /* need at least four TREs */
> +       if (nr_tres < 4)
> +               return NULL;
> +
> +       /* need an extra space */
> +       nr_tres += 1;
> +
> +       lldev = devm_kzalloc(dev, sizeof(struct hidma_lldev), GFP_KERNEL);
> +       if (!lldev)
> +               return NULL;
> +
> +       lldev->evca = evca;
> +       lldev->trca = trca;
> +       lldev->dev = dev;
> +       required_bytes = sizeof(struct hidma_tre) * nr_tres;
> +       lldev->trepool = devm_kzalloc(lldev->dev, required_bytes, GFP_KERNEL);
> +       if (!lldev->trepool)
> +               return NULL;
> +
> +       required_bytes = sizeof(lldev->pending_tre_list[0]) * nr_tres;
> +       lldev->pending_tre_list = devm_kzalloc(dev, required_bytes,
> +                                       GFP_KERNEL);
> +       if (!lldev->pending_tre_list)
> +               return NULL;
> +
> +       required_bytes = sizeof(lldev->tx_status_list[0]) * nr_tres;
> +       lldev->tx_status_list = devm_kzalloc(dev, required_bytes, GFP_KERNEL);
> +       if (!lldev->tx_status_list)
> +               return NULL;
> +
> +       lldev->tre_ring = dmam_alloc_coherent(dev, (TRE_SIZE + 1) * nr_tres,
> +                                       &lldev->tre_ring_handle, GFP_KERNEL);
> +       if (!lldev->tre_ring)
> +               return NULL;
> +
> +       memset(lldev->tre_ring, 0, (TRE_SIZE + 1) * nr_tres);
> +       lldev->tre_ring_size = TRE_SIZE * nr_tres;
> +       lldev->nr_tres = nr_tres;
> +
> +       /* the TRE ring has to be TRE_SIZE aligned */
> +       if (!IS_ALIGNED(lldev->tre_ring_handle, TRE_SIZE)) {
> +               u8  tre_ring_shift;
> +
> +               tre_ring_shift = lldev->tre_ring_handle % TRE_SIZE;
> +               tre_ring_shift = TRE_SIZE - tre_ring_shift;
> +               lldev->tre_ring_handle += tre_ring_shift;
> +               lldev->tre_ring += tre_ring_shift;
> +       }
> +
> +       lldev->evre_ring = dmam_alloc_coherent(dev, (EVRE_SIZE + 1) * nr_tres,
> +                                       &lldev->evre_ring_handle, GFP_KERNEL);
> +       if (!lldev->evre_ring)
> +               return NULL;
> +
> +       memset(lldev->evre_ring, 0, (EVRE_SIZE + 1) * nr_tres);
> +       lldev->evre_ring_size = EVRE_SIZE * nr_tres;
> +
> +       /* the EVRE ring has to be EVRE_SIZE aligned */
> +       if (!IS_ALIGNED(lldev->evre_ring_handle, EVRE_SIZE)) {
> +               u8  evre_ring_shift;
> +
> +               evre_ring_shift = lldev->evre_ring_handle % EVRE_SIZE;
> +               evre_ring_shift = EVRE_SIZE - evre_ring_shift;
> +               lldev->evre_ring_handle += evre_ring_shift;
> +               lldev->evre_ring += evre_ring_shift;
> +       }
> +       lldev->nr_tres = nr_tres;
> +       lldev->evridx = evridx;
> +
> +       rc = hidma_ll_setup(lldev);
> +       if (rc)
> +               return NULL;
> +
> +       spin_lock_init(&lldev->lock);
> +       for (i = 0; i < nr_tres; i++)
> +               tasklet_init(&lldev->trepool[i].task, hidma_ll_tre_complete,
> +                               (unsigned long)&lldev->trepool[i]);
> +       lldev->initialized = 1;
> +       hidma_ll_enable_irq(lldev, ENABLE_IRQS);
> +       return lldev;
> +}
> +
> +int hidma_ll_uninit(struct hidma_lldev *lldev)
> +{
> +       int rc = 0;
> +       u32 val;
> +
> +       if (!lldev)
> +               return -ENODEV;
> +
> +       if (lldev->initialized) {
> +               u32 required_bytes;
> +               u32 i;
> +
> +               lldev->initialized = 0;
> +
> +               required_bytes = sizeof(struct hidma_tre) * lldev->nr_tres;
> +               for (i = 0; i < lldev->nr_tres; i++)
> +                       tasklet_kill(&lldev->trepool[i].task);
> +               memset(lldev->trepool, 0, required_bytes);
> +               lldev->trepool = NULL;
> +               lldev->pending_tre_count = 0;
> +               lldev->tre_write_offset = 0;
> +
> +               rc = hidma_ll_reset(lldev);
> +
> +               /* Clear all pending interrupts again.
> +                * Otherwise, we observe reset complete interrupts.
> +                */
> +               val = readl_relaxed(lldev->evca + EVCA_IRQ_STAT_OFFSET);
> +               writel_relaxed(val, lldev->evca + EVCA_IRQ_CLR_OFFSET);
> +               hidma_ll_enable_irq(lldev, 0);
> +       }
> +       return rc;
> +}
> +
> +irqreturn_t hidma_ll_inthandler(int chirq, void *arg)
> +{
> +       struct hidma_lldev *lldev = arg;
> +
> +       hidma_ll_int_handler_internal(lldev);
> +       return IRQ_HANDLED;
> +}
> +
> +enum dma_status hidma_ll_status(struct hidma_lldev *lldev, u32 tre_ch)
> +{
> +       enum dma_status ret = DMA_ERROR;
> +       unsigned long flags;
> +       u8 err_code;
> +
> +       spin_lock_irqsave(&lldev->lock, flags);
> +       err_code = lldev->tx_status_list[tre_ch].err_code;
> +
> +       if (err_code & EVRE_STATUS_COMPLETE)
> +               ret = DMA_COMPLETE;
> +       else if (err_code & EVRE_STATUS_ERROR)
> +               ret = DMA_ERROR;
> +       else
> +               ret = DMA_IN_PROGRESS;
> +       spin_unlock_irqrestore(&lldev->lock, flags);
> +
> +       return ret;
> +}
> --
> Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/



-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02  6:07 ` [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver Sinan Kaya
  2015-11-02 15:57   ` Rob Herring
@ 2015-11-03 10:22   ` Andy Shevchenko
  2015-11-04  0:47     ` Sinan Kaya
  1 sibling, 1 reply; 41+ messages in thread
From: Andy Shevchenko @ 2015-11-03 10:22 UTC (permalink / raw)
  To: Sinan Kaya
  Cc: dmaengine, timur, cov, jcm, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel

On Mon, Nov 2, 2015 at 8:07 AM, Sinan Kaya <okaya@codeaurora.org> wrote:
> The Qualcomm Technologies HIDMA device has been designed
> to support virtualization technology. The driver has been
> divided into two to follow the hardware design. The management
> driver is executed in hypervisor context and is the main
> management entity for all channels provided by the device.
> The channel driver is executed in the hypervisor/guest OS
> context.
>
> All channel devices get probed in the hypervisor
> context during power up. They show up as DMA engine
> channels. Then, before starting the virtualization; each
> channel device is unbound from the hypervisor by VFIO
> and assigned to the guest machine for control.
>
> This management driver will be used by the system
> admin to monitor/reset the execution state of the DMA
> channels. This will be the management interface.


> +static ssize_t qcom_hidma_mgmt_evtena(struct file *file,
> +       const char __user *user_buf, size_t count, loff_t *ppos)
> +{
> +       char temp_buf[16+1];

16 is a magic number. Make it defined constant.

> +       struct qcom_hidma_mgmt_dev *mgmtdev = file->f_inode->i_private;
> +       u32 event;
> +       ssize_t ret;
> +       unsigned long val;
> +
> +       temp_buf[16] = '\0';
> +       if (copy_from_user(temp_buf, user_buf, min_t(int, count, 16)))
> +               goto out;
> +
> +       ret = kstrtoul(temp_buf, 16, &val);

kstrtoul_from_user?

> +       if (ret) {
> +               dev_warn(&mgmtdev->pdev->dev, "unknown event\n");
> +               goto out;
> +       }
> +
> +       event = (u32)val & HW_EVENTS_CFG_MASK;
> +
> +       pm_runtime_get_sync(&mgmtdev->pdev->dev);
> +       writel(event, mgmtdev->dev_virtaddr + HW_EVENTS_CFG_OFFSET);
> +       pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
> +       pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
> +out:
> +       return count;
> +}
> +
> +static const struct file_operations qcom_hidma_mgmt_evtena_fops = {
> +       .write = qcom_hidma_mgmt_evtena,
> +};
> +
> +struct fileinfo {
> +       char *name;
> +       int mode;
> +       const struct file_operations *ops;
> +};
> +
> +static struct fileinfo files[] = {
> +       {"info", S_IRUGO, &qcom_hidma_mgmt_fops},
> +       {"err", S_IRUGO,  &qcom_hidma_mgmt_err_fops},
> +       {"mhiderrclr", S_IWUSR, &qcom_hidma_mgmt_mhiderr_clrfops},
> +       {"evterrclr", S_IWUSR, &qcom_hidma_mgmt_evterr_clrfops},
> +       {"ideerrclr", S_IWUSR, &qcom_hidma_mgmt_ideerr_clrfops},
> +       {"odeerrclr", S_IWUSR, &qcom_hidma_mgmt_odeerr_clrfops},
> +       {"msierrclr", S_IWUSR, &qcom_hidma_mgmt_msierr_clrfops},
> +       {"treerrclr", S_IWUSR, &qcom_hidma_mgmt_treerr_clrfops},
> +       {"evtena", S_IWUSR, &qcom_hidma_mgmt_evtena_fops},
> +};
> +
> +static void qcom_hidma_mgmt_debug_uninit(struct qcom_hidma_mgmt_dev *mgmtdev)
> +{
> +       debugfs_remove_recursive(mgmtdev->debugfs);
> +}
> +
> +static int qcom_hidma_mgmt_debug_init(struct qcom_hidma_mgmt_dev *mgmtdev)
> +{
> +       int rc = 0;
> +       u32 i;
> +       struct dentry   *fs_entry;
> +
> +       mgmtdev->debugfs = debugfs_create_dir(dev_name(&mgmtdev->pdev->dev),
> +                                               NULL);
> +       if (!mgmtdev->debugfs) {
> +               rc = -ENODEV;
> +               return rc;
> +       }
> +
> +       for (i = 0; i < ARRAY_SIZE(files); i++) {
> +               fs_entry = debugfs_create_file(files[i].name,
> +                                       files[i].mode, mgmtdev->debugfs,
> +                                       mgmtdev, files[i].ops);
> +               if (!fs_entry) {
> +                       rc = -ENOMEM;
> +                       goto cleanup;
> +               }
> +       }
> +
> +       return 0;
> +cleanup:
> +       qcom_hidma_mgmt_debug_uninit(mgmtdev);
> +       return rc;
> +}
> +#else
> +static void qcom_hidma_mgmt_debug_uninit(struct qcom_hidma_mgmt_dev *mgmtdev)
> +{
> +}
> +static int qcom_hidma_mgmt_debug_init(struct qcom_hidma_mgmt_dev *mgmtdev)
> +{
> +       return 0;
> +}
> +#endif
> +
> +static int qcom_hidma_mgmt_setup(struct qcom_hidma_mgmt_dev *mgmtdev)
> +{
> +       u32 val;
> +       u32 i;
> +
> +       val = readl(mgmtdev->dev_virtaddr + MAX_BUS_REQ_LEN_OFFSET);
> +       val = val &
> +               ~(MAX_BUS_REQ_LEN_MASK << MAX_BUS_WR_REQ_BIT_POS);
> +       val = val |
> +               (mgmtdev->max_write_request << MAX_BUS_WR_REQ_BIT_POS);
> +       val = val & ~(MAX_BUS_REQ_LEN_MASK);
> +       val = val | (mgmtdev->max_read_request);
> +       writel(val, mgmtdev->dev_virtaddr + MAX_BUS_REQ_LEN_OFFSET);
> +
> +       val = readl(mgmtdev->dev_virtaddr + MAX_XACTIONS_OFFSET);
> +       val = val &
> +               ~(MAX_WR_XACTIONS_MASK << MAX_WR_XACTIONS_BIT_POS);
> +       val = val |
> +               (mgmtdev->max_wr_xactions << MAX_WR_XACTIONS_BIT_POS);
> +       val = val & ~(MAX_RD_XACTIONS_MASK);
> +       val = val | (mgmtdev->max_rd_xactions);
> +       writel(val, mgmtdev->dev_virtaddr + MAX_XACTIONS_OFFSET);
> +
> +       mgmtdev->sw_version = readl(mgmtdev->dev_virtaddr + SW_VERSION_OFFSET);
> +
> +       for (i = 0; i < mgmtdev->dma_channels; i++) {
> +               val = readl(mgmtdev->dev_virtaddr + QOS_N_OFFSET + (4 * i));
> +               val = val & ~(1 << PRIORITY_BIT_POS);
> +               val = val |
> +                       ((mgmtdev->priority[i] & 0x1) << PRIORITY_BIT_POS);
> +               val = val & ~(WEIGHT_MASK << WRR_BIT_POS);
> +               val = val
> +                       | ((mgmtdev->weight[i] & WEIGHT_MASK) << WRR_BIT_POS);
> +               writel(val, mgmtdev->dev_virtaddr + QOS_N_OFFSET + (4 * i));
> +       }
> +
> +       val = readl(mgmtdev->dev_virtaddr + CHRESET_TIMEOUT_OFFSET);
> +       val = val & ~CHRESET_TIMEOUUT_MASK;
> +       val = val | (mgmtdev->chreset_timeout & CHRESET_TIMEOUUT_MASK);
> +       writel(val, mgmtdev->dev_virtaddr + CHRESET_TIMEOUT_OFFSET);
> +
> +       val = readl(mgmtdev->dev_virtaddr + CFG_OFFSET);
> +       val = val | 1;
> +       writel(val, mgmtdev->dev_virtaddr + CFG_OFFSET);
> +
> +       return 0;
> +}
> +
> +static int qcom_hidma_mgmt_probe(struct platform_device *pdev)
> +{
> +       struct resource *dma_resource;
> +       int irq;
> +       int rc;
> +       u32 i;
> +       struct qcom_hidma_mgmt_dev *mgmtdev;

Better move this line to the top of definition block.

> +
> +       pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT);
> +       pm_runtime_use_autosuspend(&pdev->dev);
> +       pm_runtime_set_active(&pdev->dev);
> +       pm_runtime_enable(&pdev->dev);
> +       dma_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
> +       if (!dma_resource) {
> +               dev_err(&pdev->dev, "No memory resources found\n");
> +               rc = -ENODEV;
> +               goto out;
> +       }

Consolidate with devm_ioremap_resource()

> +
> +       irq = platform_get_irq(pdev, 0);
> +       if (irq < 0) {
> +               dev_err(&pdev->dev, "irq resources not found\n");
> +               rc = -ENODEV;
> +               goto out;
> +       }
> +
> +       mgmtdev = devm_kzalloc(&pdev->dev, sizeof(*mgmtdev), GFP_KERNEL);
> +       if (!mgmtdev) {
> +               rc = -ENOMEM;
> +               goto out;
> +       }
> +
> +       mgmtdev->pdev = pdev;
> +
> +       pm_runtime_get_sync(&mgmtdev->pdev->dev);
> +       mgmtdev->dev_addrsize = resource_size(dma_resource);
> +       mgmtdev->dev_virtaddr = devm_ioremap_resource(&pdev->dev,
> +                                                       dma_resource);
> +       if (IS_ERR(mgmtdev->dev_virtaddr)) {
> +               dev_err(&pdev->dev, "can't map i/o memory at %pa\n",
> +                       &dma_resource->start);
> +               rc = -ENOMEM;
> +               goto out;
> +       }
> +
> +       if (device_property_read_u32(&pdev->dev, "dma-channels",
> +               &mgmtdev->dma_channels)) {
> +               dev_err(&pdev->dev, "number of channels missing\n");
> +               rc = -EINVAL;
> +               goto out;
> +       }
> +
> +       if (device_property_read_u32(&pdev->dev, "channel-reset-timeout",
> +               &mgmtdev->chreset_timeout)) {
> +               dev_err(&pdev->dev, "channel reset timeout missing\n");
> +               rc = -EINVAL;
> +               goto out;
> +       }
> +
> +       if (device_property_read_u32(&pdev->dev, "max-write-burst-bytes",
> +               &mgmtdev->max_write_request)) {
> +               dev_err(&pdev->dev, "max-write-burst-bytes missing\n");
> +               rc = -EINVAL;
> +               goto out;
> +       }
> +       if ((mgmtdev->max_write_request != 128) &&
> +               (mgmtdev->max_write_request != 256) &&
> +               (mgmtdev->max_write_request != 512) &&
> +               (mgmtdev->max_write_request != 1024)) {

is_power_of_2()  && min/max ?

> +               dev_err(&pdev->dev, "invalid write request %d\n",
> +                       mgmtdev->max_write_request);
> +               rc = -EINVAL;
> +               goto out;
> +       }
> +
> +       if (device_property_read_u32(&pdev->dev, "max-read-burst-bytes",
> +               &mgmtdev->max_read_request)) {
> +               dev_err(&pdev->dev, "max-read-burst-bytes missing\n");
> +               rc = -EINVAL;
> +               goto out;
> +       }
> +
> +       if ((mgmtdev->max_read_request != 128) &&
> +               (mgmtdev->max_read_request != 256) &&
> +               (mgmtdev->max_read_request != 512) &&
> +               (mgmtdev->max_read_request != 1024)) {

Ditto.

> +               dev_err(&pdev->dev, "invalid read request %d\n",
> +                       mgmtdev->max_read_request);
> +               rc = -EINVAL;
> +               goto out;
> +       }
> +
> +       if (device_property_read_u32(&pdev->dev, "max-write-transactions",
> +               &mgmtdev->max_wr_xactions)) {
> +               dev_err(&pdev->dev, "max-write-transactions missing\n");
> +               rc = -EINVAL;
> +               goto out;
> +       }
> +
> +       if (device_property_read_u32(&pdev->dev, "max-read-transactions",
> +               &mgmtdev->max_rd_xactions)) {
> +               dev_err(&pdev->dev, "max-read-transactions missing\n");
> +               rc = -EINVAL;
> +               goto out;
> +       }
> +
> +       mgmtdev->priority = devm_kcalloc(&pdev->dev,
> +               mgmtdev->dma_channels, sizeof(*mgmtdev->priority), GFP_KERNEL);
> +       if (!mgmtdev->priority) {
> +               rc = -ENOMEM;
> +               goto out;
> +       }
> +
> +       mgmtdev->weight = devm_kcalloc(&pdev->dev,
> +               mgmtdev->dma_channels, sizeof(*mgmtdev->weight), GFP_KERNEL);
> +       if (!mgmtdev->weight) {
> +               rc = -ENOMEM;
> +               goto out;
> +       }
> +
> +       if (device_property_read_u32_array(&pdev->dev, "channel-priority",
> +                               mgmtdev->priority, mgmtdev->dma_channels)) {
> +               dev_err(&pdev->dev, "channel-priority missing\n");
> +               rc = -EINVAL;
> +               goto out;
> +       }
> +
> +       if (device_property_read_u32_array(&pdev->dev, "channel-weight",
> +                               mgmtdev->weight, mgmtdev->dma_channels)) {
> +               dev_err(&pdev->dev, "channel-weight missing\n");
> +               rc = -EINVAL;
> +               goto out;
> +       }
> +
> +       for (i = 0; i < mgmtdev->dma_channels; i++) {
> +               if (mgmtdev->weight[i] > 15) {

15 is magic.

> +                       dev_err(&pdev->dev,
> +                               "max value of weight can be 15.\n");
> +                       rc = -EINVAL;
> +                       goto out;
> +               }
> +
> +               /* weight needs to be at least one */
> +               if (mgmtdev->weight[i] == 0)
> +                       mgmtdev->weight[i] = 1;
> +       }
> +
> +       rc = qcom_hidma_mgmt_setup(mgmtdev);
> +       if (rc) {
> +               dev_err(&pdev->dev, "setup failed\n");
> +               goto out;
> +       }
> +
> +       rc = qcom_hidma_mgmt_debug_init(mgmtdev);
> +       if (rc) {
> +               dev_err(&pdev->dev, "debugfs init failed\n");
> +               goto out;
> +       }
> +
> +       dev_info(&pdev->dev,
> +               "HI-DMA engine management driver registration complete\n");

You may put some useful information here, otherwise looks like a debug message.

> +       platform_set_drvdata(pdev, mgmtdev);
> +       pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
> +       pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
> +       return 0;
> +out:
> +       pm_runtime_disable(&pdev->dev);
> +       pm_runtime_put_sync_suspend(&pdev->dev);
> +       return rc;
> +}
> +
> +static int qcom_hidma_mgmt_remove(struct platform_device *pdev)
> +{
> +       struct qcom_hidma_mgmt_dev *mgmtdev = platform_get_drvdata(pdev);
> +
> +       pm_runtime_get_sync(&mgmtdev->pdev->dev);
> +       qcom_hidma_mgmt_debug_uninit(mgmtdev);
> +       pm_runtime_put_sync_suspend(&pdev->dev);
> +       pm_runtime_disable(&pdev->dev);
> +
> +       dev_info(&pdev->dev, "HI-DMA engine management driver removed\n");

Useless message.

> +       return 0;
> +}
> +
> +#if IS_ENABLED(CONFIG_ACPI)
> +static const struct acpi_device_id qcom_hidma_mgmt_acpi_ids[] = {
> +       {"QCOM8060"},
> +       {},
> +};
> +#endif
> +
> +static const struct of_device_id qcom_hidma_mgmt_match[] = {
> +       { .compatible = "qcom,hidma-mgmt-1.0", },
> +       {},
> +};
> +MODULE_DEVICE_TABLE(of, qcom_hidma_mgmt_match);
> +
> +static struct platform_driver qcom_hidma_mgmt_driver = {
> +       .probe = qcom_hidma_mgmt_probe,
> +       .remove = qcom_hidma_mgmt_remove,
> +       .driver = {
> +               .name = "hidma-mgmt",
> +               .of_match_table = qcom_hidma_mgmt_match,
> +               .acpi_match_table = ACPI_PTR(qcom_hidma_mgmt_acpi_ids),
> +       },
> +};
> +module_platform_driver(qcom_hidma_mgmt_driver);
> +MODULE_LICENSE("GPL v2");
> --
> Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project
>
> --
> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html
> Please read the FAQ at  http://www.tux.org/lkml/



-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03  4:18     ` Sinan Kaya
  2015-11-03  6:30       ` Vinod Koul
@ 2015-11-03 14:31       ` Timur Tabi
  2015-11-03 16:10         ` Vinod Koul
  1 sibling, 1 reply; 41+ messages in thread
From: Timur Tabi @ 2015-11-03 14:31 UTC (permalink / raw)
  To: Sinan Kaya, Vinod Koul; +Cc: dmaengine, cov, jcm, Dan Williams, linux-kernel

Sinan Kaya wrote:
>
> Almost all DMA engine drivers come with some sort of selftest code
> called from probe. I followed the same design pattern.

As others have said, it appears that's outdated.

Is there a real possibility that the hardware could fail the test 
without trashing the system?  It seems that if the DMA engine is faulty, 
it won't "politely" fail.  The whole system will crash or some memory 
will get corrupted and you won't know it.

-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the
Code Aurora Forum, hosted by The Linux Foundation.

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03  7:44         ` Dan Williams
  2015-11-03  8:22           ` Andy Shevchenko
@ 2015-11-03 15:51           ` Sinan Kaya
  2015-11-03 16:06           ` Vinod Koul
  2 siblings, 0 replies; 41+ messages in thread
From: Sinan Kaya @ 2015-11-03 15:51 UTC (permalink / raw)
  To: Dan Williams, Vinod Koul; +Cc: dmaengine, timur, cov, jcm, linux-kernel



On 11/3/2015 2:44 AM, Dan Williams wrote:
> On Mon, Nov 2, 2015 at 10:30 PM, Vinod Koul <vinod.koul@intel.com> wrote:
>> On Mon, Nov 02, 2015 at 11:18:37PM -0500, Sinan Kaya wrote:
>>>
>>>
>>> On 11/2/2015 11:15 PM, Vinod Koul wrote:
>>>> On Mon, Nov 02, 2015 at 01:07:38AM -0500, Sinan Kaya wrote:
>>>>> This patch adds supporting utility functions
>>>>> for selftest. The intention is to share the self
>>>>> test code between different drivers.
>>>>>
>>>>> Supported test cases include:
>>>>> 1. dma_map_single
>>>>> 2. streaming DMA
>>>>> 3. coherent DMA
>>>>> 4. scatter-gather DMA
>>>>
>>>> This seems quite similar to dmatest, any reason why you cannot use/enhance
>>>> that?
>>>>
>>> Dmatest is a standalone kernel module intended for stress testing
>>> DMA engines from userspace with N number of threads and M size
>>> combinations etc.
>>>
>>> This one; on the other hand, is selftest to verify hardware is
>>> working as expected during power up.
>>>
>>> Almost all DMA engine drivers come with some sort of selftest code
>>> called from probe. I followed the same design pattern.
>>
>> which ones ?
>>
>>>


>>> I think the goal is to remove the duplicate self test code in all
>>> drivers over time.
>>
>> and what prevents us from having common selftest plus dmatest code. Most of
>> the code here to do selftest is _same_ dmaengine routine code used in
>> dmatest
>>
>> We can have common code which is used for dmatest as well as selftest. I do
>> not want to see same code duplicated..
>
> Originally ioatdma and iop-adma had local self tests before Haavard
> created dmatest.  I agree having the drivers also do a test each boot
> is redundant, but then again dmatest is not automatic and I saw the
> local self test catch an interrupt setup regression.

I see the following files still have some sort of self test code in them.

ioat\dma.c
ioat\dma_v2.c
ioat\dma_v3.c
iop-adma.c
mv_xor.c


>
> Maybe you could arrange for drivers to do a quick autorun through
> dmatest on load if dmatest is enabled, but otherwise load without
> testing?  Just my 2 cents from a dmaengine spectator.
>

I'm on the same boat. Almost all kernel configurations that I have seen 
do not have dmatest enabled. Dmatest is considered debug only and is not 
included into the kernel binaries.

I have no problem for moving the code from one location to the other but 
I still want to be able to run self-test code in mission mode before 
enabling DMA as today.

I'm open to suggestions.

-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03  7:44         ` Dan Williams
  2015-11-03  8:22           ` Andy Shevchenko
  2015-11-03 15:51           ` Sinan Kaya
@ 2015-11-03 16:06           ` Vinod Koul
  2 siblings, 0 replies; 41+ messages in thread
From: Vinod Koul @ 2015-11-03 16:06 UTC (permalink / raw)
  To: Dan Williams; +Cc: Sinan Kaya, dmaengine, timur, cov, jcm, linux-kernel

On Mon, Nov 02, 2015 at 11:44:23PM -0800, Dan Williams wrote:
> Originally ioatdma and iop-adma had local self tests before Haavard
> created dmatest.  I agree having the drivers also do a test each boot
> is redundant, but then again dmatest is not automatic and I saw the
> local self test catch an interrupt setup regression.

Well the question here is not about why selftest but rather code duplication
and using same routines for both selftest as well as dmatest
> 
> Maybe you could arrange for drivers to do a quick autorun through
> dmatest on load if dmatest is enabled, but otherwise load without
> testing?

That would be a good thing to do, but then most distro just enable all
config options, so this should be protected for "test" builds

>  Just my 2 cents from a dmaengine spectator.

Thats an understatement :)

> --
> To unsubscribe from this list: send the line "unsubscribe dmaengine" in
> the body of a message to majordomo@vger.kernel.org
> More majordomo info at  http://vger.kernel.org/majordomo-info.html

-- 
~Vinod

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03  8:22           ` Andy Shevchenko
@ 2015-11-03 16:08             ` Vinod Koul
  2015-11-05  2:42               ` Sinan Kaya
  0 siblings, 1 reply; 41+ messages in thread
From: Vinod Koul @ 2015-11-03 16:08 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: Dan Williams, Sinan Kaya, dmaengine, timur, cov, jcm, linux-kernel

On Tue, Nov 03, 2015 at 10:22:25AM +0200, Andy Shevchenko wrote:
> On Tue, Nov 3, 2015 at 9:44 AM, Dan Williams <dan.j.williams@intel.com> wrote:
> > On Mon, Nov 2, 2015 at 10:30 PM, Vinod Koul <vinod.koul@intel.com> wrote:
> >> On Mon, Nov 02, 2015 at 11:18:37PM -0500, Sinan Kaya wrote:
> >>> On 11/2/2015 11:15 PM, Vinod Koul wrote:
> >>> >On Mon, Nov 02, 2015 at 01:07:38AM -0500, Sinan Kaya wrote:
> 
> 
> >>> This one; on the other hand, is selftest to verify hardware is
> >>> working as expected during power up.
> 
> I prefer to have such nice case by run time parameter (let's say
> common to all DMA Engine drivers)
> 
> >> We can have common code which is used for dmatest as well as selftest. I do
> >> not want to see same code duplicated..
> 
> First thought was to merge this to dmatest, however, some DMA
> controllers doesn't have a memcpy capability.

The tests should be based on capablity of memcpy

> How would we test them?

That part is tricky, you need to do so thru clients, spi/audio/serial etc

-- 
~Vinod

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03 14:31       ` Timur Tabi
@ 2015-11-03 16:10         ` Vinod Koul
  2015-11-03 16:28           ` Sinan Kaya
  2015-11-03 16:48           ` Timur Tabi
  0 siblings, 2 replies; 41+ messages in thread
From: Vinod Koul @ 2015-11-03 16:10 UTC (permalink / raw)
  To: Timur Tabi; +Cc: Sinan Kaya, dmaengine, cov, jcm, Dan Williams, linux-kernel

On Tue, Nov 03, 2015 at 08:31:57AM -0600, Timur Tabi wrote:
> Sinan Kaya wrote:
> >
> >Almost all DMA engine drivers come with some sort of selftest code
> >called from probe. I followed the same design pattern.
> 
> As others have said, it appears that's outdated.
> 
> Is there a real possibility that the hardware could fail the test
> without trashing the system?  It seems that if the DMA engine is
> faulty, it won't "politely" fail.  The whole system will crash or
> some memory will get corrupted and you won't know it.

Failing politely would be right thing to do. If DMA starts sending data to
anywhere in system memory due to bug or wrong addresses we can't do
anything to prevent that

-- 
~Vinod

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03 16:10         ` Vinod Koul
@ 2015-11-03 16:28           ` Sinan Kaya
  2015-11-03 16:46             ` Timur Tabi
  2015-11-03 16:48           ` Timur Tabi
  1 sibling, 1 reply; 41+ messages in thread
From: Sinan Kaya @ 2015-11-03 16:28 UTC (permalink / raw)
  To: Vinod Koul, Timur Tabi; +Cc: dmaengine, cov, jcm, Dan Williams, linux-kernel



On 11/3/2015 11:10 AM, Vinod Koul wrote:
> On Tue, Nov 03, 2015 at 08:31:57AM -0600, Timur Tabi wrote:
>> Sinan Kaya wrote:
>>>
>>> Almost all DMA engine drivers come with some sort of selftest code
>>> called from probe. I followed the same design pattern.
>>
>> As others have said, it appears that's outdated.
>>
>> Is there a real possibility that the hardware could fail the test
>> without trashing the system?  It seems that if the DMA engine is
>> faulty, it won't "politely" fail.  The whole system will crash or
>> some memory will get corrupted and you won't know it.
>
> Failing politely would be right thing to do. If DMA starts sending data to
> anywhere in system memory due to bug or wrong addresses we can't do
> anything to prevent that
>
I have seen failures in three cases so far. These are the reasons, why I 
want to keep these runtime tests around.

1. Bug in ARM64 DMA subsystem.
2. Bug in IOMMU driver.
3. Bug in another newly introduced driver. The new driver would hog the 
CPU and won't allow HIDMA interrupts to execute. Therefore, the test 
times out.

In my code, when test fails; I abort all transactions in flight and 
shutdown and deregister the HIDMA driver.

Of course, there is a small window of oppurtunity; where DMA can dump 
data to incorrect place if HW was faulty right before I abort the 
transaction.

-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03 16:28           ` Sinan Kaya
@ 2015-11-03 16:46             ` Timur Tabi
  2015-11-03 16:57               ` Sinan Kaya
  0 siblings, 1 reply; 41+ messages in thread
From: Timur Tabi @ 2015-11-03 16:46 UTC (permalink / raw)
  To: Sinan Kaya, Vinod Koul; +Cc: dmaengine, cov, jcm, Dan Williams, linux-kernel

Sinan Kaya wrote:
> 1. Bug in ARM64 DMA subsystem.
> 2. Bug in IOMMU driver.
> 3. Bug in another newly introduced driver. The new driver would hog the
> CPU and won't allow HIDMA interrupts to execute. Therefore, the test
> times out.

Which driver?

Wouldn't these problems already be exposed by dmatest?  I was asking 
whether it's possible that, every now and then, your DMA internal test 
could fail and then the driver would unload.  I'm not talking about hard 
bugs in other code that always causes the DMA driver test to fail.

-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the
Code Aurora Forum, hosted by The Linux Foundation.

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03 16:10         ` Vinod Koul
  2015-11-03 16:28           ` Sinan Kaya
@ 2015-11-03 16:48           ` Timur Tabi
  1 sibling, 0 replies; 41+ messages in thread
From: Timur Tabi @ 2015-11-03 16:48 UTC (permalink / raw)
  To: Vinod Koul; +Cc: Sinan Kaya, dmaengine, cov, jcm, Dan Williams, linux-kernel

Vinod Koul wrote:
> Failing politely would be right thing to do. If DMA starts sending data to
> anywhere in system memory due to bug or wrong addresses we can't do
> anything to prevent that

My point is that I have a hard time believing that a DMA failure would 
likely result the driver politely detecting the problem and exiting 
properly.  I just don't think it's realistic to expect that.

Now maybe it's better for a catastrophic failure to occur when the 
driver loads instead of during some critical network operation.

-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the
Code Aurora Forum, hosted by The Linux Foundation.

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03 16:46             ` Timur Tabi
@ 2015-11-03 16:57               ` Sinan Kaya
  0 siblings, 0 replies; 41+ messages in thread
From: Sinan Kaya @ 2015-11-03 16:57 UTC (permalink / raw)
  To: Timur Tabi, Vinod Koul; +Cc: dmaengine, cov, jcm, Dan Williams, linux-kernel



On 11/3/2015 11:46 AM, Timur Tabi wrote:
> Sinan Kaya wrote:
>> 1. Bug in ARM64 DMA subsystem.
>> 2. Bug in IOMMU driver.
>> 3. Bug in another newly introduced driver. The new driver would hog the
>> CPU and won't allow HIDMA interrupts to execute. Therefore, the test
>> times out.
>
> Which driver?
Some other driver that is not upstream yet.

>
> Wouldn't these problems already be exposed by dmatest?  I was asking
> whether it's possible that, every now and then, your DMA internal test
> could fail and then the driver would unload.  I'm not talking about hard
> bugs in other code that always causes the DMA driver test to fail.
>
The point is that dmatest is a kernel module that requires manual 
interaction. It does not run automatically and is generally used by the 
dma engine driver developer during development only.

Other developers like UART, SATA, USB, IOMMU etc. They don't care about 
dmatest and I have seen their changes broke self-test multiple times. I 
see the value of self test all the time.

I can make dma-self test a new kernel module. It could discover all DMA 
devices with MEMCPY capability and run a self test on them. We could 
tell the self test code deregister all DMA devices that fail the test.

I can also make it kernel command line dependent and disabled by 
default. Those who want this functionality all the time can change their 
kernel command line.

I hope this addresses concerns.

-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 3/3] dma: add Qualcomm Technologies HIDMA channel driver
  2015-11-03 10:10   ` Andy Shevchenko
@ 2015-11-04  0:07     ` Sinan Kaya
  2015-11-04 17:44       ` Andy Shevchenko
  0 siblings, 1 reply; 41+ messages in thread
From: Sinan Kaya @ 2015-11-04  0:07 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: dmaengine, timur, cov, jcm, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel



On 11/3/2015 5:10 AM, Andy Shevchenko wrote:
> On Mon, Nov 2, 2015 at 8:07 AM, Sinan Kaya <okaya@codeaurora.org> wrote:
>> This patch adds support for hidma engine. The driver
>> consists of two logical blocks. The DMA engine interface
>> and the low-level interface. The hardware only supports
>> memcpy/memset and this driver only support memcpy
>> interface. HW and driver doesn't support slave interface.
>
>> +/* Linux Foundation elects GPLv2 license only.
>> + */
>
> One line?
ok

>
>> +#include <linux/dmaengine.h>
>> +#include <linux/dma-mapping.h>
>> +#include <asm/dma.h>
>
> Do you need this one explicitly?

got rid of it

>
>> +#include <linux/atomic.h>
>> +#include <linux/pm_runtime.h>
>
> + empty line?
ok
>
>> +#include <asm/div64.h>
>
> + empty line?
ok
>
>> +#include "dmaengine.h"
>> +#include "qcom_hidma.h"
>> +
>> +/* Default idle time is 2 seconds. This parameter can
>> + * be overridden by changing the following
>> + * /sys/bus/platform/devices/QCOM8061:<xy>/power/autosuspend_delay_ms
>> + * during kernel boot.
>> + */
>
ok

> Block comments usually like
> /*
>   * text
>   */
>

>> +struct hidma_chan {
>> +       bool                            paused;
>> +       bool                            allocated;
>> +       char                            name[16];
>
> So, do you need specific name? There is already one in struct dma_chan.
OK, removed.

>> +/* process completed descriptors */
>> +static void hidma_process_completed(struct hidma_dev *mdma)
>> +{
>> +       dma_cookie_t last_cookie = 0;
>> +       struct hidma_chan *mchan;
>> +       struct hidma_desc *mdesc;
>> +       struct dma_async_tx_descriptor *desc;
>> +       unsigned long irqflags;
>> +       LIST_HEAD(list);
>> +       struct dma_chan *dmach = NULL;
>> +
>> +       list_for_each_entry(dmach, &mdma->ddev.channels,
>> +                       device_node) {
>> +               mchan = to_hidma_chan(dmach);
>> +
Found a bug here now. I should have initialized the list on each 
iteration of the loop.

>> +               /* Get all completed descriptors */
>> +               spin_lock_irqsave(&mchan->lock, irqflags);
>> +               if (!list_empty(&mchan->completed))

Removed this one.

>> +                       list_splice_tail_init(&mchan->completed, &list);
>> +               spin_unlock_irqrestore(&mchan->lock, irqflags);
>> +
>> +               if (list_empty(&list))
>> +                       continue;
>

> Redundant check. It's done in both list_for_each_entry() and
> list_splice_tail_init().

ok
>
>> +
>> +               /* Execute callbacks and run dependencies */
>> +               list_for_each_entry(mdesc, &list, node) {
>> +                       desc = &mdesc->desc;
>> +
>> +                       spin_lock_irqsave(&mchan->lock, irqflags);
>> +                       dma_cookie_complete(desc);
>> +                       spin_unlock_irqrestore(&mchan->lock, irqflags);
>> +
>> +                       if (desc->callback &&
>> +                               (hidma_ll_status(mdma->lldev, mdesc->tre_ch)
>> +                               == DMA_COMPLETE))
>> +                               desc->callback(desc->callback_param);
>> +
>> +                       last_cookie = desc->cookie;
>> +                       dma_run_dependencies(desc);
>> +               }
>> +
>> +               /* Free descriptors */
>> +               spin_lock_irqsave(&mchan->lock, irqflags);
>> +               list_splice_tail_init(&list, &mchan->free);
>> +               spin_unlock_irqrestore(&mchan->lock, irqflags);
>> +       }
>> +}
>> +
>> +/*
>> + * Execute all queued DMA descriptors.
>> + * This function is called either on the first transfer attempt in tx_submit
>> + * or from the callback routine when one transfer is finished. It can only be
>> + * called from a single location since both of places check active list to be
>> + * empty and will immediately fill the active list while lock is held.
>> + *
>> + * Following requirements must be met while calling hidma_execute():
>> + *     a) mchan->lock is locked,
>> + *     b) mchan->active list contains multiple entries.
>> + *     c) pm protected
>> + */
>> +static int hidma_execute(struct hidma_chan *mchan)
>> +{
>> +       struct hidma_dev *mdma = mchan->dmadev;
>> +       int rc;
>> +
>> +       if (!hidma_ll_isenabled(mdma->lldev))
>> +               return -ENODEV;
>> +
>> +       /* Start the transfer */
>> +       if (!list_empty(&mchan->active))
>> +               rc = hidma_ll_start(mdma->lldev);
>> +
>> +       return 0;
>> +}
>> +
>> +/*
>> + * Called once for each submitted descriptor.
>> + * PM is locked once for each descriptor that is currently
>> + * in execution.
>> + */
>> +static void hidma_callback(void *data)
>> +{
>> +       struct hidma_desc *mdesc = data;
>> +       struct hidma_chan *mchan = to_hidma_chan(mdesc->desc.chan);
>> +       unsigned long irqflags;
>> +       struct dma_device *ddev = mchan->chan.device;
>> +       struct hidma_dev *dmadev = to_hidma_dev(ddev);
>> +       bool queued = false;
>> +
>> +       dev_dbg(dmadev->ddev.dev, "callback: data:0x%p\n", data);
>> +
>> +       spin_lock_irqsave(&mchan->lock, irqflags);
>> +
>> +       if (mdesc->node.next) {
>> +               /* Delete from the active list, add to completed list */
>> +               list_move_tail(&mdesc->node, &mchan->completed);
>> +               queued = true;
>> +       }
>> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
>> +
>> +       hidma_process_completed(dmadev);
>> +
>> +       if (queued) {
>> +               pm_runtime_mark_last_busy(dmadev->ddev.dev);
>> +               pm_runtime_put_autosuspend(dmadev->ddev.dev);
>> +       }
>> +}
>> +
>> +static int hidma_chan_init(struct hidma_dev *dmadev, u32 dma_sig)
>> +{
>> +       struct hidma_chan *mchan;
>> +       struct dma_device *ddev;
>> +
>> +       mchan = devm_kzalloc(dmadev->ddev.dev, sizeof(*mchan), GFP_KERNEL);
>> +       if (!mchan)
>> +               return -ENOMEM;
>> +
>> +       ddev = &dmadev->ddev;
>> +       mchan->dma_sig = dma_sig;
>> +       mchan->dmadev = dmadev;
>> +       mchan->chan.device = ddev;
>> +       dma_cookie_init(&mchan->chan);
>> +
>> +       INIT_LIST_HEAD(&mchan->free);
>> +       INIT_LIST_HEAD(&mchan->prepared);
>> +       INIT_LIST_HEAD(&mchan->active);
>> +       INIT_LIST_HEAD(&mchan->completed);
>> +
>> +       spin_lock_init(&mchan->lock);
>> +       list_add_tail(&mchan->chan.device_node, &ddev->channels);
>> +       dmadev->ddev.chancnt++;
>> +       return 0;
>> +}
>> +
>> +static void hidma_issue_pending(struct dma_chan *dmach)
>> +{
>
> Wrong. It should actually start the transfer. tx_submit() just puts
> the descriptor to a queue.
>
Depends on the design.

I started from the Freescale driver (mpc512x_dma.c). It follows the same 
model.

I'll just drop the same comment into this code too.


/*
* We are posting descriptors to the hardware as soon as
* they are ready, so this function does nothing.
*/

>> +}
>> +
>> +static enum dma_status hidma_tx_status(struct dma_chan *dmach,
>> +                                       dma_cookie_t cookie,
>> +                                       struct dma_tx_state *txstate)
>> +{
>> +       enum dma_status ret;
>> +       unsigned long irqflags;
>> +       struct hidma_chan *mchan = to_hidma_chan(dmach);
>> +
>> +       spin_lock_irqsave(&mchan->lock, irqflags);
>
> So, what are you protecting here? paused member, right?

yes.

>
>> +       if (mchan->paused)
>> +               ret = DMA_PAUSED;
>> +       else
>> +               ret = dma_cookie_status(dmach, cookie, txstate);
>
> This one has no need to be under spin lock.
ok, will remove it. Apparently, other drivers are not using locks either 
in this routine.
>
>> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
>> +
>> +       return ret;
>> +}
>> +
>> +/*
>> + * Submit descriptor to hardware.
>> + * Lock the PM for each descriptor we are sending.
>> + */
>> +static dma_cookie_t hidma_tx_submit(struct dma_async_tx_descriptor *txd)
>> +{
>> +       struct hidma_chan *mchan = to_hidma_chan(txd->chan);
>> +       struct hidma_dev *dmadev = mchan->dmadev;
>> +       struct hidma_desc *mdesc;
>> +       unsigned long irqflags;
>> +       dma_cookie_t cookie;
>> +
>> +       if (!hidma_ll_isenabled(dmadev->lldev))
>> +               return -ENODEV;
>> +
>> +       pm_runtime_get_sync(dmadev->ddev.dev);
>
> No point to do it here. It should be done on the function that
> actually starts the transfer (see issue pending).
>
comment above

>> +       mdesc = container_of(txd, struct hidma_desc, desc);
>> +       spin_lock_irqsave(&mchan->lock, irqflags);
>> +
>> +       /* Move descriptor to active */
>> +       list_move_tail(&mdesc->node, &mchan->active);
>> +
>> +       /* Update cookie */
>> +       cookie = dma_cookie_assign(txd);
>> +
>> +       hidma_ll_queue_request(dmadev->lldev, mdesc->tre_ch);
>> +       hidma_execute(mchan);
>> +
>> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
>> +
>> +       return cookie;
>> +}
>> +
>> +static int hidma_alloc_chan_resources(struct dma_chan *dmach)
>> +{
>> +       struct hidma_chan *mchan = to_hidma_chan(dmach);
>> +       struct hidma_dev *dmadev = mchan->dmadev;
>> +       int rc = 0;
>> +       struct hidma_desc *mdesc, *tmp;
>> +       unsigned long irqflags;
>> +       LIST_HEAD(descs);
>> +       u32 i;
>> +
>> +       if (mchan->allocated)
>> +               return 0;
>> +
>> +       /* Alloc descriptors for this channel */
>> +       for (i = 0; i < dmadev->nr_descriptors; i++) {
>> +               mdesc = kzalloc(sizeof(struct hidma_desc), GFP_KERNEL);
>> +               if (!mdesc) {
>> +                       dev_err(dmadev->ddev.dev, "Memory allocation error. ");
>> +                       rc = -ENOMEM;
>> +                       break;
>> +               }
>> +               dma_async_tx_descriptor_init(&mdesc->desc, dmach);
>> +               mdesc->desc.flags = DMA_CTRL_ACK;
>> +               mdesc->desc.tx_submit = hidma_tx_submit;
>> +
>> +               rc = hidma_ll_request(dmadev->lldev,
>> +                               mchan->dma_sig, "DMA engine", hidma_callback,
>> +                               mdesc, &mdesc->tre_ch);
>> +               if (rc != 1) {
>
> if (rc < 1) {

I'll fix hidma_ll_request instead and return 0 on success and change 
this line as if (rc)

>
>> +                       dev_err(dmach->device->dev,
>> +                               "channel alloc failed at %u\n", i);
>
>> +                       kfree(mdesc);
>> +                       break;
>> +               }
>> +               list_add_tail(&mdesc->node, &descs);
>> +       }
>> +
>> +       if (rc != 1) {
>
> if (rc < 1)

Fixed this too
>
>> +               /* return the allocated descriptors */
>> +               list_for_each_entry_safe(mdesc, tmp, &descs, node) {
>> +                       hidma_ll_free(dmadev->lldev, mdesc->tre_ch);
>> +                       kfree(mdesc);
>> +               }
>> +               return rc;
>> +       }
>> +
>> +       spin_lock_irqsave(&mchan->lock, irqflags);
>> +       list_splice_tail_init(&descs, &mchan->free);
>> +       mchan->allocated = true;
>> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
>> +       dev_dbg(dmadev->ddev.dev,
>> +               "allocated channel for %u\n", mchan->dma_sig);
>> +       return rc;
>> +}
>> +
>> +static void hidma_free_chan_resources(struct dma_chan *dmach)
>> +{
>> +       struct hidma_chan *mchan = to_hidma_chan(dmach);
>> +       struct hidma_dev *mdma = mchan->dmadev;
>> +       struct hidma_desc *mdesc, *tmp;
>> +       unsigned long irqflags;
>> +       LIST_HEAD(descs);
>> +
>> +       if (!list_empty(&mchan->prepared) ||
>> +               !list_empty(&mchan->active) ||
>> +               !list_empty(&mchan->completed)) {
>> +               /* We have unfinished requests waiting.
>> +                * Terminate the request from the hardware.
>> +                */
>> +               hidma_cleanup_pending_tre(mdma->lldev, 0x77, 0x77);
>
> 0x77 is magic.

Changing with meaningful macros.

>
>> +
>> +               /* Give enough time for completions to be called. */
>> +               msleep(100);
>> +       }
>> +
>> +       spin_lock_irqsave(&mchan->lock, irqflags);
>> +       /* Channel must be idle */
>> +       WARN_ON(!list_empty(&mchan->prepared));
>> +       WARN_ON(!list_empty(&mchan->active));
>> +       WARN_ON(!list_empty(&mchan->completed));
>> +
>> +       /* Move data */
>> +       list_splice_tail_init(&mchan->free, &descs);
>> +
>> +       /* Free descriptors */
>> +       list_for_each_entry_safe(mdesc, tmp, &descs, node) {
>> +               hidma_ll_free(mdma->lldev, mdesc->tre_ch);
>> +               list_del(&mdesc->node);
>> +               kfree(mdesc);
>> +       }
>> +
>> +       mchan->allocated = 0;
>> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
>> +       dev_dbg(mdma->ddev.dev, "freed channel for %u\n", mchan->dma_sig);
>> +}
>> +
>> +
>> +static struct dma_async_tx_descriptor *
>> +hidma_prep_dma_memcpy(struct dma_chan *dmach, dma_addr_t dma_dest,
>> +                       dma_addr_t dma_src, size_t len, unsigned long flags)
>> +{
>> +       struct hidma_chan *mchan = to_hidma_chan(dmach);
>> +       struct hidma_desc *mdesc = NULL;
>> +       struct hidma_dev *mdma = mchan->dmadev;
>> +       unsigned long irqflags;
>> +
>> +       dev_dbg(mdma->ddev.dev,
>> +               "memcpy: chan:%p dest:%pad src:%pad len:%zu\n", mchan,
>> +               &dma_dest, &dma_src, len);
>> +
>> +       /* Get free descriptor */
>> +       spin_lock_irqsave(&mchan->lock, irqflags);
>> +       if (!list_empty(&mchan->free)) {
>> +               mdesc = list_first_entry(&mchan->free, struct hidma_desc,
>> +                                       node);
>> +               list_del(&mdesc->node);
>> +       }
>> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
>> +
>> +       if (!mdesc)
>> +               return NULL;
>> +
>> +       hidma_ll_set_transfer_params(mdma->lldev, mdesc->tre_ch,
>> +                       dma_src, dma_dest, len, flags);
>> +
>> +       /* Place descriptor in prepared list */
>> +       spin_lock_irqsave(&mchan->lock, irqflags);
>> +       list_add_tail(&mdesc->node, &mchan->prepared);
>> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
>> +
>> +       return &mdesc->desc;
>> +}
>> +
>> +static int hidma_terminate_all(struct dma_chan *chan)
>> +{
>> +       struct hidma_dev *dmadev;
>> +       LIST_HEAD(head);
>> +       unsigned long irqflags;
>> +       LIST_HEAD(list);
>> +       struct hidma_desc *tmp, *mdesc = NULL;
>> +       int rc = 0;
>
> Useless assignment.

removed.

>
>> +       struct hidma_chan *mchan;
>> +
>> +       mchan = to_hidma_chan(chan);
>> +       dmadev = to_hidma_dev(mchan->chan.device);
>> +       dev_dbg(dmadev->ddev.dev, "terminateall: chan:0x%p\n", mchan);
>> +
>> +       pm_runtime_get_sync(dmadev->ddev.dev);
>> +       /* give completed requests a chance to finish */
>> +       hidma_process_completed(dmadev);
>> +
>> +       spin_lock_irqsave(&mchan->lock, irqflags);
>> +       list_splice_init(&mchan->active, &list);
>> +       list_splice_init(&mchan->prepared, &list);
>> +       list_splice_init(&mchan->completed, &list);
>> +       spin_unlock_irqrestore(&mchan->lock, irqflags);
>> +
>> +       /* this suspends the existing transfer */
>> +       rc = hidma_ll_pause(dmadev->lldev);
>> +       if (rc) {
>> +               dev_err(dmadev->ddev.dev, "channel did not pause\n");
>> +               goto out;
>> +       }
>> +
>> +       /* return all user requests */
>> +       list_for_each_entry_safe(mdesc, tmp, &list, node) {
>> +               struct dma_async_tx_descriptor  *txd = &mdesc->desc;
>> +               dma_async_tx_callback callback = mdesc->desc.callback;
>> +               void *param = mdesc->desc.callback_param;
>> +               enum dma_status status;
>> +
>> +               dma_descriptor_unmap(txd);
>> +
>> +               status = hidma_ll_status(dmadev->lldev, mdesc->tre_ch);
>> +               /*
>> +                * The API requires that no submissions are done from a
>> +                * callback, so we don't need to drop the lock here
>> +                */
>> +               if (callback && (status == DMA_COMPLETE))
>> +                       callback(param);
>> +
>> +               dma_run_dependencies(txd);
>> +
>> +               /* move myself to free_list */
>> +               list_move(&mdesc->node, &mchan->free);
>> +       }
>> +
>> +       /* reinitialize the hardware */
>> +       rc = hidma_ll_setup(dmadev->lldev);
>> +
>> +out:
>> +       pm_runtime_mark_last_busy(dmadev->ddev.dev);
>> +       pm_runtime_put_autosuspend(dmadev->ddev.dev);
>> +       return rc;
>> +}
>> +
>> +static int hidma_pause(struct dma_chan *chan)
>> +{
>> +       struct hidma_chan *mchan;
>> +       struct hidma_dev *dmadev;
>> +
>> +       mchan = to_hidma_chan(chan);
>> +       dmadev = to_hidma_dev(mchan->chan.device);
>> +       dev_dbg(dmadev->ddev.dev, "pause: chan:0x%p\n", mchan);
>> +
>> +       pm_runtime_get_sync(dmadev->ddev.dev);
>
> Why it's here? Here is nothing to do with the device, move it to _pause().
>

I'll move it inside the if statement. hidma_ll_pause touches the hardware.

>> +       if (!mchan->paused) {
>> +               if (hidma_ll_pause(dmadev->lldev))
>> +                       dev_warn(dmadev->ddev.dev, "channel did not stop\n");
>> +               mchan->paused = true;
>> +       }
>> +       pm_runtime_mark_last_busy(dmadev->ddev.dev);
>> +       pm_runtime_put_autosuspend(dmadev->ddev.dev);
>> +       return 0;
>> +}
>> +
>> +static int hidma_resume(struct dma_chan *chan)
>> +{
>> +       struct hidma_chan *mchan;
>> +       struct hidma_dev *dmadev;
>> +       int rc = 0;
>> +
>> +       mchan = to_hidma_chan(chan);
>> +       dmadev = to_hidma_dev(mchan->chan.device);
>> +       dev_dbg(dmadev->ddev.dev, "resume: chan:0x%p\n", mchan);
>> +
>> +       pm_runtime_get_sync(dmadev->ddev.dev);
>
> Ditto.
>

I'll do the samething as pause.

>> +       if (mchan->paused) {
>> +               rc = hidma_ll_resume(dmadev->lldev);
>> +               if (!rc)
>> +                       mchan->paused = false;
>> +               else
>> +                       dev_err(dmadev->ddev.dev,
>> +                                       "failed to resume the channel");
>> +       }
>> +       pm_runtime_mark_last_busy(dmadev->ddev.dev);
>> +       pm_runtime_put_autosuspend(dmadev->ddev.dev);
>> +       return rc;
>> +}
>> +
>> +static irqreturn_t hidma_chirq_handler(int chirq, void *arg)
>> +{
>> +       struct hidma_lldev **lldev_ptr = arg;
>> +       irqreturn_t ret;
>> +       struct hidma_dev *dmadev = to_hidma_dev_from_lldev(lldev_ptr);
>> +
>> +       pm_runtime_get_sync(dmadev->ddev.dev);
>
> Hmm... Do you have shared IRQ line or wakeup able one?
> Otherwise I can't see ways how device can generate interrupts.
> If there is a case other than described, put comment why it might happen.
>

All interrupts are request driven. HW doesn't send an interrupt by 
itself. I'll put some comment in the code.

>> +       ret = hidma_ll_inthandler(chirq, *lldev_ptr);
>> +       pm_runtime_mark_last_busy(dmadev->ddev.dev);
>> +       pm_runtime_put_autosuspend(dmadev->ddev.dev);
>> +       return ret;
>> +}
>> +
>> +static int hidma_probe(struct platform_device *pdev)
>> +{
>> +       struct hidma_dev *dmadev;
>> +       int rc = 0;
>> +       struct resource *trca_resource;
>> +       struct resource *evca_resource;
>> +       int chirq;
>> +       int current_channel_index = atomic_read(&channel_ref_count);
>> +
>> +       pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT);
>> +       pm_runtime_use_autosuspend(&pdev->dev);
>> +       pm_runtime_set_active(&pdev->dev);
>> +       pm_runtime_enable(&pdev->dev);
>> +
>> +       trca_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +       if (!trca_resource) {
>> +               rc = -ENODEV;
>> +               goto bailout;
>> +       }
>> +
>> +       evca_resource = platform_get_resource(pdev, IORESOURCE_MEM, 1);
>> +       if (!evca_resource) {
>> +               rc = -ENODEV;
>> +               goto bailout;
>> +       }
>
>
> Consolidate these with devm_ioremap_resource();
>

ok

>> +
>> +       /* This driver only handles the channel IRQs.
>> +        * Common IRQ is handled by the management driver.
>> +        */
>> +       chirq = platform_get_irq(pdev, 0);
>> +       if (chirq < 0) {
>> +               rc = -ENODEV;
>> +               goto bailout;
>> +       }
>> +
>> +       dmadev = devm_kzalloc(&pdev->dev, sizeof(*dmadev), GFP_KERNEL);
>> +       if (!dmadev) {
>> +               rc = -ENOMEM;
>> +               goto bailout;
>> +       }
>> +
>> +       INIT_LIST_HEAD(&dmadev->ddev.channels);
>> +       spin_lock_init(&dmadev->lock);
>> +       dmadev->ddev.dev = &pdev->dev;
>> +       pm_runtime_get_sync(dmadev->ddev.dev);
>> +
>> +       dma_cap_set(DMA_MEMCPY, dmadev->ddev.cap_mask);
>> +       if (WARN_ON(!pdev->dev.dma_mask)) {
>> +               rc = -ENXIO;
>> +               goto dmafree;
>> +       }
>> +
>> +       dmadev->dev_evca = devm_ioremap_resource(&pdev->dev,
>> +                                               evca_resource);
>> +       if (IS_ERR(dmadev->dev_evca)) {
>> +               rc = -ENOMEM;
>> +               goto dmafree;
>> +       }
>> +
>> +       dmadev->dev_trca = devm_ioremap_resource(&pdev->dev,
>> +                                               trca_resource);
>> +       if (IS_ERR(dmadev->dev_trca)) {
>> +               rc = -ENOMEM;
>> +               goto dmafree;
>> +       }
>> +       dmadev->ddev.device_prep_dma_memcpy = hidma_prep_dma_memcpy;
>> +       dmadev->ddev.device_alloc_chan_resources =
>> +               hidma_alloc_chan_resources;
>> +       dmadev->ddev.device_free_chan_resources = hidma_free_chan_resources;
>> +       dmadev->ddev.device_tx_status = hidma_tx_status;
>> +       dmadev->ddev.device_issue_pending = hidma_issue_pending;
>> +       dmadev->ddev.device_pause = hidma_pause;
>> +       dmadev->ddev.device_resume = hidma_resume;
>> +       dmadev->ddev.device_terminate_all = hidma_terminate_all;
>> +       dmadev->ddev.copy_align = 8;
>> +
>> +       device_property_read_u32(&pdev->dev, "desc-count",
>> +                               &dmadev->nr_descriptors);
>> +
>> +       if (!dmadev->nr_descriptors && nr_desc_prm)
>> +               dmadev->nr_descriptors = nr_desc_prm;
>> +
>> +       if (!dmadev->nr_descriptors)
>> +               goto dmafree;
>> +
>> +       if (current_channel_index > MAX_HIDMA_CHANNELS)
>> +               goto dmafree;
>> +
>> +       dmadev->evridx = -1;
>> +       device_property_read_u32(&pdev->dev, "event-channel", &dmadev->evridx);
>> +
>> +       /* kernel command line override for the guest machine */
>> +       if (event_channel_idx[current_channel_index] != -1)
>> +               dmadev->evridx = event_channel_idx[current_channel_index];
>> +
>> +       if (dmadev->evridx == -1)
>> +               goto dmafree;
>> +
>> +       /* Set DMA mask to 64 bits. */
>> +       rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
>> +       if (rc) {
>> +               dev_warn(&pdev->dev, "unable to set coherent mask to 64");
>> +               rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(32));
>> +       }
>> +       if (rc)
>> +               goto dmafree;
>> +
>> +       dmadev->lldev = hidma_ll_init(dmadev->ddev.dev,
>> +                               dmadev->nr_descriptors, dmadev->dev_trca,
>> +                               dmadev->dev_evca, dmadev->evridx);
>> +       if (!dmadev->lldev) {
>> +               rc = -EPROBE_DEFER;
>> +               goto dmafree;
>> +       }
>> +
>> +       rc = devm_request_irq(&pdev->dev, chirq, hidma_chirq_handler, 0,
>> +                             "qcom-hidma", &dmadev->lldev);
>
> Better to use request_irq().
>

why? I thought we favored managed functions over standalone functions in 
simplify the exit path.

>> +       if (rc)
>> +               goto uninit;
>> +
>> +       INIT_LIST_HEAD(&dmadev->ddev.channels);
>> +       rc = hidma_chan_init(dmadev, 0);
>> +       if (rc)
>> +               goto uninit;
>> +
>> +       rc = dma_selftest_memcpy(&dmadev->ddev);

Thanks for the review.

-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-03 10:22   ` Andy Shevchenko
@ 2015-11-04  0:47     ` Sinan Kaya
  0 siblings, 0 replies; 41+ messages in thread
From: Sinan Kaya @ 2015-11-04  0:47 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: dmaengine, timur, cov, jcm, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel



On 11/3/2015 5:22 AM, Andy Shevchenko wrote:
> On Mon, Nov 2, 2015 at 8:07 AM, Sinan Kaya <okaya@codeaurora.org> wrote:
>> The Qualcomm Technologies HIDMA device has been designed
>> to support virtualization technology. The driver has been
>> divided into two to follow the hardware design. The management
>> driver is executed in hypervisor context and is the main
>> management entity for all channels provided by the device.
>> The channel driver is executed in the hypervisor/guest OS
>> context.
>>
>> All channel devices get probed in the hypervisor
>> context during power up. They show up as DMA engine
>> channels. Then, before starting the virtualization; each
>> channel device is unbound from the hypervisor by VFIO
>> and assigned to the guest machine for control.
>>
>> This management driver will be used by the system
>> admin to monitor/reset the execution state of the DMA
>> channels. This will be the management interface.
>
>
>> +static ssize_t qcom_hidma_mgmt_evtena(struct file *file,
>> +       const char __user *user_buf, size_t count, loff_t *ppos)
>> +{
>> +       char temp_buf[16+1];
>
> 16 is a magic number. Make it defined constant.
>

removed it

>> +       struct qcom_hidma_mgmt_dev *mgmtdev = file->f_inode->i_private;
>> +       u32 event;
>> +       ssize_t ret;
>> +       unsigned long val;
>> +
>> +       temp_buf[16] = '\0';
>> +       if (copy_from_user(temp_buf, user_buf, min_t(int, count, 16)))
>> +               goto out;
>> +
>> +       ret = kstrtoul(temp_buf, 16, &val);
>
> kstrtoul_from_user?

nice, simpler.

>
>> +       if (ret) {
>> +               dev_warn(&mgmtdev->pdev->dev, "unknown event\n");
>> +               goto out;
>> +       }
>> +
>> +       event = (u32)val & HW_EVENTS_CFG_MASK;
>> +
>> +       pm_runtime_get_sync(&mgmtdev->pdev->dev);
>> +       writel(event, mgmtdev->dev_virtaddr + HW_EVENTS_CFG_OFFSET);
>> +       pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
>> +       pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
>> +out:
>> +       return count;
>> +}
>> +
>> +static const struct file_operations qcom_hidma_mgmt_evtena_fops = {
>> +       .write = qcom_hidma_mgmt_evtena,
>> +};
>> +
>> +struct fileinfo {
>> +       char *name;
>> +       int mode;
>> +       const struct file_operations *ops;
>> +};
>> +
>> +static struct fileinfo files[] = {
>> +       {"info", S_IRUGO, &qcom_hidma_mgmt_fops},
>> +       {"err", S_IRUGO,  &qcom_hidma_mgmt_err_fops},
>> +       {"mhiderrclr", S_IWUSR, &qcom_hidma_mgmt_mhiderr_clrfops},
>> +       {"evterrclr", S_IWUSR, &qcom_hidma_mgmt_evterr_clrfops},
>> +       {"ideerrclr", S_IWUSR, &qcom_hidma_mgmt_ideerr_clrfops},
>> +       {"odeerrclr", S_IWUSR, &qcom_hidma_mgmt_odeerr_clrfops},
>> +       {"msierrclr", S_IWUSR, &qcom_hidma_mgmt_msierr_clrfops},
>> +       {"treerrclr", S_IWUSR, &qcom_hidma_mgmt_treerr_clrfops},
>> +       {"evtena", S_IWUSR, &qcom_hidma_mgmt_evtena_fops},
>> +};
>> +
>> +static void qcom_hidma_mgmt_debug_uninit(struct qcom_hidma_mgmt_dev *mgmtdev)
>> +{
>> +       debugfs_remove_recursive(mgmtdev->debugfs);
>> +}
>> +
>> +static int qcom_hidma_mgmt_debug_init(struct qcom_hidma_mgmt_dev *mgmtdev)
>> +{
>> +       int rc = 0;
>> +       u32 i;
>> +       struct dentry   *fs_entry;
>> +
>> +       mgmtdev->debugfs = debugfs_create_dir(dev_name(&mgmtdev->pdev->dev),
>> +                                               NULL);
>> +       if (!mgmtdev->debugfs) {
>> +               rc = -ENODEV;
>> +               return rc;
>> +       }
>> +
>> +       for (i = 0; i < ARRAY_SIZE(files); i++) {
>> +               fs_entry = debugfs_create_file(files[i].name,
>> +                                       files[i].mode, mgmtdev->debugfs,
>> +                                       mgmtdev, files[i].ops);
>> +               if (!fs_entry) {
>> +                       rc = -ENOMEM;
>> +                       goto cleanup;
>> +               }
>> +       }
>> +
>> +       return 0;
>> +cleanup:
>> +       qcom_hidma_mgmt_debug_uninit(mgmtdev);
>> +       return rc;
>> +}
>> +#else
>> +static void qcom_hidma_mgmt_debug_uninit(struct qcom_hidma_mgmt_dev *mgmtdev)
>> +{
>> +}
>> +static int qcom_hidma_mgmt_debug_init(struct qcom_hidma_mgmt_dev *mgmtdev)
>> +{
>> +       return 0;
>> +}
>> +#endif
>> +
>> +static int qcom_hidma_mgmt_setup(struct qcom_hidma_mgmt_dev *mgmtdev)
>> +{
>> +       u32 val;
>> +       u32 i;
>> +
>> +       val = readl(mgmtdev->dev_virtaddr + MAX_BUS_REQ_LEN_OFFSET);
>> +       val = val &
>> +               ~(MAX_BUS_REQ_LEN_MASK << MAX_BUS_WR_REQ_BIT_POS);
>> +       val = val |
>> +               (mgmtdev->max_write_request << MAX_BUS_WR_REQ_BIT_POS);
>> +       val = val & ~(MAX_BUS_REQ_LEN_MASK);
>> +       val = val | (mgmtdev->max_read_request);
>> +       writel(val, mgmtdev->dev_virtaddr + MAX_BUS_REQ_LEN_OFFSET);
>> +
>> +       val = readl(mgmtdev->dev_virtaddr + MAX_XACTIONS_OFFSET);
>> +       val = val &
>> +               ~(MAX_WR_XACTIONS_MASK << MAX_WR_XACTIONS_BIT_POS);
>> +       val = val |
>> +               (mgmtdev->max_wr_xactions << MAX_WR_XACTIONS_BIT_POS);
>> +       val = val & ~(MAX_RD_XACTIONS_MASK);
>> +       val = val | (mgmtdev->max_rd_xactions);
>> +       writel(val, mgmtdev->dev_virtaddr + MAX_XACTIONS_OFFSET);
>> +
>> +       mgmtdev->sw_version = readl(mgmtdev->dev_virtaddr + SW_VERSION_OFFSET);
>> +
>> +       for (i = 0; i < mgmtdev->dma_channels; i++) {
>> +               val = readl(mgmtdev->dev_virtaddr + QOS_N_OFFSET + (4 * i));
>> +               val = val & ~(1 << PRIORITY_BIT_POS);
>> +               val = val |
>> +                       ((mgmtdev->priority[i] & 0x1) << PRIORITY_BIT_POS);
>> +               val = val & ~(WEIGHT_MASK << WRR_BIT_POS);
>> +               val = val
>> +                       | ((mgmtdev->weight[i] & WEIGHT_MASK) << WRR_BIT_POS);
>> +               writel(val, mgmtdev->dev_virtaddr + QOS_N_OFFSET + (4 * i));
>> +       }
>> +
>> +       val = readl(mgmtdev->dev_virtaddr + CHRESET_TIMEOUT_OFFSET);
>> +       val = val & ~CHRESET_TIMEOUUT_MASK;
>> +       val = val | (mgmtdev->chreset_timeout & CHRESET_TIMEOUUT_MASK);
>> +       writel(val, mgmtdev->dev_virtaddr + CHRESET_TIMEOUT_OFFSET);
>> +
>> +       val = readl(mgmtdev->dev_virtaddr + CFG_OFFSET);
>> +       val = val | 1;
>> +       writel(val, mgmtdev->dev_virtaddr + CFG_OFFSET);
>> +
>> +       return 0;
>> +}
>> +
>> +static int qcom_hidma_mgmt_probe(struct platform_device *pdev)
>> +{
>> +       struct resource *dma_resource;
>> +       int irq;
>> +       int rc;
>> +       u32 i;
>> +       struct qcom_hidma_mgmt_dev *mgmtdev;
>
> Better move this line to the top of definition block.
>
done

>> +
>> +       pm_runtime_set_autosuspend_delay(&pdev->dev, AUTOSUSPEND_TIMEOUT);
>> +       pm_runtime_use_autosuspend(&pdev->dev);
>> +       pm_runtime_set_active(&pdev->dev);
>> +       pm_runtime_enable(&pdev->dev);
>> +       dma_resource = platform_get_resource(pdev, IORESOURCE_MEM, 0);
>> +       if (!dma_resource) {
>> +               dev_err(&pdev->dev, "No memory resources found\n");
>> +               rc = -ENODEV;
>> +               goto out;
>> +       }
>
> Consolidate with devm_ioremap_resource()
>

OK

>> +
>> +       irq = platform_get_irq(pdev, 0);
>> +       if (irq < 0) {
>> +               dev_err(&pdev->dev, "irq resources not found\n");
>> +               rc = -ENODEV;
>> +               goto out;
>> +       }
>> +
>> +       mgmtdev = devm_kzalloc(&pdev->dev, sizeof(*mgmtdev), GFP_KERNEL);
>> +       if (!mgmtdev) {
>> +               rc = -ENOMEM;
>> +               goto out;
>> +       }
>> +
>> +       mgmtdev->pdev = pdev;
>> +
>> +       pm_runtime_get_sync(&mgmtdev->pdev->dev);
>> +       mgmtdev->dev_addrsize = resource_size(dma_resource);
>> +       mgmtdev->dev_virtaddr = devm_ioremap_resource(&pdev->dev,
>> +                                                       dma_resource);
>> +       if (IS_ERR(mgmtdev->dev_virtaddr)) {
>> +               dev_err(&pdev->dev, "can't map i/o memory at %pa\n",
>> +                       &dma_resource->start);
>> +               rc = -ENOMEM;
>> +               goto out;
>> +       }
>> +
>> +       if (device_property_read_u32(&pdev->dev, "dma-channels",
>> +               &mgmtdev->dma_channels)) {
>> +               dev_err(&pdev->dev, "number of channels missing\n");
>> +               rc = -EINVAL;
>> +               goto out;
>> +       }
>> +
>> +       if (device_property_read_u32(&pdev->dev, "channel-reset-timeout",
>> +               &mgmtdev->chreset_timeout)) {
>> +               dev_err(&pdev->dev, "channel reset timeout missing\n");
>> +               rc = -EINVAL;
>> +               goto out;
>> +       }
>> +
>> +       if (device_property_read_u32(&pdev->dev, "max-write-burst-bytes",
>> +               &mgmtdev->max_write_request)) {
>> +               dev_err(&pdev->dev, "max-write-burst-bytes missing\n");
>> +               rc = -EINVAL;
>> +               goto out;
>> +       }
>> +       if ((mgmtdev->max_write_request != 128) &&
>> +               (mgmtdev->max_write_request != 256) &&
>> +               (mgmtdev->max_write_request != 512) &&
>> +               (mgmtdev->max_write_request != 1024)) {
>
> is_power_of_2()  && min/max ?
>
ok

>> +               dev_err(&pdev->dev, "invalid write request %d\n",
>> +                       mgmtdev->max_write_request);
>> +               rc = -EINVAL;
>> +               goto out;
>> +       }
>> +
>> +       if (device_property_read_u32(&pdev->dev, "max-read-burst-bytes",
>> +               &mgmtdev->max_read_request)) {
>> +               dev_err(&pdev->dev, "max-read-burst-bytes missing\n");
>> +               rc = -EINVAL;
>> +               goto out;
>> +       }
>> +
>> +       if ((mgmtdev->max_read_request != 128) &&
>> +               (mgmtdev->max_read_request != 256) &&
>> +               (mgmtdev->max_read_request != 512) &&
>> +               (mgmtdev->max_read_request != 1024)) {
>
> Ditto.
>
done

>> +               dev_err(&pdev->dev, "invalid read request %d\n",
>> +                       mgmtdev->max_read_request);
>> +               rc = -EINVAL;
>> +               goto out;
>> +       }
>> +
>> +       if (device_property_read_u32(&pdev->dev, "max-write-transactions",
>> +               &mgmtdev->max_wr_xactions)) {
>> +               dev_err(&pdev->dev, "max-write-transactions missing\n");
>> +               rc = -EINVAL;
>> +               goto out;
>> +       }
>> +
>> +       if (device_property_read_u32(&pdev->dev, "max-read-transactions",
>> +               &mgmtdev->max_rd_xactions)) {
>> +               dev_err(&pdev->dev, "max-read-transactions missing\n");
>> +               rc = -EINVAL;
>> +               goto out;
>> +       }
>> +
>> +       mgmtdev->priority = devm_kcalloc(&pdev->dev,
>> +               mgmtdev->dma_channels, sizeof(*mgmtdev->priority), GFP_KERNEL);
>> +       if (!mgmtdev->priority) {
>> +               rc = -ENOMEM;
>> +               goto out;
>> +       }
>> +
>> +       mgmtdev->weight = devm_kcalloc(&pdev->dev,
>> +               mgmtdev->dma_channels, sizeof(*mgmtdev->weight), GFP_KERNEL);
>> +       if (!mgmtdev->weight) {
>> +               rc = -ENOMEM;
>> +               goto out;
>> +       }
>> +
>> +       if (device_property_read_u32_array(&pdev->dev, "channel-priority",
>> +                               mgmtdev->priority, mgmtdev->dma_channels)) {
>> +               dev_err(&pdev->dev, "channel-priority missing\n");
>> +               rc = -EINVAL;
>> +               goto out;
>> +       }
>> +
>> +       if (device_property_read_u32_array(&pdev->dev, "channel-weight",
>> +                               mgmtdev->weight, mgmtdev->dma_channels)) {
>> +               dev_err(&pdev->dev, "channel-weight missing\n");
>> +               rc = -EINVAL;
>> +               goto out;
>> +       }
>> +
>> +       for (i = 0; i < mgmtdev->dma_channels; i++) {
>> +               if (mgmtdev->weight[i] > 15) {
>
> 15 is magic.
>

I created a new macro called MAX_CHANNEL_WEIGHT.

>> +                       dev_err(&pdev->dev,
>> +                               "max value of weight can be 15.\n");
>> +                       rc = -EINVAL;
>> +                       goto out;
>> +               }
>> +
>> +               /* weight needs to be at least one */
>> +               if (mgmtdev->weight[i] == 0)
>> +                       mgmtdev->weight[i] = 1;
>> +       }
>> +
>> +       rc = qcom_hidma_mgmt_setup(mgmtdev);
>> +       if (rc) {
>> +               dev_err(&pdev->dev, "setup failed\n");
>> +               goto out;
>> +       }
>> +
>> +       rc = qcom_hidma_mgmt_debug_init(mgmtdev);
>> +       if (rc) {
>> +               dev_err(&pdev->dev, "debugfs init failed\n");
>> +               goto out;
>> +       }
>> +
>> +       dev_info(&pdev->dev,
>> +               "HI-DMA engine management driver registration complete\n");
>
> You may put some useful information here, otherwise looks like a debug message.
>
ok, I did now. I copied the syntax from another driver to here.

>> +       platform_set_drvdata(pdev, mgmtdev);
>> +       pm_runtime_mark_last_busy(&mgmtdev->pdev->dev);
>> +       pm_runtime_put_autosuspend(&mgmtdev->pdev->dev);
>> +       return 0;
>> +out:
>> +       pm_runtime_disable(&pdev->dev);
>> +       pm_runtime_put_sync_suspend(&pdev->dev);
>> +       return rc;
>> +}
>> +
>> +static int qcom_hidma_mgmt_remove(struct platform_device *pdev)
>> +{
>> +       struct qcom_hidma_mgmt_dev *mgmtdev = platform_get_drvdata(pdev);
>> +
>> +       pm_runtime_get_sync(&mgmtdev->pdev->dev);
>> +       qcom_hidma_mgmt_debug_uninit(mgmtdev);
>> +       pm_runtime_put_sync_suspend(&pdev->dev);
>> +       pm_runtime_disable(&pdev->dev);
>> +
>> +       dev_info(&pdev->dev, "HI-DMA engine management driver removed\n");
>
> Useless message.

removed .

>
>> +       return 0;
>> +}
>> +
>> +#if IS_ENABLED(CONFIG_ACPI)
>> +static const struct acpi_device_id qcom_hidma_mgmt_acpi_ids[] = {
>> +       {"QCOM8060"},
>> +       {},
>> +};
>> +#endif
>> +
>> +static const struct of_device_id qcom_hidma_mgmt_match[] = {
>> +       { .compatible = "qcom,hidma-mgmt-1.0", },
>> +       {},
>> +};
>> +MODULE_DEVICE_TABLE(of, qcom_hidma_mgmt_match);
>> +
>> +static struct platform_driver qcom_hidma_mgmt_driver = {
>> +       .probe = qcom_hidma_mgmt_probe,
>> +       .remove = qcom_hidma_mgmt_remove,
>> +       .driver = {
>> +               .name = "hidma-mgmt",
>> +               .of_match_table = qcom_hidma_mgmt_match,
>> +               .acpi_match_table = ACPI_PTR(qcom_hidma_mgmt_acpi_ids),
>> +       },
>> +};
>> +module_platform_driver(qcom_hidma_mgmt_driver);
>> +MODULE_LICENSE("GPL v2");
>> --
>> Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
>> Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a Linux Foundation Collaborative Project
>>
>> --
>> To unsubscribe from this list: send the line "unsubscribe linux-kernel" in
>> the body of a message to majordomo@vger.kernel.org
>> More majordomo info at  http://vger.kernel.org/majordomo-info.html
>> Please read the FAQ at  http://www.tux.org/lkml/
>
>
>

-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 3/3] dma: add Qualcomm Technologies HIDMA channel driver
  2015-11-04  0:07     ` Sinan Kaya
@ 2015-11-04 17:44       ` Andy Shevchenko
  2015-11-05  2:22         ` Sinan Kaya
  0 siblings, 1 reply; 41+ messages in thread
From: Andy Shevchenko @ 2015-11-04 17:44 UTC (permalink / raw)
  To: Sinan Kaya
  Cc: dmaengine, timur, cov, jcm, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel

On Wed, Nov 4, 2015 at 2:07 AM, Sinan Kaya <okaya@codeaurora.org> wrote:
> On 11/3/2015 5:10 AM, Andy Shevchenko wrote:
>> On Mon, Nov 2, 2015 at 8:07 AM, Sinan Kaya <okaya@codeaurora.org> wrote:

>>> +static void hidma_issue_pending(struct dma_chan *dmach)
>>> +{
>>
>>
>> Wrong. It should actually start the transfer. tx_submit() just puts
>> the descriptor to a queue.
>>
> Depends on the design.
>
> I started from the Freescale driver (mpc512x_dma.c). It follows the same
> model.
>
> I'll just drop the same comment into this code too.
>
>
> /*
> * We are posting descriptors to the hardware as soon as
> * they are ready, so this function does nothing.
> */

So, the Freescale driver was written before change went effective. I
guess in 2011 DMA Engine drivers should use issue pending.
Please, refactor since this behaviour is expected.

>>> +/*
>>> + * Submit descriptor to hardware.
>>> + * Lock the PM for each descriptor we are sending.
>>> + */
>>> +static dma_cookie_t hidma_tx_submit(struct dma_async_tx_descriptor *txd)
>>> +{
>>> +       struct hidma_chan *mchan = to_hidma_chan(txd->chan);
>>> +       struct hidma_dev *dmadev = mchan->dmadev;
>>> +       struct hidma_desc *mdesc;
>>> +       unsigned long irqflags;
>>> +       dma_cookie_t cookie;
>>> +
>>> +       if (!hidma_ll_isenabled(dmadev->lldev))
>>> +               return -ENODEV;
>>> +
>>> +       pm_runtime_get_sync(dmadev->ddev.dev);
>>
>>
>> No point to do it here. It should be done on the function that
>> actually starts the transfer (see issue pending).
>>
> comment above

See above as well.

>>> +static int hidma_probe(struct platform_device *pdev)
>>> +{
>>> +       struct hidma_dev *dmadev;
>>> +       int rc = 0;
>>> +       struct resource *trca_resource;
>>> +       struct resource *evca_resource;
>>> +       int chirq;
>>> +       int current_channel_index = atomic_read(&channel_ref_count);
>>> +

>>> +       /* Set DMA mask to 64 bits. */
>>> +       rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
>>> +       if (rc) {
>>> +               dev_warn(&pdev->dev, "unable to set coherent mask to
>>> 64");
>>> +               rc = dma_set_mask_and_coherent(&pdev->dev,
>>> DMA_BIT_MASK(32));
>>> +       }
>>> +       if (rc)
>>> +               goto dmafree;

Maybe move these two lines inside previous condition?

>>> +
>>> +       dmadev->lldev = hidma_ll_init(dmadev->ddev.dev,
>>> +                               dmadev->nr_descriptors, dmadev->dev_trca,
>>> +                               dmadev->dev_evca, dmadev->evridx);
>>> +       if (!dmadev->lldev) {
>>> +               rc = -EPROBE_DEFER;
>>> +               goto dmafree;
>>> +       }
>>> +
>>> +       rc = devm_request_irq(&pdev->dev, chirq, hidma_chirq_handler, 0,
>>> +                             "qcom-hidma", &dmadev->lldev);
>>
>>
>> Better to use request_irq().
>>
>
> why? I thought we favored managed functions over standalone functions in
> simplify the exit path.

IRQ is slightly different in workflow. In most cases, unfortunately,
there is no achievement by devm_ variant.
At least I know two for now. One of them is DMA Engine slave drivers,
though I didn't notice if you are using tasklet's here.
Otherwise it's okay.

-- 
With Best Regards,
Andy Shevchenko

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 3/3] dma: add Qualcomm Technologies HIDMA channel driver
  2015-11-04 17:44       ` Andy Shevchenko
@ 2015-11-05  2:22         ` Sinan Kaya
  0 siblings, 0 replies; 41+ messages in thread
From: Sinan Kaya @ 2015-11-05  2:22 UTC (permalink / raw)
  To: Andy Shevchenko
  Cc: dmaengine, timur, cov, jcm, Rob Herring, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel


>> /*
>> * We are posting descriptors to the hardware as soon as
>> * they are ready, so this function does nothing.
>> */
>
> So, the Freescale driver was written before change went effective. I
> guess in 2011 DMA Engine drivers should use issue pending.
> Please, refactor since this behaviour is expected.
>

done

>>>> +/*
>>>> + * Submit descriptor to hardware.
>>>> + * Lock the PM for each descriptor we are sending.
>>>> + */
>>>> +static dma_cookie_t hidma_tx_submit(struct dma_async_tx_descriptor *txd)
>>>> +{
>>>> +       struct hidma_chan *mchan = to_hidma_chan(txd->chan);
>>>> +       struct hidma_dev *dmadev = mchan->dmadev;
>>>> +       struct hidma_desc *mdesc;
>>>> +       unsigned long irqflags;
>>>> +       dma_cookie_t cookie;
>>>> +
>>>> +       if (!hidma_ll_isenabled(dmadev->lldev))
>>>> +               return -ENODEV;
>>>> +
>>>> +       pm_runtime_get_sync(dmadev->ddev.dev);
>>>
>>>
>>> No point to do it here. It should be done on the function that
>>> actually starts the transfer (see issue pending).
>>>
>> comment above
>
> See above as well.

done

>
>>>> +static int hidma_probe(struct platform_device *pdev)
>>>> +{
>>>> +       struct hidma_dev *dmadev;
>>>> +       int rc = 0;
>>>> +       struct resource *trca_resource;
>>>> +       struct resource *evca_resource;
>>>> +       int chirq;
>>>> +       int current_channel_index = atomic_read(&channel_ref_count);
>>>> +
>
>>>> +       /* Set DMA mask to 64 bits. */
>>>> +       rc = dma_set_mask_and_coherent(&pdev->dev, DMA_BIT_MASK(64));
>>>> +       if (rc) {
>>>> +               dev_warn(&pdev->dev, "unable to set coherent mask to
>>>> 64");
>>>> +               rc = dma_set_mask_and_coherent(&pdev->dev,
>>>> DMA_BIT_MASK(32));
>>>> +       }
>>>> +       if (rc)
>>>> +               goto dmafree;
>
> Maybe move these two lines inside previous condition?

ok

>
>>>> +
>>>> +       dmadev->lldev = hidma_ll_init(dmadev->ddev.dev,
>>>> +                               dmadev->nr_descriptors, dmadev->dev_trca,
>>>> +                               dmadev->dev_evca, dmadev->evridx);
>>>> +       if (!dmadev->lldev) {
>>>> +               rc = -EPROBE_DEFER;
>>>> +               goto dmafree;
>>>> +       }
>>>> +
>>>> +       rc = devm_request_irq(&pdev->dev, chirq, hidma_chirq_handler, 0,
>>>> +                             "qcom-hidma", &dmadev->lldev);
>>>
>>>
>>> Better to use request_irq().
>>>
>>
>> why? I thought we favored managed functions over standalone functions in
>> simplify the exit path.
>
> IRQ is slightly different in workflow. In most cases, unfortunately,
> there is no achievement by devm_ variant.
> At least I know two for now. One of them is DMA Engine slave drivers,
> though I didn't notice if you are using tasklet's here.
> Otherwise it's okay.
>
I'm keeping it as it is for maintenance reasons.

-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-03 16:08             ` Vinod Koul
@ 2015-11-05  2:42               ` Sinan Kaya
  2015-11-05 12:05                 ` Vinod Koul
  0 siblings, 1 reply; 41+ messages in thread
From: Sinan Kaya @ 2015-11-05  2:42 UTC (permalink / raw)
  To: Vinod Koul, Andy Shevchenko
  Cc: Dan Williams, dmaengine, timur, cov, jcm, linux-kernel



On 11/3/2015 11:08 AM, Vinod Koul wrote:
> On Tue, Nov 03, 2015 at 10:22:25AM +0200, Andy Shevchenko wrote:
>> On Tue, Nov 3, 2015 at 9:44 AM, Dan Williams <dan.j.williams@intel.com> wrote:
>>> On Mon, Nov 2, 2015 at 10:30 PM, Vinod Koul <vinod.koul@intel.com> wrote:
>>>> On Mon, Nov 02, 2015 at 11:18:37PM -0500, Sinan Kaya wrote:
>>>>> On 11/2/2015 11:15 PM, Vinod Koul wrote:
>>>>>> On Mon, Nov 02, 2015 at 01:07:38AM -0500, Sinan Kaya wrote:
>>
>>
>>>>> This one; on the other hand, is selftest to verify hardware is
>>>>> working as expected during power up.
>>
>> I prefer to have such nice case by run time parameter (let's say
>> common to all DMA Engine drivers)
>>
>>>> We can have common code which is used for dmatest as well as selftest. I do
>>>> not want to see same code duplicated..
>>
>> First thought was to merge this to dmatest, however, some DMA
>> controllers doesn't have a memcpy capability.
>
> The tests should be based on capablity of memcpy
>
>> How would we test them?

Here is what I proposed.

- a common file that gets compiled into a module that wants to use 
self-test with a public API. It can be called from driver's probe routine.
- the test is independent of my implementation. It uses dmaengine API 
and should be portable to most drivers.
- there *are* still drivers in the kernel that has selftest code 
embedded inside them. I followed the design pattern from other drivers 
thinking this must have been a good idea and it paid off for me.

As far as I understand, there is interest in doing more than this and 
reusing the dmatest code for code duplication.

Facts:
- Dmatest can be actually configured to run during boot.
- Nobody besides the dma driver developer uses dmatest. This leaves 
holes for regressions that are really hard to debug due to interaction 
with the rest of the system.
- Dmatest doesn't exist in most distribution kernels.

If we want to do something else, I need clear directions. I can remove 
the self test code completely from my driver. But, I need an equivalent 
functionality.

 >
 > That part is tricky, you need to do so thru clients, spi/audio/serial etc
 >

My selftest code actually attaches to all slave devices and issues a 
memcpy command and then detaches from the slave devices.

-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-05  2:42               ` Sinan Kaya
@ 2015-11-05 12:05                 ` Vinod Koul
  2015-11-05 16:17                   ` Sinan Kaya
  0 siblings, 1 reply; 41+ messages in thread
From: Vinod Koul @ 2015-11-05 12:05 UTC (permalink / raw)
  To: Sinan Kaya
  Cc: Andy Shevchenko, Dan Williams, dmaengine, timur, cov, jcm, linux-kernel

On Wed, Nov 04, 2015 at 09:42:46PM -0500, Sinan Kaya wrote:
> Here is what I proposed.
> 
> - a common file that gets compiled into a module that wants to use
> self-test with a public API. It can be called from driver's probe
> routine.
> - the test is independent of my implementation. It uses dmaengine
> API and should be portable to most drivers.
> - there *are* still drivers in the kernel that has selftest code
> embedded inside them. I followed the design pattern from other
> drivers thinking this must have been a good idea and it paid off for
> me.
> 
> As far as I understand, there is interest in doing more than this
> and reusing the dmatest code for code duplication.

the code that selftest uses to test will be very similar to dmatest code,
both of these _should_ share this common code so that fixes get done for
both!

> Facts:
> - Dmatest can be actually configured to run during boot.
> - Nobody besides the dma driver developer uses dmatest. This leaves
> holes for regressions that are really hard to debug due to
> interaction with the rest of the system.
> - Dmatest doesn't exist in most distribution kernels.

That doesn't mean it is not useful. This line of thought is not quite right.
You are trying to say dmatest in not important and selftest is. Sorry but
you are wrong, both are equally important and since both try to test and use
similar routines (dmaengien API) they need to share the code and not
duplicate it

> If we want to do something else, I need clear directions. I can
> remove the self test code completely from my driver. But, I need an
> equivalent functionality.

Add selftest to dmatest, we need both!

> 
> >
> > That part is tricky, you need to do so thru clients, spi/audio/serial etc
> >
> 
> My selftest code actually attaches to all slave devices and issues a
> memcpy command and then detaches from the slave devices.

Not everyone supports memcpy!

-- 
~Vinod

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-02 18:30               ` Timur Tabi
@ 2015-11-05 14:31                 ` Rob Herring
  2015-11-05 14:43                   ` Timur Tabi
  0 siblings, 1 reply; 41+ messages in thread
From: Rob Herring @ 2015-11-05 14:31 UTC (permalink / raw)
  To: Timur Tabi
  Cc: Sinan Kaya, dmaengine, Christopher Covington, jcm, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel

On Mon, Nov 2, 2015 at 12:30 PM, Timur Tabi <timur@codeaurora.org> wrote:
> On 11/02/2015 12:25 PM, Rob Herring wrote:
>>
>> Then document it with "<chip>" and fill that in later. Just don't make
>> up version numbers.
>
>
> I don't think you understand.  We literally have no name for our chip. The
> closest is what I used on the pin control driver, "qdf2xxx", which really
> doesn't say anything.
>
>         "qcom,qdf2xxx-hidma-mgmt"

I'm saying document it as "qcom,<chip>-hidma-mgmt" and when you have
the part number update the binding. Meanwhile push on the powers that
be to decide on a part number.

Rob

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver
  2015-11-05 14:31                 ` Rob Herring
@ 2015-11-05 14:43                   ` Timur Tabi
  0 siblings, 0 replies; 41+ messages in thread
From: Timur Tabi @ 2015-11-05 14:43 UTC (permalink / raw)
  To: Rob Herring
  Cc: Sinan Kaya, dmaengine, Christopher Covington, jcm, Pawel Moll,
	Mark Rutland, Ian Campbell, Kumar Gala, Vinod Koul, Dan Williams,
	devicetree, linux-kernel

Rob Herring wrote:
> I'm saying document it as "qcom,<chip>-hidma-mgmt" and when you have
> the part number update the binding. Meanwhile push on the powers that
> be to decide on a part number.

Got it.  But we should we do about this:

static const struct of_device_id qcom_hidma_mgmt_match[] = {
	{ .compatible = "qcom,hidma-mgmt-1.0", },
	{},
};

-- 
Sent by an employee of the Qualcomm Innovation Center, Inc.
The Qualcomm Innovation Center, Inc. is a member of the
Code Aurora Forum, hosted by The Linux Foundation.

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-05 12:05                 ` Vinod Koul
@ 2015-11-05 16:17                   ` Sinan Kaya
  2015-11-07  6:23                     ` Sinan Kaya
  0 siblings, 1 reply; 41+ messages in thread
From: Sinan Kaya @ 2015-11-05 16:17 UTC (permalink / raw)
  To: Vinod Koul
  Cc: Andy Shevchenko, Dan Williams, dmaengine, timur, cov, jcm, linux-kernel



On 11/5/2015 7:05 AM, Vinod Koul wrote:
> On Wed, Nov 04, 2015 at 09:42:46PM -0500, Sinan Kaya wrote:
>> Here is what I proposed.
>>
>> - a common file that gets compiled into a module that wants to use
>> self-test with a public API. It can be called from driver's probe
>> routine.
>> - the test is independent of my implementation. It uses dmaengine
>> API and should be portable to most drivers.
>> - there *are* still drivers in the kernel that has selftest code
>> embedded inside them. I followed the design pattern from other
>> drivers thinking this must have been a good idea and it paid off for
>> me.
>>
>> As far as I understand, there is interest in doing more than this
>> and reusing the dmatest code for code duplication.
>
> the code that selftest uses to test will be very similar to dmatest code,
> both of these _should_ share this common code so that fixes get done for
> both!
>

OK, I can move the code around and try to combine it if possible.

>> Facts:
>> - Dmatest can be actually configured to run during boot.
>> - Nobody besides the dma driver developer uses dmatest. This leaves
>> holes for regressions that are really hard to debug due to
>> interaction with the rest of the system.
>> - Dmatest doesn't exist in most distribution kernels.
>
> That doesn't mean it is not useful. This line of thought is not quite right.
> You are trying to say dmatest in not important and selftest is. Sorry but
> you are wrong, both are equally important and since both try to test and use
> similar routines (dmaengien API) they need to share the code and not
> duplicate it
>
>> If we want to do something else, I need clear directions. I can
>> remove the self test code completely from my driver. But, I need an
>> equivalent functionality.
>
> Add selftest to dmatest, we need both!
>

OK, do you have any objections to compiling dmatest along with hidma in 
the same module and calling a function from there ? or do you have 
something else in your mind ?

>>
>>>
>>> That part is tricky, you need to do so thru clients, spi/audio/serial etc
>>>
>>
>> My selftest code actually attaches to all slave devices and issues a
>> memcpy command and then detaches from the slave devices.
>
> Not everyone supports memcpy!
>
Right, last time I checked; you can request a DMA channel that supports 
MEMCPY specifically.

-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-05 16:17                   ` Sinan Kaya
@ 2015-11-07  6:23                     ` Sinan Kaya
  2015-11-08 13:53                       ` Vinod Koul
  0 siblings, 1 reply; 41+ messages in thread
From: Sinan Kaya @ 2015-11-07  6:23 UTC (permalink / raw)
  To: Vinod Koul
  Cc: Andy Shevchenko, Dan Williams, dmaengine, timur, cov, jcm, linux-kernel



On 11/5/2015 11:17 AM, Sinan Kaya wrote:
>
>
> On 11/5/2015 7:05 AM, Vinod Koul wrote:
>> On Wed, Nov 04, 2015 at 09:42:46PM -0500, Sinan Kaya wrote:
>>> Here is what I proposed.
>>>
>>> - a common file that gets compiled into a module that wants to use
>>> self-test with a public API. It can be called from driver's probe
>>> routine.
>>> - the test is independent of my implementation. It uses dmaengine
>>> API and should be portable to most drivers.
>>> - there *are* still drivers in the kernel that has selftest code
>>> embedded inside them. I followed the design pattern from other
>>> drivers thinking this must have been a good idea and it paid off for
>>> me.
>>>
>>> As far as I understand, there is interest in doing more than this
>>> and reusing the dmatest code for code duplication.
>>
>> the code that selftest uses to test will be very similar to dmatest code,
>> both of these _should_ share this common code so that fixes get done for
>> both!
>>
>
> OK, I can move the code around and try to combine it if possible.
>

I looked at this. IMO, merging selftest code and dmatest code is not a 
good idea.

Dmatest code has been well written and structured so that multiple DMA 
capabilities (XOR, PQ, MEMCPY) can be tested with the same code.

It supports threads and user space interaction.

The code I want to change (dmatest_func) is 3 levels deep in structure. 
My refactored code looked really ugly compared to the original code.


>>> Facts:
>>> - Dmatest can be actually configured to run during boot.
>>> - Nobody besides the dma driver developer uses dmatest. This leaves
>>> holes for regressions that are really hard to debug due to
>>> interaction with the rest of the system.
>>> - Dmatest doesn't exist in most distribution kernels.
>>
>> That doesn't mean it is not useful. This line of thought is not quite
>> right.
>> You are trying to say dmatest in not important and selftest is. Sorry but
>> you are wrong, both are equally important and since both try to test
>> and use
>> similar routines (dmaengien API) they need to share the code and not
>> duplicate it
>>
>>> If we want to do something else, I need clear directions. I can
>>> remove the self test code completely from my driver. But, I need an
>>> equivalent functionality.
>>
>> Add selftest to dmatest, we need both!
>>
>
> OK, do you have any objections to compiling dmatest along with hidma in
> the same module and calling a function from there ? or do you have
> something else in your mind ?
>

ping

>>>
>>>>
>>>> That part is tricky, you need to do so thru clients,
>>>> spi/audio/serial etc
>>>>
>>>
>>> My selftest code actually attaches to all slave devices and issues a
>>> memcpy command and then detaches from the slave devices.
>>
>> Not everyone supports memcpy!
>>
> Right, last time I checked; you can request a DMA channel that supports
> MEMCPY specifically.
>


-- 
Sinan Kaya
Qualcomm Technologies, Inc. on behalf of Qualcomm Innovation Center, Inc.
Qualcomm Innovation Center, Inc. is a member of Code Aurora Forum, a 
Linux Foundation Collaborative Project

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-07  6:23                     ` Sinan Kaya
@ 2015-11-08 13:53                       ` Vinod Koul
  2015-11-13 20:20                         ` okaya
  0 siblings, 1 reply; 41+ messages in thread
From: Vinod Koul @ 2015-11-08 13:53 UTC (permalink / raw)
  To: Sinan Kaya
  Cc: Andy Shevchenko, Dan Williams, dmaengine, timur, cov, jcm, linux-kernel

On Sat, Nov 07, 2015 at 01:23:34AM -0500, Sinan Kaya wrote:
> 
> 
> On 11/5/2015 11:17 AM, Sinan Kaya wrote:
> >
> >
> >On 11/5/2015 7:05 AM, Vinod Koul wrote:
> >>On Wed, Nov 04, 2015 at 09:42:46PM -0500, Sinan Kaya wrote:
> >>>Here is what I proposed.
> >>>
> >>>- a common file that gets compiled into a module that wants to use
> >>>self-test with a public API. It can be called from driver's probe
> >>>routine.
> >>>- the test is independent of my implementation. It uses dmaengine
> >>>API and should be portable to most drivers.
> >>>- there *are* still drivers in the kernel that has selftest code
> >>>embedded inside them. I followed the design pattern from other
> >>>drivers thinking this must have been a good idea and it paid off for
> >>>me.
> >>>
> >>>As far as I understand, there is interest in doing more than this
> >>>and reusing the dmatest code for code duplication.
> >>
> >>the code that selftest uses to test will be very similar to dmatest code,
> >>both of these _should_ share this common code so that fixes get done for
> >>both!
> >>
> >
> >OK, I can move the code around and try to combine it if possible.
> >
> 
> I looked at this. IMO, merging selftest code and dmatest code is not
> a good idea.
> 
> Dmatest code has been well written and structured so that multiple
> DMA capabilities (XOR, PQ, MEMCPY) can be tested with the same code.
> 
> It supports threads and user space interaction.
> 
> The code I want to change (dmatest_func) is 3 levels deep in
> structure. My refactored code looked really ugly compared to the
> original code.

dmatest_func is still a bigger fn specific to dmatest. I was thinking that we
should have rather have two common functions
1) dmatest_do_dma() which does buffer allocation, invoking dmaengine APIs and
checking results, part of dmatest_func today

2) request_channels() would also be common along with cleanup routines

> >>>Facts:
> >>>- Dmatest can be actually configured to run during boot.
> >>>- Nobody besides the dma driver developer uses dmatest. This leaves
> >>>holes for regressions that are really hard to debug due to
> >>>interaction with the rest of the system.
> >>>- Dmatest doesn't exist in most distribution kernels.
> >>
> >>That doesn't mean it is not useful. This line of thought is not quite
> >>right.
> >>You are trying to say dmatest in not important and selftest is. Sorry but
> >>you are wrong, both are equally important and since both try to test
> >>and use
> >>similar routines (dmaengien API) they need to share the code and not
> >>duplicate it
> >>
> >>>If we want to do something else, I need clear directions. I can
> >>>remove the self test code completely from my driver. But, I need an
> >>>equivalent functionality.
> >>
> >>Add selftest to dmatest, we need both!
> >>
> >
> >OK, do you have any objections to compiling dmatest along with hidma in
> >the same module and calling a function from there ? or do you have
> >something else in your mind ?
Not the dmatest completely, that won't be right, but yes for the dmatest
core which is common b/w both. We cna put this is separate file to compile
along with driver if that is users requirement

-- 
~Vinod

^ permalink raw reply	[flat|nested] 41+ messages in thread

* Re: [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions
  2015-11-08 13:53                       ` Vinod Koul
@ 2015-11-13 20:20                         ` okaya
  0 siblings, 0 replies; 41+ messages in thread
From: okaya @ 2015-11-13 20:20 UTC (permalink / raw)
  To: Vinod Koul
  Cc: Sinan Kaya, Andy Shevchenko, Dan Williams, dmaengine, timur, cov,
	jcm, linux-kernel

>>
>> I looked at this. IMO, merging selftest code and dmatest code is not
>> a good idea.
>>
>> Dmatest code has been well written and structured so that multiple
>> DMA capabilities (XOR, PQ, MEMCPY) can be tested with the same code.
>>
>> It supports threads and user space interaction.
>>
>> The code I want to change (dmatest_func) is 3 levels deep in
>> structure. My refactored code looked really ugly compared to the
>> original code.
>
> dmatest_func is still a bigger fn specific to dmatest. I was thinking that
> we
> should have rather have two common functions
> 1) dmatest_do_dma() which does buffer allocation, invoking dmaengine APIs
> and
> checking results, part of dmatest_func today
>
> 2) request_channels() would also be common along with cleanup routines
>

Tried again,

It still doesn't look right. Depending on the used API, the source and
destination buffers are different.

For instance, when you use dma_map_page, your source addresses are pages.
If you use scatter_sg, you are working on the scatter-gather list. If you
use dma_coherent, then the source cannot be a kmalloc API. Source comes
from the DMA API. If you use dma_map_single, it is a kmalloc buffer. This
is causing havoc in the compare APIs.

IMO, in the goals of sharing code, we are making it unmanageable.

I'm leaning towards dropping this patch altogether (which I'm not very
happy about) or pushing it this way and have somebody do the
reorganization in another iteration.





^ permalink raw reply	[flat|nested] 41+ messages in thread

end of thread, other threads:[~2015-11-13 20:21 UTC | newest]

Thread overview: 41+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <1446444460-21600-1-git-send-email-okaya@codeaurora.org>
2015-11-02  6:07 ` [PATCH V2 1/3] dma: add Qualcomm Technologies HIDMA management driver Sinan Kaya
2015-11-02 15:57   ` Rob Herring
2015-11-02 16:20     ` Sinan Kaya
2015-11-02 17:26       ` Timur Tabi
2015-11-02 17:42         ` Rob Herring
2015-11-02 17:48           ` Timur Tabi
2015-11-02 18:25             ` Rob Herring
2015-11-02 18:30               ` Timur Tabi
2015-11-05 14:31                 ` Rob Herring
2015-11-05 14:43                   ` Timur Tabi
2015-11-02 18:49           ` Sinan Kaya
2015-11-02 22:00             ` Arnd Bergmann
2015-11-03  5:18     ` Sinan Kaya
2015-11-03 10:22   ` Andy Shevchenko
2015-11-04  0:47     ` Sinan Kaya
2015-11-02  6:07 ` [PATCH V2 2/3] dmaselftest: add memcpy selftest support functions Sinan Kaya
2015-11-03  4:15   ` Vinod Koul
2015-11-03  4:18     ` Sinan Kaya
2015-11-03  6:30       ` Vinod Koul
2015-11-03  7:44         ` Dan Williams
2015-11-03  8:22           ` Andy Shevchenko
2015-11-03 16:08             ` Vinod Koul
2015-11-05  2:42               ` Sinan Kaya
2015-11-05 12:05                 ` Vinod Koul
2015-11-05 16:17                   ` Sinan Kaya
2015-11-07  6:23                     ` Sinan Kaya
2015-11-08 13:53                       ` Vinod Koul
2015-11-13 20:20                         ` okaya
2015-11-03 15:51           ` Sinan Kaya
2015-11-03 16:06           ` Vinod Koul
2015-11-03 14:31       ` Timur Tabi
2015-11-03 16:10         ` Vinod Koul
2015-11-03 16:28           ` Sinan Kaya
2015-11-03 16:46             ` Timur Tabi
2015-11-03 16:57               ` Sinan Kaya
2015-11-03 16:48           ` Timur Tabi
2015-11-02  6:07 ` [PATCH V2 3/3] dma: add Qualcomm Technologies HIDMA channel driver Sinan Kaya
2015-11-03 10:10   ` Andy Shevchenko
2015-11-04  0:07     ` Sinan Kaya
2015-11-04 17:44       ` Andy Shevchenko
2015-11-05  2:22         ` Sinan Kaya

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).