blob: d0525aba20afe6fb834b61c3e1e3deecfb34aed8 [file] [log] [blame]
/* drivers/gpu/arm/.../platform/gpu_notifier.c
*
* Copyright 2011 by S.LSI. Samsung Electronics Inc.
* San#24, Nongseo-Dong, Giheung-Gu, Yongin, Korea
*
* Samsung SoC Mali-T Series platform-dependent codes
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software FoundatIon.
*/
/**
* @file gpu_notifier.c
*/
#include <mali_kbase.h>
#include <linux/suspend.h>
#include <linux/pm_runtime.h>
#include "mali_kbase_platform.h"
#include "gpu_dvfs_handler.h"
#include "gpu_notifier.h"
#include "gpu_control.h"
#ifdef CONFIG_EXYNOS_THERMAL
#if LINUX_VERSION_CODE < KERNEL_VERSION(3, 17, 0)
#include <mach/tmu.h>
#else
#include <soc/samsung/tmu.h>
#endif
#endif /* CONFIG_EXYNOS_THERMAL */
#ifdef CONFIG_EXYNOS_BUSMONITOR
#include <linux/exynos-busmon.h>
#endif
extern struct kbase_device *pkbdev;
#if defined (CONFIG_EXYNOS_THERMAL) && defined(CONFIG_GPU_THERMAL)
static int gpu_tmu_hot_check_and_work(struct kbase_device *kbdev,
unsigned long event, unsigned long index) {
#ifdef CONFIG_MALI_DVFS
struct exynos_context *platform;
int lock_clock;
#ifdef CONFIG_EXYNOS_SNAPSHOT_THERMAL
char *cooling_device_name = "GPU";
#endif
KBASE_DEBUG_ASSERT(kbdev != NULL);
platform = (struct exynos_context *)kbdev->platform_context;
if (!platform)
return -ENODEV;
switch (event) {
case GPU_THROTTLING:
lock_clock = platform->tmu_lock_clk[index];
exynos_ss_thermal(NULL, 0, cooling_device_name, lock_clock);
GPU_LOG(DVFS_INFO, DUMMY, 0u, 0u, "THROTTLING[%lu]\n", index);
break;
case GPU_TRIPPING:
lock_clock = platform->tmu_lock_clk[TRIPPING];
GPU_LOG(DVFS_INFO, DUMMY, 0u, 0u, "TRIPPING\n");
break;
default:
GPU_LOG(DVFS_ERROR, DUMMY, 0u, 0u,
"%s: wrong event, %lu\n", __func__, event);
return 0;
}
gpu_dvfs_clock_lock(GPU_DVFS_MAX_LOCK, TMU_LOCK, lock_clock);
#endif /* CONFIG_MALI_DVFS */
return 0;
}
static void gpu_tmu_normal_work(struct kbase_device *kbdev)
{
#ifdef CONFIG_MALI_DVFS
struct exynos_context *platform = (struct exynos_context *)kbdev->platform_context;
if (!platform)
return;
gpu_dvfs_clock_lock(GPU_DVFS_MAX_UNLOCK, TMU_LOCK, 0);
#endif /* CONFIG_MALI_DVFS */
}
static int gpu_tmu_notifier(struct notifier_block *notifier,
unsigned long event, void *v)
{
unsigned long index;
struct exynos_context *platform = (struct exynos_context *)pkbdev->platform_context;
if (!platform)
return -ENODEV;
if (!platform->tmu_status)
return NOTIFY_OK;
platform->voltage_margin = 0;
index = *(unsigned long *)v;
if (event == GPU_COLD) {
platform->voltage_margin = platform->gpu_default_vol_margin;
} else if (event == GPU_NORMAL) {
gpu_tmu_normal_work(pkbdev);
} else if (event == GPU_THROTTLING || event == GPU_TRIPPING) {
if (gpu_tmu_hot_check_and_work(pkbdev, event, index))
GPU_LOG(DVFS_ERROR, DUMMY, 0u, 0u, "%s: failed to open device", __func__);
}
GPU_LOG(DVFS_DEBUG, LSI_TMU_VALUE, 0u, event, "tmu event %lu, level %lu\n", event, index);
gpu_set_target_clk_vol(platform->cur_clock, false);
return NOTIFY_OK;
}
static struct notifier_block gpu_tmu_nb = {
.notifier_call = gpu_tmu_notifier,
};
#endif /* CONFIG_EXYNOS_THERMAL */
static int gpu_power_on(struct kbase_device *kbdev)
{
int ret;
struct exynos_context *platform = (struct exynos_context *) kbdev->platform_context;
if (!platform)
return -ENODEV;
GPU_LOG(DVFS_INFO, DUMMY, 0u, 0u, "power on\n");
gpu_control_disable_customization(kbdev);
ret = pm_runtime_resume(kbdev->dev);
if (ret > 0) {
if (platform->early_clk_gating_status) {
GPU_LOG(DVFS_INFO, DUMMY, 0u, 0u, "already power on\n");
gpu_control_enable_clock(kbdev);
}
return 0;
} else if (ret == 0) {
return 1;
} else {
GPU_LOG(DVFS_ERROR, DUMMY, 0u, 0u, "runtime pm returned %d\n", ret);
return 0;
}
}
static void gpu_power_off(struct kbase_device *kbdev)
{
struct exynos_context *platform = (struct exynos_context *) kbdev->platform_context;
if (!platform)
return;
GPU_LOG(DVFS_INFO, DUMMY, 0u, 0u, "power off\n");
gpu_control_enable_customization(kbdev);
pm_schedule_suspend(kbdev->dev, platform->runtime_pm_delay_time);
if (platform->early_clk_gating_status)
gpu_control_disable_clock(kbdev);
}
static void gpu_power_suspend(struct kbase_device *kbdev)
{
struct exynos_context *platform = (struct exynos_context *) kbdev->platform_context;
if (!platform)
return;
GPU_LOG(DVFS_INFO, DUMMY, 0u, 0u, "power suspend\n");
gpu_control_enable_customization(kbdev);
pm_runtime_suspend(kbdev->dev);
if (platform->early_clk_gating_status)
gpu_control_disable_clock(kbdev);
}
#ifdef CONFIG_MALI_RT_PM
static int gpu_pm_notifier(struct notifier_block *nb, unsigned long event, void *cmd)
{
int err = NOTIFY_OK;
switch (event) {
case PM_SUSPEND_PREPARE:
GPU_LOG(DVFS_DEBUG, LSI_SUSPEND, 0u, 0u, "%s: PM_SUSPEND_PREPARE event\n", __func__);
break;
case PM_POST_SUSPEND:
GPU_LOG(DVFS_DEBUG, LSI_RESUME, 0u, 0u, "%s: PM_POST_SUSPEND event\n", __func__);
break;
default:
break;
}
return err;
}
static struct notifier_block gpu_pm_nb = {
.notifier_call = gpu_pm_notifier
};
static int gpu_device_runtime_init(struct kbase_device *kbdev)
{
pm_suspend_ignore_children(kbdev->dev, true);
return 0;
}
static void gpu_device_runtime_disable(struct kbase_device *kbdev)
{
pm_runtime_disable(kbdev->dev);
}
static int pm_callback_dvfs_on(struct kbase_device *kbdev)
{
#ifdef CONFIG_MALI_DVFS
struct exynos_context *platform = (struct exynos_context *) kbdev->platform_context;
gpu_dvfs_timer_control(true);
if (platform->dvfs_pending)
platform->dvfs_pending = 0;
#endif
return 0;
}
static int pm_callback_change_dvfs_level(struct kbase_device *kbdev)
{
#ifdef CONFIG_MALI_DVFS
struct exynos_context *platform = (struct exynos_context *) kbdev->platform_context;
bool enabledebug = false;
if (kbdev->vendor_callbacks->get_poweron_dbg)
enabledebug = kbdev->vendor_callbacks->get_poweron_dbg();
#if 0
if (enabledebug)
GPU_LOG(DVFS_ERROR, DUMMY, 0u, 0u, "asv table[%u] clk[%d to %d]MHz, vol[%d (margin : %d) real: %d]mV\n",
exynos_get_table_ver(), gpu_get_cur_clock(platform), platform->gpu_dvfs_start_clock,
gpu_get_cur_voltage(platform), platform->voltage_margin, platform->cur_voltage);
#endif
gpu_set_target_clk_vol(platform->gpu_dvfs_start_clock, false);
gpu_dvfs_reset_env_data(kbdev);
#endif
return 0;
}
static 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(DVFS_INFO, LSI_GPU_ON, 0u, 0u, "runtime on callback\n");
gpu_control_enable_clock(kbdev);
gpu_dvfs_start_env_data_gathering(kbdev);
platform->power_status = true;
#ifdef CONFIG_MALI_DVFS
if (platform->dvfs_status && platform->wakeup_lock)
gpu_set_target_clk_vol(platform->gpu_dvfs_start_clock, false);
else
#endif /* CONFIG_MALI_DVFS */
gpu_set_target_clk_vol(platform->cur_clock, false);
return 0;
}
extern void preload_balance_setup(struct kbase_device *kbdev);
static void pm_callback_runtime_off(struct kbase_device *kbdev)
{
struct exynos_context *platform = (struct exynos_context *) kbdev->platform_context;
if (!platform)
return;
GPU_LOG(DVFS_INFO, LSI_GPU_OFF, 0u, 0u, "runtime off callback\n");
platform->power_status = false;
gpu_dvfs_stop_env_data_gathering(kbdev);
#ifdef CONFIG_MALI_DVFS
gpu_dvfs_timer_control(false);
if (platform->dvfs_pending)
platform->dvfs_pending = 0;
#endif /* CONFIG_MALI_DVFS */
if (!platform->early_clk_gating_status)
gpu_control_disable_clock(kbdev);
#if defined(CONFIG_SOC_EXYNOS7420) || defined(CONFIG_SOC_EXYNOS7890)
preload_balance_setup(kbdev);
#endif
}
#endif /* CONFIG_MALI_RT_PM */
struct kbase_pm_callback_conf pm_callbacks = {
.power_on_callback = gpu_power_on,
.power_off_callback = gpu_power_off,
.power_suspend_callback = gpu_power_suspend,
#ifdef CONFIG_MALI_RT_PM
.power_runtime_init_callback = gpu_device_runtime_init,
.power_runtime_term_callback = gpu_device_runtime_disable,
.power_runtime_on_callback = pm_callback_runtime_on,
.power_runtime_off_callback = pm_callback_runtime_off,
.power_dvfs_on_callback = pm_callback_dvfs_on,
.power_change_dvfs_level_callback = pm_callback_change_dvfs_level,
#else /* CONFIG_MALI_RT_PM */
.power_runtime_init_callback = NULL,
.power_runtime_term_callback = NULL,
.power_runtime_on_callback = NULL,
.power_runtime_off_callback = NULL,
.power_dvfs_on_callback = NULL,
.power_change_dvfs_level_callback = NULL,
#endif /* CONFIG_MALI_RT_PM */
};
#ifdef CONFIG_EXYNOS_BUSMONITOR
static int gpu_noc_notifier(struct notifier_block *nb, unsigned long event, void *cmd)
{
if (strstr((char *)cmd, "G3D")) {
GPU_LOG(DVFS_ERROR, LSI_RESUME, 0u, 0u, "%s: gpu_noc_notifier\n", __func__);
gpu_register_dump();
}
return 0;
}
#endif
#ifdef CONFIG_EXYNOS_BUSMONITOR
static struct notifier_block gpu_noc_nb = {
.notifier_call = gpu_noc_notifier
};
#endif
int gpu_notifier_init(struct kbase_device *kbdev)
{
struct exynos_context *platform = (struct exynos_context *)kbdev->platform_context;
if (!platform)
return -ENODEV;
platform->voltage_margin = 0;
#ifdef CONFIG_EXYNOS_THERMAL
exynos_gpu_add_notifier(&gpu_tmu_nb);
#endif /* CONFIG_EXYNOS_THERMAL */
#ifdef CONFIG_MALI_RT_PM
if (register_pm_notifier(&gpu_pm_nb))
return -1;
#endif /* CONFIG_MALI_RT_PM */
#ifdef CONFIG_EXYNOS_BUSMONITOR
busmon_notifier_chain_register(&gpu_noc_nb);
#endif
pm_runtime_enable(kbdev->dev);
platform->power_status = true;
return 0;
}
void gpu_notifier_term(void)
{
#ifdef CONFIG_MALI_RT_PM
unregister_pm_notifier(&gpu_pm_nb);
#endif /* CONFIG_MALI_RT_PM */
return;
}