933 lines
21 KiB
C
933 lines
21 KiB
C
|
|
// SPDX-License-Identifier: GPL-2.0
|
||
|
|
/*
|
||
|
|
* Copyright (c) 2020 MediaTek Inc.
|
||
|
|
*/
|
||
|
|
|
||
|
|
#include <linux/err.h>
|
||
|
|
#include <linux/module.h>
|
||
|
|
#include <linux/slab.h>
|
||
|
|
#include <linux/platform_device.h>
|
||
|
|
#include <linux/delay.h>
|
||
|
|
#include <linux/mutex.h>
|
||
|
|
#include <linux/spinlock_types.h>
|
||
|
|
#include <linux/jiffies.h>
|
||
|
|
#include <linux/sched.h>
|
||
|
|
#include <linux/kthread.h>
|
||
|
|
#include <linux/init.h>
|
||
|
|
#include <linux/workqueue.h>
|
||
|
|
#include <linux/sched/clock.h>
|
||
|
|
#include <linux/of.h>
|
||
|
|
|
||
|
|
#include "apu_log.h"
|
||
|
|
#include "apusys_power_ctl.h"
|
||
|
|
#include "apusys_power_cust.h"
|
||
|
|
#include "apusys_power_debug.h"
|
||
|
|
#include "apu_platform_resource.h"
|
||
|
|
#include "hal_config_power.h"
|
||
|
|
#include "apu_power_api.h"
|
||
|
|
#include "pmic_api_buck.h"
|
||
|
|
#include "apusys_power_rule_check.h"
|
||
|
|
// #include "apusys_dbg.h"
|
||
|
|
#include "apusys_power.h"
|
||
|
|
#ifdef APUPWR_TAG_TP
|
||
|
|
#include "apu_power_tag.h"
|
||
|
|
#endif
|
||
|
|
|
||
|
|
int g_pwr_log_level = APUSYS_PWR_LOG_ERR;
|
||
|
|
int g_pm_procedure;
|
||
|
|
int power_on_off_stress;
|
||
|
|
static int apu_power_counter;
|
||
|
|
static int apusys_power_broken;
|
||
|
|
static uint64_t power_info_id;
|
||
|
|
static uint8_t power_info_force_print;
|
||
|
|
|
||
|
|
bool apusys_power_check(void)
|
||
|
|
{
|
||
|
|
#if defined(CONFIG_MACH_MT6885)
|
||
|
|
char *pwr_ptr;
|
||
|
|
bool pwr_status = true;
|
||
|
|
|
||
|
|
pwr_ptr = strstr(saved_command_line,
|
||
|
|
"apusys_status=normal");
|
||
|
|
if (pwr_ptr == 0) {
|
||
|
|
pwr_status = false;
|
||
|
|
LOG_ERR("apusys power disable !!, pwr_status=%d\n",
|
||
|
|
pwr_status);
|
||
|
|
}
|
||
|
|
LOG_INF("apusys power check, pwr_status=%d\n",
|
||
|
|
pwr_status);
|
||
|
|
return pwr_status;
|
||
|
|
#else
|
||
|
|
return true;
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apusys_power_check);
|
||
|
|
|
||
|
|
struct power_device {
|
||
|
|
enum DVFS_USER dev_usr;
|
||
|
|
int is_power_on;
|
||
|
|
struct platform_device *pdev;
|
||
|
|
struct list_head list;
|
||
|
|
};
|
||
|
|
|
||
|
|
struct power_callback_device {
|
||
|
|
enum POWER_CALLBACK_USER power_callback_usr;
|
||
|
|
void (*power_on_callback)(void *para);
|
||
|
|
void (*power_off_callback)(void *para);
|
||
|
|
struct list_head list;
|
||
|
|
};
|
||
|
|
|
||
|
|
static LIST_HEAD(power_device_list);
|
||
|
|
static LIST_HEAD(power_callback_device_list);
|
||
|
|
static struct mutex power_device_list_mtx;
|
||
|
|
static struct mutex power_ctl_mtx;
|
||
|
|
static struct mutex power_opp_mtx;
|
||
|
|
static spinlock_t power_info_lock;
|
||
|
|
static int power_callback_counter;
|
||
|
|
static struct task_struct *power_task_handle;
|
||
|
|
static uint64_t timestamp;
|
||
|
|
static struct hal_param_init_power init_power_data;
|
||
|
|
|
||
|
|
static struct workqueue_struct *wq;
|
||
|
|
static void d_work_power_info_func(struct work_struct *work);
|
||
|
|
static void d_work_power_init_func(struct work_struct *work);
|
||
|
|
static DECLARE_WORK(d_work_power_info, d_work_power_info_func);
|
||
|
|
static DECLARE_WORK(d_work_power_init, d_work_power_init_func);
|
||
|
|
struct delayed_work d_work_power;
|
||
|
|
|
||
|
|
#ifdef CONFIG_PM_SLEEP
|
||
|
|
struct wakeup_source *pwr_wake_lock;
|
||
|
|
#endif
|
||
|
|
|
||
|
|
static void apu_pwr_wake_lock(void)
|
||
|
|
{
|
||
|
|
#ifdef CONFIG_PM_SLEEP
|
||
|
|
__pm_stay_awake(pwr_wake_lock);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
static void apu_pwr_wake_unlock(void)
|
||
|
|
{
|
||
|
|
#ifdef CONFIG_PM_SLEEP
|
||
|
|
__pm_relax(pwr_wake_lock);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
static void apu_pwr_wake_init(void)
|
||
|
|
{
|
||
|
|
#ifdef CONFIG_PM_SLEEP
|
||
|
|
pwr_wake_lock = wakeup_source_register(NULL, "apupwr_wakelock");
|
||
|
|
if (IS_ERR_OR_NULL(pwr_wake_lock))
|
||
|
|
pr_info("%s: wakeup_source_register fail\n", __func__);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
/**
|
||
|
|
* print_time() - Brief description of print_time.
|
||
|
|
* @ts: nanosecond
|
||
|
|
* @buf: buffer to put [%5lu.%06lu]
|
||
|
|
*
|
||
|
|
* Transfer nanoseconds to format as [second.micro_second].
|
||
|
|
*
|
||
|
|
* Return: length of valid string
|
||
|
|
**/
|
||
|
|
static size_t print_time(u64 ts, char *buf)
|
||
|
|
{
|
||
|
|
unsigned long rem_nsec;
|
||
|
|
|
||
|
|
rem_nsec = do_div(ts, 1000000000);
|
||
|
|
|
||
|
|
if (!buf)
|
||
|
|
return snprintf(NULL, 0, "[%5lu.000000] ", (unsigned long)ts);
|
||
|
|
|
||
|
|
return sprintf(buf, "[%5lu.%06lu] ",
|
||
|
|
(unsigned long)ts, rem_nsec / 1000);
|
||
|
|
}
|
||
|
|
|
||
|
|
uint64_t apu_get_power_info(int force)
|
||
|
|
{
|
||
|
|
uint64_t ret = 0;
|
||
|
|
|
||
|
|
spin_lock(&power_info_lock);
|
||
|
|
power_info_id = sched_clock();
|
||
|
|
ret = power_info_id;
|
||
|
|
power_info_force_print = force;
|
||
|
|
spin_unlock(&power_info_lock);
|
||
|
|
|
||
|
|
queue_work(wq, &d_work_power_info);
|
||
|
|
|
||
|
|
return ret;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_get_power_info);
|
||
|
|
|
||
|
|
void apu_power_reg_dump(void)
|
||
|
|
{
|
||
|
|
mutex_lock(&power_ctl_mtx);
|
||
|
|
// keep 26M vcore clk make we can dump reg directly
|
||
|
|
hal_config_power(PWR_CMD_REG_DUMP, VPU0, NULL);
|
||
|
|
mutex_unlock(&power_ctl_mtx);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_power_reg_dump);
|
||
|
|
|
||
|
|
|
||
|
|
void apu_set_vcore_boost(bool enable)
|
||
|
|
{
|
||
|
|
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_set_vcore_boost);
|
||
|
|
|
||
|
|
void apu_qos_set_vcore(int target_volt)
|
||
|
|
{
|
||
|
|
#ifdef MTK_FPGA_PORTING
|
||
|
|
LOG_WRN("%s FPGA porting bypass DVFS\n", __func__);
|
||
|
|
#else
|
||
|
|
|
||
|
|
#if !BYPASS_DVFS_CTL
|
||
|
|
mutex_lock(&power_opp_mtx);
|
||
|
|
|
||
|
|
#if SUPPORT_VCORE_TO_IPUIF
|
||
|
|
// set opp value
|
||
|
|
apusys_set_apu_vcore(target_volt);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
// change regulator and clock if need
|
||
|
|
wake_up_process(power_task_handle);
|
||
|
|
|
||
|
|
mutex_unlock(&power_opp_mtx);
|
||
|
|
#else
|
||
|
|
LOG_WRN("%s bypass since not support DVFS\n", __func__);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_qos_set_vcore);
|
||
|
|
|
||
|
|
static struct power_device *find_out_device_by_user(enum DVFS_USER user)
|
||
|
|
{
|
||
|
|
struct power_device *pwr_dev = NULL;
|
||
|
|
|
||
|
|
if (!list_empty(&power_device_list)) {
|
||
|
|
|
||
|
|
list_for_each_entry(pwr_dev,
|
||
|
|
&power_device_list, list) {
|
||
|
|
|
||
|
|
if (pwr_dev && pwr_dev->dev_usr == user)
|
||
|
|
return pwr_dev;
|
||
|
|
}
|
||
|
|
|
||
|
|
} else {
|
||
|
|
LOG_ERR("%s empty list\n", __func__);
|
||
|
|
}
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
bool apu_get_power_on_status(enum DVFS_USER user)
|
||
|
|
{
|
||
|
|
bool power_on_status;
|
||
|
|
struct power_device *pwr_dev = find_out_device_by_user(user);
|
||
|
|
|
||
|
|
if (pwr_dev == NULL)
|
||
|
|
return false;
|
||
|
|
|
||
|
|
power_on_status = pwr_dev->is_power_on;
|
||
|
|
|
||
|
|
return power_on_status;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_get_power_on_status);
|
||
|
|
|
||
|
|
#if !BYPASS_POWER_CTL
|
||
|
|
static void power_callback_caller(int power_on)
|
||
|
|
{
|
||
|
|
struct power_callback_device *pwr_dev = NULL;
|
||
|
|
|
||
|
|
LOG_DBG("%s begin (%d)\n", __func__, power_on);
|
||
|
|
|
||
|
|
if (!list_empty(&power_callback_device_list)) {
|
||
|
|
list_for_each_entry(pwr_dev,
|
||
|
|
&power_callback_device_list, list) {
|
||
|
|
|
||
|
|
LOG_DBG("%s calling %d in state %d\n", __func__,
|
||
|
|
pwr_dev->power_callback_usr, power_on);
|
||
|
|
|
||
|
|
if (power_on) {
|
||
|
|
if (pwr_dev && pwr_dev->power_on_callback)
|
||
|
|
pwr_dev->power_on_callback(NULL);
|
||
|
|
} else {
|
||
|
|
if (pwr_dev && pwr_dev->power_off_callback)
|
||
|
|
pwr_dev->power_off_callback(NULL);
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
LOG_DBG("%s end (%d)\n", __func__, power_on);
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
static struct power_callback_device*
|
||
|
|
find_out_callback_device_by_user(enum POWER_CALLBACK_USER user)
|
||
|
|
{
|
||
|
|
struct power_callback_device *pwr_dev = NULL;
|
||
|
|
|
||
|
|
if (!list_empty(&power_callback_device_list)) {
|
||
|
|
|
||
|
|
list_for_each_entry(pwr_dev,
|
||
|
|
&power_callback_device_list, list) {
|
||
|
|
|
||
|
|
if (pwr_dev && pwr_dev->power_callback_usr == user)
|
||
|
|
return pwr_dev;
|
||
|
|
}
|
||
|
|
|
||
|
|
} else {
|
||
|
|
LOG_ERR("%s empty list\n", __func__);
|
||
|
|
}
|
||
|
|
|
||
|
|
return NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
int apu_device_power_suspend(enum DVFS_USER user, int is_suspend)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
#if !BYPASS_POWER_OFF
|
||
|
|
char time_stmp[32];
|
||
|
|
struct power_device *pwr_dev = NULL;
|
||
|
|
#if TIME_PROFILING
|
||
|
|
struct profiling_timestamp power_profiling;
|
||
|
|
|
||
|
|
power_profiling.begin = sched_clock();
|
||
|
|
#endif
|
||
|
|
pwr_dev = find_out_device_by_user(user);
|
||
|
|
if (pwr_dev == NULL) {
|
||
|
|
LOG_ERR("%s fail, dev of user %d is NULL\n", __func__, user);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
LOG_DBG("%s waiting for lock, user = %d\n", __func__, user);
|
||
|
|
mutex_lock(&power_ctl_mtx);
|
||
|
|
|
||
|
|
if (is_suspend)
|
||
|
|
g_pm_procedure = 1;
|
||
|
|
|
||
|
|
if (apusys_power_broken) {
|
||
|
|
mutex_unlock(&power_ctl_mtx);
|
||
|
|
hal_config_power(PWR_CMD_DUMP_FAIL_STATE, VPU0, NULL);
|
||
|
|
LOG_ERR(
|
||
|
|
"APUPWR_BROKEN, user:%d fail to pwr off, is_suspend:%d\n",
|
||
|
|
user, is_suspend);
|
||
|
|
return -ENODEV;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pwr_dev->is_power_on == 0) {
|
||
|
|
mutex_unlock(&power_ctl_mtx);
|
||
|
|
LOG_ERR(
|
||
|
|
"APUPWR_OFF_FAIL, not allow user:%d to pwr off twice, is_suspend:%d\n",
|
||
|
|
user, is_suspend);
|
||
|
|
return -ECANCELED;
|
||
|
|
}
|
||
|
|
|
||
|
|
LOG_DBG("%s for user:%d, cnt:%d, is_suspend:%d\n", __func__,
|
||
|
|
user, power_callback_counter, is_suspend);
|
||
|
|
|
||
|
|
if (!g_pm_procedure)
|
||
|
|
apu_pwr_wake_lock();
|
||
|
|
|
||
|
|
power_callback_counter--;
|
||
|
|
if (power_callback_counter == 0) {
|
||
|
|
// call passive power off function list
|
||
|
|
#if !BYPASS_POWER_CTL
|
||
|
|
power_callback_caller(0);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
// for debug
|
||
|
|
// dump_stack();
|
||
|
|
#if !BYPASS_POWER_CTL
|
||
|
|
ret = apusys_power_off(user);
|
||
|
|
#endif
|
||
|
|
if (!ret) {
|
||
|
|
pwr_dev->is_power_on = 0;
|
||
|
|
} else {
|
||
|
|
apusys_power_broken = 1;
|
||
|
|
power_callback_counter++;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!g_pm_procedure)
|
||
|
|
apu_pwr_wake_unlock();
|
||
|
|
|
||
|
|
mutex_unlock(&power_ctl_mtx);
|
||
|
|
|
||
|
|
// for pwr saving, get pwr info in case pwr ctl fail or enable debug log
|
||
|
|
if (g_pwr_log_level >= APUSYS_PWR_LOG_WARN || ret) {
|
||
|
|
/* prepare time stamp format */
|
||
|
|
memset(time_stmp, 0, sizeof(time_stmp));
|
||
|
|
print_time(apu_get_power_info(0), time_stmp);
|
||
|
|
LOG_PM(
|
||
|
|
"%s for user:%d, ret:%d, cnt:%d, is_suspend:%d, info_id: %s\n",
|
||
|
|
__func__, user, ret,
|
||
|
|
power_callback_counter, is_suspend,
|
||
|
|
ret ? 0 : time_stmp);
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
LOG_WRN("%s by user:%d bypass\n", __func__, user);
|
||
|
|
#endif // BYPASS_POWER_OFF
|
||
|
|
|
||
|
|
if (ret) {
|
||
|
|
hal_config_power(PWR_CMD_DUMP_FAIL_STATE, VPU0, NULL);
|
||
|
|
#ifndef APUSYS_POWER_BRINGUP
|
||
|
|
// apusys_reg_dump();
|
||
|
|
#endif
|
||
|
|
apu_aee_warn("APUPWR_OFF_FAIL", "user:%d, is_suspend:%d\n",
|
||
|
|
user, is_suspend);
|
||
|
|
return -ENODEV;
|
||
|
|
} else {
|
||
|
|
#if TIME_PROFILING
|
||
|
|
power_profiling.end = sched_clock();
|
||
|
|
apu_profiling(&power_profiling, __func__);
|
||
|
|
#endif
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_device_power_suspend);
|
||
|
|
|
||
|
|
int apu_device_power_off(enum DVFS_USER user)
|
||
|
|
{
|
||
|
|
return apu_device_power_suspend(user, 0);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_device_power_off);
|
||
|
|
|
||
|
|
int apu_device_power_on(enum DVFS_USER user)
|
||
|
|
{
|
||
|
|
struct power_device *pwr_dev = NULL;
|
||
|
|
int ret = 0;
|
||
|
|
char time_stmp[32];
|
||
|
|
#if TIME_PROFILING
|
||
|
|
struct profiling_timestamp power_profiling;
|
||
|
|
|
||
|
|
power_profiling.begin = sched_clock();
|
||
|
|
#endif
|
||
|
|
pwr_dev = find_out_device_by_user(user);
|
||
|
|
if (pwr_dev == NULL) {
|
||
|
|
LOG_ERR("%s fail, dev of user %d is NULL\n", __func__, user);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
LOG_DBG("%s waiting for lock, user:%d\n", __func__, user);
|
||
|
|
mutex_lock(&power_ctl_mtx);
|
||
|
|
|
||
|
|
g_pm_procedure = 0;
|
||
|
|
|
||
|
|
if (apusys_power_broken) {
|
||
|
|
mutex_unlock(&power_ctl_mtx);
|
||
|
|
hal_config_power(PWR_CMD_DUMP_FAIL_STATE, VPU0, NULL);
|
||
|
|
LOG_ERR("APUPWR_BROKEN, user:%d fail to pwr on\n", user);
|
||
|
|
return -ENODEV;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (pwr_dev->is_power_on == 1) {
|
||
|
|
mutex_unlock(&power_ctl_mtx);
|
||
|
|
#if !BYPASS_POWER_OFF
|
||
|
|
LOG_ERR("APUPWR_ON_FAIL, not allow user:%d to pwr on twice\n",
|
||
|
|
user);
|
||
|
|
return -ECANCELED;
|
||
|
|
#else
|
||
|
|
LOG_WRN("%s by user:%d bypass\n", __func__, user);
|
||
|
|
return 0;
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
LOG_DBG("%s for user:%d, cnt:%d\n", __func__,
|
||
|
|
user, power_callback_counter);
|
||
|
|
|
||
|
|
if (!g_pm_procedure)
|
||
|
|
apu_pwr_wake_lock();
|
||
|
|
// for debug
|
||
|
|
// dump_stack();
|
||
|
|
#if !BYPASS_POWER_CTL
|
||
|
|
ret = apusys_power_on(user);
|
||
|
|
#endif
|
||
|
|
if (!ret) {
|
||
|
|
pwr_dev->is_power_on = 1;
|
||
|
|
power_callback_counter++;
|
||
|
|
} else {
|
||
|
|
apusys_power_broken = 1;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (power_callback_counter == 1) {
|
||
|
|
// call passive power on function list
|
||
|
|
#if !BYPASS_POWER_CTL
|
||
|
|
power_callback_caller(1);
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
|
||
|
|
if (!g_pm_procedure)
|
||
|
|
apu_pwr_wake_unlock();
|
||
|
|
|
||
|
|
mutex_unlock(&power_ctl_mtx);
|
||
|
|
|
||
|
|
// for pwr saving, get pwr info in case pwr ctl fail or enable debug log
|
||
|
|
if (g_pwr_log_level >= APUSYS_PWR_LOG_WARN || ret) {
|
||
|
|
/* prepare time stamp format */
|
||
|
|
memset(time_stmp, 0, sizeof(time_stmp));
|
||
|
|
print_time(apu_get_power_info(0), time_stmp);
|
||
|
|
LOG_PM("%s for user:%d, ret:%d, cnt:%d, info_id: %s\n",
|
||
|
|
__func__, user, ret, power_callback_counter,
|
||
|
|
ret ? 0 : time_stmp);
|
||
|
|
}
|
||
|
|
|
||
|
|
if (ret) {
|
||
|
|
hal_config_power(PWR_CMD_DUMP_FAIL_STATE, VPU0, NULL);
|
||
|
|
#ifndef APUSYS_POWER_BRINGUP
|
||
|
|
// apusys_reg_dump();
|
||
|
|
#endif
|
||
|
|
apu_aee_warn("APUPWR_ON_FAIL", "user:%d\n", user);
|
||
|
|
return -ENODEV;
|
||
|
|
} else {
|
||
|
|
#if TIME_PROFILING
|
||
|
|
power_profiling.end = sched_clock();
|
||
|
|
apu_profiling(&power_profiling, __func__);
|
||
|
|
#endif
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_device_power_on);
|
||
|
|
|
||
|
|
int apu_power_device_register(enum DVFS_USER user, struct platform_device *pdev)
|
||
|
|
{
|
||
|
|
struct power_device *pwr_dev = NULL;
|
||
|
|
|
||
|
|
pwr_dev = kzalloc(sizeof(struct power_device), GFP_KERNEL);
|
||
|
|
|
||
|
|
if (pwr_dev == NULL) {
|
||
|
|
LOG_ERR("%s fail in dvfs user %d\n", __func__, user);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
pwr_dev->dev_usr = user;
|
||
|
|
pwr_dev->is_power_on = 0;
|
||
|
|
pwr_dev->pdev = pdev;
|
||
|
|
|
||
|
|
/* add to device link list */
|
||
|
|
mutex_lock(&power_device_list_mtx);
|
||
|
|
|
||
|
|
list_add_tail(&pwr_dev->list, &power_device_list);
|
||
|
|
// call apusys_power_init in probe
|
||
|
|
#if 0
|
||
|
|
if (apu_power_counter == 0) {
|
||
|
|
// prepare clock and get regulator handle
|
||
|
|
apusys_power_init(user, (void *)&init_power_data);
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
apu_power_counter++;
|
||
|
|
|
||
|
|
mutex_unlock(&power_device_list_mtx);
|
||
|
|
|
||
|
|
LOG_INF("%s add dvfs user %d success\n", __func__, user);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_power_device_register);
|
||
|
|
|
||
|
|
int apu_power_callback_device_register(enum POWER_CALLBACK_USER user,
|
||
|
|
void (*power_on_callback)(void *para), void (*power_off_callback)(void *para))
|
||
|
|
{
|
||
|
|
struct power_callback_device *pwr_dev = NULL;
|
||
|
|
|
||
|
|
pwr_dev = kzalloc(sizeof(struct power_callback_device), GFP_KERNEL);
|
||
|
|
|
||
|
|
if (pwr_dev == NULL) {
|
||
|
|
LOG_ERR("%s fail in power callback user %d\n", __func__, user);
|
||
|
|
return -1;
|
||
|
|
}
|
||
|
|
|
||
|
|
pwr_dev->power_callback_usr = user;
|
||
|
|
pwr_dev->power_on_callback = power_on_callback;
|
||
|
|
pwr_dev->power_off_callback = power_off_callback;
|
||
|
|
|
||
|
|
/* add to device link list */
|
||
|
|
mutex_lock(&power_device_list_mtx);
|
||
|
|
|
||
|
|
list_add_tail(&pwr_dev->list, &power_callback_device_list);
|
||
|
|
|
||
|
|
mutex_unlock(&power_device_list_mtx);
|
||
|
|
|
||
|
|
LOG_INF("%s add power callback user %d success\n", __func__, user);
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_power_callback_device_register);
|
||
|
|
|
||
|
|
void apu_power_device_unregister(enum DVFS_USER user)
|
||
|
|
{
|
||
|
|
struct power_device *pwr_dev = find_out_device_by_user(user);
|
||
|
|
|
||
|
|
mutex_lock(&power_device_list_mtx);
|
||
|
|
|
||
|
|
apu_power_counter--;
|
||
|
|
if (apu_power_counter == 0)
|
||
|
|
apusys_power_uninit(user);
|
||
|
|
|
||
|
|
/* remove from device link list */
|
||
|
|
list_del_init(&pwr_dev->list);
|
||
|
|
kfree(pwr_dev);
|
||
|
|
|
||
|
|
mutex_unlock(&power_device_list_mtx);
|
||
|
|
|
||
|
|
LOG_INF("%s remove dvfs user %d success\n", __func__, user);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_power_device_unregister);
|
||
|
|
|
||
|
|
void apu_power_callback_device_unregister(enum POWER_CALLBACK_USER user)
|
||
|
|
{
|
||
|
|
struct power_callback_device *pwr_dev
|
||
|
|
= find_out_callback_device_by_user(user);
|
||
|
|
|
||
|
|
mutex_lock(&power_device_list_mtx);
|
||
|
|
|
||
|
|
/* remove from device link list */
|
||
|
|
list_del_init(&pwr_dev->list);
|
||
|
|
kfree(pwr_dev);
|
||
|
|
|
||
|
|
mutex_unlock(&power_device_list_mtx);
|
||
|
|
|
||
|
|
LOG_INF("%s remove power callback user %d success\n", __func__, user);
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_power_callback_device_unregister);
|
||
|
|
|
||
|
|
static void d_work_power_info_func(struct work_struct *work)
|
||
|
|
{
|
||
|
|
struct apu_power_info info = {0};
|
||
|
|
|
||
|
|
spin_lock(&power_info_lock);
|
||
|
|
info.id = power_info_id;
|
||
|
|
info.force_print = power_info_force_print;
|
||
|
|
spin_unlock(&power_info_lock);
|
||
|
|
|
||
|
|
info.type = 1;
|
||
|
|
hal_config_power(PWR_CMD_GET_POWER_INFO, VPU0, &info);
|
||
|
|
}
|
||
|
|
|
||
|
|
#if DEFAULT_POWER_ON
|
||
|
|
static void default_power_on_func(void)
|
||
|
|
{
|
||
|
|
int dev_id = 0;
|
||
|
|
|
||
|
|
LOG_ERR("### apusys power on all device ###\n");
|
||
|
|
|
||
|
|
for (dev_id = 0 ; dev_id < APUSYS_DVFS_USER_NUM ; dev_id++) {
|
||
|
|
apu_power_counter++;
|
||
|
|
apusys_power_on(dev_id);
|
||
|
|
udelay(200);
|
||
|
|
}
|
||
|
|
|
||
|
|
apu_get_power_info(1);
|
||
|
|
}
|
||
|
|
#endif
|
||
|
|
|
||
|
|
static void d_work_power_init_func(struct work_struct *work)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
|
||
|
|
ret = apusys_power_init(VPU0, (void *)&init_power_data);
|
||
|
|
if (ret == -EPROBE_DEFER) {
|
||
|
|
INIT_DEFERRABLE_WORK(&d_work_power, d_work_power_init_func);
|
||
|
|
queue_delayed_work(wq, &d_work_power,
|
||
|
|
msecs_to_jiffies(100));
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
#if DEFAULT_POWER_ON
|
||
|
|
default_power_on_func();
|
||
|
|
#endif // DEFAULT_POWER_ON
|
||
|
|
}
|
||
|
|
|
||
|
|
static int apusys_power_task(void *arg)
|
||
|
|
{
|
||
|
|
int keep_loop = 0;
|
||
|
|
struct apu_power_info info = {0};
|
||
|
|
unsigned long rem_nsec;
|
||
|
|
|
||
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
||
|
|
|
||
|
|
LOG_INF("%s first time wakeup and enter sleep now\n", __func__);
|
||
|
|
schedule();
|
||
|
|
|
||
|
|
for (;;) {
|
||
|
|
|
||
|
|
if (kthread_should_stop())
|
||
|
|
break;
|
||
|
|
#if 1
|
||
|
|
mutex_lock(&power_opp_mtx);
|
||
|
|
keep_loop = apusys_check_opp_change();
|
||
|
|
mutex_unlock(&power_opp_mtx);
|
||
|
|
#else
|
||
|
|
keep_loop = 1;
|
||
|
|
mdelay(1000);
|
||
|
|
#endif
|
||
|
|
if (keep_loop) {
|
||
|
|
timestamp = sched_clock();
|
||
|
|
info.id = timestamp;
|
||
|
|
info.type = 0;
|
||
|
|
// call dvfs API and bring timestamp to id
|
||
|
|
rem_nsec = do_div(timestamp, 1000000000);
|
||
|
|
LOG_INF("%s call DVFS handler, id:[%5lu.%06lu]\n",
|
||
|
|
__func__,
|
||
|
|
(unsigned long)timestamp, rem_nsec / 1000);
|
||
|
|
|
||
|
|
apusys_dvfs_policy(info.id);
|
||
|
|
hal_config_power(PWR_CMD_GET_POWER_INFO, VPU0, &info);
|
||
|
|
#if SUPPORT_VCORE_TO_IPUIF
|
||
|
|
apusys_ipuif_opp_change();
|
||
|
|
#endif
|
||
|
|
#if DVFS_ASSERTION_CHECK
|
||
|
|
apu_power_assert_check(&info);
|
||
|
|
#endif
|
||
|
|
#ifdef APUPWR_TASK_DEBOUNCE
|
||
|
|
task_debounce();
|
||
|
|
#endif
|
||
|
|
} else {
|
||
|
|
LOG_DBG("%s enter sleep\n", __func__);
|
||
|
|
set_current_state(TASK_INTERRUPTIBLE);
|
||
|
|
schedule();
|
||
|
|
}
|
||
|
|
|
||
|
|
}
|
||
|
|
|
||
|
|
LOG_WRN("%s task stop\n", __func__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
void apu_device_set_opp(enum DVFS_USER user, uint8_t opp)
|
||
|
|
{
|
||
|
|
#ifdef MTK_FPGA_PORTING
|
||
|
|
LOG_WRN("%s FPGA porting bypass DVFS\n", __func__);
|
||
|
|
#else
|
||
|
|
|
||
|
|
#if !BYPASS_DVFS_CTL
|
||
|
|
if (user >= 0 && user < APUSYS_DVFS_USER_NUM
|
||
|
|
&& opp < APUSYS_MAX_NUM_OPPS) {
|
||
|
|
|
||
|
|
mutex_lock(&power_opp_mtx);
|
||
|
|
|
||
|
|
// set opp value
|
||
|
|
apusys_set_opp(user, opp);
|
||
|
|
|
||
|
|
// change regulator and clock if need
|
||
|
|
wake_up_process(power_task_handle);
|
||
|
|
|
||
|
|
mutex_unlock(&power_opp_mtx);
|
||
|
|
}
|
||
|
|
#else
|
||
|
|
LOG_WRN("%s bypass since not support DVFS\n", __func__);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#endif
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_device_set_opp);
|
||
|
|
|
||
|
|
/**
|
||
|
|
* apu_profiling() - Brief description of apu_profiling.
|
||
|
|
* @profile: struct profiling_timestamp with begin/end timestamp.
|
||
|
|
* @tag: the string need to print prefix.
|
||
|
|
*
|
||
|
|
* Calcualte abs(end-begin) and show result in unit of us with "tag" prefix.
|
||
|
|
*
|
||
|
|
* Return: void
|
||
|
|
**/
|
||
|
|
void apu_profiling(struct profiling_timestamp *profile, const char *tag)
|
||
|
|
{
|
||
|
|
u64 nanosec = 0;
|
||
|
|
u64 time = 0;
|
||
|
|
|
||
|
|
time = abs(profile->end - profile->begin),
|
||
|
|
nanosec = do_div(time, 1000000000);
|
||
|
|
pr_info("%s: %s take %lu (us)\n", __func__, tag,
|
||
|
|
((unsigned long)nanosec / 1000));
|
||
|
|
}
|
||
|
|
EXPORT_SYMBOL(apu_profiling);
|
||
|
|
|
||
|
|
void event_trigger_dvfs_policy(void)
|
||
|
|
{
|
||
|
|
wake_up_process(power_task_handle);
|
||
|
|
}
|
||
|
|
|
||
|
|
static int apu_power_probe(struct platform_device *pdev)
|
||
|
|
{
|
||
|
|
int ret = 0;
|
||
|
|
int err = 0;
|
||
|
|
|
||
|
|
if (!apusys_power_check())
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
ret = init_platform_resource(pdev, &init_power_data);
|
||
|
|
|
||
|
|
if (ret)
|
||
|
|
goto err_exit;
|
||
|
|
|
||
|
|
wq = create_workqueue("apu_pwr_drv_wq");
|
||
|
|
if (IS_ERR(wq)) {
|
||
|
|
LOG_ERR("%s create power driver wq fail\n", __func__);
|
||
|
|
goto err_exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
power_task_handle = kthread_create(apusys_power_task,
|
||
|
|
(void *)NULL, "apu_pwr_policy");
|
||
|
|
if (IS_ERR(power_task_handle)) {
|
||
|
|
LOG_ERR("%s create power task fail\n", __func__);
|
||
|
|
goto err_exit;
|
||
|
|
}
|
||
|
|
|
||
|
|
mutex_init(&power_device_list_mtx);
|
||
|
|
mutex_init(&power_ctl_mtx);
|
||
|
|
mutex_init(&power_opp_mtx);
|
||
|
|
spin_lock_init(&power_info_lock);
|
||
|
|
apu_pwr_wake_init();
|
||
|
|
|
||
|
|
queue_work(wq, &d_work_power_init);
|
||
|
|
queue_work(wq, &d_work_power_info);
|
||
|
|
wake_up_process(power_task_handle);
|
||
|
|
|
||
|
|
#if AUTO_BUCK_OFF_SUSPEND
|
||
|
|
// buck auto power off in suspend
|
||
|
|
pmic_buck_vproc1_lp(SRCLKEN0, 1, 1, HW_OFF);
|
||
|
|
pmic_buck_vproc2_lp(SRCLKEN0, 1, 1, HW_OFF);
|
||
|
|
pmic_ldo_vsram_md_lp(SRCLKEN0, 1, 1, HW_OFF);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
#if AUTO_BUCK_OFF_DEEPIDLE
|
||
|
|
// buck auto power off in deeep idle
|
||
|
|
pmic_buck_vproc1_lp(SRCLKEN2, 1, 1, HW_OFF);
|
||
|
|
pmic_buck_vproc2_lp(SRCLKEN2, 1, 1, HW_OFF);
|
||
|
|
pmic_ldo_vsram_md_lp(SRCLKEN2, 1, 1, HW_OFF);
|
||
|
|
#endif
|
||
|
|
|
||
|
|
apusys_power_debugfs_init();
|
||
|
|
#if defined(CONFIG_MACH_MT6877)
|
||
|
|
apusys_power_create_procfs();
|
||
|
|
#endif
|
||
|
|
#ifdef APUPWR_TAG_TP
|
||
|
|
apupwr_init_drv_tags();
|
||
|
|
#endif
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
|
||
|
|
err_exit:
|
||
|
|
if (power_task_handle != NULL) {
|
||
|
|
kfree(power_task_handle);
|
||
|
|
power_task_handle = NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
if (wq != NULL) {
|
||
|
|
flush_workqueue(wq);
|
||
|
|
destroy_workqueue(wq);
|
||
|
|
kfree(wq);
|
||
|
|
wq = NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
return err;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int apu_power_suspend(struct platform_device *pdev, pm_message_t mesg)
|
||
|
|
{
|
||
|
|
struct hal_param_pm pm;
|
||
|
|
enum DVFS_USER user;
|
||
|
|
|
||
|
|
LOG_PM("%s begin\n", __func__);
|
||
|
|
|
||
|
|
if (power_on_off_stress) {
|
||
|
|
LOG_PM("%s, power_on_off_stress: %d\n",
|
||
|
|
__func__, power_on_off_stress);
|
||
|
|
for (user = VPU0; user < APUSYS_DVFS_USER_NUM; user++)
|
||
|
|
apu_device_power_suspend(user, 1);
|
||
|
|
}
|
||
|
|
|
||
|
|
mutex_lock(&power_ctl_mtx);
|
||
|
|
pm.is_suspend = 1;
|
||
|
|
hal_config_power(PWR_CMD_PM_HANDLER, VPU0, &pm);
|
||
|
|
mutex_unlock(&power_ctl_mtx);
|
||
|
|
|
||
|
|
LOG_PM("%s end\n", __func__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int apu_power_resume(struct platform_device *pdev)
|
||
|
|
{
|
||
|
|
struct hal_param_pm pm;
|
||
|
|
enum DVFS_USER user;
|
||
|
|
|
||
|
|
LOG_PM("%s begin\n", __func__);
|
||
|
|
|
||
|
|
mutex_lock(&power_ctl_mtx);
|
||
|
|
pm.is_suspend = 0;
|
||
|
|
hal_config_power(PWR_CMD_PM_HANDLER, VPU0, &pm);
|
||
|
|
g_pm_procedure = 0;
|
||
|
|
mutex_unlock(&power_ctl_mtx);
|
||
|
|
|
||
|
|
if (power_on_off_stress) {
|
||
|
|
LOG_PM("%s, power_on_off_stress: %d\n",
|
||
|
|
__func__, power_on_off_stress);
|
||
|
|
for (user = VPU0; user < APUSYS_DVFS_USER_NUM; user++)
|
||
|
|
apu_device_power_on(user);
|
||
|
|
}
|
||
|
|
|
||
|
|
LOG_PM("%s end\n", __func__);
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
static int apu_power_remove(struct platform_device *pdev)
|
||
|
|
{
|
||
|
|
apusys_power_debugfs_exit();
|
||
|
|
#ifdef APUPWR_TAG_TP
|
||
|
|
apupwr_exit_drv_tags();
|
||
|
|
#endif
|
||
|
|
|
||
|
|
if (power_task_handle)
|
||
|
|
kthread_stop(power_task_handle);
|
||
|
|
|
||
|
|
mutex_destroy(&power_opp_mtx);
|
||
|
|
mutex_destroy(&power_ctl_mtx);
|
||
|
|
mutex_destroy(&power_device_list_mtx);
|
||
|
|
|
||
|
|
if (wq) {
|
||
|
|
flush_workqueue(wq);
|
||
|
|
destroy_workqueue(wq);
|
||
|
|
kfree(wq);
|
||
|
|
wq = NULL;
|
||
|
|
}
|
||
|
|
|
||
|
|
return 0;
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
static const struct of_device_id apu_power_of_match[] = {
|
||
|
|
{ .compatible = "mediatek,apusys_power" },
|
||
|
|
{ /* end of list */},
|
||
|
|
};
|
||
|
|
|
||
|
|
|
||
|
|
static struct platform_driver apu_power_driver = {
|
||
|
|
.probe = apu_power_probe,
|
||
|
|
.remove = apu_power_remove,
|
||
|
|
.suspend = apu_power_suspend,
|
||
|
|
.resume = apu_power_resume,
|
||
|
|
.driver = {
|
||
|
|
.name = "apusys_power",
|
||
|
|
.of_match_table = apu_power_of_match,
|
||
|
|
},
|
||
|
|
};
|
||
|
|
|
||
|
|
static int __init apu_power_drv_init(void)
|
||
|
|
{
|
||
|
|
return platform_driver_register(&apu_power_driver);
|
||
|
|
}
|
||
|
|
|
||
|
|
module_init(apu_power_drv_init)
|
||
|
|
|
||
|
|
static void __exit apu_power_drv_exit(void)
|
||
|
|
{
|
||
|
|
platform_driver_unregister(&apu_power_driver);
|
||
|
|
}
|
||
|
|
module_exit(apu_power_drv_exit)
|
||
|
|
|
||
|
|
MODULE_LICENSE("GPL v2");
|
||
|
|
MODULE_DESCRIPTION("apu power driver");
|
||
|
|
|