/* 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 #include #if IS_ENABLED(CONFIG_EXYNOS_PMU) #include #endif #if IS_ENABLED(CONFIG_EXYNOS_PMU_IF) #include #endif #include #include #include #include #include #include #include #include #include #include #include #include #include #include #if IS_ENABLED(CONFIG_MALI_EXYNOS_SECURE_RENDERING_ARM) && IS_ENABLED(CONFIG_SOC_S5E8825) #include #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) { return pm.power_status; } void gpex_pm_lock(void) { gpexbe_pm_access_lock(); } void gpex_pm_unlock(void) { 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 = ± return 0; } void gpex_pm_term(void) { memset(&pm, 0, sizeof(struct pm_info)); return; }