kernel_samsung_a53x/drivers/gpu/arm/exynos/frontend/gpex_pm.c

401 lines
9.3 KiB
C
Raw Normal View History

2024-06-15 16:02:09 -03:00
/* SPDX-License-Identifier: GPL-2.0 */
/*
* (C) COPYRIGHT 2021 Samsung Electronics Inc. All rights reserved.
*
* This program is free software and is provided to you under the terms of the
* GNU General Public License version 2 as published by the Free Software
* Foundation, and any use by you of this program is subject to the terms
* of such GNU licence.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, you can access it online at
* http://www.gnu.org/licenses/gpl-2.0.html.
*/
#include <linux/suspend.h>
#include <linux/pm_runtime.h>
#if IS_ENABLED(CONFIG_EXYNOS_PMU)
#include <soc/samsung/exynos-pmu.h>
#endif
#if IS_ENABLED(CONFIG_EXYNOS_PMU_IF)
#include <soc/samsung/exynos-pmu-if.h>
#endif
#include <gpex_ifpo.h>
#include <gpex_dvfs.h>
#include <gpex_clock.h>
#include <gpex_qos.h>
#include <gpex_utils.h>
#include <gpex_debug.h>
#include <gpexbe_devicetree.h>
#include <gpexbe_pm.h>
#include <gpex_pm.h>
#include <gpexbe_secure.h>
#include <gpexbe_smc.h>
#include <gpex_tsg.h>
#include <gpex_clboost.h>
#include <gpexwa_wakeup_clock.h>
#if IS_ENABLED(CONFIG_MALI_EXYNOS_SECURE_RENDERING_ARM) && IS_ENABLED(CONFIG_SOC_S5E8825)
#include <soc/samsung/exynos-smc.h>
#endif
struct pm_info {
int runtime_pm_delay_time;
bool power_status;
int state;
bool skip_auto_suspend;
struct device *dev;
};
static struct pm_info pm;
int gpex_pm_set_state(int state)
{
if (GPEX_PM_STATE_START <= state && state <= GPEX_PM_STATE_END) {
pm.state = state;
GPU_LOG(MALI_EXYNOS_INFO, "gpex_pm: gpex_pm state is set as 0x%X", pm.state);
return 0;
}
GPU_LOG(MALI_EXYNOS_WARNING,
"gpex_pm: Attempted to set gpex_pm state with invalid value 0x%x", state);
return -1;
}
int gpex_pm_get_state(int *state)
{
if (GPEX_PM_STATE_START <= pm.state && pm.state <= GPEX_PM_STATE_END) {
*state = pm.state;
return 0;
}
GPU_LOG(MALI_EXYNOS_WARNING, "gpex_pm: gpex_pm has invalid state now 0x%x", pm.state);
return -1;
}
int gpex_pm_get_status(bool clock_lock)
{
int ret = 0;
//unsigned int val = 0;
if (clock_lock)
gpex_clock_mutex_lock();
ret = gpexbe_pm_get_status();
if (clock_lock)
gpex_clock_mutex_unlock();
return ret;
}
/* Read the power_status value set by gpex_pm module */
int gpex_pm_get_power_status(void)
2024-06-15 16:02:09 -03:00
{
return pm.power_status;
}
void gpex_pm_lock(void)
2024-06-15 16:02:09 -03:00
{
gpexbe_pm_access_lock();
}
void gpex_pm_unlock(void)
2024-06-15 16:02:09 -03:00
{
gpexbe_pm_access_unlock();
}
static ssize_t show_power_state(char *buf)
{
ssize_t ret = 0;
ret += snprintf(buf + ret, PAGE_SIZE - ret, "%d", gpex_pm_get_status(true));
if (ret < PAGE_SIZE - 1) {
ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
} else {
buf[PAGE_SIZE - 2] = '\n';
buf[PAGE_SIZE - 1] = '\0';
ret = PAGE_SIZE - 1;
}
return ret;
}
CREATE_SYSFS_DEVICE_READ_FUNCTION(show_power_state);
/******************************
* RTPM callback functions
* ***************************/
int gpex_pm_power_on(struct device *dev)
{
int ret = 0;
pm.skip_auto_suspend = false;
#if IS_ENABLED(CONFIG_MALI_EXYNOS_BLOCK_RPM_WHILE_SUSPEND_RESUME)
if (pm.state == GPEX_PM_STATE_RESUME_BEGIN && gpex_pm_get_status(false) > 0) {
pm.skip_auto_suspend = true;
} else {
gpex_debug_new_record(HIST_RTPM);
ret = pm_runtime_get_sync(dev);
gpex_debug_record(HIST_RTPM, 0, PM_RUNTIME_GET_SYNC, ret);
}
#else
gpex_debug_new_record(HIST_RTPM);
ret = pm_runtime_get_sync(dev);
gpex_debug_record(HIST_RTPM, 0, PM_RUNTIME_GET_SYNC, ret);
#endif
if (ret >= 0) {
gpex_ifpo_power_up();
GPU_LOG_DETAILED(MALI_EXYNOS_INFO, LSI_GPU_RPM_RESUME_API, ret, 0u, "power on\n");
} else {
GPU_LOG(MALI_EXYNOS_ERROR, "runtime pm returned %d\n", ret);
gpex_debug_incr_error_cnt(HIST_RTPM);
}
gpex_dvfs_start();
return ret;
}
void gpex_pm_power_autosuspend(struct device *dev)
{
int ret = 0;
gpex_ifpo_power_down();
if (!pm.skip_auto_suspend) {
pm_runtime_mark_last_busy(dev);
ret = pm_runtime_put_autosuspend(dev);
}
GPU_LOG_DETAILED(MALI_EXYNOS_INFO, LSI_GPU_RPM_SUSPEND_API, ret, 0u,
"power autosuspend prepare\n");
}
void gpex_pm_suspend(struct device *dev)
{
int ret = 0;
gpexwa_wakeup_clock_suspend();
gpex_qos_set_from_clock(0);
gpex_debug_new_record(HIST_SUSPEND);
ret = pm_runtime_suspend(dev);
gpex_debug_record(HIST_SUSPEND, 0, PM_RUNTIME_SUSPEND, ret);
if (ret < 0)
gpex_debug_incr_error_cnt(HIST_SUSPEND);
GPU_LOG_DETAILED(MALI_EXYNOS_INFO, LSI_SUSPEND_CALLBACK, ret, 0u, "power suspend\n");
}
static struct delayed_work gpu_poweroff_delay_set_work;
static void gpu_poweroff_delay_recovery_callback(struct work_struct *data)
{
if (!pm.dev->power.use_autosuspend)
return;
device_lock(pm.dev);
pm_runtime_set_autosuspend_delay(pm.dev, pm.runtime_pm_delay_time);
device_unlock(pm.dev);
gpex_clock_lock_clock(GPU_CLOCK_MIN_UNLOCK, MM_LOCK, 0);
GPU_LOG(MALI_EXYNOS_DEBUG, "gpu poweroff delay recovery done & clock min unlock\n");
gpex_clboost_set_state(CLBOOST_ENABLE);
}
static int gpu_poweroff_delay_recovery(unsigned int period)
{
#define POWEROFF_DELAY_MAX_PERIOD 1500
#define POWEROFF_DELAY_MIN_PERIOD 50
/* boundary */
if (period > POWEROFF_DELAY_MAX_PERIOD)
period = POWEROFF_DELAY_MAX_PERIOD;
else if (period < POWEROFF_DELAY_MIN_PERIOD)
period = POWEROFF_DELAY_MIN_PERIOD;
GPU_LOG(MALI_EXYNOS_DEBUG, "gpu poweroff delay set wq start(%u)\n", period);
cancel_delayed_work_sync(&gpu_poweroff_delay_set_work);
schedule_delayed_work(&gpu_poweroff_delay_set_work, msecs_to_jiffies(period));
return 0;
}
static ssize_t set_sysfs_poweroff_delay(const char *buf, size_t count)
{
long delay;
if (!pm.dev->power.use_autosuspend)
return -EIO;
if (kstrtol(buf, 10, &delay) != 0 || delay != (int)delay)
return -EINVAL;
if (delay < pm.runtime_pm_delay_time)
delay = pm.runtime_pm_delay_time;
device_lock(pm.dev);
pm_runtime_set_autosuspend_delay(pm.dev, delay);
device_unlock(pm.dev);
gpu_poweroff_delay_recovery((unsigned int)delay);
return count;
}
CREATE_SYSFS_KOBJECT_WRITE_FUNCTION(set_sysfs_poweroff_delay)
static ssize_t show_sysfs_poweroff_delay(char *buf)
{
ssize_t ret = 0;
ret += snprintf(buf + ret, PAGE_SIZE - ret, "%d", pm.runtime_pm_delay_time);
if (ret < PAGE_SIZE - 1) {
ret += snprintf(buf + ret, PAGE_SIZE - ret, "\n");
} else {
buf[PAGE_SIZE - 2] = '\n';
buf[PAGE_SIZE - 1] = '\0';
ret = PAGE_SIZE - 1;
}
return ret;
}
CREATE_SYSFS_KOBJECT_READ_FUNCTION(show_sysfs_poweroff_delay)
static void gpu_poweroff_delay_wq_init(void)
{
INIT_DELAYED_WORK(&gpu_poweroff_delay_set_work, gpu_poweroff_delay_recovery_callback);
}
static void gpu_poweroff_delay_wq_deinit(void)
{
cancel_delayed_work_sync(&gpu_poweroff_delay_set_work);
}
int gpex_pm_runtime_init(struct device *dev)
{
int ret = 0;
pm_runtime_set_autosuspend_delay(dev, pm.runtime_pm_delay_time);
pm_runtime_use_autosuspend(dev);
pm_runtime_set_active(dev);
pm_runtime_enable(dev);
gpu_poweroff_delay_wq_init();
if (!pm_runtime_enabled(dev)) {
dev_warn(dev, "pm_runtime not enabled");
ret = -ENOSYS;
}
return ret;
}
void gpex_pm_runtime_term(struct device *dev)
{
pm_runtime_disable(dev);
gpu_poweroff_delay_wq_deinit();
}
int gpex_pm_runtime_on_prepare(struct device *dev)
{
GPU_LOG_DETAILED(MALI_EXYNOS_DEBUG, LSI_GPU_ON, 0u, 0u, "runtime on callback\n");
pm.power_status = true;
gpexbe_smc_notify_power_on();
gpexwa_wakeup_clock_restore();
return 0;
}
/* TODO: 9830 need to store and restore clock before and after power off/on */
#if 0
int pm_callback_runtime_on(struct kbase_device *kbdev)
{
struct exynos_context *platform = (struct exynos_context *) kbdev->platform_context;
if (!platform)
return -ENODEV;
GPU_LOG(MALI_EXYNOS_DEBUG, LSI_GPU_ON, 0u, 0u, "runtime on callback\n");
platform->power_status = true;
/* Set clock - restore previous g3d clock, after g3d runtime on */
if (gpex_dvfs_get_status() && platform->wakeup_lock) {
if (platform->restore_clock > G3D_DVFS_MIDDLE_CLOCK) {
gpex_clock_set(platform->restore_clock);
GPU_LOG(MALI_EXYNOS_DEBUG, LSI_GPU_ON, platform->restore_clock, gpex_clock_get_cur_clock(), "runtime on callback - restore clock = %d, cur clock = %d\n", platform->restore_clock, gpex_clock_get_cur_clock());
platform->restore_clock = 0;
}
}
return 0;
}
#endif
void gpex_pm_runtime_off_prepare(struct device *dev)
{
CSTD_UNUSED(dev);
GPU_LOG_DETAILED(MALI_EXYNOS_DEBUG, LSI_GPU_OFF, 0u, 0u, "runtime off callback\n");
gpexbe_smc_notify_power_off();
/* power up from ifpo down state before going to full rtpm power off */
gpex_ifpo_power_up();
gpex_tsg_reset_count(0);
gpex_dvfs_stop();
gpex_clock_prepare_runtime_off();
gpexwa_wakeup_clock_set();
gpex_qos_set_from_clock(0);
pm.power_status = false;
}
int gpex_pm_init(void)
{
pm.power_status = true;
pm.runtime_pm_delay_time = gpexbe_devicetree_get_int(gpu_runtime_pm_delay_time);
GPEX_UTILS_SYSFS_DEVICE_FILE_ADD_RO(power_state, show_power_state);
GPEX_UTILS_SYSFS_KOBJECT_FILE_ADD(gpu_poweroff_delay, show_sysfs_poweroff_delay,
set_sysfs_poweroff_delay);
pm.state = GPEX_PM_STATE_START;
pm.skip_auto_suspend = false;
pm.dev = gpex_utils_get_device();
gpex_utils_get_exynos_context()->pm = &pm;
return 0;
}
void gpex_pm_term(void)
{
memset(&pm, 0, sizeof(struct pm_info));
return;
}