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 managment for all channels provided by the device. The channel driver is exected in the guest OS context. Signed-off-by: Sinan Kaya <okaya@xxxxxxxxxxxxxx> --- .../devicetree/bindings/dma/qcom_hidma_mgmt.txt | 42 + drivers/dma/Kconfig | 11 + drivers/dma/Makefile | 1 + drivers/dma/qcom_hidma_mgmt.c | 868 +++++++++++++++++++++ 4 files changed, 922 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..81674ab --- /dev/null +++ b/Documentation/devicetree/bindings/dma/qcom_hidma_mgmt.txt @@ -0,0 +1,42 @@ +Qualcomm Technologies HIDMA Management interface + +Required properties: +- compatible: must contain "qcom,hidma_mgmt" +- reg: Address range for DMA device +- interrupts: Should contain the one interrupt shared by all channels +- nr-channels: Number of channels supported by this DMA controller. +- max-write: Maximum write burst in bytes. A memcpy requested is + fragmented to multiples of this amount. +- max-read: Maximum read burst in bytes. A memcpy request is + fragmented to multiples of this amount. +- max-wxactions: Maximum write transactions to perform in a burst +- max-rdactions: Maximum read transactions to perform in a burst +- max-memset-limit: Maximum memset limit +- ch-priority-#n: Priority of the channel +- ch-weight-#n: Round robin weight of the channel +Example: + + hidma-mgmt@f9984000 = { + compatible = "qcom,hidma_mgmt"; + reg = <0xf9984000 0x15000>; + interrupts = <0 94 0>; + nr-channels = 6; + max-write = 1024; + max-read = 1024; + max-wxactions = 31; + max-rdactions = 31; + max-memset-limit = 8; + ch-priority-0 = 0; + ch-priority-1 = 1; + ch-priority-2 = 1; + ch-priority-3 = 0; + ch-priority-4 = 0; + ch-priority-5 = 0; + ch-weight-0 = 1; + ch-weight-1 = 13; + ch-weight-2 = 10; + ch-weight-3 = 3; + ch-weight-4 = 4; + ch-weight-5 = 5; + }; + diff --git a/drivers/dma/Kconfig b/drivers/dma/Kconfig index b458475..76a5a5e 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 + bool "Qualcomm Technologies HIDMA Managment 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 driver can start + managing the channels. In a virtualized environment, the guest + OS would run QCOM_HIDMA driver and the hypervisor would run + the QCOM_HIDMA_MGMT 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..8fcad4d --- /dev/null +++ b/drivers/dma/qcom_hidma_mgmt.c @@ -0,0 +1,868 @@ +/* + * 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> +#include "qcom_hidma.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_TIMEOUUT_OFFSET 0x500 +#define MEMSET_LIMIT_OFFSET 0x600 +#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 MEMSET_LIMIT_MASK 0x1F +#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 MODULE_NAME "hidma-mgmt" +#define PREFIX MODULE_NAME ": " +#define AUTOSUSPEND_TIMEOUT 2000 + +#define HIDMA_RUNTIME_GET(dmadev) \ +do { \ + atomic_inc(&(dmadev)->pm_counter); \ + TRC_PM(&(dmadev)->pdev->dev, \ + "%s:%d pm_runtime_get %d\n", __func__, __LINE__,\ + atomic_read(&(dmadev)->pm_counter)); \ + pm_runtime_get_sync(&(dmadev)->pdev->dev); \ +} while (0) + +#define HIDMA_RUNTIME_SET(dmadev) \ +do { \ + atomic_dec(&(dmadev)->pm_counter); \ + TRC_PM(&(dmadev)->pdev->dev, \ + "%s:%d pm_runtime_put_autosuspend:%d\n", \ + __func__, __LINE__, \ + atomic_read(&(dmadev)->pm_counter)); \ + pm_runtime_mark_last_busy(&(dmadev)->pdev->dev); \ + pm_runtime_put_autosuspend(&(dmadev)->pdev->dev); \ +} while (0) + +struct qcom_hidma_mgmt_dev { + u8 max_wr_xactions; + u8 max_rd_xactions; + u8 max_memset_limit; + u16 max_write_request; + u16 max_read_request; + u16 nr_channels; + u32 chreset_timeout; + u32 sw_version; + u8 *priority; + u8 *weight; + + atomic_t pm_counter; + /* Hardware device constants */ + dma_addr_t dev_physaddr; + void __iomem *dev_virtaddr; + resource_size_t dev_addrsize; + + struct dentry *debugfs; + struct dentry *info; + struct dentry *err; + struct dentry *mhid_errclr; + struct dentry *evt_errclr; + struct dentry *ide_errclr; + struct dentry *ode_errclr; + struct dentry *msi_errclr; + struct dentry *tre_errclr; + struct dentry *evt_ena; + 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)"); + +#define TRC_PM(...) do { \ + if (debug_pm) \ + dev_info(__VA_ARGS__); \ + } while (0) + + +#if IS_ENABLED(CONFIG_DEBUG_FS) + +#define HIDMA_SHOW(dma, name) \ + seq_printf(s, #name "=0x%x\n", dma->name) + +#define HIDMA_READ_SHOW(dma, name, offset) \ + do { \ + u32 val; \ + val = readl(dma->dev_virtaddr + offset); \ + seq_printf(s, name "=0x%x\n", val); \ + } while (0) + +/** + * 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; + int i; + + HIDMA_RUNTIME_GET(mgmtdev); + HIDMA_SHOW(mgmtdev, sw_version); + + val = readl(mgmtdev->dev_virtaddr + CFG_OFFSET); + seq_printf(s, "ENABLE=%d\n", val & 0x1); + + val = readl(mgmtdev->dev_virtaddr + CHRESET_TIMEOUUT_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, "nr_virt_tr_channel=%d\n", mgmtdev->nr_channels); + seq_printf(s, "dev_virtaddr=%p\n", &mgmtdev->dev_virtaddr); + seq_printf(s, "dev_physaddr=%pap\n", &mgmtdev->dev_physaddr); + seq_printf(s, "dev_addrsize=%pap\n", &mgmtdev->dev_addrsize); + + val = readl(mgmtdev->dev_virtaddr + MEMSET_LIMIT_OFFSET); + seq_printf(s, "MEMSET_LIMIT_OFFSET=%d\n", val & MEMSET_LIMIT_MASK); + + 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->nr_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); + } + HIDMA_RUNTIME_SET(mgmtdev); + + 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; + + HIDMA_RUNTIME_GET(mgmtdev); + 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(mgmtdev, "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(mgmtdev, "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(mgmtdev, "IDE ADDR_LSB", IDE_BUS_ERR1_OFFSET); + HIDMA_READ_SHOW(mgmtdev, "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(mgmtdev, "ODE ADDR_LSB", ODE_BUS_ERR1_OFFSET); + HIDMA_READ_SHOW(mgmtdev, "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(mgmtdev, "HW_EVENTS_CFG_OFFSET", + HW_EVENTS_CFG_OFFSET); + + HIDMA_RUNTIME_SET(mgmtdev); + 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; + + HIDMA_RUNTIME_GET(mgmtdev); + writel(1, mgmtdev->dev_virtaddr + MHID_BUS_ERR_CLR_OFFSET); + HIDMA_RUNTIME_SET(mgmtdev); + 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; + + HIDMA_RUNTIME_GET(mgmtdev); + writel(1, mgmtdev->dev_virtaddr + EVT_BUS_ERR_CLR_OFFSET); + HIDMA_RUNTIME_SET(mgmtdev); + 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; + + HIDMA_RUNTIME_GET(mgmtdev); + writel(1, mgmtdev->dev_virtaddr + IDE_BUS_ERR_CLR_OFFSET); + HIDMA_RUNTIME_SET(mgmtdev); + 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; + + HIDMA_RUNTIME_GET(mgmtdev); + writel(1, mgmtdev->dev_virtaddr + ODE_BUS_ERR_CLR_OFFSET); + HIDMA_RUNTIME_SET(mgmtdev); + 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; + + HIDMA_RUNTIME_GET(mgmtdev); + writel(1, mgmtdev->dev_virtaddr + MSI_BUS_ERR_CLR_OFFSET); + HIDMA_RUNTIME_SET(mgmtdev); + 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; + + HIDMA_RUNTIME_GET(mgmtdev); + writel(1, mgmtdev->dev_virtaddr + TRE_ERR_CLR_OFFSET); + HIDMA_RUNTIME_SET(mgmtdev); + 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) { + pr_warn(PREFIX "unknown event\n"); + goto out; + } + + event = (u32)val & HW_EVENTS_CFG_MASK; + + HIDMA_RUNTIME_GET(mgmtdev); + writel(event, mgmtdev->dev_virtaddr + HW_EVENTS_CFG_OFFSET); + HIDMA_RUNTIME_SET(mgmtdev); +out: + return count; +} + +static const struct file_operations qcom_hidma_mgmt_evtena_fops = { + .write = qcom_hidma_mgmt_evtena, +}; + +static void qcom_hidma_mgmt_debug_uninit(struct qcom_hidma_mgmt_dev *mgmtdev) +{ + debugfs_remove(mgmtdev->evt_ena); + debugfs_remove(mgmtdev->tre_errclr); + debugfs_remove(mgmtdev->msi_errclr); + debugfs_remove(mgmtdev->ode_errclr); + debugfs_remove(mgmtdev->ide_errclr); + debugfs_remove(mgmtdev->evt_errclr); + debugfs_remove(mgmtdev->mhid_errclr); + debugfs_remove(mgmtdev->err); + debugfs_remove(mgmtdev->info); + debugfs_remove(mgmtdev->debugfs); +} + +static int qcom_hidma_mgmt_debug_init(struct qcom_hidma_mgmt_dev *mgmtdev) +{ + int rc = 0; + + mgmtdev->debugfs = debugfs_create_dir(dev_name(&mgmtdev->pdev->dev), + NULL); + if (!mgmtdev->debugfs) { + rc = -ENODEV; + return rc; + } + + mgmtdev->info = debugfs_create_file("info", S_IRUGO, + mgmtdev->debugfs, mgmtdev, &qcom_hidma_mgmt_fops); + if (!mgmtdev->info) { + rc = -ENOMEM; + goto cleanup; + } + + mgmtdev->err = debugfs_create_file("err", S_IRUGO, + mgmtdev->debugfs, mgmtdev, + &qcom_hidma_mgmt_err_fops); + if (!mgmtdev->err) { + rc = -ENOMEM; + goto cleanup; + } + + mgmtdev->mhid_errclr = debugfs_create_file("mhiderrclr", S_IWUSR, + mgmtdev->debugfs, mgmtdev, + &qcom_hidma_mgmt_mhiderr_clrfops); + if (!mgmtdev->mhid_errclr) { + rc = -ENOMEM; + goto cleanup; + } + + mgmtdev->evt_errclr = debugfs_create_file("evterrclr", S_IWUSR, + mgmtdev->debugfs, mgmtdev, + &qcom_hidma_mgmt_evterr_clrfops); + if (!mgmtdev->evt_errclr) { + rc = -ENOMEM; + goto cleanup; + } + + mgmtdev->ide_errclr = debugfs_create_file("ideerrclr", S_IWUSR, + mgmtdev->debugfs, mgmtdev, + &qcom_hidma_mgmt_ideerr_clrfops); + if (!mgmtdev->ide_errclr) { + rc = -ENOMEM; + goto cleanup; + } + + mgmtdev->ode_errclr = debugfs_create_file("odeerrclr", S_IWUSR, + mgmtdev->debugfs, mgmtdev, + &qcom_hidma_mgmt_odeerr_clrfops); + if (!mgmtdev->ode_errclr) { + rc = -ENOMEM; + goto cleanup; + } + + mgmtdev->msi_errclr = debugfs_create_file("msierrclr", S_IWUSR, + mgmtdev->debugfs, mgmtdev, + &qcom_hidma_mgmt_msierr_clrfops); + if (!mgmtdev->msi_errclr) { + rc = -ENOMEM; + goto cleanup; + } + + mgmtdev->tre_errclr = debugfs_create_file("treerrclr", S_IWUSR, + mgmtdev->debugfs, mgmtdev, + &qcom_hidma_mgmt_treerr_clrfops); + if (!mgmtdev->tre_errclr) { + rc = -ENOMEM; + goto cleanup; + } + + mgmtdev->evt_ena = debugfs_create_file("evtena", S_IWUSR, + mgmtdev->debugfs, mgmtdev, + &qcom_hidma_mgmt_evtena_fops); + if (!mgmtdev->evt_ena) { + 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 irqreturn_t qcom_hidma_mgmt_irq_handler(int irq, void *arg) +{ + /* TODO: handle irq here */ + return IRQ_HANDLED; +} + +static int qcom_hidma_mgmt_setup(struct qcom_hidma_mgmt_dev *mgmtdev) +{ + u32 val; + int i; + + val = readl(mgmtdev->dev_virtaddr + MAX_BUS_REQ_LEN_OFFSET); + + if (mgmtdev->max_write_request) { + 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); + } + if (mgmtdev->max_read_request) { + 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); + if (mgmtdev->max_wr_xactions) { + val = val & + ~(MAX_WR_XACTIONS_MASK << MAX_WR_XACTIONS_BIT_POS); + val = val | + (mgmtdev->max_wr_xactions << MAX_WR_XACTIONS_BIT_POS); + } + if (mgmtdev->max_rd_xactions) { + val = val & ~(MAX_RD_XACTIONS_MASK); + val = val | (mgmtdev->max_rd_xactions); + } + writel(val, mgmtdev->dev_virtaddr + MAX_XACTIONS_OFFSET); + + val = readl(mgmtdev->dev_virtaddr + MAX_BUS_REQ_LEN_OFFSET); + mgmtdev->max_write_request = + (val >> MAX_BUS_WR_REQ_BIT_POS) & MAX_BUS_REQ_LEN_MASK; + mgmtdev->max_read_request = val & MAX_BUS_REQ_LEN_MASK; + + val = readl(mgmtdev->dev_virtaddr + MAX_XACTIONS_OFFSET); + mgmtdev->max_wr_xactions = + (val >> MAX_WR_XACTIONS_BIT_POS) & MAX_WR_XACTIONS_MASK; + mgmtdev->max_rd_xactions = val & MAX_RD_XACTIONS_MASK; + + mgmtdev->sw_version = readl(mgmtdev->dev_virtaddr + SW_VERSION_OFFSET); + + for (i = 0; i < mgmtdev->nr_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)); + } + + if (mgmtdev->chreset_timeout > 0) { + val = readl(mgmtdev->dev_virtaddr + CHRESET_TIMEOUUT_OFFSET); + val = val & ~CHRESET_TIMEOUUT_MASK; + val = val | (mgmtdev->chreset_timeout & CHRESET_TIMEOUUT_MASK); + writel(val, mgmtdev->dev_virtaddr + CHRESET_TIMEOUUT_OFFSET); + } + val = readl(mgmtdev->dev_virtaddr + CHRESET_TIMEOUUT_OFFSET); + mgmtdev->chreset_timeout = val & CHRESET_TIMEOUUT_MASK; + + if (mgmtdev->max_memset_limit > 0) { + val = readl(mgmtdev->dev_virtaddr + MEMSET_LIMIT_OFFSET); + val = val & ~MEMSET_LIMIT_MASK; + val = val | (mgmtdev->max_memset_limit & MEMSET_LIMIT_MASK); + writel(val, mgmtdev->dev_virtaddr + MEMSET_LIMIT_OFFSET); + } + val = readl(mgmtdev->dev_virtaddr + MEMSET_LIMIT_OFFSET); + mgmtdev->max_memset_limit = val & MEMSET_LIMIT_MASK; + + 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, 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; + HIDMA_RUNTIME_GET(mgmtdev); + + rc = devm_request_irq(&pdev->dev, irq, qcom_hidma_mgmt_irq_handler, + IRQF_SHARED, "qcom-hidmamgmt", mgmtdev); + if (rc) { + dev_err(&pdev->dev, "irq registration failed: %d\n", irq); + goto out; + } + + mgmtdev->dev_physaddr = dma_resource->start; + mgmtdev->dev_addrsize = resource_size(dma_resource); + + dev_dbg(&pdev->dev, "dev_physaddr:%pa\n", &mgmtdev->dev_physaddr); + dev_dbg(&pdev->dev, "dev_addrsize:%pa\n", &mgmtdev->dev_addrsize); + + 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", + &mgmtdev->dev_physaddr); + rc = -ENOMEM; + goto out; + } + + if (device_property_read_u16(&pdev->dev, "nr-channels", + &mgmtdev->nr_channels)) { + dev_err(&pdev->dev, "number of channels missing\n"); + rc = -EINVAL; + goto out; + } + + device_property_read_u16(&pdev->dev, "max-write", + &mgmtdev->max_write_request); + 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; + } + + device_property_read_u16(&pdev->dev, "max-read", + &mgmtdev->max_read_request); + 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; + } + + device_property_read_u8(&pdev->dev, "max-wxactions", + &mgmtdev->max_wr_xactions); + + device_property_read_u8(&pdev->dev, "max-rdactions", + &mgmtdev->max_rd_xactions); + + device_property_read_u8(&pdev->dev, "max-memset-limit", + &mgmtdev->max_memset_limit); + + /* needs to be at least one */ + if (mgmtdev->max_memset_limit == 0) + mgmtdev->max_memset_limit = 1; + + mgmtdev->priority = devm_kcalloc(&pdev->dev, + mgmtdev->nr_channels, sizeof(*mgmtdev->priority), GFP_KERNEL); + if (!mgmtdev->priority) { + rc = -ENOMEM; + goto out; + } + + mgmtdev->weight = devm_kcalloc(&pdev->dev, + mgmtdev->nr_channels, sizeof(*mgmtdev->weight), GFP_KERNEL); + if (!mgmtdev->weight) { + rc = -ENOMEM; + goto out; + } + + for (i = 0; i < mgmtdev->nr_channels; i++) { + char name[30]; + + sprintf(name, "ch-priority-%d", i); + device_property_read_u8(&pdev->dev, name, + &mgmtdev->priority[i]); + + sprintf(name, "ch-weight-%d", i); + device_property_read_u8(&pdev->dev, name, + &mgmtdev->weight[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); + HIDMA_RUNTIME_SET(mgmtdev); + 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); + + HIDMA_RUNTIME_GET(mgmtdev); + 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", }, + {}, +}; +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 = MODULE_NAME, + .owner = THIS_MODULE, + .of_match_table = of_match_ptr(qcom_hidma_mgmt_match), + .acpi_match_table = ACPI_PTR(qcom_hidma_mgmt_acpi_ids), + }, +}; + +static int __init qcom_hidma_mgmt_init(void) +{ + return platform_driver_register(&qcom_hidma_mgmt_driver); +} +device_initcall(qcom_hidma_mgmt_init); + +static void __exit qcom_hidma_mgmt_exit(void) +{ + platform_driver_unregister(&qcom_hidma_mgmt_driver); +} +module_exit(qcom_hidma_mgmt_exit); -- 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 devicetree" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html