unplugged-kernel/drivers/misc/mediatek/apusys/power/2.0/apusys_power_debug.c

715 lines
16 KiB
C

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) 2020 MediaTek Inc.
*/
#include <linux/types.h>
#include <linux/delay.h>
#include <linux/platform_device.h>
#include <linux/seq_file.h>
#include <linux/slab.h>
#include <linux/vmalloc.h>
#include <linux/mm.h>
#include <linux/mman.h>
#include <linux/module.h>
#include <linux/mutex.h>
#include <linux/fs.h>
#include <linux/debugfs.h>
#include <linux/dcache.h>
#include <linux/kthread.h>
#include <linux/uaccess.h>
#include <linux/string.h>
#include <linux/random.h>
#include <linux/proc_fs.h>
#include "apusys_power_ctl.h"
#include "hal_config_power.h"
#include "apu_log.h"
#include "apusys_power_debug.h"
#include "apusys_power.h"
#include "apu_power_api.h"
#include "apu_platform_debug.h"
#include "apusys_power_rule_check.h"
static struct mutex power_fix_dvfs_mtx;
static int g_debug_option;
bool is_power_debug_lock;
int fixed_opp;
int apu_power_power_stress(int type, int device, int opp)
{
int id = 0;
LOG_WRN("%s begin with type %d +++\n", __func__, type);
if (type < 0 || type >= 10) {
LOG_ERR("%s err with type = %d\n", __func__, type);
return -1;
}
if (device != 9 && (device < 0 || device >= APUSYS_POWER_USER_NUM)) {
LOG_ERR("%s err with device = %d\n", __func__, device);
return -1;
}
if (type != 5 && (opp < 0 || opp >= APUSYS_MAX_NUM_OPPS)) {
LOG_ERR("%s err with opp = %d\n", __func__, opp);
return -1;
}
switch (type) {
case 0: // config opp
if (device == 9) { // all devices
for (id = 0 ; id < APUSYS_DVFS_USER_NUM ; id++) {
if (dvfs_power_domain_support(id) == false)
continue;
apu_device_set_opp(id, opp);
}
} else {
apu_device_set_opp(device, opp);
}
udelay(100);
break;
case 1: // config power on
if (device == 9) { // all devices
for (id = 0 ; id < APUSYS_POWER_USER_NUM ; id++) {
if (dvfs_power_domain_support(id) == false)
continue;
apu_device_power_on(id);
}
} else {
apu_device_power_on(device);
}
break;
case 2: // config power off
if (device == 9) { // all devices
for (id = 0 ; id < APUSYS_POWER_USER_NUM ; id++) {
if (dvfs_power_domain_support(id) == false)
continue;
apu_device_power_off(id);
}
} else {
apu_device_power_off(device);
}
break;
case 4: // power driver debug func
#if defined(CONFIG_MACH_MT6893) || defined(CONFIG_MACH_MT6877)
// device: binning test
// opp: raise test
opp = ((device & 0xF) << 8) | opp;
#endif
hal_config_power(PWR_CMD_DEBUG_FUNC, VPU0, &opp);
break;
case 5: // dvfs all combination test , opp = run count
constraints_check_stress(opp);
break;
case 6:
if (opp <= 3) {
#if SUPPORT_VCORE_TO_IPUIF
apu_qos_set_vcore(g_ipuif_opp_table[opp].ipuif_vcore);
#endif
}
break;
case 7: // power on/off suspend stress
if (power_on_off_stress == 0)
power_on_off_stress = 1;
else
power_on_off_stress = 0;
break;
case 8: // dump power info and options
LOG_WRN("%s, BYPASS_POWER_OFF : %d\n",
__func__, BYPASS_POWER_OFF);
LOG_WRN("%s, BYPASS_POWER_CTL : %d\n",
__func__, BYPASS_POWER_CTL);
LOG_WRN("%s, BYPASS_DVFS_CTL : %d\n",
__func__, BYPASS_DVFS_CTL);
LOG_WRN("%s, DEFAULT_POWER_ON : %d\n",
__func__, DEFAULT_POWER_ON);
LOG_WRN("%s, AUTO_BUCK_OFF_SUSPEND : %d\n",
__func__, AUTO_BUCK_OFF_SUSPEND);
LOG_WRN("%s, AUTO_BUCK_OFF_DEEPIDLE : %d\n",
__func__, AUTO_BUCK_OFF_DEEPIDLE);
LOG_WRN("%s, VCORE_DVFS_SUPPORT : %d\n",
__func__, VCORE_DVFS_SUPPORT);
LOG_WRN("%s, ASSERTION_PERCENTAGE : %d\n",
__func__, ASSERTION_PERCENTAGE);
#ifdef AGING_MARGIN
LOG_WRN("%s, AGING_MARGIN : %d\n",
__func__, AGING_MARGIN);
#endif
LOG_WRN("%s, BINNING_VOLTAGE_SUPPORT : %d\n",
__func__, BINNING_VOLTAGE_SUPPORT);
#ifdef CCF_SET_RATE
LOG_WRN("%s, CCF_SET_RATE : %d\n",
__func__, CCF_SET_RATE);
#endif
LOG_WRN("%s, g_pwr_log_level : %d\n",
__func__, g_pwr_log_level);
LOG_WRN("%s, power_on_off_stress : %d\n",
__func__, power_on_off_stress);
LOG_WRN("%s, is_power_debug_lock : %d, fixed_opp: %d\n",
__func__, is_power_debug_lock, fixed_opp);
apu_get_power_info(0);
break;
default:
LOG_WRN("%s invalid type %d !\n", __func__, type);
}
LOG_WRN("%s end with type %d ---\n", __func__, type);
return 0;
}
static void change_log_level(int new_level)
{
g_pwr_log_level = new_level;
PWR_LOG_INF("%s, new log level = %d\n", __func__, g_pwr_log_level);
}
void fix_dvfs_debug(void)
{
enum DVFS_VOLTAGE_DOMAIN i;
enum DVFS_BUCK buck;
mutex_lock(&power_fix_dvfs_mtx);
for (i = 0; i < APUSYS_BUCK_DOMAIN_NUM; i++) {
if (dvfs_power_domain_support(i) == false)
continue;
apusys_opps.next_opp_index[i] = fixed_opp;
buck = apusys_buck_domain_to_buck[i];
apusys_opps.next_buck_volt[buck] =
max(apusys_opps.opps[fixed_opp][i].voltage,
apusys_opps.next_buck_volt[buck]);
}
is_power_debug_lock = true;
apusys_dvfs_policy(0);
#if SUPPORT_VCORE_TO_IPUIF
apusys_opps.qos_apu_vcore =
apusys_opps.opps[fixed_opp][V_VCORE].voltage;
apusys_ipuif_opp_change();
#endif
mutex_unlock(&power_fix_dvfs_mtx);
}
static int apusys_debug_power_show(struct seq_file *s, void *unused)
{
switch (g_debug_option) {
case POWER_PARAM_OPP_TABLE:
apu_power_dump_opp_table(s);
break;
case POWER_PARAM_CURR_STATUS:
apu_power_dump_curr_status(s, 0);
break;
case POWER_PARAM_LOG_LEVEL:
seq_printf(s, "g_pwr_log_level = %d\n", g_pwr_log_level);
break;
default:
apu_power_dump_curr_status(s, 1); // one line string
}
return 0;
}
static int apusys_debug_power_open(struct inode *inode, struct file *file)
{
return single_open(file, apusys_debug_power_show, inode->i_private);
}
static int apusys_set_power_parameter(uint8_t param, int argc, int *args)
{
int ret = 0;
switch (param) {
case POWER_PARAM_FIX_OPP:
ret = (argc == 1) ? 0 : -EINVAL;
if (ret) {
PWR_LOG_ERR(
"invalid argument, expected:1, received:%d\n",
argc);
goto out;
}
switch (args[0]) {
case 0:
is_power_debug_lock = false;
#if SUPPORT_VCORE_TO_IPUIF
apusys_opps.qos_apu_vcore =
VCORE_SHUTDOWN_VOLT;
#endif
break;
case 1:
is_power_debug_lock = true;
break;
default:
if (ret) {
PWR_LOG_ERR("invalid argument, received:%d\n",
(int)(args[0]));
goto out;
}
ret = -EINVAL;
goto out;
}
break;
case POWER_PARAM_DVFS_DEBUG:
ret = (argc == 1) ? 0 : -EINVAL;
if (ret) {
PWR_LOG_ERR(
"invalid argument, expected:1, received:%d\n",
argc);
goto out;
}
ret = args[0] >= APUSYS_MAX_NUM_OPPS;
if (ret) {
PWR_LOG_ERR("opp (%d) is out-of-bound, max opp:%d\n",
(int)(args[0]), APUSYS_MAX_NUM_OPPS - 1);
goto out;
}
PWR_LOG_INF("@@test%d\n", argc);
PWR_LOG_INF("lock opp=%d\n", (int)(args[0]));
fixed_opp = args[0];
fix_dvfs_debug();
break;
case POWER_HAL_CTL:
{
ret = (argc == 3) ? 0 : -EINVAL;
if (ret) {
PWR_LOG_ERR(
"invalid argument, expected:1, received:%d\n",
argc);
goto out;
}
if (args[0] < 0 || args[0] >= APUSYS_DVFS_USER_NUM) {
PWR_LOG_ERR("user(%d) is invalid\n",
(int)(args[0]));
goto out;
}
if (args[1] > 100 || args[1] < 0) {
PWR_LOG_ERR("min boost(%d) is out-of-bound\n",
(int)(args[1]));
goto out;
}
if (args[2] > 100 || args[2] < 0) {
PWR_LOG_ERR("max boost(%d) is out-of-bound\n",
(int)(args[2]));
goto out;
}
/* setting max/min opp of user, args[0] */
apusys_opps.power_lock_max_opp[args[0]] =
apusys_boost_value_to_opp(args[0], args[1]);
apusys_opps.power_lock_min_opp[args[0]] =
apusys_boost_value_to_opp(args[0], args[2]);
apusys_dvfs_policy(0);
break;
}
case POWER_PARAM_SET_USER_OPP:
ret = (argc == 2) ? 0 : -EINVAL;
if (ret) {
PWR_LOG_INF(
"invalid argument, expected:1, received:%d\n",
argc);
goto out;
}
apusys_set_opp(args[0], args[1]);
apusys_dvfs_policy(0);
break;
case POWER_PARAM_SET_THERMAL_OPP:
ret = (argc == 2) ? 0 : -EINVAL;
if (ret) {
PWR_LOG_ERR(
"invalid argument, expected:1, received:%d\n",
argc);
goto out;
}
/* make sure args[0], DVFS_USER, within the range */
if (args[0] < 0 || args[0] >= APUSYS_DVFS_USER_NUM) {
PWR_LOG_ERR("user(%d) is invalid\n",
(int)(args[0]));
goto out;
}
/* make sure args[1], OPP, within the range */
ret = (args[1] >= APUSYS_MAX_NUM_OPPS);
if (ret) {
PWR_LOG_ERR("opp-%d is too big, max opp:%d\n",
(int)(args[0]),
APUSYS_MAX_NUM_OPPS - 1);
goto out;
}
apusys_opps.thermal_opp[args[0]] = args[1];
apusys_dvfs_policy(0);
break;
case POWER_PARAM_SET_POWER_HAL_OPP:
ret = (argc == 3) ? 0 : -EINVAL;
if (ret) {
PWR_LOG_INF(
"invalid argument, expected:1, received:%d\n",
argc);
goto out;
}
/* make sure args[0], DVFS_USER, within the range */
if (args[0] < 0 || args[0] >= APUSYS_DVFS_USER_NUM) {
PWR_LOG_ERR("user(%d) is invalid\n",
(int)(args[0]));
goto out;
}
/*
* Make sure args[1], min of power lock
* within the range
*/
ret = (args[1] >= APUSYS_MAX_NUM_OPPS);
if (ret) {
PWR_LOG_ERR("opp-%d is too big, max opp:%d\n",
(int)(args[1]),
APUSYS_MAX_NUM_OPPS - 1);
goto out;
}
/*
* Make sure args[2], max of power lock
* within the range
*/
ret = (args[1] >= APUSYS_MAX_NUM_OPPS);
if (ret) {
PWR_LOG_ERR("opp-%d is too big, max opp:%d\n",
(int)(args[1]),
APUSYS_MAX_NUM_OPPS - 1);
goto out;
}
apusys_opps.power_lock_min_opp[args[0]] = args[1];
apusys_opps.power_lock_max_opp[args[0]] = args[2];
break;
case POWER_PARAM_GET_POWER_REG:
ret = (argc == 1) ? 0 : -EINVAL;
if (ret) {
PWR_LOG_INF(
"invalid argument, expected:1, received:%d\n",
argc);
goto out;
}
apu_power_reg_dump();
break;
case POWER_PARAM_POWER_STRESS:
ret = (argc == 3) ? 0 : -EINVAL;
if (ret) {
PWR_LOG_ERR(
"invalid argument, expected:3, received:%d\n",
argc);
goto out;
}
/*
* arg0 : type
* arg1 : device , 9 = all devices
* arg2 : opp
*/
apu_power_power_stress(args[0], args[1], args[2]);
break;
case POWER_PARAM_OPP_TABLE:
ret = (argc == 1) ? 0 : -EINVAL;
if (ret) {
PWR_LOG_ERR(
"invalid argument, expected:1, received:%d\n",
argc);
goto out;
}
g_debug_option = POWER_PARAM_OPP_TABLE;
break;
case POWER_PARAM_CURR_STATUS:
ret = (argc == 1) ? 0 : -EINVAL;
if (ret) {
PWR_LOG_ERR(
"invalid argument, expected:1, received:%d\n",
argc);
goto out;
}
g_debug_option = POWER_PARAM_CURR_STATUS;
break;
case POWER_PARAM_LOG_LEVEL:
ret = (argc == 1) ? 0 : -EINVAL;
if (ret) {
PWR_LOG_ERR(
"invalid argument, expected:1, received:%d\n",
argc);
goto out;
}
if (args[0] == 9)
g_debug_option = POWER_PARAM_LOG_LEVEL;
else
change_log_level(args[0]);
break;
default:
PWR_LOG_ERR("unsupport the power parameter:%d\n", param);
break;
}
out:
return ret;
}
static ssize_t apusys_debug_power_write(struct file *flip,
const char __user *buffer,
size_t count, loff_t *f_pos)
{
char *tmp, *token, *cursor;
int ret, i, param;
const int max_arg = 5;
unsigned int args[max_arg];
tmp = kzalloc(count + 1, GFP_KERNEL);
if (!tmp)
return -ENOMEM;
ret = copy_from_user(tmp, buffer, count);
if (ret) {
PWR_LOG_INF("copy_from_user failed, ret=%d\n", ret);
goto out;
}
tmp[count] = '\0';
cursor = tmp;
/* parse a command */
token = strsep(&cursor, " ");
if (strcmp(token, "fix_opp") == 0)
param = POWER_PARAM_FIX_OPP;
else if (strcmp(token, "dvfs_debug") == 0)
param = POWER_PARAM_DVFS_DEBUG;
else if (strcmp(token, "power_hal") == 0)
param = POWER_HAL_CTL;
else if (strcmp(token, "user_opp") == 0)
param = POWER_PARAM_SET_USER_OPP;
else if (strcmp(token, "thermal_opp") == 0)
param = POWER_PARAM_SET_THERMAL_OPP;
else if (strcmp(token, "power_hal_opp") == 0)
param = POWER_PARAM_SET_POWER_HAL_OPP;
else if (strcmp(token, "reg_dump") == 0)
param = POWER_PARAM_GET_POWER_REG;
else if (strcmp(token, "power_stress") == 0)
param = POWER_PARAM_POWER_STRESS;
else if (strcmp(token, "opp_table") == 0)
param = POWER_PARAM_OPP_TABLE;
else if (strcmp(token, "curr_status") == 0)
param = POWER_PARAM_CURR_STATUS;
else if (strcmp(token, "log_level") == 0)
param = POWER_PARAM_LOG_LEVEL;
else {
ret = -EINVAL;
PWR_LOG_INF("no power param[%s]!\n", token);
goto out;
}
/* parse arguments */
for (i = 0; i < max_arg && (token = strsep(&cursor, " ")); i++) {
ret = kstrtouint(token, 10, &args[i]);
if (ret) {
PWR_LOG_INF("fail to parse args[%d]\n", i);
goto out;
}
}
apusys_set_power_parameter(param, i, args);
ret = count;
out:
kfree(tmp);
return ret;
}
static int apusys_power_fail_open(struct inode *inode, struct file *file)
{
return single_open(file, apusys_power_fail_show, inode->i_private);
}
static ssize_t apusys_power_fail_write(struct file *flip,
const char __user *buffer,
size_t count, loff_t *f_pos)
{
return 0;
}
static const struct file_operations apusys_power_fail_fops = {
.open = apusys_power_fail_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = apusys_power_fail_write,
};
static const struct file_operations apusys_debug_power_fops = {
.open = apusys_debug_power_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
.write = apusys_debug_power_write,
};
struct dentry *apusys_power_dir;
void apusys_power_debugfs_init(void)
{
// struct dentry *apusys_power_dir;
int ret;
PWR_LOG_INF("%s\n", __func__);
mutex_init(&power_fix_dvfs_mtx);
apusys_power_dir = debugfs_create_dir("apusys", NULL);
ret = IS_ERR_OR_NULL(apusys_power_dir);
if (ret) {
LOG_ERR("failed to create debug dir.\n");
return;
}
debugfs_create_file("power", (0644),
apusys_power_dir, NULL, &apusys_debug_power_fops);
debugfs_create_file("power_dump_fail_log", (0644),
apusys_power_dir, NULL, &apusys_power_fail_fops);
}
void apusys_power_debugfs_exit(void)
{
debugfs_remove(apusys_power_dir);
}
#if defined(CONFIG_MACH_MT6877)
static int mt_apupwr_minOPP_proc_show(struct seq_file *m, void *v)
{
if (is_power_debug_lock)
seq_puts(m, "apupwr minOPP enabled\n");
else
seq_puts(m, "apupwr minOPP disabled\n");
seq_printf(m, "fixed_opp: %d\n",
fixed_opp);
return 0;
}
static ssize_t mt_apupwr_minOPP_proc_write
(struct file *file, const char __user *buffer, size_t count, loff_t *data)
{
char desc[32];
int len = 0;
int minOPP = 0;
len = (count < (sizeof(desc) - 1)) ? count : (sizeof(desc) - 1);
if (copy_from_user(desc, buffer, len))
return 0;
desc[len] = '\0';
if (kstrtoint(desc, 10, &minOPP) == 0) {
if (minOPP == 0)
is_power_debug_lock = false;
else if (minOPP == 1) {
fixed_opp = APUSYS_MAX_NUM_OPPS - 1;
fix_dvfs_debug();
} else
pr_notice("should be [0:disable,1:enable]\n");
} else
pr_notice("should be [0:disable,1:enable]\n");
return count;
}
#define PROC_FOPS_RW(name) \
static int mt_ ## name ## _proc_open(struct inode *inode, struct file *file)\
{ \
return single_open(file, mt_ ## name ## _proc_show, PDE_DATA(inode));\
} \
static const struct file_operations mt_ ## name ## _proc_fops = { \
.owner = THIS_MODULE, \
.open = mt_ ## name ## _proc_open, \
.read = seq_read, \
.llseek = seq_lseek, \
.release = single_release, \
.write = mt_ ## name ## _proc_write, \
}
#define PROC_FOPS_RO(name) \
static int mt_ ## name ## _proc_open(struct inode *inode, struct file *file)\
{ \
return single_open(file, mt_ ## name ## _proc_show, PDE_DATA(inode));\
} \
static const struct file_operations mt_ ## name ## _proc_fops = { \
.owner = THIS_MODULE, \
.open = mt_ ## name ## _proc_open, \
.read = seq_read, \
.llseek = seq_lseek, \
.release = single_release, \
}
#define PROC_ENTRY(name) {__stringify(name), &mt_ ## name ## _proc_fops}
PROC_FOPS_RW(apupwr_minOPP);
int apusys_power_create_procfs(void)
{
struct proc_dir_entry *dir = NULL;
int i;
struct pentry {
const char *name;
const struct file_operations *fops;
};
const struct pentry entries[] = {
PROC_ENTRY(apupwr_minOPP),
};
dir = proc_mkdir("apupwr", NULL);
if (!dir) {
pr_notice("fail to create /proc/apupwr @ %s()\n", __func__);
return -ENOMEM;
}
for (i = 0; i < ARRAY_SIZE(entries); i++) {
if (!proc_create
(entries[i].name, 0664, dir, entries[i].fops))
pr_notice("@%s: create /proc/apupwr/%s failed\n", __func__,
entries[i].name);
}
return 0;
}
#else
int apusys_power_create_procfs(void)
{
return 0;
}
#endif