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.