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

Commit a687b708 authored by Linux Build Service Account's avatar Linux Build Service Account Committed by Gerrit - the friendly Code Review server
Browse files

Merge "drivers: qcom: add RPM stats driver" into msm-4.9

parents 69895c93 6d0cfea2
Loading
Loading
Loading
Loading
+33 −0
Original line number 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 Diff line number Diff line
@@ -645,3 +645,13 @@ config QCOM_DCC_V2
	  This option enables driver for Data Capture and Compare engine. DCC
	  driver provides interface to configure DCC block and read back
	  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 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_APSS_CORE_EA)	+= msm-core.o debug_core.o
obj-$(CONFIG_QCOM_DCC_V2) += dcc_v2.o
obj-$(CONFIG_QTI_RPM_STATS_LOG) += rpm_stats.o
+381 −0
Original line number 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);