diff options
Diffstat (limited to 'drivers/gpu/arm/midgard/mali_kbase_pm_metrics.c')
-rw-r--r-- | drivers/gpu/arm/midgard/mali_kbase_pm_metrics.c | 267 |
1 files changed, 267 insertions, 0 deletions
diff --git a/drivers/gpu/arm/midgard/mali_kbase_pm_metrics.c b/drivers/gpu/arm/midgard/mali_kbase_pm_metrics.c new file mode 100644 index 000000000000..dbd5da1e6d5e --- /dev/null +++ b/drivers/gpu/arm/midgard/mali_kbase_pm_metrics.c @@ -0,0 +1,267 @@ +/* + * + * (C) COPYRIGHT ARM Limited. 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. + * + * A copy of the licence is included with the program, and can also be obtained + * from Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + * Boston, MA 02110-1301, USA. + * + */ + + + + + +/** + * @file mali_kbase_pm_metrics.c + * Metrics for power management + */ + +#include <mali_kbase.h> +#include <mali_kbase_pm.h> +#if KBASE_PM_EN +/* When VSync is being hit aim for utilisation between 70-90% */ +#define KBASE_PM_VSYNC_MIN_UTILISATION 70 +#define KBASE_PM_VSYNC_MAX_UTILISATION 90 +/* Otherwise aim for 10-40% */ +#define KBASE_PM_NO_VSYNC_MIN_UTILISATION 10 +#define KBASE_PM_NO_VSYNC_MAX_UTILISATION 40 + +/* Shift used for kbasep_pm_metrics_data.time_busy/idle - units of (1 << 8) ns + This gives a maximum period between samples of 2^(32+8)/100 ns = slightly under 11s. + Exceeding this will cause overflow */ +#define KBASE_PM_TIME_SHIFT 8 + +static enum hrtimer_restart dvfs_callback(struct hrtimer *timer) +{ + unsigned long flags; + kbase_pm_dvfs_action action; + struct kbasep_pm_metrics_data *metrics; + + KBASE_DEBUG_ASSERT(timer != NULL); + + metrics = container_of(timer, struct kbasep_pm_metrics_data, timer); + action = kbase_pm_get_dvfs_action(metrics->kbdev); + + spin_lock_irqsave(&metrics->lock, flags); + + if (metrics->timer_active) + hrtimer_start(timer, + HR_TIMER_DELAY_MSEC(metrics->kbdev->pm.platform_dvfs_frequency), + HRTIMER_MODE_REL); + + spin_unlock_irqrestore(&metrics->lock, flags); + + return HRTIMER_NORESTART; +} + +mali_error kbasep_pm_metrics_init(struct kbase_device *kbdev) +{ + KBASE_DEBUG_ASSERT(kbdev != NULL); + + kbdev->pm.metrics.kbdev = kbdev; + kbdev->pm.metrics.vsync_hit = 0; + kbdev->pm.metrics.utilisation = 0; + + kbdev->pm.metrics.time_period_start = ktime_get(); + kbdev->pm.metrics.time_busy = 0; + kbdev->pm.metrics.time_idle = 0; + kbdev->pm.metrics.gpu_active = MALI_TRUE; + kbdev->pm.metrics.timer_active = MALI_TRUE; + + spin_lock_init(&kbdev->pm.metrics.lock); + + hrtimer_init(&kbdev->pm.metrics.timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL); + kbdev->pm.metrics.timer.function = dvfs_callback; + + hrtimer_start(&kbdev->pm.metrics.timer, HR_TIMER_DELAY_MSEC(kbdev->pm.platform_dvfs_frequency), HRTIMER_MODE_REL); + + kbase_pm_register_vsync_callback(kbdev); + + return MALI_ERROR_NONE; +} + +KBASE_EXPORT_TEST_API(kbasep_pm_metrics_init) + +void kbasep_pm_metrics_term(struct kbase_device *kbdev) +{ + unsigned long flags; + KBASE_DEBUG_ASSERT(kbdev != NULL); + + spin_lock_irqsave(&kbdev->pm.metrics.lock, flags); + kbdev->pm.metrics.timer_active = MALI_FALSE; + spin_unlock_irqrestore(&kbdev->pm.metrics.lock, flags); + + hrtimer_cancel(&kbdev->pm.metrics.timer); + + kbase_pm_unregister_vsync_callback(kbdev); +} + +KBASE_EXPORT_TEST_API(kbasep_pm_metrics_term) + +void kbasep_pm_record_gpu_idle(struct kbase_device *kbdev) +{ + unsigned long flags; + ktime_t now; + ktime_t diff; + + KBASE_DEBUG_ASSERT(kbdev != NULL); + + spin_lock_irqsave(&kbdev->pm.metrics.lock, flags); + + KBASE_DEBUG_ASSERT(kbdev->pm.metrics.gpu_active == MALI_TRUE); + + kbdev->pm.metrics.gpu_active = MALI_FALSE; + + now = ktime_get(); + diff = ktime_sub(now, kbdev->pm.metrics.time_period_start); + + kbdev->pm.metrics.time_busy += (u32) (ktime_to_ns(diff) >> KBASE_PM_TIME_SHIFT); + kbdev->pm.metrics.time_period_start = now; + + spin_unlock_irqrestore(&kbdev->pm.metrics.lock, flags); +} + +KBASE_EXPORT_TEST_API(kbasep_pm_record_gpu_idle) + +void kbasep_pm_record_gpu_active(struct kbase_device *kbdev) +{ + unsigned long flags; + ktime_t now; + ktime_t diff; + + KBASE_DEBUG_ASSERT(kbdev != NULL); + + spin_lock_irqsave(&kbdev->pm.metrics.lock, flags); + + KBASE_DEBUG_ASSERT(kbdev->pm.metrics.gpu_active == MALI_FALSE); + + kbdev->pm.metrics.gpu_active = MALI_TRUE; + + now = ktime_get(); + diff = ktime_sub(now, kbdev->pm.metrics.time_period_start); + + kbdev->pm.metrics.time_idle += (u32) (ktime_to_ns(diff) >> KBASE_PM_TIME_SHIFT); + kbdev->pm.metrics.time_period_start = now; + + spin_unlock_irqrestore(&kbdev->pm.metrics.lock, flags); +} + +KBASE_EXPORT_TEST_API(kbasep_pm_record_gpu_active) + +void kbase_pm_report_vsync(struct kbase_device *kbdev, int buffer_updated) +{ + unsigned long flags; + KBASE_DEBUG_ASSERT(kbdev != NULL); + + spin_lock_irqsave(&kbdev->pm.metrics.lock, flags); + kbdev->pm.metrics.vsync_hit = buffer_updated; + spin_unlock_irqrestore(&kbdev->pm.metrics.lock, flags); +} + +KBASE_EXPORT_TEST_API(kbase_pm_report_vsync) + +/*caller needs to hold kbdev->pm.metrics.lock before calling this function*/ +int kbase_pm_get_dvfs_utilisation(struct kbase_device *kbdev) +{ + int utilisation = 0; + ktime_t now = ktime_get(); + ktime_t diff; + + KBASE_DEBUG_ASSERT(kbdev != NULL); + + diff = ktime_sub(now, kbdev->pm.metrics.time_period_start); + + if (kbdev->pm.metrics.gpu_active) { + kbdev->pm.metrics.time_busy += (u32) (ktime_to_ns(diff) >> KBASE_PM_TIME_SHIFT); + kbdev->pm.metrics.time_period_start = now; + } else { + kbdev->pm.metrics.time_idle += (u32) (ktime_to_ns(diff) >> KBASE_PM_TIME_SHIFT); + kbdev->pm.metrics.time_period_start = now; + } + + if (kbdev->pm.metrics.time_idle + kbdev->pm.metrics.time_busy == 0) { + /* No data - so we return NOP */ + utilisation = -1; + goto out; + } + + utilisation = (100 * kbdev->pm.metrics.time_busy) / (kbdev->pm.metrics.time_idle + kbdev->pm.metrics.time_busy); + + out: + + kbdev->pm.metrics.time_idle = 0; + kbdev->pm.metrics.time_busy = 0; + + return utilisation; +} + +kbase_pm_dvfs_action kbase_pm_get_dvfs_action(struct kbase_device *kbdev) +{ + unsigned long flags; + int utilisation; + kbase_pm_dvfs_action action; + + KBASE_DEBUG_ASSERT(kbdev != NULL); + + spin_lock_irqsave(&kbdev->pm.metrics.lock, flags); + + utilisation = kbase_pm_get_dvfs_utilisation(kbdev); + + if (utilisation < 0) { + action = KBASE_PM_DVFS_NOP; + utilisation = 0; + goto out; + } + + if (kbdev->pm.metrics.vsync_hit) { + /* VSync is being met */ + if (utilisation < KBASE_PM_VSYNC_MIN_UTILISATION) + action = KBASE_PM_DVFS_CLOCK_DOWN; + else if (utilisation > KBASE_PM_VSYNC_MAX_UTILISATION) + action = KBASE_PM_DVFS_CLOCK_UP; + else + action = KBASE_PM_DVFS_NOP; + } else { + /* VSync is being missed */ + if (utilisation < KBASE_PM_NO_VSYNC_MIN_UTILISATION) + action = KBASE_PM_DVFS_CLOCK_DOWN; + else if (utilisation > KBASE_PM_NO_VSYNC_MAX_UTILISATION) + action = KBASE_PM_DVFS_CLOCK_UP; + else + action = KBASE_PM_DVFS_NOP; + } + + kbdev->pm.metrics.utilisation = utilisation; + out: +#ifdef CONFIG_MALI_MIDGARD_DVFS + kbase_platform_dvfs_event(kbdev, utilisation); +#endif /*CONFIG_MALI_MIDGARD_DVFS */ + kbdev->pm.metrics.time_idle = 0; + kbdev->pm.metrics.time_busy = 0; + spin_unlock_irqrestore(&kbdev->pm.metrics.lock, flags); + + return action; +} +KBASE_EXPORT_TEST_API(kbase_pm_get_dvfs_action) + +mali_bool kbase_pm_metrics_is_active(struct kbase_device *kbdev) +{ + mali_bool isactive; + unsigned long flags; + + KBASE_DEBUG_ASSERT(kbdev != NULL); + + spin_lock_irqsave(&kbdev->pm.metrics.lock, flags); + isactive = (kbdev->pm.metrics.timer_active == MALI_TRUE); + spin_unlock_irqrestore(&kbdev->pm.metrics.lock, flags); + + return isactive; +} +KBASE_EXPORT_TEST_API(kbase_pm_metrics_is_active) +#endif /* KBASE_PM_EN */ |