[PATCH 2/2] drivers: qcom: Add SoC sleep stats driver

From: Maulik Shah
Date: Thu Aug 08 2019 - 02:13:46 EST


Qualcomm Technologies Inc's (QTI) chipsets support SoC level
low power modes. Statistics for SoC sleep stats are produced
by remote processor.

Lets's add a driver to read the shared memory exported by the
remote processor and export to sysfs.

Signed-off-by: Mahesh Sivasubramanian <msivasub@xxxxxxxxxxxxxx>
Signed-off-by: Lina Iyer <ilina@xxxxxxxxxxxxxx>
Signed-off-by: Maulik Shah <mkshah@xxxxxxxxxxxxxx>
---
drivers/soc/qcom/Kconfig | 9 ++
drivers/soc/qcom/Makefile | 1 +
drivers/soc/qcom/soc_sleep_stats.c | 249 +++++++++++++++++++++++++++++
3 files changed, 259 insertions(+)
create mode 100644 drivers/soc/qcom/soc_sleep_stats.c

diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig
index 880cf0290962..7aac24430e99 100644
--- a/drivers/soc/qcom/Kconfig
+++ b/drivers/soc/qcom/Kconfig
@@ -163,6 +163,15 @@ config QCOM_SMSM
Say yes here to support the Qualcomm Shared Memory State Machine.
The state machine is represented by bits in shared memory.

+config QCOM_SOC_SLEEP_STATS
+ tristate "Qualcomm Technologies Inc. (QTI) SoC sleep stats driver"
+ depends on ARCH_QCOM
+ help
+ Qualcomm Technologies Inc. (QTI) SoC sleep stats driver to read
+ the shared memory exported by the remote processor related to
+ various SoC level low power modes statistics and export to sysfs
+ interface.
+
config QCOM_WCNSS_CTRL
tristate "Qualcomm WCNSS control driver"
depends on ARCH_QCOM || COMPILE_TEST
diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile
index ffe519b0cb66..1530e0e73075 100644
--- a/drivers/soc/qcom/Makefile
+++ b/drivers/soc/qcom/Makefile
@@ -17,6 +17,7 @@ obj-$(CONFIG_QCOM_SMEM) += smem.o
obj-$(CONFIG_QCOM_SMEM_STATE) += smem_state.o
obj-$(CONFIG_QCOM_SMP2P) += smp2p.o
obj-$(CONFIG_QCOM_SMSM) += smsm.o
+obj-$(CONFIG_QCOM_SOC_SLEEP_STATS) += soc_sleep_stats.o
obj-$(CONFIG_QCOM_WCNSS_CTRL) += wcnss_ctrl.o
obj-$(CONFIG_QCOM_APR) += apr.o
obj-$(CONFIG_QCOM_LLCC) += llcc-slice.o
diff --git a/drivers/soc/qcom/soc_sleep_stats.c b/drivers/soc/qcom/soc_sleep_stats.c
new file mode 100644
index 000000000000..5b95d68512ec
--- /dev/null
+++ b/drivers/soc/qcom/soc_sleep_stats.c
@@ -0,0 +1,249 @@
+// SPDX-License-Identifier: GPL-2.0-only
+
+/*
+ * Copyright (c) 2011-2019, The Linux Foundation. All rights reserved.
+ */
+
+#define pr_fmt(fmt) "%s: " fmt, __func__
+
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/of.h>
+#include <linux/platform_device.h>
+#include <linux/slab.h>
+#include <linux/uaccess.h>
+
+#define ARCH_TIMER_FREQ 19200000
+
+struct stats_config {
+ u32 offset_addr;
+ u32 num_records;
+ bool appended_stats_avail;
+};
+
+struct soc_sleep_stats_data {
+ phys_addr_t stats_base;
+ resource_size_t stats_size;
+ const struct stats_config *config;
+ struct kobject *kobj;
+ struct kobj_attribute ka;
+ struct mutex lock;
+};
+
+struct entry {
+ __le32 stat_type;
+ __le32 count;
+ __le64 last_entered_at;
+ __le64 last_exited_at;
+ __le64 accumulated;
+};
+
+struct appended_entry {
+ __le32 client_votes;
+ __le32 reserved[3];
+};
+
+struct stats_entry {
+ struct entry entry;
+ struct appended_entry appended_entry;
+};
+
+static inline u64 get_time_in_sec(u64 counter)
+{
+ do_div(counter, ARCH_TIMER_FREQ);
+
+ return counter;
+}
+
+static inline ssize_t append_data_to_buf(char *buf, int length,
+ struct stats_entry *data)
+{
+ char stat_type[5] = {0};
+
+ memcpy(stat_type, &data->entry.stat_type, sizeof(u32));
+
+ return scnprintf(buf, length,
+ "%s\n"
+ "\tCount :%u\n"
+ "\tLast Entered At(sec) :%llu\n"
+ "\tLast Exited At(sec) :%llu\n"
+ "\tAccumulated Duration(sec):%llu\n"
+ "\tClient Votes :0x%x\n\n",
+ stat_type, data->entry.count,
+ data->entry.last_entered_at,
+ data->entry.last_exited_at,
+ data->entry.accumulated,
+ data->appended_entry.client_votes);
+}
+
+static ssize_t stats_show(struct kobject *obj, struct kobj_attribute *attr,
+ char *buf)
+{
+ void __iomem *reg;
+ int i;
+ uint32_t offset;
+ ssize_t length = 0, op_length;
+ struct stats_entry data;
+ struct entry *e = &data.entry;
+ struct appended_entry *ae = &data.appended_entry;
+ struct soc_sleep_stats_data *drv = container_of(attr,
+ struct soc_sleep_stats_data, ka);
+
+ mutex_lock(&drv->lock);
+ reg = ioremap_nocache(drv->stats_base, drv->stats_size);
+ if (!reg) {
+ pr_err("io remap failed\n");
+ mutex_unlock(&drv->lock);
+ return length;
+ }
+
+ for (i = 0; i < drv->config->num_records; i++) {
+ offset = offsetof(struct entry, stat_type);
+ e->stat_type = le32_to_cpu(readl_relaxed(reg + offset));
+
+ offset = offsetof(struct entry, count);
+ e->count = le32_to_cpu(readl_relaxed(reg + offset));
+
+ offset = offsetof(struct entry, last_entered_at);
+ e->last_entered_at = le64_to_cpu(readq_relaxed(reg + offset));
+
+ offset = offsetof(struct entry, last_exited_at);
+ e->last_exited_at = le64_to_cpu(readq_relaxed(reg + offset));
+
+ offset = offsetof(struct entry, last_exited_at);
+ e->accumulated = le64_to_cpu(readq_relaxed(reg + offset));
+
+ e->last_entered_at = get_time_in_sec(e->last_entered_at);
+ e->last_exited_at = get_time_in_sec(e->last_exited_at);
+ e->accumulated = get_time_in_sec(e->accumulated);
+
+ reg += sizeof(struct entry);
+
+ if (drv->config->appended_stats_avail) {
+ offset = offsetof(struct appended_entry, client_votes);
+ ae->client_votes = le32_to_cpu(readl_relaxed(reg +
+ offset));
+
+ reg += sizeof(struct appended_entry);
+ } else
+ ae->client_votes = 0;
+
+ op_length = append_data_to_buf(buf + length, PAGE_SIZE - length,
+ &data);
+ if (op_length >= PAGE_SIZE - length)
+ goto exit;
+
+ length += op_length;
+ }
+exit:
+ iounmap(reg);
+ mutex_unlock(&drv->lock);
+ return length;
+}
+
+static int soc_sleep_stats_create_sysfs(struct platform_device *pdev,
+ struct soc_sleep_stats_data *drv)
+{
+ int ret = -ENOMEM;
+
+ drv->kobj = kobject_create_and_add("soc_sleep", power_kobj);
+ if (!drv->kobj)
+ goto fail;
+
+ sysfs_attr_init(drv->ka.attr);
+ drv->ka.attr.mode = 0444;
+ drv->ka.attr.name = "stats";
+ drv->ka.show = stats_show;
+
+ ret = sysfs_create_file(drv->kobj, &drv->ka.attr);
+ if (ret)
+ goto fail;
+
+ platform_set_drvdata(pdev, drv);
+fail:
+ return ret;
+}
+
+static const struct stats_config rpm_data = {
+ .offset_addr = 0x14,
+ .num_records = 2,
+ .appended_stats_avail = true,
+};
+
+static const struct stats_config rpmh_data = {
+ .offset_addr = 0x4,
+ .num_records = 3,
+ .appended_stats_avail = false,
+};
+
+static const struct of_device_id soc_sleep_stats_table[] = {
+ { .compatible = "qcom,rpm-sleep-stats", .data = &rpm_data},
+ { .compatible = "qcom,rpmh-sleep-stats", .data = &rpmh_data},
+ { },
+};
+
+static int soc_sleep_stats_probe(struct platform_device *pdev)
+{
+ const struct of_device_id *match;
+ struct soc_sleep_stats_data *drv;
+ struct resource *res;
+ void __iomem *offset_addr;
+ int ret;
+
+ drv = devm_kzalloc(&pdev->dev, sizeof(*drv), GFP_KERNEL);
+ if (!drv)
+ return -ENOMEM;
+
+ match = of_match_node(soc_sleep_stats_table, pdev->dev.of_node);
+ if (!match)
+ return -ENODEV;
+
+ drv->config = match->data;
+
+ res = platform_get_resource(pdev, IORESOURCE_MEM, 0);
+ if (!res)
+ return PTR_ERR(res);
+
+ offset_addr = ioremap_nocache(res->start + drv->config->offset_addr,
+ sizeof(u32));
+ if (IS_ERR(offset_addr))
+ return PTR_ERR(offset_addr);
+
+ drv->stats_base = res->start | readl_relaxed(offset_addr);
+ drv->stats_size = resource_size(res);
+ iounmap(offset_addr);
+ mutex_init(&drv->lock);
+
+ ret = soc_sleep_stats_create_sysfs(pdev, drv);
+ if (ret)
+ pr_info("Failed to create sysfs interface\n");
+
+ return ret;
+}
+
+static int soc_sleep_stats_remove(struct platform_device *pdev)
+{
+ struct soc_sleep_stats_data *drv = platform_get_drvdata(pdev);
+
+ sysfs_remove_file(drv->kobj, &drv->ka.attr);
+ kobject_put(drv->kobj);
+ platform_set_drvdata(pdev, NULL);
+
+ return 0;
+}
+
+static struct platform_driver soc_sleep_stats_driver = {
+ .probe = soc_sleep_stats_probe,
+ .remove = soc_sleep_stats_remove,
+ .driver = {
+ .name = "soc_sleep_stats",
+ .of_match_table = soc_sleep_stats_table,
+ },
+};
+module_platform_driver(soc_sleep_stats_driver);
+
+MODULE_DESCRIPTION("Qualcomm Technologies, Inc. SoC sleep stats driver");
+MODULE_LICENSE("GPL v2");
--
QUALCOMM INDIA, on behalf of Qualcomm Innovation Center, Inc. is a member of the Code Aurora Forum, hosted by The Linux Foundation.