316 lines
7.1 KiB
C
316 lines
7.1 KiB
C
/* Copyright (c) 2014-2015, 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/kernel.h>
|
|
#include <linux/module.h>
|
|
#include <linux/slab.h>
|
|
#include <linux/uaccess.h>
|
|
#include <linux/string.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/ctype.h>
|
|
#include <linux/cpu.h>
|
|
#include "soc/qcom/msm-core.h"
|
|
|
|
#define MAX_PSTATES 50
|
|
|
|
enum arg_offset {
|
|
CPU_OFFSET,
|
|
FREQ_OFFSET,
|
|
POWER_OFFSET,
|
|
};
|
|
|
|
struct core_debug {
|
|
int cpu;
|
|
struct cpu_pstate_pwr *head;
|
|
int enabled;
|
|
int len;
|
|
struct cpu_pwr_stats *ptr;
|
|
struct cpu_pstate_pwr *driver_data;
|
|
int driver_len;
|
|
};
|
|
|
|
static DEFINE_PER_CPU(struct core_debug, c_dgfs);
|
|
static struct cpu_pwr_stats *msm_core_data;
|
|
static struct debugfs_blob_wrapper help_msg = {
|
|
.data =
|
|
"MSM CORE Debug-FS Support\n"
|
|
"\n"
|
|
"Hierarchy schema\n"
|
|
"/sys/kernel/debug/msm_core\n"
|
|
" /help - Static help text\n"
|
|
" /ptable - write to p-state table\n"
|
|
" /enable - enable the written p-state table\n"
|
|
" /ptable_dump - Dump the debug ptable\n"
|
|
"\n"
|
|
"Usage\n"
|
|
" Input test frequency and power information in ptable:\n"
|
|
" echo \"0 300000 120\" > ptable\n"
|
|
" format: <cpu> <frequency in khz> <power>\n"
|
|
"\n"
|
|
" Enable the ptable for the cpu:\n"
|
|
" echo \"0 1\" > enable\n"
|
|
" format: <cpu> <1 to enable, 0 to disable>\n"
|
|
" Note: Writing 0 to disable will reset/clear the ptable\n"
|
|
"\n"
|
|
" Dump the entire ptable:\n"
|
|
" cat ptable\n"
|
|
" ----- CPU0 - Enabled ---------\n"
|
|
" Freq Power\n"
|
|
" 700000 120\n"
|
|
"----- CPU0 - Live numbers -----\n"
|
|
" Freq Power\n"
|
|
" 300000 218\n"
|
|
" ----- CPU1 - Written ---------\n"
|
|
" Freq Power\n"
|
|
" 700000 120\n"
|
|
" Ptable dump will dump the status of the table as well\n"
|
|
" It shows:\n"
|
|
" Enabled -> for a cpu that debug ptable enabled\n"
|
|
" Written -> for a cpu that has debug ptable values written\n"
|
|
" but not enabled\n"
|
|
"\n",
|
|
|
|
};
|
|
|
|
static void add_to_ptable(uint64_t *arg)
|
|
{
|
|
struct core_debug *node;
|
|
int i, cpu = arg[CPU_OFFSET];
|
|
|
|
if (!cpu_possible(cpu))
|
|
return;
|
|
|
|
node = &per_cpu(c_dgfs, cpu);
|
|
if (!node->head) {
|
|
node->head = kzalloc(sizeof(struct cpu_pstate_pwr) *
|
|
(MAX_PSTATES + 1),
|
|
GFP_KERNEL);
|
|
if (!node->head)
|
|
return;
|
|
}
|
|
for (i = 0; i < MAX_PSTATES; i++) {
|
|
if (node->head[i].freq == arg[FREQ_OFFSET]) {
|
|
node->head[i].power = arg[POWER_OFFSET];
|
|
return;
|
|
}
|
|
if (node->head[i].freq == 0)
|
|
break;
|
|
}
|
|
|
|
if (i == MAX_PSTATES) {
|
|
pr_warn("Dropped ptable update - no space left.\n");
|
|
return;
|
|
}
|
|
|
|
/* Insert a new frequency (may need to move things around to
|
|
keep in ascending order). */
|
|
for (i = MAX_PSTATES - 1; i > 0; i--) {
|
|
if (node->head[i-1].freq > arg[FREQ_OFFSET]) {
|
|
node->head[i].freq = node->head[i-1].freq;
|
|
node->head[i].power = node->head[i-1].power;
|
|
} else if (node->head[i-1].freq != 0) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
node->head[i].freq = arg[FREQ_OFFSET];
|
|
node->head[i].power = arg[POWER_OFFSET];
|
|
node->len++;
|
|
|
|
if (node->ptr)
|
|
node->ptr->len = node->len;
|
|
}
|
|
|
|
static int split_ptable_args(char *line, uint64_t *arg)
|
|
{
|
|
char *args;
|
|
int i;
|
|
int ret = 0;
|
|
|
|
for (i = 0; line; i++) {
|
|
args = strsep(&line, " ");
|
|
ret = kstrtoull(args, 10, &arg[i]);
|
|
}
|
|
return ret;
|
|
}
|
|
|
|
static ssize_t msm_core_ptable_write(struct file *file,
|
|
const char __user *ubuf, size_t len, loff_t *offp)
|
|
{
|
|
char *kbuf;
|
|
int ret;
|
|
uint64_t arg[3];
|
|
|
|
if (len == 0)
|
|
return 0;
|
|
|
|
kbuf = kzalloc(len + 1, GFP_KERNEL);
|
|
if (!kbuf)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(kbuf, ubuf, len)) {
|
|
ret = -EFAULT;
|
|
goto done;
|
|
}
|
|
kbuf[len] = '\0';
|
|
ret = split_ptable_args(kbuf, arg);
|
|
if (!ret) {
|
|
add_to_ptable(arg);
|
|
ret = len;
|
|
}
|
|
done:
|
|
kfree(kbuf);
|
|
return ret;
|
|
}
|
|
|
|
static void print_table(struct seq_file *m, struct cpu_pstate_pwr *c_n,
|
|
int len)
|
|
{
|
|
int i;
|
|
|
|
seq_puts(m, " Freq Power\n");
|
|
for (i = 0; i < len; i++)
|
|
seq_printf(m, " %d %u\n", c_n[i].freq,
|
|
c_n[i].power);
|
|
|
|
}
|
|
|
|
static int msm_core_ptable_read(struct seq_file *m, void *data)
|
|
{
|
|
int cpu;
|
|
struct core_debug *node;
|
|
|
|
for_each_possible_cpu(cpu) {
|
|
node = &per_cpu(c_dgfs, cpu);
|
|
if (node->head) {
|
|
seq_printf(m, "----- CPU%d - %s - Debug -------\n",
|
|
cpu, node->enabled == 1 ? "Enabled" : "Written");
|
|
print_table(m, node->head, node->len);
|
|
}
|
|
if (msm_core_data[cpu].ptable) {
|
|
seq_printf(m, "--- CPU%d - Live numbers at %ldC---\n",
|
|
cpu, node->ptr->temp);
|
|
print_table(m, msm_core_data[cpu].ptable,
|
|
msm_core_data[cpu].len);
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
static ssize_t msm_core_enable_write(struct file *file,
|
|
const char __user *ubuf, size_t len, loff_t *offp)
|
|
{
|
|
char *kbuf;
|
|
int ret;
|
|
uint64_t arg[3];
|
|
int cpu;
|
|
|
|
if (len == 0)
|
|
return 0;
|
|
|
|
kbuf = kzalloc(len + 1, GFP_KERNEL);
|
|
if (!kbuf)
|
|
return -ENOMEM;
|
|
|
|
if (copy_from_user(kbuf, ubuf, len)) {
|
|
ret = -EFAULT;
|
|
goto done;
|
|
}
|
|
kbuf[len] = '\0';
|
|
ret = split_ptable_args(kbuf, arg);
|
|
if (ret)
|
|
goto done;
|
|
cpu = arg[CPU_OFFSET];
|
|
|
|
if (cpu_possible(cpu)) {
|
|
struct core_debug *node = &per_cpu(c_dgfs, cpu);
|
|
|
|
if (arg[FREQ_OFFSET]) {
|
|
msm_core_data[cpu].ptable = node->head;
|
|
msm_core_data[cpu].len = node->len;
|
|
} else {
|
|
msm_core_data[cpu].ptable = node->driver_data;
|
|
msm_core_data[cpu].len = node->driver_len;
|
|
node->len = 0;
|
|
}
|
|
node->enabled = arg[FREQ_OFFSET];
|
|
}
|
|
ret = len;
|
|
blocking_notifier_call_chain(
|
|
get_power_update_notifier(), cpu, NULL);
|
|
|
|
done:
|
|
kfree(kbuf);
|
|
return ret;
|
|
}
|
|
|
|
static const struct file_operations msm_core_enable_ops = {
|
|
.write = msm_core_enable_write,
|
|
};
|
|
|
|
static int msm_core_dump_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, msm_core_ptable_read, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations msm_core_ptable_ops = {
|
|
.open = msm_core_dump_open,
|
|
.read = seq_read,
|
|
.write = msm_core_ptable_write,
|
|
.llseek = seq_lseek,
|
|
.release = single_release,
|
|
};
|
|
|
|
int msm_core_debug_init(void)
|
|
{
|
|
struct dentry *dir;
|
|
struct dentry *file;
|
|
int i;
|
|
|
|
msm_core_data = get_cpu_pwr_stats();
|
|
if (!msm_core_data)
|
|
goto fail;
|
|
|
|
dir = debugfs_create_dir("msm_core", NULL);
|
|
if (IS_ERR_OR_NULL(dir))
|
|
return PTR_ERR(dir);
|
|
|
|
file = debugfs_create_file("enable",
|
|
S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP, dir, NULL,
|
|
&msm_core_enable_ops);
|
|
if (IS_ERR_OR_NULL(file))
|
|
goto fail;
|
|
|
|
file = debugfs_create_file("ptable",
|
|
S_IRUSR|S_IRGRP|S_IWUSR|S_IWGRP, dir, NULL,
|
|
&msm_core_ptable_ops);
|
|
if (IS_ERR_OR_NULL(file))
|
|
goto fail;
|
|
|
|
help_msg.size = strlen(help_msg.data);
|
|
file = debugfs_create_blob("help", S_IRUGO, dir, &help_msg);
|
|
if (IS_ERR_OR_NULL(file))
|
|
goto fail;
|
|
|
|
for (i = 0; i < num_possible_cpus(); i++) {
|
|
per_cpu(c_dgfs, i).ptr = &msm_core_data[i];
|
|
per_cpu(c_dgfs, i).driver_data = msm_core_data[i].ptable;
|
|
per_cpu(c_dgfs, i).driver_len = msm_core_data[i].len;
|
|
}
|
|
return 0;
|
|
fail:
|
|
debugfs_remove(dir);
|
|
return PTR_ERR(file);
|
|
}
|
|
late_initcall(msm_core_debug_init);
|