From df113d8828b59bad8da887b075ef15c7b245b3f8 Mon Sep 17 00:00:00 2001 From: Maulik Shah Date: Fri, 26 Apr 2019 13:57:01 +0530 Subject: [PATCH] drivers: soc: qcom: Add ddr stats driver DDR stats driver display various ddr statistic via sysfs. Change-Id: Ie5d7e9d7ab418cbb6efdeb1d51e2b3f2d256758d Signed-off-by: Maulik Shah --- drivers/soc/qcom/Kconfig | 10 ++ drivers/soc/qcom/Makefile | 1 + drivers/soc/qcom/ddr_stats.c | 268 +++++++++++++++++++++++++++++++++++ 3 files changed, 279 insertions(+) create mode 100644 drivers/soc/qcom/ddr_stats.c diff --git a/drivers/soc/qcom/Kconfig b/drivers/soc/qcom/Kconfig index 08f584e78ddb..0a7ae6c72365 100644 --- a/drivers/soc/qcom/Kconfig +++ b/drivers/soc/qcom/Kconfig @@ -439,4 +439,14 @@ config QGKI This flag is absent if the build is a GKI build. If this flag is enabled, all the ABI compatibilities are not applicable. +config QTI_DDR_STATS_LOG + tristate "Qualcomm Technologies Inc (QTI) DDR Stats Driver" + depends on QCOM_RPMH + depends on QGKI + help + This option enables a driver which reads DDR statistical information + from AOP shared memory location such as DDR low power modes and DDR + frequency residency and counts. The driver outputs information using + sysfs. + endmenu diff --git a/drivers/soc/qcom/Makefile b/drivers/soc/qcom/Makefile index e46b168011c6..ddc7b8228238 100644 --- a/drivers/soc/qcom/Makefile +++ b/drivers/soc/qcom/Makefile @@ -47,3 +47,4 @@ obj-$(CONFIG_SENSORS_SSC) += sensors_ssc.o obj-$(CONFIG_QSEE_IPC_IRQ) += qsee_ipc_irq.o obj-$(CONFIG_QCOM_GLINK) += glink_probe.o obj-$(CONFIG_MSM_GLINK_SSR) += msm_glink_ssr.o +obj-$(CONFIG_QTI_DDR_STATS_LOG) += ddr_stats.o diff --git a/drivers/soc/qcom/ddr_stats.c b/drivers/soc/qcom/ddr_stats.c new file mode 100644 index 000000000000..72e2c7838a81 --- /dev/null +++ b/drivers/soc/qcom/ddr_stats.c @@ -0,0 +1,268 @@ +// SPDX-License-Identifier: GPL-2.0-only + +/* + * Copyright (c) 2019, The Linux Foundation. All rights reserved. + */ + +#define pr_fmt(fmt) "%s: " fmt, __func__ + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define MAGIC_KEY1 0xA1157A75 +#define MAX_NUM_MODES 0x14 +#define MSM_ARCH_TIMER_FREQ 19200000 + +#define GET_PDATA_OF_ATTR(attr) \ + (container_of(attr, struct ddr_stats_kobj_attr, ka)->pd) + +struct ddr_stats_platform_data { + phys_addr_t phys_addr_base; + u32 phys_size; +}; + +struct stats_entry { + uint32_t name; + uint32_t count; + uint64_t duration; +}; + +struct ddr_stats_data { + uint32_t key; + uint32_t entry_count; + struct stats_entry entry[MAX_NUM_MODES]; +}; + +struct ddr_stats_kobj_attr { + struct kobject *kobj; + struct kobj_attribute ka; + struct ddr_stats_platform_data *pd; +}; + +static inline u64 get_time_in_msec(u64 counter) +{ + do_div(counter, MSM_ARCH_TIMER_FREQ); + counter *= MSEC_PER_SEC; + return counter; +} + +static ssize_t ddr_stats_append_data_to_buf(char *buf, int length, int *count, + struct stats_entry *data, u64 accumulated_duration) +{ + u32 cp_idx = 0; + u32 name; + u32 duration; + + if (accumulated_duration) + duration = (data->duration * 100) / accumulated_duration; + + name = (data->name >> 8) & 0xFF; + + if (name == 0x0) { + name = (data->name) & 0xFF; + *count = *count + 1; + return snprintf(buf, length, + "LPM %d:\tName:0x%x\tcount:%u\tTime(msec):%llu (~%d%%)\n", + *count, name, data->count, + data->duration, duration); + } else if (name == 0x1) { + cp_idx = data->name & 0x1F; + name = data->name >> 16; + + if (!name || !data->count) + return 0; + + return snprintf(buf, length, + "Freq %dMhz:\tCP IDX:%u\tcount:%u\tTime(msec):%llu (~%d%%)\n", + name, cp_idx, data->count, + data->duration, duration); + } + + return 0; +} + +static ssize_t ddr_stats_copy_stats(char *buf, int size, void __iomem *reg, + u32 entry_count) +{ + struct stats_entry data[MAX_NUM_MODES]; + u64 accumulated_duration = 0; + int lpm_count = 0, i; + ssize_t length, op_length; + + reg += offsetofend(struct ddr_stats_data, entry_count); + + for (i = 0; i < entry_count; i++) { + data[i].count = readl_relaxed(reg + offsetof( + struct stats_entry, count)); + + data[i].name = readl_relaxed(reg + offsetof( + struct stats_entry, name)); + + data[i].duration = readq_relaxed(reg + offsetof( + struct stats_entry, duration)); + + data[i].duration = get_time_in_msec(data[i].duration); + accumulated_duration += data[i].duration; + reg += sizeof(struct stats_entry); + } + + for (i = 0, length = 0; i < entry_count; i++) { + op_length = ddr_stats_append_data_to_buf(buf + length, + size - length, &lpm_count, + &data[i], accumulated_duration); + if (op_length >= size - length) + return length; + + length += op_length; + } + + return length; +} + +static ssize_t ddr_stats_show(struct kobject *kobj, + struct kobj_attribute *attr, char *buf) +{ + struct ddr_stats_platform_data *pdata = NULL; + void __iomem *reg; + ssize_t length; + u32 key, entry_count; + + pdata = GET_PDATA_OF_ATTR(attr); + + reg = ioremap_nocache(pdata->phys_addr_base, pdata->phys_size); + if (!reg) { + pr_err("ERROR could not ioremap start=%pa, len=%u\n", + &pdata->phys_addr_base, pdata->phys_size); + return 0; + } + + key = readl_relaxed(reg + offsetof(struct ddr_stats_data, key)); + if (key != MAGIC_KEY1) { + pr_err("Invalid key\n"); + return 0; + } + + entry_count = readl_relaxed(reg + offsetof(struct ddr_stats_data, + entry_count)); + if (entry_count > MAX_NUM_MODES) { + pr_err("Invalid entry count\n"); + return 0; + } + + length = ddr_stats_copy_stats(buf, PAGE_SIZE, reg, entry_count); + iounmap(reg); + + return length; +} + +static int ddr_stats_create_sysfs(struct platform_device *pdev, + struct ddr_stats_platform_data *pd) +{ + struct kobject *ddr_stats_kobj = NULL; + struct ddr_stats_kobj_attr *ddr_stats_ka = NULL; + + ddr_stats_kobj = kobject_create_and_add("ddr", power_kobj); + if (!ddr_stats_kobj) { + pr_err("Cannot create ddr stats kobject\n"); + return -ENODEV; + } + + ddr_stats_ka = devm_kzalloc(&pdev->dev, sizeof(*ddr_stats_ka), + GFP_KERNEL); + if (!ddr_stats_ka) { + kobject_put(ddr_stats_kobj); + return -ENOMEM; + } + + ddr_stats_ka->kobj = ddr_stats_kobj; + + sysfs_attr_init(&ddr_stats_ka->ka.attr); + ddr_stats_ka->pd = pd; + ddr_stats_ka->ka.attr.mode = 0444; + ddr_stats_ka->ka.attr.name = "residency"; + ddr_stats_ka->ka.show = ddr_stats_show; + ddr_stats_ka->ka.store = NULL; + + platform_set_drvdata(pdev, ddr_stats_ka); + + return sysfs_create_file(ddr_stats_kobj, &ddr_stats_ka->ka.attr); +} + +static int ddr_stats_probe(struct platform_device *pdev) +{ + struct ddr_stats_platform_data *pdata; + struct resource *res = NULL, *offset = NULL; + u32 offset_addr = 0; + void __iomem *phys_ptr = NULL; + + pdata = devm_kzalloc(&pdev->dev, sizeof(*pdata), GFP_KERNEL); + if (!pdata) + return -ENOMEM; + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "phys_addr_base"); + if (!res) + return -ENODEV; + + offset = platform_get_resource_byname(pdev, IORESOURCE_MEM, + "offset_addr"); + if (offset) { + /* Remap the ddr stats pointer */ + phys_ptr = ioremap_nocache(offset->start, SZ_4); + if (!phys_ptr) { + pr_err("Failed to ioremap offset address\n"); + return -ENODEV; + } + offset_addr = readl_relaxed(phys_ptr); + iounmap(phys_ptr); + } + + if (!offset_addr) + return -ENODEV; + + pdata->phys_addr_base = res->start + offset_addr; + pdata->phys_size = resource_size(res); + + return ddr_stats_create_sysfs(pdev, pdata); +} + +static int ddr_stats_remove(struct platform_device *pdev) +{ + struct ddr_stats_kobj_attr *ddr_stats_ka; + + ddr_stats_ka = (struct ddr_stats_kobj_attr *) + platform_get_drvdata(pdev); + + sysfs_remove_file(ddr_stats_ka->kobj, &ddr_stats_ka->ka.attr); + kobject_put(ddr_stats_ka->kobj); + platform_set_drvdata(pdev, NULL); + + return 0; +} + +static const struct of_device_id ddr_stats_table[] = { + { .compatible = "qcom,ddr-stats" }, + { }, +}; + +static struct platform_driver ddr_stats_driver = { + .probe = ddr_stats_probe, + .remove = ddr_stats_remove, + .driver = { + .name = "ddr_stats", + .of_match_table = ddr_stats_table, + }, +}; +module_platform_driver(ddr_stats_driver); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("MSM DDR Statistics driver"); +MODULE_ALIAS("platform:msm_ddr_stats_log");