summaryrefslogtreecommitdiff
path: root/drivers/soc
diff options
context:
space:
mode:
authorShashank Mittal <mittals@codeaurora.org>2015-04-13 18:39:59 -0700
committerJeevan Shriram <jshriram@codeaurora.org>2016-05-16 20:10:35 -0700
commitace81764b1675e963425b51d74d294befe2228bc (patch)
tree989190bbf2b502e0c27eab3a7f94db2319bb773e /drivers/soc
parentcf4266513cf20c214b1f668e21fc24abc2dbfe95 (diff)
soc: qcom: dcc: add support for DCC driver
DCC (Data Capture and Compare) is a DMA engine which is used to save configuration data or system memory contents during catastrophic failure or SW trigger. It can also perform CRC over the same configuration or memory space. Change-Id: Ic8a804250ab8b7ac501bd186d2e6f7506bb9b21a Signed-off-by: Shashank Mittal <mittals@codeaurora.org>
Diffstat (limited to 'drivers/soc')
-rw-r--r--drivers/soc/qcom/Kconfig7
-rw-r--r--drivers/soc/qcom/Makefile1
-rw-r--r--drivers/soc/qcom/dcc.c1009
3 files changed, 1017 insertions, 0 deletions
diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index bf61797653be..cc5a7b4de476 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -219,6 +219,13 @@ config MSM_HVC
Partner/Manufacturer function identifier subrange but supports other
service call subranges as well.
+config QCOM_DCC
+ bool "QCOM Data Capture and Compare enigne support"
+ help
+ This option enables driver for Data Capture and Compare engine. DCC
+ driver provides interface to configure DCC block and read back
+ captured data from DCC's internal SRAM.
+
config MSM_IPC_ROUTER_SMD_XPRT
depends on MSM_SMD
depends on IPC_ROUTER
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 40c7f4c5f598..a2fcc3c36922 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -64,6 +64,7 @@ obj-$(CONFIG_QCOM_SCM_XPU) += scm-xpu.o
obj-$(CONFIG_QCOM_WATCHDOG_V2) += watchdog_v2.o
obj-$(CONFIG_QCOM_MEMORY_DUMP) += memory_dump.o
obj-$(CONFIG_QCOM_MEMORY_DUMP_V2) += memory_dump_v2.o
+obj-$(CONFIG_QCOM_DCC) += dcc.o
obj-$(CONFIG_QCOM_WATCHDOG_V2) += watchdog_v2.o
obj-$(CONFIG_QCOM_COMMON_LOG) += common_log.o
obj-$(CONFIG_TRACER_PKT) += tracer_pkt.o
diff --git a/drivers/soc/qcom/dcc.c b/drivers/soc/qcom/dcc.c
new file mode 100644
index 000000000000..7f6bfd1e15a5
--- /dev/null
+++ b/drivers/soc/qcom/dcc.c
@@ -0,0 +1,1009 @@
+/* Copyright (c) 2015-2016, 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/module.h>
+#include <linux/bitops.h>
+#include <linux/cdev.h>
+#include <linux/clk.h>
+#include <linux/delay.h>
+#include <linux/io.h>
+#include <linux/iopoll.h>
+#include <linux/fs.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+#include <soc/qcom/memory_dump.h>
+
+#define TIMEOUT_US (100)
+
+#define BM(lsb, msb) ((BIT(msb) - BIT(lsb)) + BIT(msb))
+#define BMVAL(val, lsb, msb) ((val & BM(lsb, msb)) >> lsb)
+#define BVAL(val, n) ((val & BIT(n)) >> n)
+
+#define dcc_writel(drvdata, val, off) \
+ __raw_writel((val), drvdata->base + off)
+#define dcc_readl(drvdata, off) \
+ __raw_readl(drvdata->base + off)
+
+#define dcc_sram_writel(drvdata, val, off) \
+ __raw_writel((val), drvdata->ram_base + off)
+#define dcc_sram_readl(drvdata, off) \
+ __raw_readl(drvdata->ram_base + off)
+
+/* DCC registers */
+#define DCC_HW_VERSION (0x00)
+#define DCC_HW_INFO (0x04)
+#define DCC_CGC_CFG (0x10)
+#define DCC_LL (0x14)
+#define DCC_RAM_CFG (0x18)
+#define DCC_CFG (0x1C)
+#define DCC_SW_CTL (0x20)
+#define DCC_STATUS (0x24)
+#define DCC_FETCH_ADDR (0x28)
+#define DCC_SRAM_ADDR (0x2C)
+#define DCC_INT_ENABLE (0x30)
+#define DCC_INT_STATUS (0x34)
+#define DCC_QSB_CFG (0x38)
+
+#define DCC_REG_DUMP_MAGIC_V2 (0x42445953)
+#define DCC_REG_DUMP_VER (1)
+
+enum dcc_func_type {
+ DCC_FUNC_TYPE_CAPTURE,
+ DCC_FUNC_TYPE_CRC,
+};
+
+static const char * const str_dcc_func_type[] = {
+ [DCC_FUNC_TYPE_CAPTURE] = "cap",
+ [DCC_FUNC_TYPE_CRC] = "crc",
+};
+
+enum dcc_data_sink {
+ DCC_DATA_SINK_ATB,
+ DCC_DATA_SINK_SRAM
+};
+
+static const char * const str_dcc_data_sink[] = {
+ [DCC_DATA_SINK_ATB] = "atb",
+ [DCC_DATA_SINK_SRAM] = "sram",
+};
+
+struct dcc_config_entry {
+ uint32_t base;
+ uint32_t offset;
+ uint32_t len;
+ uint32_t index;
+ struct list_head list;
+};
+
+struct dcc_drvdata {
+ void __iomem *base;
+ uint32_t reg_size;
+ struct device *dev;
+ struct mutex mutex;
+ void __iomem *ram_base;
+ uint32_t ram_size;
+ struct clk *clk;
+ enum dcc_data_sink data_sink;
+ enum dcc_func_type func_type;
+ uint32_t ram_cfg;
+ bool enable;
+ char *sram_node;
+ struct cdev sram_dev;
+ struct class *sram_class;
+ struct list_head config_head;
+ uint32_t nr_config;
+ void *reg_buf;
+ struct msm_dump_data reg_data;
+ bool save_reg;
+ void *sram_buf;
+ struct msm_dump_data sram_data;
+};
+
+static bool dcc_ready(struct dcc_drvdata *drvdata)
+{
+ uint32_t val;
+
+ /* poll until DCC ready */
+ if (!readx_poll_timeout(__raw_readl, (drvdata->base + DCC_STATUS), val,
+ (BVAL(val, 4) == 1), 1, TIMEOUT_US))
+ return true;
+
+ return false;
+}
+
+static int dcc_sw_trigger(struct dcc_drvdata *drvdata)
+{
+ int ret;
+
+ ret = 0;
+ mutex_lock(&drvdata->mutex);
+
+ if (!drvdata->enable) {
+ dev_err(drvdata->dev,
+ "DCC is disabled. Can't send sw trigger.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ if (!dcc_ready(drvdata)) {
+ dev_err(drvdata->dev, "DCC is not ready!\n");
+ ret = -EBUSY;
+ goto err;
+ }
+
+ dcc_writel(drvdata, 1, DCC_SW_CTL);
+
+ if (!dcc_ready(drvdata)) {
+ dev_err(drvdata->dev,
+ "DCC is busy after receiving sw tigger.\n");
+ ret = -EBUSY;
+ goto err;
+ }
+err:
+ mutex_unlock(&drvdata->mutex);
+ return ret;
+}
+
+static int __dcc_ll_cfg(struct dcc_drvdata *drvdata)
+{
+ int ret = 0;
+ uint32_t sram_offset = 0;
+ uint32_t prev_addr, addr;
+ uint32_t prev_off, off;
+ uint32_t link;
+ uint32_t pos;
+ struct dcc_config_entry *entry;
+
+ if (list_empty(&drvdata->config_head)) {
+ dev_err(drvdata->dev,
+ "No configuration is available to program in DCC SRAM!\n");
+ return -EINVAL;
+ }
+
+ memset_io(drvdata->ram_base, 0, drvdata->ram_size);
+
+ prev_addr = 0;
+ link = 0;
+
+ list_for_each_entry(entry, &drvdata->config_head, list) {
+ /* Address type */
+ addr = (entry->base >> 4) & BM(0, 27);
+ addr |= BIT(31);
+ off = entry->offset/4;
+
+ if (!prev_addr || prev_addr != addr || prev_off > off) {
+ /* Check if we need to write link of prev entry */
+ if (link) {
+ dcc_sram_writel(drvdata, link, sram_offset);
+ sram_offset += 4;
+ }
+
+ /* Write address */
+ dcc_sram_writel(drvdata, addr, sram_offset);
+ sram_offset += 4;
+
+ /* Reset link and prev_off */
+ link = 0;
+ prev_off = 0;
+ }
+
+ if ((off - prev_off) > 0xFF || entry->len > 0x7F) {
+ dev_err(drvdata->dev,
+ "DCC: Progamming error! Base: 0x%x, offset 0x%x.\n",
+ entry->base, entry->offset);
+ ret = -EINVAL;
+ goto err;
+ }
+
+ if (link) {
+ /*
+ * link already has one offset-length so new
+ * offset-length needs to be placed at bits [31:16]
+ */
+ pos = 16;
+
+ /* Clear bits [31:16] */
+ link &= BM(0, 15);
+
+ } else {
+ /*
+ * link is empty, so new offset-length needs to be
+ * placed at bits [15:0]
+ */
+ pos = 0;
+ link = 1 << 16;
+ }
+
+ /* write new offset-length pair to correct position */
+ link |= (((off-prev_off) & BM(0, 7)) |
+ ((entry->len << 8) & BM(8, 14))) << pos;
+
+ if (pos) {
+ dcc_sram_writel(drvdata, link, sram_offset);
+ sram_offset += 4;
+ link = 0;
+ }
+
+ prev_off = off;
+ prev_addr = addr;
+ }
+
+ if (link) {
+ dcc_sram_writel(drvdata, link, sram_offset);
+ sram_offset += 4;
+ }
+
+ /* Setting zero to indicate end of the list */
+ dcc_sram_writel(drvdata, 0, sram_offset);
+ sram_offset += 4;
+
+ drvdata->ram_cfg = (sram_offset / 4);
+
+err:
+ return ret;
+}
+
+static void __dcc_reg_dump(struct dcc_drvdata *drvdata)
+{
+ uint32_t *reg_buf;
+
+ if (!drvdata->reg_buf)
+ return;
+
+ drvdata->reg_data.version = DCC_REG_DUMP_VER;
+
+ reg_buf = drvdata->reg_buf;
+
+ reg_buf[0] = dcc_readl(drvdata, DCC_HW_VERSION);
+ reg_buf[1] = dcc_readl(drvdata, DCC_HW_INFO);
+ reg_buf[2] = dcc_readl(drvdata, DCC_CGC_CFG);
+ reg_buf[3] = dcc_readl(drvdata, DCC_LL);
+ reg_buf[4] = dcc_readl(drvdata, DCC_RAM_CFG);
+ reg_buf[5] = dcc_readl(drvdata, DCC_CFG);
+ reg_buf[6] = dcc_readl(drvdata, DCC_SW_CTL);
+ reg_buf[7] = dcc_readl(drvdata, DCC_STATUS);
+ reg_buf[8] = dcc_readl(drvdata, DCC_FETCH_ADDR);
+ reg_buf[9] = dcc_readl(drvdata, DCC_SRAM_ADDR);
+ reg_buf[10] = dcc_readl(drvdata, DCC_INT_ENABLE);
+ reg_buf[11] = dcc_readl(drvdata, DCC_INT_STATUS);
+ reg_buf[12] = dcc_readl(drvdata, DCC_QSB_CFG);
+
+ drvdata->reg_data.magic = DCC_REG_DUMP_MAGIC_V2;
+}
+
+static int dcc_enable(struct dcc_drvdata *drvdata)
+{
+ int ret = 0;
+
+ mutex_lock(&drvdata->mutex);
+
+ if (drvdata->enable) {
+ dev_err(drvdata->dev, "DCC is already enabled!\n");
+ mutex_unlock(&drvdata->mutex);
+ return 0;
+ }
+
+ /* 1. Prepare and enable DCC clock */
+ ret = clk_prepare_enable(drvdata->clk);
+ if (ret)
+ goto err;
+
+ dcc_writel(drvdata, 0, DCC_LL);
+
+ /* 2. Program linked-list in the SRAM */
+ ret = __dcc_ll_cfg(drvdata);
+ if (ret)
+ goto err_prog_ll;
+
+ /* 3. If in capture mode program DCC_RAM_CFG reg */
+ if (drvdata->func_type == DCC_FUNC_TYPE_CAPTURE)
+ dcc_writel(drvdata, drvdata->ram_cfg, DCC_RAM_CFG);
+
+ /* 4. Configure data sink and function type */
+ dcc_writel(drvdata, ((drvdata->data_sink << 4) | (drvdata->func_type)),
+ DCC_CFG);
+
+ /* 5. Clears interrupt status register */
+ dcc_writel(drvdata, 0, DCC_INT_STATUS);
+
+ /* Make sure all config is written in sram */
+ mb();
+
+ /* 6. Set LL bit */
+ dcc_writel(drvdata, 1, DCC_LL);
+ drvdata->enable = 1;
+
+ /* Save DCC registers */
+ if (drvdata->save_reg)
+ __dcc_reg_dump(drvdata);
+
+err_prog_ll:
+ if (!drvdata->enable)
+ clk_disable_unprepare(drvdata->clk);
+err:
+ mutex_unlock(&drvdata->mutex);
+ return ret;
+}
+
+static void dcc_disable(struct dcc_drvdata *drvdata)
+{
+ mutex_lock(&drvdata->mutex);
+ if (!drvdata->enable) {
+ mutex_unlock(&drvdata->mutex);
+ return;
+ }
+
+ if (!dcc_ready(drvdata))
+ dev_err(drvdata->dev, "DCC is not ready! Disabling DCC...\n");
+
+ dcc_writel(drvdata, 0, DCC_LL);
+ drvdata->enable = 0;
+
+ /* Save DCC registers */
+ if (drvdata->save_reg)
+ __dcc_reg_dump(drvdata);
+
+ clk_disable_unprepare(drvdata->clk);
+
+ mutex_unlock(&drvdata->mutex);
+}
+
+static ssize_t dcc_show_func_type(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n",
+ str_dcc_func_type[drvdata->func_type]);
+}
+
+static ssize_t dcc_store_func_type(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+ char str[10] = "";
+ int ret;
+
+ if (strlen(buf) >= 10)
+ return -EINVAL;
+ if (sscanf(buf, "%s", str) != 1)
+ return -EINVAL;
+
+ mutex_lock(&drvdata->mutex);
+ if (drvdata->enable) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ if (!strcmp(str, str_dcc_func_type[DCC_FUNC_TYPE_CAPTURE]))
+ drvdata->func_type = DCC_FUNC_TYPE_CAPTURE;
+ else if (!strcmp(str, str_dcc_func_type[DCC_FUNC_TYPE_CRC]))
+ drvdata->func_type = DCC_FUNC_TYPE_CRC;
+ else {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = size;
+out:
+ mutex_unlock(&drvdata->mutex);
+ return ret;
+}
+static DEVICE_ATTR(func_type, S_IRUGO | S_IWUSR,
+ dcc_show_func_type, dcc_store_func_type);
+
+static ssize_t dcc_show_data_sink(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%s\n",
+ str_dcc_data_sink[drvdata->data_sink]);
+}
+
+static ssize_t dcc_store_data_sink(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+ char str[10] = "";
+ int ret;
+
+ if (strlen(buf) >= 10)
+ return -EINVAL;
+ if (sscanf(buf, "%s", str) != 1)
+ return -EINVAL;
+
+ mutex_lock(&drvdata->mutex);
+ if (drvdata->enable) {
+ ret = -EBUSY;
+ goto out;
+ }
+
+ if (!strcmp(str, str_dcc_data_sink[DCC_DATA_SINK_SRAM]))
+ drvdata->data_sink = DCC_DATA_SINK_SRAM;
+ else if (!strcmp(str, str_dcc_data_sink[DCC_DATA_SINK_ATB]))
+ drvdata->data_sink = DCC_DATA_SINK_ATB;
+ else {
+ ret = -EINVAL;
+ goto out;
+ }
+
+ ret = size;
+out:
+ mutex_unlock(&drvdata->mutex);
+ return ret;
+}
+static DEVICE_ATTR(data_sink, S_IRUGO | S_IWUSR,
+ dcc_show_data_sink, dcc_store_data_sink);
+
+static ssize_t dcc_store_trigger(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret = 0;
+ unsigned long val;
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+
+ if (kstrtoul(buf, 16, &val))
+ return -EINVAL;
+ if (val != 1)
+ return -EINVAL;
+
+ ret = dcc_sw_trigger(drvdata);
+ if (ret)
+ return ret;
+
+ return size;
+}
+static DEVICE_ATTR(trigger, S_IWUSR, NULL, dcc_store_trigger);
+
+static ssize_t dcc_show_enable(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+
+ return scnprintf(buf, PAGE_SIZE, "%u\n",
+ (unsigned)drvdata->enable);
+}
+
+static ssize_t dcc_store_enable(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret = 0;
+ unsigned long val;
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+
+ if (kstrtoul(buf, 16, &val))
+ return -EINVAL;
+
+ if (val)
+ ret = dcc_enable(drvdata);
+ else
+ dcc_disable(drvdata);
+
+ if (ret)
+ return ret;
+ return size;
+
+}
+static DEVICE_ATTR(enable, S_IRUGO | S_IWUSR, dcc_show_enable,
+ dcc_store_enable);
+
+static ssize_t dcc_show_config(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+ struct dcc_config_entry *entry;
+ char local_buf[64];
+ int len = 0, count = 0;
+
+ buf[0] = '\0';
+
+ mutex_lock(&drvdata->mutex);
+ list_for_each_entry(entry, &drvdata->config_head, list) {
+ len = snprintf(local_buf, 64,
+ "Index: 0x%x, Base: 0x%x, Offset: 0x%x, len: 0x%x\n",
+ entry->index, entry->base,
+ entry->offset, entry->len);
+
+ if ((count + len) > PAGE_SIZE) {
+ dev_err(dev, "DCC: Couldn't write complete config!\n");
+ break;
+ }
+
+ strlcat(buf, local_buf, PAGE_SIZE);
+ count += len;
+ }
+
+ mutex_unlock(&drvdata->mutex);
+
+ return count;
+}
+
+static int dcc_config_add(struct dcc_drvdata *drvdata, unsigned base,
+ unsigned offset, unsigned len)
+{
+ int ret;
+ struct dcc_config_entry *entry;
+
+ mutex_lock(&drvdata->mutex);
+
+ /* Validate data */
+ if (!len || len > BM(0, 6)) {
+ dev_err(drvdata->dev,
+ "DCC: Invalid length! Expected range [1, %u] in words.\n",
+ (unsigned int)BM(0, 6));
+ ret = -EINVAL;
+ goto err;
+ }
+
+ if (base & BM(0, 3)) {
+ dev_err(drvdata->dev,
+ "DCC: Invalid base! Expected [31:4] bits of actual address.\n");
+ ret = -EINVAL;
+ goto err;
+ }
+
+ entry = devm_kzalloc(drvdata->dev, sizeof(*entry), GFP_KERNEL);
+ if (!entry) {
+ ret = -ENOMEM;
+ goto err;
+ }
+
+ entry->base = base;
+ entry->offset = offset;
+ entry->len = len;
+ entry->index = drvdata->nr_config++;
+ INIT_LIST_HEAD(&entry->list);
+ list_add_tail(&entry->list, &drvdata->config_head);
+
+ mutex_unlock(&drvdata->mutex);
+ return 0;
+err:
+ mutex_unlock(&drvdata->mutex);
+ return ret;
+}
+
+static ssize_t dcc_store_config(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ int ret;
+ unsigned base, offset, len;
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+
+ if (sscanf(buf, "%x %x %u", &base, &offset, &len) != 3)
+ return -EINVAL;
+
+ ret = dcc_config_add(drvdata, base, offset, len);
+ if (ret)
+ return ret;
+
+ return size;
+
+}
+static DEVICE_ATTR(config, S_IRUGO | S_IWUSR, dcc_show_config,
+ dcc_store_config);
+
+static void dcc_config_reset(struct dcc_drvdata *drvdata)
+{
+ struct dcc_config_entry *entry, *temp;
+
+ mutex_lock(&drvdata->mutex);
+
+ list_for_each_entry_safe(entry, temp, &drvdata->config_head, list) {
+ list_del(&entry->list);
+ devm_kfree(drvdata->dev, entry);
+ drvdata->nr_config--;
+ }
+
+ mutex_unlock(&drvdata->mutex);
+}
+
+static ssize_t dcc_store_config_reset(struct device *dev,
+ struct device_attribute *attr,
+ const char *buf, size_t size)
+{
+ unsigned long val;
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+
+ if (kstrtoul(buf, 16, &val))
+ return -EINVAL;
+
+ if (val)
+ dcc_config_reset(drvdata);
+
+ return size;
+}
+static DEVICE_ATTR(config_reset, S_IWUSR, NULL, dcc_store_config_reset);
+
+static ssize_t dcc_show_crc_error(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+
+ mutex_lock(&drvdata->mutex);
+ if (!drvdata->enable) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = scnprintf(buf, PAGE_SIZE, "%u\n",
+ (unsigned)BVAL(dcc_readl(drvdata, DCC_STATUS), 0));
+err:
+ mutex_unlock(&drvdata->mutex);
+ return ret;
+}
+static DEVICE_ATTR(crc_error, S_IRUGO, dcc_show_crc_error, NULL);
+
+static ssize_t dcc_show_ready(struct device *dev,
+ struct device_attribute *attr, char *buf)
+{
+ int ret;
+ struct dcc_drvdata *drvdata = dev_get_drvdata(dev);
+
+ mutex_lock(&drvdata->mutex);
+ if (!drvdata->enable) {
+ ret = -EINVAL;
+ goto err;
+ }
+
+ ret = scnprintf(buf, PAGE_SIZE, "%u\n",
+ (unsigned)BVAL(dcc_readl(drvdata, DCC_STATUS), 4));
+err:
+ mutex_unlock(&drvdata->mutex);
+ return ret;
+}
+static DEVICE_ATTR(ready, S_IRUGO, dcc_show_ready, NULL);
+
+static const struct device_attribute *dcc_attrs[] = {
+ &dev_attr_func_type,
+ &dev_attr_data_sink,
+ &dev_attr_trigger,
+ &dev_attr_enable,
+ &dev_attr_config,
+ &dev_attr_config_reset,
+ &dev_attr_ready,
+ &dev_attr_crc_error,
+ NULL,
+};
+
+static int dcc_create_files(struct device *dev,
+ const struct device_attribute **attrs)
+{
+ int ret = 0, i;
+
+ for (i = 0; attrs[i] != NULL; i++) {
+ ret = device_create_file(dev, attrs[i]);
+ if (ret) {
+ dev_err(dev, "DCC: Couldn't create sysfs attribute: %s!\n",
+ attrs[i]->attr.name);
+ break;
+ }
+ }
+ return ret;
+}
+
+static int dcc_sram_open(struct inode *inode, struct file *file)
+{
+ struct dcc_drvdata *drvdata = container_of(inode->i_cdev,
+ struct dcc_drvdata,
+ sram_dev);
+ file->private_data = drvdata;
+ return 0;
+}
+
+static ssize_t dcc_sram_read(struct file *file, char __user *data,
+ size_t len, loff_t *ppos)
+{
+ int ret;
+ unsigned char *buf;
+ struct dcc_drvdata *drvdata = file->private_data;
+
+ /* EOF check */
+ if (drvdata->ram_size <= *ppos)
+ return 0;
+
+ if ((*ppos + len) > drvdata->ram_size)
+ len = (drvdata->ram_size - *ppos);
+
+ buf = kzalloc(len, GFP_KERNEL);
+ if (!buf)
+ return -ENOMEM;
+
+ ret = clk_prepare_enable(drvdata->clk);
+ if (ret) {
+ kfree(buf);
+ return ret;
+ }
+
+ memcpy_fromio(buf, (drvdata->ram_base + *ppos), len);
+
+ clk_disable_unprepare(drvdata->clk);
+
+ if (copy_to_user(data, buf, len)) {
+ dev_err(drvdata->dev,
+ "DCC: Couldn't copy all data to user!\n");
+ kfree(buf);
+ return -EFAULT;
+ }
+
+ *ppos += len;
+
+ kfree(buf);
+
+ return len;
+}
+
+static int dcc_sram_release(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static const struct file_operations dcc_sram_fops = {
+ .owner = THIS_MODULE,
+ .open = dcc_sram_open,
+ .read = dcc_sram_read,
+ .release = dcc_sram_release,
+ .llseek = no_llseek,
+};
+
+static int dcc_sram_dev_register(struct dcc_drvdata *drvdata)
+{
+ int ret;
+ struct device *device;
+ dev_t dev;
+
+ ret = alloc_chrdev_region(&dev, 0, 1, drvdata->sram_node);
+ if (ret)
+ goto err_alloc;
+
+ cdev_init(&drvdata->sram_dev, &dcc_sram_fops);
+
+ drvdata->sram_dev.owner = THIS_MODULE;
+ ret = cdev_add(&drvdata->sram_dev, dev, 1);
+ if (ret)
+ goto err_cdev_add;
+
+ drvdata->sram_class = class_create(THIS_MODULE,
+ drvdata->sram_node);
+ if (IS_ERR(drvdata->sram_class)) {
+ ret = PTR_ERR(drvdata->sram_class);
+ goto err_class_create;
+ }
+
+ device = device_create(drvdata->sram_class, NULL,
+ drvdata->sram_dev.dev, drvdata,
+ drvdata->sram_node);
+ if (IS_ERR(device)) {
+ ret = PTR_ERR(device);
+ goto err_dev_create;
+ }
+
+ return 0;
+err_dev_create:
+ class_destroy(drvdata->sram_class);
+err_class_create:
+ cdev_del(&drvdata->sram_dev);
+err_cdev_add:
+ unregister_chrdev_region(drvdata->sram_dev.dev, 1);
+err_alloc:
+ return ret;
+}
+
+static void dcc_sram_dev_deregister(struct dcc_drvdata *drvdata)
+{
+ device_destroy(drvdata->sram_class, drvdata->sram_dev.dev);
+ class_destroy(drvdata->sram_class);
+ cdev_del(&drvdata->sram_dev);
+ unregister_chrdev_region(drvdata->sram_dev.dev, 1);
+}
+
+static int dcc_sram_dev_init(struct dcc_drvdata *drvdata)
+{
+ int ret = 0;
+ size_t node_size;
+ char *node_name = "dcc_sram";
+ struct device *dev = drvdata->dev;
+
+ node_size = strlen(node_name) + 1;
+
+ drvdata->sram_node = devm_kzalloc(dev, node_size, GFP_KERNEL);
+ if (!drvdata->sram_node)
+ return -ENOMEM;
+
+ strlcpy(drvdata->sram_node, node_name, node_size);
+ ret = dcc_sram_dev_register(drvdata);
+ if (ret)
+ dev_err(drvdata->dev, "DCC: sram node not registered.\n");
+
+ return ret;
+}
+
+static void dcc_sram_dev_exit(struct dcc_drvdata *drvdata)
+{
+ dcc_sram_dev_deregister(drvdata);
+}
+
+static void dcc_allocate_dump_mem(struct dcc_drvdata *drvdata)
+{
+ int ret;
+ struct device *dev = drvdata->dev;
+ struct msm_dump_entry reg_dump_entry, sram_dump_entry;
+
+ /* Allocate memory for dcc reg dump */
+ drvdata->reg_buf = devm_kzalloc(dev, drvdata->reg_size, GFP_KERNEL);
+ if (drvdata->reg_buf) {
+ drvdata->reg_data.addr = virt_to_phys(drvdata->reg_buf);
+ drvdata->reg_data.len = drvdata->reg_size;
+ reg_dump_entry.id = MSM_DUMP_DATA_DCC_REG;
+ reg_dump_entry.addr = virt_to_phys(&drvdata->reg_data);
+ ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS,
+ &reg_dump_entry);
+ if (ret) {
+ dev_err(dev, "DCC REG dump setup failed\n");
+ devm_kfree(dev, drvdata->reg_buf);
+ }
+ } else {
+ dev_err(dev, "DCC REG dump allocation failed\n");
+ }
+
+ /* Allocate memory for dcc sram dump */
+ drvdata->sram_buf = devm_kzalloc(dev, drvdata->ram_size, GFP_KERNEL);
+ if (drvdata->sram_buf) {
+ drvdata->sram_data.addr = virt_to_phys(drvdata->sram_buf);
+ drvdata->sram_data.len = drvdata->ram_size;
+ sram_dump_entry.id = MSM_DUMP_DATA_DCC_SRAM;
+ sram_dump_entry.addr = virt_to_phys(&drvdata->sram_data);
+ ret = msm_dump_data_register(MSM_DUMP_TABLE_APPS,
+ &sram_dump_entry);
+ if (ret) {
+ dev_err(dev, "DCC SRAM dump setup failed\n");
+ devm_kfree(dev, drvdata->sram_buf);
+ }
+ } else {
+ dev_err(dev, "DCC SRAM dump allocation failed\n");
+ }
+}
+
+static int dcc_probe(struct platform_device *pdev)
+{
+ int ret, i;
+ struct device *dev = &pdev->dev;
+ struct dcc_drvdata *drvdata;
+ struct resource *res;
+ const char *data_sink;
+
+ drvdata = devm_kzalloc(dev, sizeof(*drvdata), GFP_KERNEL);
+ if (!drvdata)
+ return -ENOMEM;
+
+ drvdata->dev = &pdev->dev;
+ platform_set_drvdata(pdev, drvdata);
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "dcc-base");
+ if (!res)
+ return -ENODEV;
+
+ drvdata->reg_size = resource_size(res);
+ drvdata->base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!drvdata->base)
+ return -ENOMEM;
+
+ res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+ "dcc-ram-base");
+ if (!res)
+ return -ENODEV;
+
+ drvdata->ram_size = resource_size(res);
+ drvdata->ram_base = devm_ioremap(dev, res->start, resource_size(res));
+ if (!drvdata->ram_base)
+ return -ENOMEM;
+
+ drvdata->clk = devm_clk_get(dev, "dcc_clk");
+ if (IS_ERR(drvdata->clk)) {
+ ret = PTR_ERR(drvdata->clk);
+ goto err;
+ }
+
+ drvdata->save_reg = of_property_read_bool(pdev->dev.of_node,
+ "qcom,save-reg");
+
+ mutex_init(&drvdata->mutex);
+
+ INIT_LIST_HEAD(&drvdata->config_head);
+ drvdata->nr_config = 0;
+
+ ret = clk_prepare_enable(drvdata->clk);
+ if (ret)
+ goto err;
+
+ memset_io(drvdata->ram_base, 0, drvdata->ram_size);
+
+ clk_disable_unprepare(drvdata->clk);
+
+ drvdata->data_sink = DCC_DATA_SINK_SRAM;
+ ret = of_property_read_string(pdev->dev.of_node, "qcom,data-sink",
+ &data_sink);
+ if (!ret) {
+ for (i = 0; i < ARRAY_SIZE(str_dcc_data_sink); i++)
+ if (!strcmp(data_sink, str_dcc_data_sink[i])) {
+ drvdata->data_sink = i;
+ break;
+ }
+
+ if (i == ARRAY_SIZE(str_dcc_data_sink)) {
+ dev_err(dev, "Unknown sink type for DCC! Using '%s' as data sink\n",
+ str_dcc_data_sink[drvdata->data_sink]);
+ }
+ }
+
+ ret = dcc_sram_dev_init(drvdata);
+ if (ret)
+ goto err;
+
+ ret = dcc_create_files(dev, dcc_attrs);
+ if (ret)
+ goto err;
+
+ dcc_allocate_dump_mem(drvdata);
+
+ return 0;
+err:
+ return ret;
+}
+
+static int dcc_remove(struct platform_device *pdev)
+{
+ struct dcc_drvdata *drvdata = platform_get_drvdata(pdev);
+
+ dcc_sram_dev_exit(drvdata);
+
+ dcc_config_reset(drvdata);
+
+ return 0;
+}
+
+static const struct of_device_id msm_dcc_match[] = {
+ { .compatible = "qcom,dcc"},
+ {}
+};
+
+static struct platform_driver dcc_driver = {
+ .probe = dcc_probe,
+ .remove = dcc_remove,
+ .driver = {
+ .name = "msm-dcc",
+ .owner = THIS_MODULE,
+ .of_match_table = msm_dcc_match,
+ },
+};
+
+static int __init dcc_init(void)
+{
+ return platform_driver_register(&dcc_driver);
+}
+module_init(dcc_init);
+
+static void __exit dcc_exit(void)
+{
+ platform_driver_unregister(&dcc_driver);
+}
+module_exit(dcc_exit);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MSM data capture and compare engine");