Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d43f367c authored by Maulik Shah's avatar Maulik Shah
Browse files

drivers: soc: qcom: Add ddr stats driver



DDR stats driver display various ddr statistic via sysfs.

Change-Id: Ie5d7e9d7ab418cbb6efdeb1d51e2b3f2d256758d
Signed-off-by: default avatarMaulik Shah <mkshah@codeaurora.org>
parent 5604b1fa
Loading
Loading
Loading
Loading
+9 −0
Original line number Diff line number Diff line
@@ -713,6 +713,15 @@ config QTI_RPM_STATS_LOG
	  the low power modes that RPM enters. The drivers outputs the message
	  via a sysfs node.

config QTI_DDR_STATS_LOG
	tristate "Qualcomm Technologies Inc (QTI) DDR Stats Driver"
	depends on QCOM_RPMH
	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.

config MSM_JTAGV8
	bool "Debug and ETM trace support across power collapse for ARMv8"
	default y if CORESIGHT_SOURCE_ETM4X
+1 −0
Original line number Diff line number Diff line
@@ -79,6 +79,7 @@ obj-$(CONFIG_MSM_IDLE_STATS) += lpm-stats.o
obj-$(CONFIG_QTI_RPM_STATS_LOG) += rpmh_master_stat.o
obj-$(CONFIG_QTI_RPM_STATS_LOG) += rpm_stats.o
obj-$(CONFIG_QCOM_MEM_OFFLINE) += mem-offline.o
obj-$(CONFIG_QTI_DDR_STATS_LOG) += ddr_stats.o
obj-$(CONFIG_QMP_DEBUGFS_CLIENT) += qmp-debugfs-client.o
obj-$(CONFIG_QCOM_HYP_CORE_CTL) += hyp_core_ctl.o
obj-$(CONFIG_MSM_PERFORMANCE) += msm_performance.o
+268 −0
Original line number Diff line number Diff line
// SPDX-License-Identifier: GPL-2.0-only

/*
 * Copyright (c) 2019, The Linux Foundation. All rights reserved.
 */

#define pr_fmt(fmt) "%s: " fmt, __func__

#include <linux/errno.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/types.h>
#include <linux/mm.h>
#include <linux/of.h>
#include <linux/of_address.h>
#include <linux/uaccess.h>
#include <asm/arch_timer.h>

#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");