388 lines
8.8 KiB
C
388 lines
8.8 KiB
C
/*
|
|
* Copyright (c) 2012, 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.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "%s: " fmt, __func__
|
|
|
|
#include <linux/kernel.h>
|
|
#include <linux/io.h>
|
|
#include <linux/debugfs.h>
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/err.h>
|
|
#include <linux/errno.h>
|
|
#include <linux/cpu.h>
|
|
#include <linux/smp.h>
|
|
|
|
#include <mach/msm_bus.h>
|
|
#include <mach/msm-krait-l2-accessors.h>
|
|
|
|
#include "acpuclock-krait.h"
|
|
|
|
static struct drv_data *drv;
|
|
static DEFINE_MUTEX(debug_lock);
|
|
|
|
struct acg_action {
|
|
bool set;
|
|
bool enable;
|
|
};
|
|
static int l2_acg_en_val;
|
|
static struct dentry *base_dir;
|
|
static struct dentry *sc_dir[MAX_SCALABLES];
|
|
|
|
static void cpu_action(void *info)
|
|
{
|
|
struct acg_action *action = info;
|
|
|
|
u32 val;
|
|
asm volatile ("mrc p15, 7, %[cpmr0], c15, c0, 5\n\t"
|
|
: [cpmr0]"=r" (val));
|
|
if (action->set) {
|
|
if (action->enable)
|
|
val &= ~BIT(0);
|
|
else
|
|
val |= BIT(0);
|
|
asm volatile ("mcr p15, 7, %[cpmr0], c15, c0, 5\n\t"
|
|
: : [cpmr0]"r" (val));
|
|
} else {
|
|
action->enable = !(val & BIT(0));
|
|
}
|
|
}
|
|
|
|
/* Disable auto clock-gating for a scalable. */
|
|
static void disable_acg(int sc_id)
|
|
{
|
|
u32 regval;
|
|
|
|
if (sc_id == L2) {
|
|
regval = get_l2_indirect_reg(drv->scalable[sc_id].l2cpmr_iaddr);
|
|
l2_acg_en_val = regval & (0x3 << 10);
|
|
regval |= (0x3 << 10);
|
|
set_l2_indirect_reg(drv->scalable[sc_id].l2cpmr_iaddr, regval);
|
|
} else {
|
|
struct acg_action action = { .set = true, .enable = false };
|
|
smp_call_function_single(sc_id, cpu_action, &action, 1);
|
|
}
|
|
}
|
|
|
|
/* Enable auto clock-gating for a scalable. */
|
|
static void enable_acg(int sc_id)
|
|
{
|
|
u32 regval;
|
|
|
|
if (sc_id == L2) {
|
|
regval = get_l2_indirect_reg(drv->scalable[sc_id].l2cpmr_iaddr);
|
|
regval &= ~(0x3 << 10);
|
|
regval |= l2_acg_en_val;
|
|
set_l2_indirect_reg(drv->scalable[sc_id].l2cpmr_iaddr, regval);
|
|
} else {
|
|
struct acg_action action = { .set = true, .enable = true };
|
|
smp_call_function_single(sc_id, cpu_action, &action, 1);
|
|
}
|
|
}
|
|
|
|
/* Check if auto clock-gating for a scalable. */
|
|
static bool acg_is_enabled(int sc_id)
|
|
{
|
|
u32 regval;
|
|
|
|
if (sc_id == L2) {
|
|
regval = get_l2_indirect_reg(drv->scalable[sc_id].l2cpmr_iaddr);
|
|
return ((regval >> 10) & 0x3) != 0x3;
|
|
} else {
|
|
struct acg_action action = { .set = false };
|
|
smp_call_function_single(sc_id, cpu_action, &action, 1);
|
|
return action.enable;
|
|
}
|
|
}
|
|
|
|
/* Enable/Disable auto clock gating. */
|
|
static int acg_set(void *data, u64 val)
|
|
{
|
|
int ret = 0;
|
|
int sc_id = (int)data;
|
|
|
|
mutex_lock(&debug_lock);
|
|
get_online_cpus();
|
|
if (!sc_dir[sc_id]) {
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
if (val == 0 && acg_is_enabled(sc_id))
|
|
disable_acg(sc_id);
|
|
else if (val == 1)
|
|
enable_acg(sc_id);
|
|
out:
|
|
put_online_cpus();
|
|
mutex_unlock(&debug_lock);
|
|
|
|
return ret;
|
|
}
|
|
|
|
/* Get auto clock-gating state. */
|
|
static int acg_get(void *data, u64 *val)
|
|
{
|
|
int ret = 0;
|
|
int sc_id = (int)data;
|
|
|
|
mutex_lock(&debug_lock);
|
|
get_online_cpus();
|
|
if (!sc_dir[sc_id]) {
|
|
ret = -ENODEV;
|
|
goto out;
|
|
}
|
|
|
|
*val = acg_is_enabled(sc_id);
|
|
out:
|
|
put_online_cpus();
|
|
mutex_unlock(&debug_lock);
|
|
|
|
return ret;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(acgd_fops, acg_get, acg_set, "%lld\n");
|
|
|
|
/* Get the rate */
|
|
static int rate_get(void *data, u64 *val)
|
|
{
|
|
int sc_id = (int)data;
|
|
*val = drv->scalable[sc_id].cur_speed->khz;
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(rate_fops, rate_get, NULL, "%lld\n");
|
|
|
|
/* Get the HFPLL's L-value. */
|
|
static int hfpll_l_get(void *data, u64 *val)
|
|
{
|
|
int sc_id = (int)data;
|
|
*val = drv->scalable[sc_id].cur_speed->pll_l_val;
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(hfpll_l_fops, hfpll_l_get, NULL, "%lld\n");
|
|
|
|
/* Get the L2 rate vote. */
|
|
static int l2_vote_get(void *data, u64 *val)
|
|
{
|
|
int level, sc_id = (int)data;
|
|
|
|
level = drv->scalable[sc_id].l2_vote;
|
|
*val = drv->l2_freq_tbl[level].speed.khz;
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(l2_vote_fops, l2_vote_get, NULL, "%lld\n");
|
|
|
|
/* Get the bandwidth vote. */
|
|
static int bw_vote_get(void *data, u64 *val)
|
|
{
|
|
struct l2_level *l;
|
|
|
|
l = container_of(drv->scalable[L2].cur_speed,
|
|
struct l2_level, speed);
|
|
*val = drv->bus_scale->usecase[l->bw_level].vectors->ib;
|
|
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(bw_vote_fops, bw_vote_get, NULL, "%lld\n");
|
|
|
|
/* Get the name of the currently-selected clock source. */
|
|
static int src_name_show(struct seq_file *m, void *unused)
|
|
{
|
|
const char *const src_names[NUM_SRC_ID] = {
|
|
[PLL_0] = "PLL0",
|
|
[HFPLL] = "HFPLL",
|
|
[PLL_8] = "PLL8",
|
|
};
|
|
int src, sc_id = (int)m->private;
|
|
|
|
src = drv->scalable[sc_id].cur_speed->src;
|
|
if (src > ARRAY_SIZE(src_names))
|
|
return -EINVAL;
|
|
|
|
seq_printf(m, "%s\n", src_names[src]);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int src_name_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, src_name_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations src_name_fops = {
|
|
.open = src_name_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
/* Get speed_bin ID */
|
|
static int speed_bin_get(void *data, u64 *val)
|
|
{
|
|
*val = drv->speed_bin;
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(speed_bin_fops, speed_bin_get, NULL, "%lld\n");
|
|
|
|
/* Get pvs_bin ID */
|
|
static int pvs_bin_get(void *data, u64 *val)
|
|
{
|
|
*val = drv->pvs_bin;
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(pvs_bin_fops, pvs_bin_get, NULL, "%lld\n");
|
|
|
|
/* Get boost_uv */
|
|
static int boost_get(void *data, u64 *val)
|
|
{
|
|
*val = drv->boost_uv;
|
|
return 0;
|
|
}
|
|
DEFINE_SIMPLE_ATTRIBUTE(boost_fops, boost_get, NULL, "%lld\n");
|
|
|
|
static int acpu_table_show(struct seq_file *m, void *unused)
|
|
{
|
|
const struct acpu_level *level;
|
|
|
|
seq_printf(m, "CPU_KHz PLL_L_Val L2_KHz VDD_Dig VDD_Mem ");
|
|
seq_printf(m, "BW_Mbps VDD_Core UA_Core AVS\n");
|
|
|
|
for (level = drv->acpu_freq_tbl; level->speed.khz != 0; level++) {
|
|
|
|
const struct l2_level *l2 =
|
|
&drv->l2_freq_tbl[level->l2_level];
|
|
u32 bw = drv->bus_scale->usecase[l2->bw_level].vectors[0].ib;
|
|
|
|
if (!level->use_for_scaling)
|
|
continue;
|
|
|
|
/* CPU speed information */
|
|
seq_printf(m, "%7lu %9u ",
|
|
level->speed.khz,
|
|
level->speed.pll_l_val);
|
|
|
|
/* L2 level information */
|
|
seq_printf(m, "%7lu %7d %7d %7u ",
|
|
l2->speed.khz,
|
|
l2->vdd_dig,
|
|
l2->vdd_mem,
|
|
bw / 1000000);
|
|
|
|
/* Core voltage information */
|
|
seq_printf(m, "%8d %7d %3d\n",
|
|
level->vdd_core,
|
|
level->ua_core,
|
|
level->avsdscr_setting);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int acpu_table_open(struct inode *inode, struct file *file)
|
|
{
|
|
return single_open(file, acpu_table_show, inode->i_private);
|
|
}
|
|
|
|
static const struct file_operations acpu_table_fops = {
|
|
.open = acpu_table_open,
|
|
.read = seq_read,
|
|
.llseek = seq_lseek,
|
|
.release = seq_release,
|
|
};
|
|
|
|
static void __cpuinit add_scalable_dir(int sc_id)
|
|
{
|
|
char sc_name[8];
|
|
|
|
if (sc_id == L2)
|
|
snprintf(sc_name, sizeof(sc_name), "l2");
|
|
else
|
|
snprintf(sc_name, sizeof(sc_name), "cpu%d", sc_id);
|
|
|
|
sc_dir[sc_id] = debugfs_create_dir(sc_name, base_dir);
|
|
if (!sc_dir[sc_id])
|
|
return;
|
|
|
|
debugfs_create_file("auto_gating", S_IRUGO | S_IWUSR,
|
|
sc_dir[sc_id], (void *)sc_id, &acgd_fops);
|
|
|
|
debugfs_create_file("rate", S_IRUGO,
|
|
sc_dir[sc_id], (void *)sc_id, &rate_fops);
|
|
|
|
debugfs_create_file("hfpll_l", S_IRUGO,
|
|
sc_dir[sc_id], (void *)sc_id, &hfpll_l_fops);
|
|
|
|
debugfs_create_file("src", S_IRUGO,
|
|
sc_dir[sc_id], (void *)sc_id, &src_name_fops);
|
|
|
|
if (sc_id == L2)
|
|
debugfs_create_file("bw_ib_vote", S_IRUGO,
|
|
sc_dir[sc_id], (void *)sc_id, &bw_vote_fops);
|
|
else
|
|
debugfs_create_file("l2_vote", S_IRUGO,
|
|
sc_dir[sc_id], (void *)sc_id, &l2_vote_fops);
|
|
}
|
|
|
|
static void __cpuinit remove_scalable_dir(int sc_id)
|
|
{
|
|
debugfs_remove_recursive(sc_dir[sc_id]);
|
|
sc_dir[sc_id] = NULL;
|
|
}
|
|
|
|
static int __cpuinit debug_cpu_callback(struct notifier_block *nfb,
|
|
unsigned long action, void *hcpu)
|
|
{
|
|
int cpu = (int)hcpu;
|
|
|
|
switch (action & ~CPU_TASKS_FROZEN) {
|
|
case CPU_DOWN_FAILED:
|
|
case CPU_UP_PREPARE:
|
|
add_scalable_dir(cpu);
|
|
break;
|
|
case CPU_UP_CANCELED:
|
|
case CPU_DOWN_PREPARE:
|
|
remove_scalable_dir(cpu);
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
return NOTIFY_OK;
|
|
}
|
|
|
|
static struct notifier_block __cpuinitdata debug_cpu_notifier = {
|
|
.notifier_call = debug_cpu_callback,
|
|
};
|
|
|
|
void __init acpuclk_krait_debug_init(struct drv_data *drv_data)
|
|
{
|
|
int cpu;
|
|
drv = drv_data;
|
|
|
|
base_dir = debugfs_create_dir("acpuclk", NULL);
|
|
if (!base_dir)
|
|
return;
|
|
|
|
debugfs_create_file("speed_bin", S_IRUGO, base_dir, NULL,
|
|
&speed_bin_fops);
|
|
debugfs_create_file("pvs_bin", S_IRUGO, base_dir, NULL, &pvs_bin_fops);
|
|
debugfs_create_file("boost_uv", S_IRUGO, base_dir, NULL, &boost_fops);
|
|
debugfs_create_file("acpu_table", S_IRUGO, base_dir, NULL,
|
|
&acpu_table_fops);
|
|
|
|
for_each_online_cpu(cpu)
|
|
add_scalable_dir(cpu);
|
|
add_scalable_dir(L2);
|
|
|
|
register_hotcpu_notifier(&debug_cpu_notifier);
|
|
}
|