summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMahesh Sivasubramanian <msivasub@codeaurora.org>2016-04-25 16:49:31 -0600
committerKyle Yan <kyan@codeaurora.org>2016-06-07 11:55:46 -0700
commit1d321eb86c2ff3ad61fb56fe7d7653af581da993 (patch)
treea29f01fd3ac9db0d6830c9d7a4137387423cace8
parent5b4b23763df6b15a5c1c90e8ce00ec425ace80b6 (diff)
drivers: soc: qcom: system_stats: Support for System stats
RPM stats and RPM master stats provide the similar information via different interfaces. Combine them into a single driver to provide a unified information. Change-Id: If5a5ef6e080ab6d75139472c5204eb6d5e9c6614 Signed-off-by: Mahesh Sivasubramanian <msivasub@codeaurora.org>
-rw-r--r--Documentation/devicetree/bindings/arm/msm/system_stats.txt93
-rw-r--r--drivers/soc/qcom/Makefile2
-rw-r--r--drivers/soc/qcom/system_stats.c425
3 files changed, 519 insertions, 1 deletions
diff --git a/Documentation/devicetree/bindings/arm/msm/system_stats.txt b/Documentation/devicetree/bindings/arm/msm/system_stats.txt
new file mode 100644
index 000000000000..39961ca534a5
--- /dev/null
+++ b/Documentation/devicetree/bindings/arm/msm/system_stats.txt
@@ -0,0 +1,93 @@
+* System sleep stats
+
+Resource Power manager maintains information about system sleep and the time
+each master spent in respective sleep modes. These were previously exported
+through two modules, one for system and another for the respective master
+votes. The two modules are now combined into a system stats drivers to provide
+better visibility into system sleep modes.
+
+Main node properties
+
+- compatible
+ Usage: Required
+ Value type: <string>
+ Definition: must be "qcom,system-stats".
+
+- qcom,rpm-msg-ram
+ Usage: Required
+ Value type: <phandle>
+ Definition: phandle to RPM's message ram registers.
+
+- qcom,rpm-code-ram
+ Usage: Required
+ Value type: <phandle>
+ Definition: phandle to RPM's code ram registers.
+
+- qcom,masters:
+ Usage: Required
+ Value type: <string list>
+
+Definition of memory DT phandles that system stats module is dependent on.
+
+qcom,rpm-msg-ram:
+
+The required phandle pointed to by qcom,rpm-msg-ram are:
+Node properties:
+- compatible:
+ Usage: Required
+ Value Type: <string>
+ Definition: must be "qcom,rpm-msg-ram"
+
+- reg:
+ Usage: Required
+ Value Type: <prop-encoded-array>
+ Definition: Addresses and sizes for RPM address as visible to Apps and
+ Stats address location.
+
+- reg-names:
+ Usage: Required
+ Value Type: <stringlist>
+ Definition: Address names. Must be "phys_addr_base" or "msg-ram-base".
+ Must be specified in the same order as the
+ corresponding addresses in the reg property.
+
+qcom,rpm-code-ram:
+The required phandle pointed to by qcom,rpm-msg-ram are:
+
+Node properties:
+- compatible:
+ Usage: Required
+ Value Type: <string>
+ Definition: must be "qcom,rpm-code-ram".
+- reg:
+ Usage: Required
+ Value Type: <prop-encoded-array>
+ Definition: Address and size for RPM code address.
+
+- reg-names:
+ Usage: Required
+ Value Type: <stringlist>
+ Definition: Address names. Must be "msg-ram-base"
+
+Example:
+
+ rpm_code_ram: rpm-memory@0x68000 {
+ compatible = "qcom,rpm-code-ram";
+ reg = <0x68000 0x5000>;
+ reg-name = "msg-ram-base";
+ };
+
+ rpm_msg_ram: memory@0x200000 {
+ compatible = "qcom,rpm-msg-ram";
+ reg = <0x200000 0x1000>,
+ <0x290000 0x1000>;
+ reg-names = "phys_addr_base",
+ "code-ram-base";
+ };
+
+ qcom,system-stats@68140 {
+ compatible = "qcom,system-stats";
+ qcom,rpm-msg-ram = <&rpm_msg_ram>;
+ qcom,rpm-code-ram = <&rpm_code_ram>;
+ qcom,masters = "APSS", "MPSS", "ADSP", "SLPI";
+ };
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index 027e52439301..70d0a2dca5bf 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -90,5 +90,5 @@ obj-$(CONFIG_MSM_RTB) += msm_rtb-hotplug.o
obj-$(CONFIG_QCOM_REMOTEQDSS) += remoteqdss.o
obj-$(CONFIG_MSM_SERVICE_LOCATOR) += service-locator.o
obj-$(CONFIG_MSM_RPM_RBCPR_STATS_V2_LOG) += rpm_rbcpr_stats_v2.o
-obj-$(CONFIG_MSM_RPM_STATS_LOG) += rpm_stats.o rpm_master_stat.o
+obj-$(CONFIG_MSM_RPM_STATS_LOG) += rpm_stats.o rpm_master_stat.o system_stats.o
obj-$(CONFIG_MSM_RPM_LOG) += rpm_log.o
diff --git a/drivers/soc/qcom/system_stats.c b/drivers/soc/qcom/system_stats.c
new file mode 100644
index 000000000000..476d2f6dca27
--- /dev/null
+++ b/drivers/soc/qcom/system_stats.c
@@ -0,0 +1,425 @@
+/* Copyright (c) 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/debugfs.h>
+#include <linux/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/types.h>
+#include <linux/of.h>
+#include <linux/of_address.h>
+#include <linux/uaccess.h>
+#include <asm/arch_timer.h>
+
+#define SCLK_HZ 32768
+#define MSM_ARCH_TIMER_FREQ 19200000
+#define NUM_STATS_RECORD 2
+#define STATS_OFFSET 0x14
+#define HEAP_OFFSET 0x1c
+#define MASTER_START_ADDR_OFFSET 0x150
+#define MASTER_OFFSET_ADDRESS 0x1000
+
+struct rpm_system_stats {
+ void __iomem *rpm_stats_addr;
+ void __iomem *rpm_master_addr;
+ int num_masters;
+ char **master;
+} ss;
+
+struct msm_rpmstats_record {
+ char name[32];
+ uint32_t id;
+ uint32_t val;
+};
+
+struct msm_rpm_stats_data {
+ uint32_t stat_type;
+ uint32_t count;
+ uint64_t last_entered_at;
+ uint64_t last_exited_at;
+ uint64_t accumulated;
+ uint32_t client_votes;
+ uint32_t reserved[3];
+};
+
+struct rpm_master_stats_data {
+ uint32_t active_cores;
+ uint32_t numshutdowns;
+ uint64_t shutdown_req;
+ uint64_t wakeup_ind;
+ uint64_t bringup_req;
+ uint64_t bringup_ack;
+ uint32_t wakeup_reason; /* 0 = rude wakeup, 1 = scheduled wakeup */
+ uint32_t last_sleep_transition_duration;
+ uint32_t last_wake_transition_duration;
+ uint32_t xo_count;
+ uint64_t xo_last_entered_at;
+ uint64_t xo_last_exited_at;
+ uint64_t xo_accumulated_duration;
+};
+
+static inline uint32_t msm_rpmstats_read_long_register(void __iomem *regbase,
+ int index, int offset)
+{
+ return readl_relaxed(regbase + offset +
+ index * sizeof(struct msm_rpm_stats_data));
+}
+
+static inline uint64_t msm_rpmstats_read_quad_register(void __iomem *regbase,
+ int index, int offset)
+{
+ uint64_t dst;
+
+ memcpy_fromio(&dst,
+ regbase + offset + index * sizeof(struct msm_rpm_stats_data),
+ 8);
+ return dst;
+}
+
+static inline unsigned long msm_rpmstats_read_register(void __iomem *regbase,
+ int index, int offset)
+{
+ return readl_relaxed(regbase + index * 12 + (offset + 1) * 4);
+}
+
+static inline uint64_t get_time_in_msec(u64 counter)
+{
+ do_div(counter, MSM_ARCH_TIMER_FREQ);
+ counter *= MSEC_PER_SEC;
+ return counter;
+}
+
+static inline uint64_t get_time_in_sec(u64 counter)
+{
+ do_div(counter, MSM_ARCH_TIMER_FREQ);
+ return counter;
+}
+
+static void rpm_stats_copy_data(struct msm_rpm_stats_data *data, int idx)
+{
+ void __iomem *reg = ss.rpm_stats_addr;
+
+ data->stat_type = msm_rpmstats_read_long_register(reg, idx,
+ offsetof(struct msm_rpm_stats_data, stat_type));
+
+ data->count = msm_rpmstats_read_long_register(reg, idx,
+ offsetof(struct msm_rpm_stats_data, count));
+ data->last_entered_at = msm_rpmstats_read_quad_register(reg,
+ idx, offsetof(struct msm_rpm_stats_data,
+ last_entered_at));
+ data->last_exited_at = msm_rpmstats_read_quad_register(reg,
+ idx, offsetof(struct msm_rpm_stats_data,
+ last_exited_at));
+
+ data->accumulated = msm_rpmstats_read_quad_register(reg,
+ idx, offsetof(struct msm_rpm_stats_data,
+ accumulated));
+ data->client_votes = msm_rpmstats_read_long_register(reg,
+ idx, offsetof(struct msm_rpm_stats_data,
+ client_votes));
+}
+
+static int rpm_stats_write_buf(struct seq_file *m)
+{
+ struct msm_rpm_stats_data rs;
+ int i;
+
+ for (i = 0; i < NUM_STATS_RECORD; i++) {
+ char stat_type[5] = {0};
+ uint64_t time;
+
+ rpm_stats_copy_data(&rs, i);
+ memcpy(stat_type, &rs.stat_type, sizeof(uint32_t));
+ seq_printf(m, "RPM Mode:%s\n", stat_type);
+ seq_printf(m, "\tcount:%d\n", rs.count);
+
+ time = rs.last_exited_at - rs.last_entered_at;
+ time = get_time_in_msec(time);
+ seq_printf(m, "\ttime in last mode(msec):%llu\n", time);
+
+ time = arch_counter_get_cntpct() - rs.last_exited_at;
+ time = get_time_in_sec(time);
+ seq_printf(m, "\ttime since last mode(sec):%llu\n", time);
+
+ time = get_time_in_msec(rs.accumulated);
+ seq_printf(m, "\tactual last sleep(msec):%llu\n", time);
+
+ seq_printf(m, "\tclient votes: %#010x\n\n", rs.client_votes);
+ }
+
+ return 0;
+}
+
+static void master_stats_copy_data(struct rpm_master_stats_data *data, int idx)
+{
+ data->shutdown_req = readq_relaxed(ss.rpm_master_addr +
+ idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data, shutdown_req));
+
+ data->wakeup_ind = readq_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data, wakeup_ind)));
+
+ data->bringup_req = readq_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data, bringup_req)));
+
+ data->bringup_ack = readq_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data, bringup_ack)));
+
+ data->xo_last_entered_at = readq_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data,
+ xo_last_entered_at)));
+
+ data->xo_last_exited_at = readq_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data,
+ xo_last_exited_at)));
+
+ data->xo_accumulated_duration =
+ readq_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data,
+ xo_accumulated_duration)));
+
+ data->last_sleep_transition_duration =
+ readl_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data,
+ last_sleep_transition_duration)));
+
+ data->last_wake_transition_duration =
+ readl_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data,
+ last_wake_transition_duration)));
+
+ data->xo_count =
+ readl_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data,
+ xo_count)));
+
+ data->wakeup_reason = readl_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data,
+ wakeup_reason)));
+
+ data->numshutdowns = readl_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS +
+ offsetof(struct rpm_master_stats_data, numshutdowns)));
+
+ data->active_cores = readl_relaxed(ss.rpm_master_addr +
+ (idx * MASTER_OFFSET_ADDRESS) +
+ offsetof(struct rpm_master_stats_data, active_cores));
+
+}
+
+static int master_stats_write_buf(struct seq_file *m)
+{
+ struct rpm_master_stats_data ms;
+ int i;
+
+ for (i = 0; i < ss.num_masters; i++) {
+
+ master_stats_copy_data(&ms, i);
+
+ seq_printf(m, "%s\n", ss.master[i]);
+ seq_printf(m, "\tShutdown Request:0x%llX\n", ms.shutdown_req);
+ seq_printf(m, "\tWakeup interrupt:0x%llX\n", ms.wakeup_ind);
+ seq_printf(m, "\tBringup Req::0x%llX\n", ms.bringup_req);
+ seq_printf(m, "\tBringup Ack:0x%llX\n", ms.bringup_ack);
+ seq_printf(m, "\tLast XO Entry:0x%llX\n",
+ ms.xo_last_entered_at);
+ seq_printf(m, "\tLast XO Exit:0x%llX\n", ms.xo_last_exited_at);
+ seq_printf(m, "\tAccumulated XO duration:0x%llX\n",
+ ms.xo_accumulated_duration);
+ seq_printf(m, "\tLast sleep transition duration:0x%x\n",
+ ms.last_sleep_transition_duration);
+ seq_printf(m, "\tLast wake transition duration:0x%x\n",
+ ms.last_wake_transition_duration);
+ seq_printf(m, "\tXO Count:0x%x\n", ms.xo_count);
+ seq_printf(m, "\tWakeup Reason:0x%s\n",
+ ms.wakeup_reason ? "Sched" : "Rude");
+ seq_printf(m, "\tNum Shutdowns:0x%x\n", ms.numshutdowns);
+ seq_printf(m, "\tActive Cores:0x%x\n", ms.active_cores);
+ }
+ return 0;
+}
+
+static int ss_file_show(struct seq_file *m, void *v)
+{
+ int ret = 0;
+
+ ret = rpm_stats_write_buf(m);
+ if (ret)
+ return ret;
+
+ return master_stats_write_buf(m);
+}
+
+static int ss_file_open(struct inode *inode, struct file *file)
+{
+ return single_open(file, ss_file_show, inode->i_private);
+}
+
+static const struct file_operations ss_file_ops = {
+ .owner = THIS_MODULE,
+ .open = ss_file_open,
+ .read = seq_read,
+ .llseek = no_llseek,
+ .release = single_release,
+};
+
+static int msm_rpmstats_probe(struct platform_device *pdev)
+{
+ struct dentry *dent = NULL;
+ struct device_node *node = NULL;
+ uint32_t offset = 0;
+ void __iomem *offset_addr = NULL;
+ struct resource res;
+ int i, ret = 0;
+
+ if (!pdev)
+ return -EINVAL;
+
+ node = of_parse_phandle(pdev->dev.of_node, "qcom,rpm-msg-ram", 0);
+ if (!node)
+ return -EINVAL;
+
+ ret = of_address_to_resource(node, 1, &res);
+ if (ret)
+ return ret;
+
+ offset_addr = ioremap_nocache(res.start + STATS_OFFSET,
+ resource_size(&res));
+ if (!offset_addr)
+ return -ENOMEM;
+
+ offset = readl_relaxed(offset_addr);
+ iounmap(offset_addr);
+
+ ret = of_address_to_resource(node, 0, &res);
+ if (ret)
+ return ret;
+
+ ss.rpm_stats_addr = devm_ioremap_nocache(&pdev->dev,
+ res.start + offset,
+ resource_size(&res));
+
+ if (!ss.rpm_stats_addr)
+ return -ENOMEM;
+
+ node = of_parse_phandle(pdev->dev.of_node, "qcom,rpm-code-ram", 0);
+ if (!node)
+ return -EINVAL;
+
+ ret = of_address_to_resource(node, 0, &res);
+ if (ret)
+ return ret;
+
+ ss.rpm_master_addr = devm_ioremap_nocache(
+ &pdev->dev, res.start + MASTER_START_ADDR_OFFSET,
+ resource_size(&res));
+
+ if (!ss.rpm_master_addr)
+ return -ENOMEM;
+
+ ss.num_masters = of_property_count_strings(pdev->dev.of_node,
+ "qcom,masters");
+ if (ss.num_masters < 0) {
+ dev_err(&pdev->dev, "Failed to get number of masters =%d\n",
+ ss.num_masters);
+ return -EINVAL;
+ }
+
+ ss.master = devm_kzalloc(&pdev->dev,
+ sizeof(char *) * ss.num_masters, GFP_KERNEL);
+
+ if (!ss.master) {
+ dev_err(&pdev->dev, "%s:Failed to allocated memory\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ /*
+ * Read master names from DT
+ */
+ for (i = 0; i < ss.num_masters; i++) {
+ const char *master_name;
+
+ of_property_read_string_index(pdev->dev.of_node,
+ "qcom,masters",
+ i, &master_name);
+ ss.master[i] = devm_kzalloc(&pdev->dev,
+ sizeof(char) * strlen(master_name) + 1,
+ GFP_KERNEL);
+ if (!ss.master[i]) {
+ pr_err("%s:Failed to get memory\n", __func__);
+ return -ENOMEM;
+ }
+ strlcpy(ss.master[i], master_name,
+ strlen(master_name) + 1);
+ }
+
+ dent = debugfs_create_file("system_stats", S_IRUGO, NULL,
+ &ss, &ss_file_ops);
+
+ if (!dent) {
+ pr_err("%s: ERROR rpm_stats debugfs_create_file fail\n",
+ __func__);
+ return -ENOMEM;
+ }
+
+ platform_set_drvdata(pdev, dent);
+ return 0;
+}
+
+static int msm_rpmstats_remove(struct platform_device *pdev)
+{
+ struct dentry *dent;
+
+ dent = platform_get_drvdata(pdev);
+ debugfs_remove(dent);
+ return 0;
+}
+
+static const struct of_device_id rpm_stats_table[] = {
+ {.compatible = "qcom,system-stats"},
+ {},
+};
+
+static struct platform_driver msm_system_stats_driver = {
+ .probe = msm_rpmstats_probe,
+ .remove = msm_rpmstats_remove,
+ .driver = {
+ .name = "msm_stat",
+ .owner = THIS_MODULE,
+ .of_match_table = rpm_stats_table,
+ },
+};
+
+module_platform_driver(msm_system_stats_driver);
+
+MODULE_LICENSE("GPL v2");
+MODULE_DESCRIPTION("MSM Statistics driver");
+MODULE_ALIAS("platform:stat_log");