From 1b5b05b0abbc7555b5469fe3efb9150cd6b38dbc Mon Sep 17 00:00:00 2001 From: Minghao Zhang Date: Mon, 10 May 2021 23:28:45 +0800 Subject: [PATCH] soc: qcom: ddr_stats: API to read ddr residency DDR residency can be read using sysfs node. This change provides an API to get ddr residency so that it can be read from any kernel drivers. Change-Id: I6f0d508994a1f5e427e2cbce6f0229013fec387f Signed-off-by: Minghao Zhang --- drivers/soc/qcom/ddr_stats.c | 113 +++++++++++++++++++++++------ include/linux/soc/qcom/ddr_stats.h | 17 +++++ 2 files changed, 106 insertions(+), 24 deletions(-) create mode 100644 include/linux/soc/qcom/ddr_stats.h diff --git a/drivers/soc/qcom/ddr_stats.c b/drivers/soc/qcom/ddr_stats.c index f620dfc9b6d3..89a9e5d52e8a 100644 --- a/drivers/soc/qcom/ddr_stats.c +++ b/drivers/soc/qcom/ddr_stats.c @@ -15,6 +15,7 @@ #include #include #include +#include #include #include @@ -38,6 +39,9 @@ struct ddr_stats_platform_data { void __iomem *reg; + int freq_count; + int entry_count; + struct mutex ddr_stats_lock; }; struct stats_entry { @@ -104,15 +108,10 @@ static ssize_t ddr_stats_append_data_to_buf(char *buf, int length, int *count, return 0; } -static ssize_t ddr_stats_copy_stats(char *buf, int size, void __iomem *reg, - u32 entry_count) +static void ddr_stats_fill_data(void __iomem *reg, u32 entry_count, + struct stats_entry *data, u64 *accumulated_duration) { - 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); + int i; for (i = 0; i < entry_count; i++) { data[i].count = readl_relaxed(reg + offsetof( @@ -125,9 +124,21 @@ static ssize_t ddr_stats_copy_stats(char *buf, int size, void __iomem *reg, struct stats_entry, duration)); data[i].duration = get_time_in_msec(data[i].duration); - accumulated_duration += data[i].duration; + *accumulated_duration += data[i].duration; reg += sizeof(struct stats_entry); } +} + +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); + + ddr_stats_fill_data(reg, entry_count, data, &accumulated_duration); for (i = 0, length = 0; i < entry_count; i++) { op_length = ddr_stats_append_data_to_buf(buf + length, @@ -142,25 +153,60 @@ static ssize_t ddr_stats_copy_stats(char *buf, int size, void __iomem *reg, return length; } +int ddr_stats_get_freq_count(void) +{ + if (!ddr_pdata) + return -ENODEV; + + return ddr_pdata->freq_count; +} +EXPORT_SYMBOL(ddr_stats_get_freq_count); + +int ddr_stats_get_residency(int freq_count, struct ddr_freq_residency *data) +{ + void __iomem *reg; + u32 name; + int i, j, num; + uint64_t duration = 0; + struct stats_entry stats_data[MAX_NUM_MODES]; + + if (freq_count < 0 || !data || !ddr_pdata || !ddr_pdata->reg) + return -EINVAL; + + if (!ddr_pdata->entry_count) + return -EINVAL; + + mutex_lock(&ddr_pdata->ddr_stats_lock); + num = freq_count > ddr_pdata->freq_count ? ddr_pdata->freq_count : freq_count; + reg = ddr_pdata->reg + offsetofend(struct ddr_stats_data, entry_count); + + ddr_stats_fill_data(reg, ddr_pdata->entry_count, stats_data, &duration); + + for (i = 0, j = 0; i < ddr_pdata->entry_count; i++) { + name = stats_data[i].name; + if (((name >> 8) & 0xFF) == 0x1) { + data[j].freq = name >> 16; + data[j].residency = stats_data[i].duration; + if (++j > num) + break; + } + reg += sizeof(struct stats_entry); + } + + mutex_unlock(&ddr_pdata->ddr_stats_lock); + + return j; +} +EXPORT_SYMBOL(ddr_stats_get_residency); + 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 entry_count; pdata = GET_PDATA_OF_ATTR(attr); - reg = pdata->reg; - - 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); + length = ddr_stats_copy_stats(buf, PAGE_SIZE, pdata->reg, pdata->entry_count); return length; } @@ -201,15 +247,16 @@ static int ddr_stats_create_sysfs(struct platform_device *pdev, static int ddr_stats_probe(struct platform_device *pdev) { struct resource *res = NULL, *offset = NULL; - u32 offset_addr = 0; - void __iomem *phys_ptr = NULL; + u32 offset_addr = 0, phys_size, key, name; + void __iomem *phys_ptr = NULL, *reg; phys_addr_t phys_addr_base; - u32 phys_size, key; + int i; ddr_pdata = devm_kzalloc(&pdev->dev, sizeof(*ddr_pdata), GFP_KERNEL); if (!ddr_pdata) return -ENOMEM; + mutex_init(&ddr_pdata->ddr_stats_lock); res = platform_get_resource_byname(pdev, IORESOURCE_MEM, "phys_addr_base"); if (!res) @@ -247,6 +294,24 @@ static int ddr_stats_probe(struct platform_device *pdev) return -EINVAL; } + ddr_pdata->entry_count = readl_relaxed(ddr_pdata->reg + offsetof(struct ddr_stats_data, + entry_count)); + if (ddr_pdata->entry_count > MAX_NUM_MODES) { + pr_err("Invalid entry count\n"); + return 0; + } + + reg = ddr_pdata->reg + offsetofend(struct ddr_stats_data, entry_count); + + for (i = 0; i < ddr_pdata->entry_count; i++) { + name = readl_relaxed(reg + offsetof(struct stats_entry, name)); + name = (name >> 8) & 0xFF; + if (name == 0x1) + ddr_pdata->freq_count++; + + reg += sizeof(struct stats_entry); + } + return ddr_stats_create_sysfs(pdev, ddr_pdata); } diff --git a/include/linux/soc/qcom/ddr_stats.h b/include/linux/soc/qcom/ddr_stats.h new file mode 100644 index 000000000000..41c67ddf0e44 --- /dev/null +++ b/include/linux/soc/qcom/ddr_stats.h @@ -0,0 +1,17 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Copyright (c) 2021, The Linux Foundation. All rights reserved. + */ + +#ifndef __DDR_STATS_H__ +#define __DDR_STATS_H__ + +struct ddr_freq_residency { + uint32_t freq; + uint64_t residency; +}; + +int ddr_stats_get_freq_count(void); +int ddr_stats_get_residency(int freq_count, struct ddr_freq_residency *data); + +#endif /*__DDR_STATS_H_ */