blob: 031b2dc0825c6a5aefcb6d1a2a231bde2fc3c7a7 [file] [log] [blame]
/* Copyright (c) 2012-2013, 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/module.h>
#include <linux/kernel.h>
#include <linux/pm.h>
#include <linux/mutex.h>
#include <linux/uaccess.h>
#include <linux/debugfs.h>
#include <linux/slab.h>
#include <linux/delay.h>
#include <linux/ctype.h>
#include <linux/moduleparam.h>
#include <linux/platform_device.h>
#include <mach/socinfo.h>
#if defined(CONFIG_MSM_RPM)
#include "rpm_resources.h"
#endif
#if defined(CONFIG_MSM_RPM_SMD)
#include "lpm_resources.h"
#endif
#include "timer.h"
#include "test-lpm.h"
#define LPM_STATS_RESET "reset"
#define LPM_TEST_ALL_LEVELS "lpm"
#define LPM_TEST_LATENCIES "latency"
#define LPM_TEST_CLEAR "clear"
#define BUF_SIZE 200
#define STAT_BUF_EXTRA_SIZE 500
#define WAIT_FOR_XO 1
#define COMM_BUF_SIZE 15
#define INPUT_COUNT_BUF 10
#define LPM_DEFAULT_CPU 0
#define SNPRINTF(buf, size, format, ...) \
{ \
if (size > 0) { \
int ret; \
ret = snprintf(buf, size, format, ## __VA_ARGS__); \
if (ret > size) { \
buf += size; \
size = 0; \
} else { \
buf += ret; \
size -= ret; \
} \
} \
} \
static DEFINE_MUTEX(lpm_stats_mutex);
struct lpm_level_stat {
char level_name[BUF_SIZE];
int64_t min_time;
int64_t max_time;
int64_t avg_time;
int64_t exit_early;
int64_t count;
unsigned long min_threshold;
uint32_t kernel_sleep_time;
bool entered;
};
static DEFINE_PER_CPU(struct lpm_level_stat *, lpm_levels);
static struct dentry *lpm_stat;
static struct dentry *lpm_ext_comm;
static struct msm_rpmrs_level *lpm_supp_level;
static int lpm_level_count;
static int lpm_level_iter;
static bool msm_lpm_use_qtimer;
static unsigned long lpm_sleep_time;
static bool lpm_latency_test;
static unsigned int timer_interval = 5000;
module_param_named(lpm_timer_interval_msec, timer_interval, uint,
S_IRUGO | S_IWUSR | S_IWGRP);
static unsigned int latency_test_interval = 50;
module_param_named(lpm_latency_timer_interval_usec, latency_test_interval, uint,
S_IRUGO | S_IWUSR | S_IWGRP);
static unsigned int cpu_to_debug = LPM_DEFAULT_CPU;
static int lpm_cpu_update(const char *val, const struct kernel_param *kp)
{
int ret = 0;
unsigned int debug_val;
ret = kstrtouint(val, 10, &debug_val);
if ((ret < 0) || (debug_val >= num_possible_cpus()))
return -EINVAL;
cpu_to_debug = debug_val;
return ret;
}
static struct kernel_param_ops cpu_debug_events = {
.set = lpm_cpu_update,
};
module_param_cb(cpu_to_debug, &cpu_debug_events, &cpu_to_debug,
S_IRUGO | S_IWUSR | S_IWGRP);
static void lpm_populate_name(struct lpm_level_stat *stat,
struct msm_rpmrs_level *supp)
{
char nm[BUF_SIZE] = {0};
char default_buf[20];
switch (supp->sleep_mode) {
case MSM_PM_SLEEP_MODE_WAIT_FOR_INTERRUPT:
strlcat(nm, "WFI ", BUF_SIZE);
break;
case MSM_PM_SLEEP_MODE_RAMP_DOWN_AND_WAIT_FOR_INTERRUPT:
strlcat(nm, "WFI voltage Rampdown ", BUF_SIZE);
break;
case MSM_PM_SLEEP_MODE_RETENTION:
strlcat(nm, "Retention ", BUF_SIZE);
break;
case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_STANDALONE:
strlcat(nm, "Standalone Power collapse ", BUF_SIZE);
break;
case MSM_PM_SLEEP_MODE_POWER_COLLAPSE:
strlcat(nm, "Idle Power collapse ", BUF_SIZE);
break;
case MSM_PM_SLEEP_MODE_POWER_COLLAPSE_SUSPEND:
strlcat(nm, "Suspend Power collapse ", BUF_SIZE);
break;
default:
strlcat(nm, "Invalid Mode ", BUF_SIZE);
break;
}
switch (msm_pm_get_pxo(&(supp->rs_limits))) {
case MSM_PM(PXO_OFF):
strlcat(nm, "XO: OFF ", BUF_SIZE);
break;
case MSM_PM(PXO_ON):
strlcat(nm, "XO: ON ", BUF_SIZE);
break;
default:
snprintf(default_buf, sizeof(default_buf),
"XO : %d ", msm_pm_get_pxo(&(supp->rs_limits)));
strlcat(nm, default_buf , BUF_SIZE);
break;
}
switch (msm_pm_get_l2_cache(&(supp->rs_limits))) {
case MSM_PM(L2_CACHE_HSFS_OPEN):
strlcat(nm, "L2: HSFS ", BUF_SIZE);
break;
case MSM_PM(L2_CACHE_GDHS):
strlcat(nm, "L2: GDHS ", BUF_SIZE);
break;
case MSM_PM(L2_CACHE_RETENTION):
strlcat(nm, "L2: Retention ", BUF_SIZE);
break;
case MSM_PM(L2_CACHE_ACTIVE):
strlcat(nm, "L2: Active ", BUF_SIZE);
break;
default:
snprintf(default_buf, sizeof(default_buf),
"L2 : %d ", msm_pm_get_l2_cache(&(supp->rs_limits)));
strlcat(nm, default_buf , BUF_SIZE);
break;
}
snprintf(default_buf, sizeof(default_buf),
"Vdd_mem : %d ", msm_pm_get_vdd_mem(&(supp->rs_limits)));
strlcat(nm, default_buf , BUF_SIZE);
snprintf(default_buf, sizeof(default_buf),
"Vdd_dig : %d ", msm_pm_get_vdd_dig(&(supp->rs_limits)));
strlcat(nm, default_buf , BUF_SIZE);
strlcpy(stat->level_name, nm, strnlen(nm, BUF_SIZE));
}
static int64_t msm_lpm_get_time(void)
{
if (msm_lpm_use_qtimer)
return ktime_to_ns(ktime_get());
return msm_timer_get_sclk_time(NULL);
}
static bool lpm_get_level(void *v, unsigned int *ct)
{
bool ret = false;
int it;
struct msm_rpmrs_level *level_enter;
level_enter = container_of(((struct msm_lpm_sleep_data *)v)->limits,
struct msm_rpmrs_level, rs_limits);
if (level_enter) {
for (it = 0; it < lpm_level_count; it++)
if (!memcmp(level_enter , lpm_supp_level + it,
sizeof(struct msm_rpmrs_level))) {
*ct = it;
ret = true;
break;
}
}
return ret;
}
static int lpm_callback(struct notifier_block *self, unsigned long cmd,
void *sleep_data)
{
static int64_t time;
unsigned int ct;
struct lpm_level_stat *stats;
stats = per_cpu(lpm_levels, cpu_to_debug);
/* Update the stats and get the start/stop time */
if (cmd == MSM_LPM_STATE_ENTER && !lpm_latency_test) {
time = msm_lpm_get_time();
stats[lpm_level_iter].entered = true;
} else if ((cmd == MSM_LPM_STATE_EXIT) && (time)
&& (!lpm_latency_test)) {
int64_t time1;
time1 = msm_lpm_get_time();
time = time1 - time;
if ((time < stats[lpm_level_iter].min_time) ||
(!stats[lpm_level_iter].min_time))
stats[lpm_level_iter].min_time = time;
if (time > stats[lpm_level_iter].max_time)
stats[lpm_level_iter].max_time = time;
time1 = stats[lpm_level_iter].avg_time *
stats[lpm_level_iter].count + time;
do_div(time1, ++(stats[lpm_level_iter].count));
stats[lpm_level_iter].avg_time = time1;
do_div(time, NSEC_PER_USEC);
if (time < lpm_supp_level[lpm_level_iter].
time_overhead_us)
stats[lpm_level_iter].exit_early++;
time = 0;
} else if (cmd == MSM_LPM_STATE_ENTER && lpm_latency_test) {
struct msm_lpm_sleep_data *data = sleep_data;
if ((lpm_get_level(sleep_data, &ct)) &&
(stats[ct].min_threshold == 0) &&
data->kernel_sleep <= lpm_sleep_time) {
stats[ct].min_threshold = lpm_sleep_time;
stats[ct].kernel_sleep_time =
data->kernel_sleep;
}
}
return 0;
}
static struct notifier_block lpm_idle_nb = {
.notifier_call = lpm_callback,
};
static void lpm_test_initiate(int lpm_level_test)
{
int test_ret;
/* This will communicate to 'stat' debugfs to skip latency printing*/
lpm_sleep_time = 0;
lpm_latency_test = false;
/* Unregister any infinitely registered level*/
msm_lpm_unregister_notifier(cpu_to_debug, &lpm_idle_nb);
/* Register/Unregister for Notification */
while (lpm_level_iter < lpm_level_count) {
test_ret = msm_lpm_register_notifier(cpu_to_debug,
lpm_level_iter, &lpm_idle_nb, false);
if (test_ret < 0) {
pr_err("%s: Registering notifier failed\n", __func__);
return;
}
if (!timer_interval)
break;
msleep(timer_interval);
msm_lpm_unregister_notifier(cpu_to_debug, &lpm_idle_nb);
if (lpm_level_test == lpm_level_count)
lpm_level_iter++;
else
break;
}
}
static void lpm_latency_test_initiate(unsigned long max_time)
{
int test_ret;
lpm_latency_test = true;
lpm_sleep_time = latency_test_interval;
msm_lpm_unregister_notifier(cpu_to_debug, &lpm_idle_nb);
if (max_time > lpm_sleep_time) {
do {
test_ret = msm_lpm_register_notifier(cpu_to_debug,
lpm_level_count + 1,
&lpm_idle_nb, true);
if (test_ret) {
pr_err("%s: Registering notifier failed\n",
__func__);
return;
}
usleep(lpm_sleep_time);
/*Unregister to ensure that we dont update the latency
during the timer value transistion*/
msm_lpm_unregister_notifier(cpu_to_debug,
&lpm_idle_nb);
lpm_sleep_time += latency_test_interval;
} while (lpm_sleep_time < max_time);
} else
pr_err("%s: Invalid time interval specified\n", __func__);
lpm_latency_test = false;
}
static ssize_t lpm_test_comm_read(struct file *fp, char __user *user_buffer,
size_t buffer_length, loff_t *position)
{
int i = 0;
int count = buffer_length;
int alloc_size = 100 * lpm_level_count;
char *temp_buf;
char *comm_buf;
ssize_t ret;
comm_buf = kzalloc(alloc_size, GFP_KERNEL);
if (!comm_buf) {
pr_err("%s:Memory alloc failed\n", __func__);
ret = 0;
goto com_read_failed;
}
temp_buf = comm_buf;
SNPRINTF(temp_buf, count, "Low power modes available:\n");
for (i = 0; i < lpm_level_count; i++)
SNPRINTF(temp_buf, count, "%d. %s\n", i,
per_cpu(lpm_levels, cpu_to_debug)[i].level_name);
SNPRINTF(temp_buf, count, "%d. MSM test all lpm\n", i++);
SNPRINTF(temp_buf, count, "%d. MSM determine latency\n", i);
ret = simple_read_from_buffer(user_buffer, buffer_length - count,
position, comm_buf, alloc_size);
kfree(comm_buf);
com_read_failed:
return ret;
}
char *trimspaces(char *time_buf)
{
int len;
char *tail;
len = strnlen(time_buf, INPUT_COUNT_BUF);
tail = time_buf + len;
while (isspace(*time_buf) && (time_buf != tail))
time_buf++;
if (time_buf == tail) {
time_buf = NULL;
goto exit_trim_spaces;
}
len = strnlen(time_buf, INPUT_COUNT_BUF);
tail = time_buf + len - 1;
while (isspace(*tail) && tail != time_buf) {
*tail = '\0';
tail--;
}
exit_trim_spaces:
return time_buf;
}
static ssize_t lpm_test_comm_write(struct file *fp, const char __user
*user_buffer, size_t count, loff_t *position)
{
ssize_t ret;
int str_ret;
int lpm_level_test;
char *new_ptr;
char *comm_buf;
comm_buf = kzalloc(COMM_BUF_SIZE, GFP_KERNEL);
if (!comm_buf) {
pr_err("\'%s\': kzalloc failed\n", __func__);
return -EINVAL;
}
memset(comm_buf, '\0', COMM_BUF_SIZE);
ret = simple_write_to_buffer(comm_buf, COMM_BUF_SIZE, position,
user_buffer, count);
new_ptr = trimspaces(comm_buf);
if (!new_ptr) {
pr_err("%s: Test case number input invalid\n", __func__);
goto write_com_failed;
}
if (!memcmp(comm_buf, LPM_TEST_ALL_LEVELS,
sizeof(LPM_TEST_ALL_LEVELS) - 1)) {
lpm_level_test = lpm_level_count;
lpm_level_iter = 0;
lpm_test_initiate(lpm_level_test);
goto write_com_success;
} else if (!memcmp(comm_buf, LPM_TEST_LATENCIES,
sizeof(LPM_TEST_LATENCIES) - 1)) {
lpm_level_test = lpm_level_count + 1;
lpm_latency_test_initiate(timer_interval * USEC_PER_MSEC);
goto write_com_success;
} else if (!memcmp(comm_buf, LPM_TEST_CLEAR,
sizeof(LPM_TEST_CLEAR) - 1)) {
msm_lpm_unregister_notifier(cpu_to_debug, &lpm_idle_nb);
goto write_com_success;
}
str_ret = kstrtoint(new_ptr, 10, &lpm_level_test);
if ((str_ret) || (lpm_level_test > (lpm_level_count + 1)) ||
(lpm_level_test < 0))
goto write_com_failed;
lpm_level_iter = lpm_level_test;
lpm_test_initiate(lpm_level_test);
goto write_com_success;
write_com_failed:
ret = -EINVAL;
write_com_success:
kfree(comm_buf);
return ret;
}
static ssize_t lpm_test_stat_read(struct file *fp, char __user *user_buffer,
size_t buffer_length, loff_t *position)
{
int i = 0;
int j = 0;
int count = buffer_length;
char *stat_buf;
char *stat_buf_start;
size_t stat_buf_size;
ssize_t ret;
int64_t min_ns;
int64_t max_ns;
int64_t avg_ns;
uint32_t min_ms;
uint32_t max_ms;
uint32_t avg_ms;
stat_buf_size = ((sizeof(struct lpm_level_stat) * lpm_level_count) +
STAT_BUF_EXTRA_SIZE);
stat_buf = kzalloc(stat_buf_size, GFP_KERNEL);
if (!stat_buf) {
pr_err("\'%s\': kzalloc failed\n", __func__);
return -EINVAL;
}
stat_buf_start = stat_buf;
mutex_lock(&lpm_stats_mutex);
memset(stat_buf, '\0', stat_buf_size);
SNPRINTF(stat_buf, count, "\n\nStats for CPU: %d\nTotal Levels: %d\n",
cpu_to_debug, lpm_level_count);
if (!lpm_sleep_time) {
SNPRINTF(stat_buf, count, "Level(s) failed: ");
for (i = 0 ; i < lpm_level_count; i++) {
if (per_cpu(lpm_levels, cpu_to_debug)[i].entered)
continue;
else {
SNPRINTF(stat_buf, count,
"\n%d. %s", ++j, per_cpu(lpm_levels,
cpu_to_debug)[i].level_name);
}
}
SNPRINTF(stat_buf, count, "\n\nSTATS:");
for (i = 0; i < lpm_level_count; i++) {
min_ns = per_cpu(lpm_levels, cpu_to_debug)[i].min_time;
min_ms = do_div(min_ns, NSEC_PER_MSEC);
max_ns = per_cpu(lpm_levels, cpu_to_debug)[i].max_time;
max_ms = do_div(max_ns, NSEC_PER_MSEC);
avg_ns = per_cpu(lpm_levels, cpu_to_debug)[i].avg_time;
avg_ms = do_div(avg_ns, NSEC_PER_MSEC);
SNPRINTF(stat_buf, count, "\nLEVEL: %s\n"
"Entered : %lld\n"
"Early wakeup : %lld\n"
"Min Time (mSec): %lld.%06u\n"
"Max Time (mSec): %lld.%06u\n"
"Avg Time (mSec): %lld.%06u\n",
per_cpu(lpm_levels, cpu_to_debug)[i].level_name,
per_cpu(lpm_levels, cpu_to_debug)[i].count,
per_cpu(lpm_levels, cpu_to_debug)[i].exit_early,
min_ns, min_ms,
max_ns, max_ms,
avg_ns, avg_ms);
}
} else {
for (i = 0; i < lpm_level_count; i++) {
SNPRINTF(stat_buf, count, "\nLEVEL: %s\n"
"Min Timer value (uSec): %lu\n"
"Kernel sleep time (uSec): %u\n",
per_cpu(lpm_levels, cpu_to_debug)[i].level_name,
per_cpu(lpm_levels, cpu_to_debug)[i].
min_threshold,
per_cpu(lpm_levels,
cpu_to_debug)[i].kernel_sleep_time);
}
}
ret = simple_read_from_buffer(user_buffer, buffer_length - count,
position, stat_buf_start, stat_buf_size);
mutex_unlock(&lpm_stats_mutex);
kfree(stat_buf_start);
return ret;
}
static ssize_t lpm_test_stat_write(struct file *fp, const char __user
*user_buffer, size_t count, loff_t *position)
{
char buf[sizeof(LPM_STATS_RESET)];
int ret;
int i;
struct lpm_level_stat *stats;
if (count > sizeof(LPM_STATS_RESET)) {
ret = -EINVAL;
goto write_debug_failed;
}
simple_write_to_buffer(buf, sizeof(LPM_STATS_RESET), position,
user_buffer, count);
if (memcmp(buf, LPM_STATS_RESET, sizeof(LPM_STATS_RESET) - 1)) {
ret = -EINVAL;
goto write_debug_failed;
}
mutex_lock(&lpm_stats_mutex);
stats = per_cpu(lpm_levels, cpu_to_debug);
for (i = 0 ; i < lpm_level_count; i++) {
stats[i].entered = 0;
stats[i].min_time = 0;
stats[i].max_time = 0;
stats[i].avg_time = 0;
stats[i].count = 0;
stats[i].exit_early = 0;
stats[i].min_threshold = 0;
stats[i].kernel_sleep_time = 0;
}
mutex_unlock(&lpm_stats_mutex);
return count;
write_debug_failed:
return ret;
}
static void lpm_init_rpm_levels(int test_lpm_level_count,
struct msm_rpmrs_level *test_levels)
{
int i = 0;
unsigned int m_cpu = 0;
struct lpm_level_stat *stat_levels = NULL;
if (test_lpm_level_count < 0)
return;
lpm_level_count = test_lpm_level_count;
lpm_supp_level = test_levels;
for_each_possible_cpu(m_cpu) {
stat_levels = kzalloc(sizeof(struct lpm_level_stat) *
lpm_level_count, GFP_KERNEL);
if (!stat_levels) {
for (i = m_cpu - 1; i >= 0; i--)
kfree(per_cpu(lpm_levels, i));
return;
}
for (i = 0; i < lpm_level_count; i++)
lpm_populate_name(&stat_levels[i], &lpm_supp_level[i]);
per_cpu(lpm_levels, m_cpu) = stat_levels;
}
}
static const struct file_operations fops_stat = {
.read = lpm_test_stat_read,
.write = lpm_test_stat_write,
};
static const struct file_operations fops_comm = {
.read = lpm_test_comm_read,
.write = lpm_test_comm_write,
};
static int __devinit lpm_test_init(int test_lpm_level_count,
struct msm_rpmrs_level *test_levels)
{
int filevalue;
int lpm_comm;
int ret = -EINVAL;
struct dentry *parent_dir = NULL;
parent_dir = debugfs_create_dir("msm_lpm_debug", NULL);
if (!parent_dir) {
pr_err("%s: debugfs directory creation failed\n",
__func__);
goto init_err;
}
lpm_stat = debugfs_create_file("stat",
S_IRUGO | S_IWUSR | S_IWGRP, parent_dir,
&filevalue, &fops_stat);
if (!lpm_stat) {
pr_err("%s: lpm_stats debugfs creation failed\n",
__func__);
goto init_err;
}
lpm_ext_comm = debugfs_create_file("comm",
S_IRUGO | S_IWUSR | S_IWGRP, parent_dir, &lpm_comm,
&fops_comm);
if (!lpm_ext_comm) {
pr_err("%s: lpm_comm debugfs creation failed\n",
__func__);
debugfs_remove(lpm_stat);
goto init_err;
}
/*Query RPM resources and allocate the data sturctures*/
lpm_init_rpm_levels(test_lpm_level_count, test_levels);
ret = 0;
init_err:
return ret;
}
static int __devexit lpm_test_exit(struct platform_device *pdev)
{
unsigned int m_cpu = 0;
kfree(lpm_supp_level);
for_each_possible_cpu(m_cpu)
kfree(per_cpu(lpm_levels, m_cpu));
debugfs_remove(lpm_stat);
debugfs_remove(lpm_ext_comm);
return 0;
}
static int __devinit lpm_test_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
struct lpm_test_platform_data *pdata;
struct msm_rpmrs_level *test_levels;
int test_lpm_level_count;
pdata = pdev->dev.platform_data;
if (!pdata) {
dev_err(dev, "no platform data specified\n");
return -EINVAL;
}
test_levels = pdata->msm_lpm_test_levels;
test_lpm_level_count = pdata->msm_lpm_test_level_count;
if (pdata->use_qtimer)
msm_lpm_use_qtimer = true;
lpm_test_init(test_lpm_level_count, test_levels);
return 0;
}
static struct platform_driver lpm_test_driver = {
.probe = lpm_test_probe,
.remove = lpm_test_exit,
.driver = {
.name = "lpm_test",
.owner = THIS_MODULE,
},
};
static int __init lpm_test_platform_driver_init(void)
{
return platform_driver_register(&lpm_test_driver);
}
late_initcall(lpm_test_platform_driver_init);