msm: Driver module to read RPM RBCPR stats
New driver module to read RPM RBCPR stats. RPM processor
maintains the RBCPR stats in MSG RAM. This driver
module will read the stats from the MSG RAM and display the stats.
Users can acess the RPM RBCPR stats through debugfs. Currently
these stats are only maintained for MSM8930.
CRs-Fixed: 364785
Change-Id: I306409609e9aeb103a88207be7fc3a7ab2638d36
Signed-off-by: Girish Mahadevan <girishm@codeaurora.org>
diff --git a/arch/arm/mach-msm/Kconfig b/arch/arm/mach-msm/Kconfig
index 4f14698..2346cf8 100644
--- a/arch/arm/mach-msm/Kconfig
+++ b/arch/arm/mach-msm/Kconfig
@@ -2064,6 +2064,16 @@
the low power modes that RPM enters. The drivers outputs the message
via a debugfs node.
+config MSM_RPM_RBCPR_STATS_LOG
+ tristate "MSM Resource Power Manager RPBCPR Stat Driver"
+ depends on DEBUG_FS
+ depends on MSM_RPM
+ help
+ This option enables a driver which reads RPM messages from a shared
+ memory location. These messages provide statistical information about
+ RBCPR (Rapid Bridge Core Power Reduction) information . The drivers
+ outputs the message via a debugfs node.
+
config MSM_DIRECT_SCLK_ACCESS
bool "Direct access to the SCLK timer"
default n
diff --git a/arch/arm/mach-msm/Makefile b/arch/arm/mach-msm/Makefile
index aff2251..841ed3c 100644
--- a/arch/arm/mach-msm/Makefile
+++ b/arch/arm/mach-msm/Makefile
@@ -328,6 +328,7 @@
obj-$(CONFIG_MSM_MPM_OF) += mpm-of.o
obj-$(CONFIG_MSM_MPM) += mpm.o
obj-$(CONFIG_MSM_RPM_STATS_LOG) += rpm_stats.o
+obj-$(CONFIG_MSM_RPM_RBCPR_STATS_LOG) += rpm_rbcpr_stats.o
obj-$(CONFIG_MSM_RPM_LOG) += rpm_log.o
obj-$(CONFIG_MSM_TZ_LOG) += tz_log.o
obj-$(CONFIG_MSM_XO) += msm_xo.o
diff --git a/arch/arm/mach-msm/board-8930.c b/arch/arm/mach-msm/board-8930.c
index d7a077c..2b62eb0 100644
--- a/arch/arm/mach-msm/board-8930.c
+++ b/arch/arm/mach-msm/board-8930.c
@@ -2226,6 +2226,7 @@
#endif
&msm8930_rpm_device,
&msm8930_rpm_log_device,
+ &msm8930_rpm_rbcpr_device,
&msm8930_rpm_stat_device,
#ifdef CONFIG_ION_MSM
&msm8930_ion_dev,
diff --git a/arch/arm/mach-msm/devices-8930.c b/arch/arm/mach-msm/devices-8930.c
index 1f954c8..f9a5093 100644
--- a/arch/arm/mach-msm/devices-8930.c
+++ b/arch/arm/mach-msm/devices-8930.c
@@ -30,6 +30,7 @@
#include "devices.h"
#include "rpm_log.h"
#include "rpm_stats.h"
+#include "rpm_rbcpr_stats.h"
#include "footswitch.h"
#ifdef CONFIG_MSM_MPM
@@ -287,6 +288,31 @@
},
};
+static struct resource msm_rpm_rbcpr_resource = {
+ .start = 0x0010CB00,
+ .end = 0x0010CB00 + SZ_8K - 1,
+ .flags = IORESOURCE_MEM,
+};
+
+static struct msm_rpmrbcpr_platform_data msm_rpm_rbcpr_pdata = {
+ .rbcpr_data = {
+ .upside_steps = 1,
+ .downside_steps = 2,
+ .svs_voltage = 1050000,
+ .nominal_voltage = 1162500,
+ .turbo_voltage = 1287500,
+ },
+};
+
+struct platform_device msm8930_rpm_rbcpr_device = {
+ .name = "msm_rpm_rbcpr",
+ .id = -1,
+ .dev = {
+ .platform_data = &msm_rpm_rbcpr_pdata,
+ },
+ .resource = &msm_rpm_rbcpr_resource,
+};
+
static int msm8930_LPM_latency = 1000; /* >100 usec for WFI */
struct platform_device msm8930_cpu_idle_device = {
diff --git a/arch/arm/mach-msm/devices.h b/arch/arm/mach-msm/devices.h
index daf70a8..42e34e6 100644
--- a/arch/arm/mach-msm/devices.h
+++ b/arch/arm/mach-msm/devices.h
@@ -328,6 +328,7 @@
extern struct platform_device msm8930_rpm_device;
extern struct platform_device msm8930_rpm_stat_device;
extern struct platform_device msm8930_rpm_log_device;
+extern struct platform_device msm8930_rpm_rbcpr_device;
extern struct platform_device msm8660_rpm_device;
extern struct platform_device msm8660_rpm_stat_device;
diff --git a/arch/arm/mach-msm/rpm_rbcpr_stats.c b/arch/arm/mach-msm/rpm_rbcpr_stats.c
new file mode 100644
index 0000000..7f27efc
--- /dev/null
+++ b/arch/arm/mach-msm/rpm_rbcpr_stats.c
@@ -0,0 +1,415 @@
+/* Copyright (c) 2012, Code Aurora Forum. 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/delay.h>
+#include <linux/errno.h>
+#include <linux/init.h>
+#include <linux/io.h>
+#include <linux/kernel.h>
+#include <linux/module.h>
+#include <linux/platform_device.h>
+#include <linux/sched.h>
+#include <linux/slab.h>
+#include <linux/types.h>
+#include <linux/mm.h>
+#include <linux/spinlock.h>
+#include <linux/sort.h>
+#include <asm/uaccess.h>
+#include <mach/msm_iomap.h>
+#include "timer.h"
+#include "rpm_rbcpr_stats.h"
+
+#define RBCPR_USER_BUF (2000)
+#define STR(a) (#a)
+#define GETFIELD(a) ((strnstr(STR(a), "->", 80) + 2))
+#define PRINTFIELD(buf, buf_size, pos, format, ...) \
+ ((pos < buf_size) ? snprintf((buf + pos), (buf_size - pos), format,\
+ ## __VA_ARGS__) : 0)
+
+enum {
+ RBCPR_CORNER_SVS = 0,
+ RBCPR_CORNER_NOMINAL,
+ RBCPR_CORNER_TURBO,
+ RBCPR_CORNERS_COUNT,
+ RBCPR_CORNER_INVALID = 0x7FFFFFFF,
+};
+
+struct msm_rpmrbcpr_recmnd {
+ uint32_t voltage;
+ uint32_t timestamp;
+};
+
+struct msm_rpmrbcpr_corners {
+ int efuse_adjustment;
+ struct msm_rpmrbcpr_recmnd *rpm_rcmnd;
+ uint32_t programmed_voltage;
+ uint32_t isr_counter;
+ uint32_t min_counter;
+ uint32_t max_counter;
+};
+
+struct msm_rpmrbcpr_stats {
+ uint32_t status_count;
+ uint32_t num_corners;
+ uint32_t num_latest_recommends;
+ struct msm_rpmrbcpr_corners *rbcpr_corners;
+ uint32_t current_corner;
+ uint32_t railway_voltage;
+ uint32_t enable;
+};
+
+struct msm_rpmrbcpr_stats_internal {
+ void __iomem *regbase;
+ uint32_t len;
+ char buf[RBCPR_USER_BUF];
+};
+
+static DEFINE_SPINLOCK(rpm_rbcpr_lock);
+static struct msm_rpmrbcpr_design_data rbcpr_design_data;
+static struct msm_rpmrbcpr_stats rbcpr_stats;
+static struct msm_rpmrbcpr_stats_internal pvtdata;
+
+static inline unsigned long msm_rpmrbcpr_read_data(void __iomem *regbase,
+ int offset)
+{
+ return readl_relaxed(regbase + (offset * 4));
+}
+
+static int msm_rpmrbcpr_cmp_func(const void *a, const void *b)
+{
+ struct msm_rpmrbcpr_recmnd *pa = (struct msm_rpmrbcpr_recmnd *)(a);
+ struct msm_rpmrbcpr_recmnd *pb = (struct msm_rpmrbcpr_recmnd *)(b);
+ return pa->timestamp - pb->timestamp;
+}
+
+static char *msm_rpmrbcpr_corner_string(uint32_t corner)
+{
+ switch (corner) {
+ case RBCPR_CORNER_SVS:
+ return STR(RBCPR_CORNER_SVS);
+ break;
+ case RBCPR_CORNER_NOMINAL:
+ return STR(RBCPR_CORNER_NOMINAL);
+ break;
+ case RBCPR_CORNER_TURBO:
+ return STR(RBCPR_CORNER_TURBO);
+ break;
+ case RBCPR_CORNERS_COUNT:
+ case RBCPR_CORNER_INVALID:
+ default:
+ return STR(RBCPR_CORNER_INVALID);
+ break;
+ }
+}
+
+static int msm_rpmrbcpr_print_buf(struct msm_rpmrbcpr_stats *pdata,
+ struct msm_rpmrbcpr_design_data *pdesdata,
+ char *buf)
+{
+ int pos = 0;
+ struct msm_rpmrbcpr_corners *corners;
+ struct msm_rpmrbcpr_recmnd *rcmnd;
+ int i, j;
+ int current_timestamp = msm_timer_get_sclk_ticks();
+
+ if (!pdata->enable) {
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ "RBCPR Stats not enabled at RPM");
+ return pos;
+ }
+
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ ":RBCPR Platform Data");
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: %u)", GETFIELD(pdesdata->upside_steps),
+ pdesdata->upside_steps);
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s:%u)", GETFIELD(pdesdata->downside_steps),
+ pdesdata->downside_steps);
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: %d)", GETFIELD(pdesdata->svs_voltage),
+ pdesdata->svs_voltage);
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: %d)", GETFIELD(pdesdata->nominal_voltage),
+ pdesdata->nominal_voltage);
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: %d)\n", GETFIELD(pdesdata->turbo_voltage),
+ pdesdata->turbo_voltage);
+
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ ":RBCPR Stats");
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: %u)", GETFIELD(pdata->status_counter),
+ pdata->status_count);
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: %s)", GETFIELD(pdata->current_corner),
+ msm_rpmrbcpr_corner_string(pdata->current_corner));
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (current_timestamp: 0x%x)",
+ current_timestamp);
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: %u)\n", GETFIELD(pdata->railway_voltage),
+ pdata->railway_voltage);
+
+ for (i = 0; i < pdata->num_corners; i++) {
+ corners = &pdata->rbcpr_corners[i];
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ ":\tRBCPR Corner Data");
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (name: %s)", msm_rpmrbcpr_corner_string(i));
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: %d)", GETFIELD(corners->efuse_adjustment),
+ corners->efuse_adjustment);
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: %u)", GETFIELD(corners->programmed_voltage),
+ corners->programmed_voltage);
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: %u)", GETFIELD(corners->isr_counter),
+ corners->isr_counter);
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ "(%s: %u)", GETFIELD(corners->min_counter),
+ corners->min_counter);
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ "(%s:%u)\n", GETFIELD(corners->max_counter),
+ corners->max_counter);
+ for (j = 0; j < pdata->num_latest_recommends; j++) {
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ ":\t\tVoltage History[%d]", j);
+ rcmnd = &corners->rpm_rcmnd[j];
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: %u)", GETFIELD(rcmnd->voltage),
+ rcmnd->voltage);
+ pos += PRINTFIELD(buf, RBCPR_USER_BUF, pos,
+ " (%s: 0x%x)\n", GETFIELD(rcmnd->timestamp),
+ rcmnd->timestamp);
+ }
+ }
+ return pos;
+}
+
+
+static void msm_rpmrbcpr_copy_data(struct msm_rpmrbcpr_stats_internal *pdata,
+ struct msm_rpmrbcpr_stats *prbcpr_stats)
+{
+ struct msm_rpmrbcpr_corners *corners;
+ struct msm_rpmrbcpr_recmnd *rcmnd;
+ int i, j;
+ int offset = (offsetof(struct msm_rpmrbcpr_stats, rbcpr_corners) / 4);
+
+ if (!prbcpr_stats)
+ return;
+
+ for (i = 0; i < prbcpr_stats->num_corners; i++) {
+ corners = &prbcpr_stats->rbcpr_corners[i];
+ corners->efuse_adjustment = msm_rpmrbcpr_read_data(
+ pdata->regbase, offset++);
+ for (j = 0; j < prbcpr_stats->num_latest_recommends; j++) {
+ rcmnd = &corners->rpm_rcmnd[j];
+ rcmnd->voltage = msm_rpmrbcpr_read_data(
+ pdata->regbase, offset++);
+ rcmnd->timestamp = msm_rpmrbcpr_read_data(
+ pdata->regbase, offset++);
+ }
+ sort(&corners->rpm_rcmnd[0],
+ prbcpr_stats->num_latest_recommends,
+ sizeof(struct msm_rpmrbcpr_recmnd),
+ msm_rpmrbcpr_cmp_func, NULL);
+ corners->programmed_voltage = msm_rpmrbcpr_read_data(
+ pdata->regbase, offset++);
+ corners->isr_counter = msm_rpmrbcpr_read_data(pdata->regbase,
+ offset++);
+ corners->min_counter = msm_rpmrbcpr_read_data(pdata->regbase,
+ offset++);
+ corners->max_counter = msm_rpmrbcpr_read_data(pdata->regbase,
+ offset++);
+ }
+ prbcpr_stats->current_corner = msm_rpmrbcpr_read_data(pdata->regbase,
+ offset++);
+ prbcpr_stats->railway_voltage = msm_rpmrbcpr_read_data
+ (pdata->regbase, offset++);
+ prbcpr_stats->enable = msm_rpmrbcpr_read_data(pdata->regbase, offset++);
+}
+
+static int msm_rpmrbcpr_file_read(struct file *file, char __user *bufu,
+ size_t count, loff_t *ppos)
+{
+ struct msm_rpmrbcpr_stats_internal *pdata = file->private_data;
+ int ret;
+ int status_counter;
+
+ if (!pdata) {
+ pr_info("%s pdata is null", __func__);
+ return -EINVAL;
+ }
+
+ if (!bufu || count < 0) {
+ pr_info("%s count %d ", __func__, count);
+ return -EINVAL;
+ }
+
+ if (*ppos > pdata->len || !pdata->len) {
+ /* Read RPM stats */
+ status_counter = readl_relaxed(pdata->regbase +
+ offsetof(struct msm_rpmrbcpr_stats, status_count));
+ if (status_counter != rbcpr_stats.status_count) {
+ spin_lock(&rpm_rbcpr_lock);
+ msm_rpmrbcpr_copy_data(pdata, &rbcpr_stats);
+ rbcpr_stats.status_count = status_counter;
+ spin_unlock(&rpm_rbcpr_lock);
+ }
+ pdata->len = msm_rpmrbcpr_print_buf(&rbcpr_stats,
+ &rbcpr_design_data, pdata->buf);
+ *ppos = 0;
+ }
+ /* copy to user data */
+ ret = simple_read_from_buffer(bufu, count, ppos, pdata->buf,
+ pdata->len);
+ return ret;
+}
+
+static void msm_rpmrbcpr_free_mem(struct msm_rpmrbcpr_stats_internal *pvtdata,
+ struct msm_rpmrbcpr_stats *prbcpr_stats)
+{
+ int i;
+ if (pvtdata->regbase)
+ iounmap(pvtdata->regbase);
+
+
+ if (prbcpr_stats) {
+ for (i = 0; i < prbcpr_stats->num_corners; i++) {
+ kfree(prbcpr_stats->rbcpr_corners[i].rpm_rcmnd);
+ prbcpr_stats->rbcpr_corners[i].rpm_rcmnd = NULL;
+ }
+
+ kfree(prbcpr_stats->rbcpr_corners);
+ prbcpr_stats->rbcpr_corners = NULL;
+ }
+}
+
+static int msm_rpmrbcpr_allocate_mem(struct msm_rpmrbcpr_platform_data *pdata,
+ struct resource *res)
+{
+ int i;
+
+ pvtdata.regbase = ioremap(res->start, (res->end - res->start + 1));
+ memcpy(&rbcpr_design_data, &pdata->rbcpr_data,
+ sizeof(struct msm_rpmrbcpr_design_data));
+
+
+ rbcpr_stats.num_corners = readl_relaxed(pvtdata.regbase +
+ offsetof(struct msm_rpmrbcpr_stats, num_corners));
+ rbcpr_stats.num_latest_recommends = readl_relaxed(pvtdata.regbase +
+ offsetof(struct msm_rpmrbcpr_stats,
+ num_latest_recommends));
+
+ rbcpr_stats.rbcpr_corners = kzalloc(
+ sizeof(struct msm_rpmrbcpr_corners)
+ * rbcpr_stats.num_corners, GFP_KERNEL);
+
+ if (!rbcpr_stats.rbcpr_corners) {
+ msm_rpmrbcpr_free_mem(&pvtdata, &rbcpr_stats);
+ return -ENOMEM;
+ }
+
+ for (i = 0; i < rbcpr_stats.num_corners; i++) {
+ rbcpr_stats.rbcpr_corners[i].rpm_rcmnd =
+ kzalloc(sizeof(struct msm_rpmrbcpr_corners)
+ * rbcpr_stats.num_latest_recommends,
+ GFP_KERNEL);
+
+ if (!rbcpr_stats.rbcpr_corners[i].rpm_rcmnd) {
+ msm_rpmrbcpr_free_mem(&pvtdata, &rbcpr_stats);
+ return -ENOMEM;
+ }
+ }
+ return 0;
+}
+
+static int msm_rpmrbcpr_file_open(struct inode *inode, struct file *file)
+{
+ file->private_data = &pvtdata;
+ pvtdata.len = 0;
+
+ if (!pvtdata.regbase)
+ return -EBUSY;
+
+ return 0;
+}
+
+static int msm_rpmrbcpr_file_close(struct inode *inode, struct file *file)
+{
+ return 0;
+}
+
+static const struct file_operations msm_rpmrbcpr_fops = {
+ .owner = THIS_MODULE,
+ .open = msm_rpmrbcpr_file_open,
+ .read = msm_rpmrbcpr_file_read,
+ .release = msm_rpmrbcpr_file_close,
+ .llseek = no_llseek,
+};
+
+static int __devinit msm_rpmrbcpr_probe(struct platform_device *pdev)
+{
+ struct dentry *dent;
+ struct msm_rpmrbcpr_platform_data *pdata;
+ int ret = 0;
+
+ pdata = pdev->dev.platform_data;
+ if (!pdata)
+ return -EINVAL;
+ dent = debugfs_create_file("rpm_rbcpr", S_IRUGO, NULL,
+ pdev->dev.platform_data, &msm_rpmrbcpr_fops);
+
+ if (!dent) {
+ pr_err("%s: ERROR debugfs_create_file failed\n", __func__);
+ return -ENOMEM;
+ }
+ platform_set_drvdata(pdev, dent);
+ ret = msm_rpmrbcpr_allocate_mem(pdata, pdev->resource);
+ return ret;
+}
+
+static int __devexit msm_rpmrbcpr_remove(struct platform_device *pdev)
+{
+ struct dentry *dent;
+
+ msm_rpmrbcpr_free_mem(&pvtdata, &rbcpr_stats);
+ dent = platform_get_drvdata(pdev);
+ debugfs_remove(dent);
+ platform_set_drvdata(pdev, NULL);
+ return 0;
+}
+
+static struct platform_driver msm_rpmrbcpr_driver = {
+ .probe = msm_rpmrbcpr_probe,
+ .remove = __devexit_p(msm_rpmrbcpr_remove),
+ .driver = {
+ .name = "msm_rpm_rbcpr",
+ .owner = THIS_MODULE,
+ },
+};
+
+static int __init msm_rpmrbcpr_init(void)
+{
+ return platform_driver_register(&msm_rpmrbcpr_driver);
+}
+
+static void __exit msm_rpmrbcpr_exit(void)
+{
+ platform_driver_unregister(&msm_rpmrbcpr_driver);
+}
+
+module_init(msm_rpmrbcpr_init);
+module_exit(msm_rpmrbcpr_exit);
diff --git a/arch/arm/mach-msm/rpm_rbcpr_stats.h b/arch/arm/mach-msm/rpm_rbcpr_stats.h
new file mode 100644
index 0000000..55644d0
--- /dev/null
+++ b/arch/arm/mach-msm/rpm_rbcpr_stats.h
@@ -0,0 +1,30 @@
+/* Copyright (c) 2012, Code Aurora Forum. 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.
+ *
+ */
+
+#ifndef __ARCH_ARM_MACH_MSM_RPM_RBCPR_STATS_H
+#define __ARCH_ARM_MACH_MSM_RPM_RBCPR_STATS_H
+
+#include <linux/types.h>
+
+struct msm_rpmrbcpr_design_data {
+ u32 upside_steps;
+ u32 downside_steps;
+ int svs_voltage;
+ int nominal_voltage;
+ int turbo_voltage;
+};
+
+struct msm_rpmrbcpr_platform_data {
+ struct msm_rpmrbcpr_design_data rbcpr_data;
+};
+#endif