msm: rpm_log: Support RPM log on 8974

Support to read RPM Log from the RPM RAM region on
8974. Read required data from device tree. Also read
required parameters to support read raw data.

Change-Id: I54ba39d3cbd02f0691fa7e8307fee52a033d1a33
Signed-off-by: Priyanka Mathur <pmathur@codeaurora.org>
diff --git a/Documentation/devicetree/bindings/arm/msm/rpm-log.txt b/Documentation/devicetree/bindings/arm/msm/rpm-log.txt
new file mode 100644
index 0000000..552955b
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/msm/rpm-log.txt
@@ -0,0 +1,43 @@
+* RPM Log
+
+RPM maintains Ulog in the RPM RAM. A device tree node is added
+that will hold the address of the RPM RAM region from where
+Ulog is read. The physical address from the RPM RAM region
+contains a header where various parameters to read the log are
+defined. These parameter's offsets in the header are also stored
+as a part of the device tree node.
+
+The required properties for rpm-log are:
+
+- compatible: "qcom,rpm-log"
+- reg: Specifies the base physical address and the size of the RPM
+	registers from where ulog is read.
+- qcom,rpm-addr-phys: RPM reads physical address of the RPM RAM region
+		differently when compared to Apps. Physical address of
+		the RPM RAM region is at an offset when seen from Apps.
+		This property specifies the offset which will get added
+		to the physical address of RPM RAM to make it
+		accessible to the Apps.
+- qcom,offset-version: Offset from the start of the phys_addr_base where version
+			information is stored.
+- qcom,offset-page-buffer-addr: Offset from the start of the phys_addr_base
+				where raw log start address is stored. Raw log
+				start address is the start of raw log in the
+				RPM address space as it should be seen from rpm.
+- qcom,offset-log-len: Offset from the start of the phy_addr_base where log
+			length is stored.
+- qcom,offset-log-len-mask: Offset from the start of the phy_addr_base where
+				log length mask is stored.
+- qcom,offset-page-indices: Offset from the start of the phy_addr_base where
+				index to the writer is stored.
+Example:
+qcom,rpm-log@fc19dc00 {
+	compatible = "qcom,rpm-log";
+	reg = <0xfc19dc00 0x2000>,
+	qcom,offset-rpm-addr = <0xfc000000>;
+	qcom,offset-version = <4>;
+	qcom,offset-page-buffer-addr = <36>;
+	qcom,offset-log-len = <40>;
+	qcom,offset-log-len-mask = <44>;
+	qcom,offset-page-indices = <56>;
+};
diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig
index 5cf6bd2..647518b 100644
--- a/arch/arm/mach-msm/Kconfig
+++ b/arch/arm/mach-msm/Kconfig
@@ -2196,7 +2196,7 @@
 config MSM_RPM_LOG
 	tristate "MSM Resource Power Manager Log Driver"
 	depends on DEBUG_FS
-	depends on MSM_RPM
+	depends on MSM_RPM || MSM_RPM_SMD
 	default n
 	help
 	  This option enables a driver which can read from a circular buffer
diff --git a/arch/arm/mach-msm/rpm_log.c b/arch/arm/mach-msm/rpm_log.c
index 7a1d64f..a2c74a5 100644
--- a/arch/arm/mach-msm/rpm_log.c
+++ b/arch/arm/mach-msm/rpm_log.c
@@ -15,6 +15,7 @@
 #include <linux/errno.h>
 #include <linux/init.h>
 #include <linux/io.h>
+#include <linux/of.h>
 #include <linux/kernel.h>
 #include <linux/module.h>
 #include <linux/platform_device.h>
@@ -43,6 +44,10 @@
 /* number of ms to wait between checking for new messages in the RPM log */
 #define RECHECK_TIME (50)
 
+#define VERSION_8974 0x1000
+#define RPM_ULOG_LENGTH_SHIFT 16
+#define RPM_ULOG_LENGTH_MASK  0xFFFF0000
+
 struct msm_rpm_log_buffer {
 	char *data;
 	u32 len;
@@ -116,6 +121,7 @@
 			*read_idx = head_idx;
 			continue;
 		}
+
 		/*
 		 * Ensure that all indices are 4 byte aligned.
 		 * This conditions is required to interact with a ULog buffer
@@ -127,6 +133,14 @@
 		msg_len = msm_rpm_log_read(pdata, MSM_RPM_LOG_PAGE_BUFFER,
 				((*read_idx) & pdata->log_len_mask) >> 2);
 
+		/* Message length for 8974 is first 2 bytes.
+		 * Exclude message length and format from message length.
+		 */
+		if (pdata->version == VERSION_8974) {
+			msg_len = (msg_len & RPM_ULOG_LENGTH_MASK) >>
+					RPM_ULOG_LENGTH_SHIFT;
+			msg_len -= 4;
+		}
 
 		/* handle messages that claim to be longer than the log */
 		if (PADDED_LENGTH(msg_len) > tail_idx - *read_idx - 4)
@@ -231,7 +245,7 @@
 
 /*
  * msm_rpm_log_file_open() - Allows a new reader to open the RPM log virtual
- *                           file
+ *			      file
  *
  * One local buffer is kmalloc'ed for each reader, so no resource sharing has
  * to take place (besides the read only access to the RPM log buffer).
@@ -295,23 +309,158 @@
 {
 	struct dentry *dent;
 	struct msm_rpm_log_platform_data *pdata;
+	struct resource *res = NULL;
+	struct device_node *node = NULL;
+	phys_addr_t page_buffer_address, rpm_addr_phys;
+	int ret = 0;
+	char *key = NULL;
+	uint32_t val = 0;
 
-	pdata = pdev->dev.platform_data;
-	if (!pdata)
-		return -EINVAL;
+	node = pdev->dev.of_node;
 
-	pdata->reg_base = ioremap(pdata->phys_addr_base, pdata->phys_size);
-	if (!pdata->reg_base) {
-		pr_err("%s: ERROR could not ioremap: start=%p, len=%u\n",
-			__func__, (void *) pdata->phys_addr_base,
-			pdata->phys_size);
-		return -EBUSY;
+	if (node) {
+		pdata = kzalloc(sizeof(struct msm_rpm_log_platform_data),
+				GFP_KERNEL);
+		if (!pdata)
+			return -ENOMEM;
+
+		res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+		if (!res) {
+			kfree(pdata);
+			return -EINVAL;
+		}
+
+		pdata->phys_addr_base = res->start;
+		pdata->phys_size = resource_size(res);
+
+		pdata->reg_base = ioremap_nocache(pdata->phys_addr_base,
+					pdata->phys_size);
+		if (!pdata->reg_base) {
+			pr_err("%s: ERROR could not ioremap: start=%p, len=%u\n",
+				__func__, (void *) pdata->phys_addr_base,
+				pdata->phys_size);
+			kfree(pdata);
+			return -EBUSY;
+		}
+		/* Read various parameters from the header if the
+		 * version of the RPM Ulog is 0x1000. This version
+		 * corresponds to the node in the rpm header which
+		 * holds RPM log on 8974.
+		 *
+		 * offset-page-buffer-addr: At this offset header
+		 * contains address of the location where raw log
+		 * starts
+		 * offset-log-len: At this offset header contains
+		 * the length of the log buffer.
+		 * offset-log-len-mask: At this offset header contains
+		 * the log length mask for the buffer.
+		 * offset-page-indices: At this offset header contains
+		 * the index for writer. */
+
+		key = "qcom,offset-version";
+		ret = of_property_read_u32(node, key, &val);
+		if (ret) {
+			pr_err("%s: Error in name %s key %s\n",
+				__func__, node->full_name, key);
+			ret = -EFAULT;
+			goto fail;
+		}
+
+		pdata->version = readl_relaxed(pdata->reg_base + val);
+		if (pdata->version == VERSION_8974) {
+			key = "qcom,rpm-addr-phys";
+			ret = of_property_read_u32(node, key, &val);
+			if (ret) {
+				pr_err("%s: Error in name %s key %s\n",
+					__func__, node->full_name, key);
+				ret = -EFAULT;
+				goto fail;
+			}
+
+			rpm_addr_phys = val;
+
+			key = "qcom,offset-page-buffer-addr";
+			ret = of_property_read_u32(node, key, &val);
+			if (ret) {
+				pr_err("%s: Error in name %s key %s\n",
+					__func__, node->full_name, key);
+				ret = -EFAULT;
+				goto fail;
+			}
+
+			page_buffer_address = rpm_addr_phys +
+				readl_relaxed(pdata->reg_base + val);
+			pdata->reg_offsets[MSM_RPM_LOG_PAGE_BUFFER] =
+				page_buffer_address - pdata->phys_addr_base;
+
+			key = "qcom,offset-log-len";
+			ret = of_property_read_u32(node, key, &val);
+			if (ret) {
+				pr_err("%s: Error in name %s key %s\n",
+					__func__, node->full_name, key);
+				ret = -EFAULT;
+				goto fail;
+			}
+			pdata->log_len = readl_relaxed(pdata->reg_base + val);
+
+			if (pdata->log_len > pdata->phys_size) {
+				pr_err("%s: Error phy size: %d should be atleast log length: %d\n",
+					__func__, pdata->phys_size,
+					pdata->log_len);
+
+				ret = -EINVAL;
+				goto fail;
+			}
+
+			key = "qcom,offset-log-len-mask";
+			ret = of_property_read_u32(node, key, &val);
+			if (ret) {
+				pr_err("%s: Error in name %s key %s\n",
+					__func__, node->full_name, key);
+				ret = -EFAULT;
+				goto fail;
+			}
+			pdata->log_len_mask = readl_relaxed(pdata->reg_base
+					+ val);
+
+			key = "qcom,offset-page-indices";
+			ret = of_property_read_u32(node, key, &val);
+			if (ret) {
+				pr_err("%s: Error in name %s key %s\n",
+					__func__, node->full_name, key);
+				ret = -EFAULT;
+				goto fail;
+			}
+			pdata->reg_offsets[MSM_RPM_LOG_PAGE_INDICES] =
+						val;
+		} else{
+			ret = -EINVAL;
+			goto fail;
+		}
+
+	} else{
+		pdata = pdev->dev.platform_data;
+		if (!pdata)
+			return -EINVAL;
+
+		pdata->reg_base = ioremap(pdata->phys_addr_base,
+				pdata->phys_size);
+		if (!pdata->reg_base) {
+			pr_err("%s: ERROR could not ioremap: start=%p, len=%u\n",
+				__func__, (void *) pdata->phys_addr_base,
+				pdata->phys_size);
+			return -EBUSY;
+		}
 	}
 
 	dent = debugfs_create_file("rpm_log", S_IRUGO, NULL,
-			pdev->dev.platform_data, &msm_rpm_log_file_fops);
+			pdata, &msm_rpm_log_file_fops);
 	if (!dent) {
 		pr_err("%s: ERROR debugfs_create_file failed\n", __func__);
+		if (pdata->version == VERSION_8974) {
+			ret = -ENOMEM;
+			goto fail;
+		}
 		return -ENOMEM;
 	}
 
@@ -319,6 +468,11 @@
 
 	pr_notice("%s: OK\n", __func__);
 	return 0;
+
+fail:
+	iounmap(pdata->reg_base);
+	kfree(pdata);
+	return ret;
 }
 
 static int __devexit msm_rpm_log_remove(struct platform_device *pdev)
@@ -338,12 +492,18 @@
 	return 0;
 }
 
+static struct of_device_id rpm_log_table[] = {
+	       {.compatible = "qcom,rpm-log"},
+	       {},
+};
+
 static struct platform_driver msm_rpm_log_driver = {
 	.probe		= msm_rpm_log_probe,
 	.remove		= __devexit_p(msm_rpm_log_remove),
 	.driver		= {
 		.name = "msm_rpm_log",
 		.owner = THIS_MODULE,
+		.of_match_table = rpm_log_table,
 	},
 };
 
diff --git a/arch/arm/mach-msm/rpm_log.h b/arch/arm/mach-msm/rpm_log.h
index 95639cb..f75937e 100644
--- a/arch/arm/mach-msm/rpm_log.h
+++ b/arch/arm/mach-msm/rpm_log.h
@@ -1,4 +1,4 @@
-/* Copyright (c) 2010, The Linux Foundation. All rights reserved.
+/* Copyright (c) 2010, 2013, 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
@@ -28,6 +28,7 @@
 	u32 log_len_mask;
 	phys_addr_t phys_addr_base;
 	u32 phys_size;
+	u32 version;
 	void __iomem *reg_base;
 };