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

Commit 6d0cfea2 authored by Mahesh Sivasubramanian's avatar Mahesh Sivasubramanian Committed by Lina Iyer
Browse files

drivers: qcom: add RPM stats driver



RPM stats driver provides statistical information on the low power modes
entered by the SoC. The information is gathered form the number of times
the hardware blocks are configured to enter low power state and exported
by the always-on processor to other processors using shared memory.

This driver is based off the RPM stats driver available in 'commit
cc15c6703e90: (drivers: soc: qcom: rpm_stats: Switch to use
arch_counter_get_cntvct())' in the msm-4.4 branch.

Some changes made to the original include -
	- kmalloc/kzalloc -> kzalloc/devm_kzalloc
	- Whitespace issues addressed
	- Code re-org and cleanup
	- Add DT bindings documentation
	- Copyright updates
	- Use builtin platform driver instead of module
	- Kconfig documentation updates (MSM -> QTI)
	- rpm_stats.h is no longer needed.
	- Pruned old code and V1 format support

Change-Id: Ibceaa8f948e203c39e3df55b135c0a394f39ca5f
Signed-off-by: default avatarMahesh Sivasubramanian <msivasub@codeaurora.org>
Signed-off-by: default avatarArchana Sathyakumar <asathyak@codeaurora.org>
Signed-off-by: default avatarLina Iyer <ilina@codeaurora.org>
parent 0a4d1ddb
Loading
Loading
Loading
Loading
+33 −0
Original line number Original line Diff line number Diff line
* RPM Stats

RPM maintains a counter of the number of times the SoC entered a deeper sleep
mode involving lowering or powering down the backbone rails - Cx and Mx and
the oscillator clock, XO.

PROPERTIES

- compatible:
	Usage: required
	Value type: <string>
	Definition: Should be "qcom,rpm-stats".

- reg:
	Usage: required
	Value type: <prop-encoded-array>
	Definition: The address on the RPM RAM from where the stats are read
	            should be provided as "phys_addr_base". The offset from
	            which the stats are available should be provided as
	            "offset_addr".

- reg-names:
	Usage: required
	Value type: <prop-encoded-array>
	Definition: Provides labels for the reg property.

EXAMPLE:

	qcom,rpm-stats@c000000 {
		compatible = "qcom,rpm-stats";
		reg = <0xC000000 0x1000>, <0x3F0000 0x4>;
		reg-names = "phys_addr_base", "offset_addr";
	};
+10 −0
Original line number Original line Diff line number Diff line
@@ -645,3 +645,13 @@ config QCOM_DCC_V2
	  This option enables driver for Data Capture and Compare engine. DCC
	  This option enables driver for Data Capture and Compare engine. DCC
	  driver provides interface to configure DCC block and read back
	  driver provides interface to configure DCC block and read back
	  captured data from DCC's internal SRAM.
	  captured data from DCC's internal SRAM.

config QTI_RPM_STATS_LOG
	bool "Qualcomm Technologies RPM Stats Driver"
	depends on DEBUG_FS
	default n
	help
	  This option enables a driver which reads RPM messages from a shared
	  memory location. These messages provide statistical information about
	  the low power modes that RPM enters. The drivers outputs the message
	  via a debugfs node.
+1 −0
Original line number Original line Diff line number Diff line
@@ -68,3 +68,4 @@ obj-$(CONFIG_MSM_EVENT_TIMER) += event_timer.o
obj-$(CONFIG_MSM_IDLE_STATS)	+= lpm-stats.o
obj-$(CONFIG_MSM_IDLE_STATS)	+= lpm-stats.o
obj-$(CONFIG_APSS_CORE_EA)	+= msm-core.o debug_core.o
obj-$(CONFIG_APSS_CORE_EA)	+= msm-core.o debug_core.o
obj-$(CONFIG_QCOM_DCC_V2) += dcc_v2.o
obj-$(CONFIG_QCOM_DCC_V2) += dcc_v2.o
obj-$(CONFIG_QTI_RPM_STATS_LOG) += rpm_stats.o
+381 −0
Original line number Original line Diff line number Diff line
/* Copyright (c) 2011-2017, The Linux Foundation. All rights reserved.
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License version 2 and
 * only version 2 as published by the Free Software Foundation.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 */

#include <linux/debugfs.h>
#include <linux/init.h>
#include <linux/io.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/platform_device.h>
#include <linux/slab.h>
#include <linux/of.h>
#include <linux/uaccess.h>
#include <asm/arch_timer.h>

#define RPM_STATS_NUM_REC	2
#define MSM_ARCH_TIMER_FREQ	19200000

#define GET_PDATA_OF_ATTR(attr) \
	(container_of(attr, struct msm_rpmstats_kobj_attr, ka)->pd)

struct msm_rpmstats_record {
	char name[32];
	u32 id;
	u32 val;
};

struct msm_rpmstats_platform_data {
	phys_addr_t phys_addr_base;
	u32 phys_size;
};

struct msm_rpmstats_private_data {
	void __iomem *reg_base;
	u32 num_records;
	u32 read_idx;
	u32 len;
	char buf[320];
	struct msm_rpmstats_platform_data *platform_data;
};

struct msm_rpm_stats_data {
	u32 stat_type;
	u32 count;
	u64 last_entered_at;
	u64 last_exited_at;
	u64 accumulated;
};

struct msm_rpmstats_kobj_attr {
	struct kobj_attribute ka;
	struct msm_rpmstats_platform_data *pd;
};

static inline u64 get_time_in_sec(u64 counter)
{
	do_div(counter, MSM_ARCH_TIMER_FREQ);

	return counter;
}

static inline u64 get_time_in_msec(u64 counter)
{
	do_div(counter, MSM_ARCH_TIMER_FREQ);
	counter *= MSEC_PER_SEC;

	return counter;
}

static inline int msm_rpmstats_append_data_to_buf(char *buf,
		struct msm_rpm_stats_data *data, int buflength)
{
	char stat_type[5];
	u64 time_in_last_mode;
	u64 time_since_last_mode;
	u64 actual_last_sleep;

	stat_type[4] = 0;
	memcpy(stat_type, &data->stat_type, sizeof(u32));

	time_in_last_mode = data->last_exited_at - data->last_entered_at;
	time_in_last_mode = get_time_in_msec(time_in_last_mode);
	time_since_last_mode = arch_counter_get_cntvct() - data->last_exited_at;
	time_since_last_mode = get_time_in_sec(time_since_last_mode);
	actual_last_sleep = get_time_in_msec(data->accumulated);

	return snprintf(buf, buflength,
		"RPM Mode:%s\n\t count:%d\ntime in last mode(msec):%llu\n"
		"time since last mode(sec):%llu\nactual last sleep(msec):%llu\n\n",
		stat_type, data->count, time_in_last_mode,
		time_since_last_mode, actual_last_sleep);
}

static inline u32 msm_rpmstats_read_long_register(void __iomem *regbase,
		int index, int offset)
{
	return readl_relaxed(regbase + offset +
			index * sizeof(struct msm_rpm_stats_data));
}

static inline u64 msm_rpmstats_read_quad_register(void __iomem *regbase,
		int index, int offset)
{
	u64 dst;

	memcpy_fromio(&dst,
		regbase + offset + index * sizeof(struct msm_rpm_stats_data),
		8);
	return dst;
}

static inline int msm_rpmstats_copy_stats(
			struct msm_rpmstats_private_data *prvdata)
{
	void __iomem *reg;
	struct msm_rpm_stats_data data;
	int i, length;

	reg = prvdata->reg_base;

	for (i = 0, length = 0; i < prvdata->num_records; i++) {
		data.stat_type = msm_rpmstats_read_long_register(reg, i,
				offsetof(struct msm_rpm_stats_data,
					stat_type));
		data.count = msm_rpmstats_read_long_register(reg, i,
				offsetof(struct msm_rpm_stats_data, count));
		data.last_entered_at = msm_rpmstats_read_quad_register(reg,
				i, offsetof(struct msm_rpm_stats_data,
					last_entered_at));
		data.last_exited_at = msm_rpmstats_read_quad_register(reg,
				i, offsetof(struct msm_rpm_stats_data,
					last_exited_at));
		data.accumulated = msm_rpmstats_read_quad_register(reg,
				i, offsetof(struct msm_rpm_stats_data,
					accumulated));
		length += msm_rpmstats_append_data_to_buf(prvdata->buf + length,
				&data, sizeof(prvdata->buf) - length);
		prvdata->read_idx++;
	}

	return length;
}

static inline unsigned long  msm_rpmstats_read_register(void __iomem *regbase,
		int index, int offset)
{
	return  readl_relaxed(regbase + index * 12 + (offset + 1) * 4);
}

static ssize_t msm_rpmstats_file_read(struct file *file, char __user *bufu,
				  size_t count, loff_t *ppos)
{
	struct msm_rpmstats_private_data *prvdata;

	prvdata = file->private_data;
	if (!prvdata)
		return -EINVAL;

	if (!bufu || count == 0)
		return -EINVAL;

	if ((*ppos >= prvdata->len) &&
		(prvdata->read_idx < prvdata->num_records)) {
		prvdata->len = msm_rpmstats_copy_stats(prvdata);
		*ppos = 0;
	}

	return simple_read_from_buffer(bufu, count, ppos,
			prvdata->buf, prvdata->len);
}

static int msm_rpmstats_file_open(struct inode *inode, struct file *file)
{
	struct msm_rpmstats_private_data *prvdata;
	struct msm_rpmstats_platform_data *pdata;

	pdata = inode->i_private;

	file->private_data = kzalloc(sizeof(*prvdata), GFP_KERNEL);
	if (!file->private_data)
		return -ENOMEM;

	prvdata = file->private_data;

	prvdata->reg_base = ioremap_nocache(pdata->phys_addr_base,
					pdata->phys_size);
	if (!prvdata->reg_base) {
		kfree(file->private_data);
		prvdata = NULL;
		pr_err("%s: ERROR could not ioremap start=%pa, len=%u\n",
			__func__, &pdata->phys_addr_base,
			pdata->phys_size);
		return -EBUSY;
	}

	prvdata->read_idx = prvdata->len = 0;
	prvdata->platform_data = pdata;
	prvdata->num_records = RPM_STATS_NUM_REC;

	return 0;
}

static int msm_rpmstats_file_close(struct inode *inode, struct file *file)
{
	struct msm_rpmstats_private_data *private = file->private_data;

	if (private->reg_base)
		iounmap(private->reg_base);
	kfree(file->private_data);

	return 0;
}

static const struct file_operations msm_rpmstats_fops = {
	.owner    = THIS_MODULE,
	.open     = msm_rpmstats_file_open,
	.read     = msm_rpmstats_file_read,
	.release  = msm_rpmstats_file_close,
	.llseek   = no_llseek,
};

static ssize_t rpmstats_show(struct kobject *kobj,
			struct kobj_attribute *attr, char *buf)
{
	struct msm_rpmstats_private_data *prvdata = NULL;
	struct msm_rpmstats_platform_data *pdata = NULL;

	pdata = GET_PDATA_OF_ATTR(attr);

	prvdata =
		kmalloc(sizeof(*prvdata), GFP_KERNEL);
	if (!prvdata)
		return -ENOMEM;

	prvdata->reg_base = ioremap_nocache(pdata->phys_addr_base,
					pdata->phys_size);
	if (!prvdata->reg_base) {
		kfree(prvdata);
		pr_err("%s: ERROR could not ioremap start=%pa, len=%u\n",
			__func__, &pdata->phys_addr_base,
			pdata->phys_size);
		return -EBUSY;
	}

	prvdata->read_idx = prvdata->len = 0;
	prvdata->platform_data = pdata;
	prvdata->num_records = RPM_STATS_NUM_REC;

	if (prvdata->read_idx < prvdata->num_records)
		prvdata->len = msm_rpmstats_copy_stats(prvdata);

	return snprintf(buf, prvdata->len, prvdata->buf);
}

static int msm_rpmstats_create_sysfs(struct msm_rpmstats_platform_data *pd)
{
	struct kobject *module_kobj = NULL;
	struct kobject *rpmstats_kobj = NULL;
	struct msm_rpmstats_kobj_attr *rpms_ka = NULL;
	int ret = 0;

	module_kobj = kset_find_obj(module_kset, KBUILD_MODNAME);
	if (!module_kobj) {
		pr_err("%s: Cannot find module_kset\n", __func__);
		return -ENODEV;
	}

	rpmstats_kobj = kobject_create_and_add("rpmstats", module_kobj);
	if (!rpmstats_kobj) {
		pr_err("%s: Cannot create rpmstats kobject\n", __func__);
		ret = -ENOMEM;
		goto fail;
	}

	rpms_ka = kzalloc(sizeof(*rpms_ka), GFP_KERNEL);
	if (!rpms_ka) {
		kobject_put(rpmstats_kobj);
		ret = -ENOMEM;
		goto fail;
	}

	sysfs_attr_init(&rpms_ka->ka.attr);
	rpms_ka->pd = pd;
	rpms_ka->ka.attr.mode = 0444;
	rpms_ka->ka.attr.name = "stats";
	rpms_ka->ka.show = rpmstats_show;
	rpms_ka->ka.store = NULL;

	ret = sysfs_create_file(rpmstats_kobj, &rpms_ka->ka.attr);

fail:
	return ret;
}

static int msm_rpmstats_probe(struct platform_device *pdev)
{
	struct dentry *dent = NULL;
	struct msm_rpmstats_platform_data *pdata;
	struct msm_rpmstats_platform_data *pd;
	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 -EINVAL;

	offset = platform_get_resource_byname(pdev, IORESOURCE_MEM,
							"offset_addr");
	if (offset) {
		/* Remap the rpm-stats pointer */
		phys_ptr = ioremap_nocache(offset->start, SZ_4);
		if (!phys_ptr) {
			pr_err("%s: Failed to ioremap address: %x\n",
					__func__, offset_addr);
			return -ENODEV;
		}
		offset_addr = readl_relaxed(phys_ptr);
		iounmap(phys_ptr);
	}

	pdata->phys_addr_base  = res->start + offset_addr;
	pdata->phys_size = resource_size(res);

	if (pdev->dev.platform_data)
		pd = pdev->dev.platform_data;

	dent = debugfs_create_file("rpm_stats", 0444, NULL,
					pdata, &msm_rpmstats_fops);
	if (!dent) {
		pr_err("%s: ERROR rpm_stats debugfs_create_file	fail\n",
				__func__);
		return -ENOMEM;
	}

	msm_rpmstats_create_sysfs(pdata);

	platform_set_drvdata(pdev, dent);
	return 0;
}

static int msm_rpmstats_remove(struct platform_device *pdev)
{
	struct dentry *dent;

	dent = platform_get_drvdata(pdev);
	debugfs_remove(dent);
	platform_set_drvdata(pdev, NULL);

	return 0;
}

static const struct of_device_id rpm_stats_table[] = {
	{ .compatible = "qcom,rpm-stats" },
	{ },
};

static struct platform_driver msm_rpmstats_driver = {
	.probe = msm_rpmstats_probe,
	.remove = msm_rpmstats_remove,
	.driver = {
		.name = "msm_rpm_stat",
		.owner = THIS_MODULE,
		.of_match_table = rpm_stats_table,
	},
};
builtin_platform_driver(msm_rpmstats_driver);