/* 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 #include #include #include #include #include #include #include #include #include #include #include #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);