From 102a0743326a03cd1a1202ceda21e175b7d3575c Mon Sep 17 00:00:00 2001
From: hc <hc@nodka.com>
Date: Tue, 20 Feb 2024 01:20:52 +0000
Subject: [PATCH] add new system file
---
kernel/drivers/gpu/arm/bifrost/backend/gpu/mali_kbase_pm_backend.c | 510 +++++++++++++++++++++++++++++++++++++++++++++++++-------
1 files changed, 446 insertions(+), 64 deletions(-)
diff --git a/kernel/drivers/gpu/arm/bifrost/backend/gpu/mali_kbase_pm_backend.c b/kernel/drivers/gpu/arm/bifrost/backend/gpu/mali_kbase_pm_backend.c
index 38baa0d..5c71fdf 100644
--- a/kernel/drivers/gpu/arm/bifrost/backend/gpu/mali_kbase_pm_backend.c
+++ b/kernel/drivers/gpu/arm/bifrost/backend/gpu/mali_kbase_pm_backend.c
@@ -1,7 +1,7 @@
// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
*
- * (C) COPYRIGHT 2010-2021 ARM Limited. All rights reserved.
+ * (C) COPYRIGHT 2010-2022 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
@@ -32,8 +32,11 @@
#include <mali_kbase_hwaccess_jm.h>
#include <backend/gpu/mali_kbase_js_internal.h>
#include <backend/gpu/mali_kbase_jm_internal.h>
+#else
+#include <linux/pm_runtime.h>
+#include <mali_kbase_reset_gpu.h>
#endif /* !MALI_USE_CSF */
-#include <mali_kbase_hwcnt_context.h>
+#include <hwcnt/mali_kbase_hwcnt_context.h>
#include <backend/gpu/mali_kbase_pm_internal.h>
#include <backend/gpu/mali_kbase_devfreq.h>
#include <mali_kbase_dummy_job_wa.h>
@@ -69,6 +72,10 @@
callbacks->power_runtime_idle_callback;
kbdev->pm.backend.callback_soft_reset =
callbacks->soft_reset_callback;
+ kbdev->pm.backend.callback_power_runtime_gpu_idle =
+ callbacks->power_runtime_gpu_idle_callback;
+ kbdev->pm.backend.callback_power_runtime_gpu_active =
+ callbacks->power_runtime_gpu_active_callback;
if (callbacks->power_runtime_init_callback)
return callbacks->power_runtime_init_callback(kbdev);
@@ -86,15 +93,16 @@
kbdev->pm.backend.callback_power_runtime_off = NULL;
kbdev->pm.backend.callback_power_runtime_idle = NULL;
kbdev->pm.backend.callback_soft_reset = NULL;
+ kbdev->pm.backend.callback_power_runtime_gpu_idle = NULL;
+ kbdev->pm.backend.callback_power_runtime_gpu_active = NULL;
return 0;
}
void kbase_pm_runtime_term(struct kbase_device *kbdev)
{
- if (kbdev->pm.callback_power_runtime_term) {
+ if (kbdev->pm.callback_power_runtime_term)
kbdev->pm.callback_power_runtime_term(kbdev);
- }
}
void kbase_pm_register_access_enable(struct kbase_device *kbdev)
@@ -120,10 +128,10 @@
callbacks = (struct kbase_pm_callback_conf *)POWER_MANAGEMENT_CALLBACKS;
+ kbdev->pm.backend.gpu_powered = false;
+
if (callbacks)
callbacks->power_off_callback(kbdev);
-
- kbdev->pm.backend.gpu_powered = false;
}
int kbase_hwaccess_pm_init(struct kbase_device *kbdev)
@@ -192,6 +200,14 @@
INIT_WORK(&kbdev->pm.backend.hwcnt_disable_work,
kbase_pm_hwcnt_disable_worker);
kbase_hwcnt_context_disable(kbdev->hwcnt_gpu_ctx);
+
+#if MALI_USE_CSF && defined(KBASE_PM_RUNTIME)
+ kbdev->pm.backend.gpu_sleep_supported =
+ kbase_hw_has_feature(kbdev, BASE_HW_FEATURE_GPU_SLEEP) &&
+ !kbase_hw_has_issue(kbdev, BASE_HW_ISSUE_TURSEHW_1997) &&
+ kbdev->pm.backend.callback_power_runtime_gpu_active &&
+ kbdev->pm.backend.callback_power_runtime_gpu_idle;
+#endif
if (IS_ENABLED(CONFIG_MALI_HW_ERRATA_1485982_NOT_AFFECTED)) {
kbdev->pm.backend.l2_always_on = false;
@@ -263,6 +279,76 @@
*/
}
+static void pm_handle_power_off(struct kbase_device *kbdev)
+{
+ struct kbase_pm_backend_data *backend = &kbdev->pm.backend;
+#if MALI_USE_CSF
+ enum kbase_mcu_state mcu_state;
+#endif
+ unsigned long flags;
+
+ lockdep_assert_held(&kbdev->pm.lock);
+
+ if (backend->poweron_required)
+ return;
+
+ spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
+#if MALI_USE_CSF && defined(KBASE_PM_RUNTIME)
+ if (kbdev->pm.backend.gpu_wakeup_override) {
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+ return;
+ }
+#endif
+ WARN_ON(backend->shaders_state !=
+ KBASE_SHADERS_OFF_CORESTACK_OFF ||
+ backend->l2_state != KBASE_L2_OFF);
+#if MALI_USE_CSF
+ mcu_state = backend->mcu_state;
+ WARN_ON(!kbase_pm_is_mcu_inactive(kbdev, mcu_state));
+#endif
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+
+#if MALI_USE_CSF && defined(KBASE_PM_RUNTIME)
+ if (backend->callback_power_runtime_gpu_idle) {
+ WARN_ON(backend->gpu_idled);
+ backend->callback_power_runtime_gpu_idle(kbdev);
+ backend->gpu_idled = true;
+ return;
+ }
+#endif
+
+ /* Disable interrupts and turn the clock off */
+ if (!kbase_pm_clock_off(kbdev)) {
+ /*
+ * Page/bus faults are pending, must drop locks to
+ * process. Interrupts are disabled so no more faults
+ * should be generated at this point.
+ */
+ kbase_pm_unlock(kbdev);
+ kbase_flush_mmu_wqs(kbdev);
+ kbase_pm_lock(kbdev);
+
+#ifdef CONFIG_MALI_ARBITER_SUPPORT
+ /* poweron_required may have changed while pm lock
+ * was released.
+ */
+ if (kbase_pm_is_gpu_lost(kbdev))
+ backend->poweron_required = false;
+#endif
+
+ /* Turn off clock now that fault have been handled. We
+ * dropped locks so poweron_required may have changed -
+ * power back on if this is the case (effectively only
+ * re-enabling of the interrupts would be done in this
+ * case, as the clocks to GPU were not withdrawn yet).
+ */
+ if (backend->poweron_required)
+ kbase_pm_clock_on(kbdev, false);
+ else
+ WARN_ON(!kbase_pm_clock_off(kbdev));
+ }
+}
+
static void kbase_pm_gpu_poweroff_wait_wq(struct work_struct *data)
{
struct kbase_device *kbdev = container_of(data, struct kbase_device,
@@ -270,6 +356,8 @@
struct kbase_pm_device_data *pm = &kbdev->pm;
struct kbase_pm_backend_data *backend = &pm->backend;
unsigned long flags;
+
+ KBASE_KTRACE_ADD(kbdev, PM_POWEROFF_WAIT_WQ, NULL, 0);
#if !MALI_USE_CSF
/* Wait for power transitions to complete. We do this with no locks held
@@ -280,51 +368,7 @@
kbase_pm_lock(kbdev);
-#ifdef CONFIG_MALI_ARBITER_SUPPORT
- if (kbase_pm_is_gpu_lost(kbdev))
- backend->poweron_required = false;
-#endif
-
- if (!backend->poweron_required) {
- unsigned long flags;
-
- spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
- WARN_ON(backend->shaders_state !=
- KBASE_SHADERS_OFF_CORESTACK_OFF ||
- backend->l2_state != KBASE_L2_OFF);
- spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
-
- /* Disable interrupts and turn the clock off */
- if (!kbase_pm_clock_off(kbdev)) {
- /*
- * Page/bus faults are pending, must drop locks to
- * process. Interrupts are disabled so no more faults
- * should be generated at this point.
- */
- kbase_pm_unlock(kbdev);
- kbase_flush_mmu_wqs(kbdev);
- kbase_pm_lock(kbdev);
-
-#ifdef CONFIG_MALI_ARBITER_SUPPORT
- /* poweron_required may have changed while pm lock
- * was released.
- */
- if (kbase_pm_is_gpu_lost(kbdev))
- backend->poweron_required = false;
-#endif
-
- /* Turn off clock now that fault have been handled. We
- * dropped locks so poweron_required may have changed -
- * power back on if this is the case (effectively only
- * re-enabling of the interrupts would be done in this
- * case, as the clocks to GPU were not withdrawn yet).
- */
- if (backend->poweron_required)
- kbase_pm_clock_on(kbdev, false);
- else
- WARN_ON(!kbase_pm_clock_off(kbdev));
- }
- }
+ pm_handle_power_off(kbdev);
spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
backend->poweroff_wait_in_progress = false;
@@ -378,8 +422,7 @@
return;
/* Stop the metrics gathering framework */
- if (kbase_pm_metrics_is_active(kbdev))
- kbase_pm_metrics_stop(kbdev);
+ kbase_pm_metrics_stop(kbdev);
/* Keep the current freq to restore it upon resume */
kbdev->previous_frequency = clk_get_rate(clk);
@@ -512,6 +555,78 @@
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
}
+#if MALI_USE_CSF && defined(KBASE_PM_RUNTIME)
+/**
+ * kbase_pm_do_poweroff_sync - Do the synchronous power down of GPU
+ *
+ * @kbdev: The kbase device structure for the device (must be a valid pointer)
+ *
+ * This function is called at the time of system suspend or device unload
+ * to power down the GPU synchronously. This is needed as the power down of GPU
+ * would usually happen from the runtime suspend callback function (if gpu_active
+ * and gpu_idle callbacks are used) and runtime suspend operation is disabled
+ * when system suspend takes place.
+ * The function first waits for the @gpu_poweroff_wait_work to complete, which
+ * could have been enqueued after the last PM reference was released.
+ *
+ * Return: 0 on success, negative value otherwise.
+ */
+static int kbase_pm_do_poweroff_sync(struct kbase_device *kbdev)
+{
+ struct kbase_pm_backend_data *backend = &kbdev->pm.backend;
+ unsigned long flags;
+ int ret = 0;
+
+ WARN_ON(kbdev->pm.active_count);
+
+ kbase_pm_wait_for_poweroff_work_complete(kbdev);
+
+ kbase_pm_lock(kbdev);
+ spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
+ WARN_ON(backend->poweroff_wait_in_progress);
+ WARN_ON(backend->gpu_sleep_mode_active);
+ if (backend->gpu_powered) {
+
+ backend->mcu_desired = false;
+ backend->l2_desired = false;
+ kbase_pm_update_state(kbdev);
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+
+ ret = kbase_pm_wait_for_desired_state(kbdev);
+ if (ret) {
+ dev_warn(
+ kbdev->dev,
+ "Wait for pm state change failed on synchronous power off");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ /* Due to the power policy, GPU could have been kept active
+ * throughout and so need to invoke the idle callback before
+ * the power down.
+ */
+ if (backend->callback_power_runtime_gpu_idle &&
+ !backend->gpu_idled) {
+ backend->callback_power_runtime_gpu_idle(kbdev);
+ backend->gpu_idled = true;
+ }
+
+ if (!kbase_pm_clock_off(kbdev)) {
+ dev_warn(
+ kbdev->dev,
+ "Failed to turn off GPU clocks on synchronous power off, MMU faults pending");
+ ret = -EBUSY;
+ }
+ } else {
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+ }
+
+out:
+ kbase_pm_unlock(kbdev);
+ return ret;
+}
+#endif
+
void kbase_pm_do_poweroff(struct kbase_device *kbdev)
{
unsigned long flags;
@@ -561,12 +676,38 @@
return ret;
}
-void kbase_pm_wait_for_poweroff_complete(struct kbase_device *kbdev)
+void kbase_pm_wait_for_poweroff_work_complete(struct kbase_device *kbdev)
{
wait_event_killable(kbdev->pm.backend.poweroff_wait,
is_poweroff_in_progress(kbdev));
}
-KBASE_EXPORT_TEST_API(kbase_pm_wait_for_poweroff_complete);
+KBASE_EXPORT_TEST_API(kbase_pm_wait_for_poweroff_work_complete);
+
+/**
+ * is_gpu_powered_down - Check whether GPU is powered down
+ *
+ * @kbdev: kbase device
+ *
+ * Return: true if GPU is powered down, false otherwise
+ */
+static bool is_gpu_powered_down(struct kbase_device *kbdev)
+{
+ bool ret;
+ unsigned long flags;
+
+ spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
+ ret = !kbdev->pm.backend.gpu_powered;
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+
+ return ret;
+}
+
+void kbase_pm_wait_for_gpu_power_down(struct kbase_device *kbdev)
+{
+ wait_event_killable(kbdev->pm.backend.poweroff_wait,
+ is_gpu_powered_down(kbdev));
+}
+KBASE_EXPORT_TEST_API(kbase_pm_wait_for_gpu_power_down);
int kbase_hwaccess_pm_powerup(struct kbase_device *kbdev,
unsigned int flags)
@@ -612,6 +753,15 @@
* cores off
*/
kbdev->pm.active_count = 1;
+#if MALI_USE_CSF && KBASE_PM_RUNTIME
+ if (kbdev->pm.backend.callback_power_runtime_gpu_active) {
+ /* Take the RPM reference count to match with the internal
+ * PM reference count
+ */
+ kbdev->pm.backend.callback_power_runtime_gpu_active(kbdev);
+ WARN_ON(kbdev->pm.backend.gpu_idled);
+ }
+#endif
spin_lock_irqsave(&kbdev->pm.backend.gpu_cycle_counter_requests_lock,
irq_flags);
@@ -653,11 +803,15 @@
{
KBASE_DEBUG_ASSERT(kbdev != NULL);
+#if MALI_USE_CSF && defined(KBASE_PM_RUNTIME)
+ WARN_ON(kbase_pm_do_poweroff_sync(kbdev));
+#else
mutex_lock(&kbdev->pm.lock);
kbase_pm_do_poweroff(kbdev);
mutex_unlock(&kbdev->pm.lock);
- kbase_pm_wait_for_poweroff_complete(kbdev);
+ kbase_pm_wait_for_poweroff_work_complete(kbdev);
+#endif
}
KBASE_EXPORT_TEST_API(kbase_hwaccess_pm_halt);
@@ -710,7 +864,7 @@
kbase_pm_update_state(kbdev);
#if !MALI_USE_CSF
- kbase_backend_slot_update(kbdev);
+ kbase_backend_slot_update(kbdev);
#endif /* !MALI_USE_CSF */
spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
@@ -735,7 +889,7 @@
lockdep_assert_held(&kbdev->pm.lock);
if (kbase_dummy_job_wa_enabled(kbdev)) {
- dev_warn(kbdev->dev, "Change of core mask not supported for slot 0 as dummy job WA is enabled");
+ dev_warn_once(kbdev->dev, "Change of core mask not supported for slot 0 as dummy job WA is enabled");
new_core_mask_js0 = kbdev->pm.debug_core_mask[0];
}
@@ -759,8 +913,15 @@
kbase_pm_update_active(kbdev);
}
-void kbase_hwaccess_pm_suspend(struct kbase_device *kbdev)
+int kbase_hwaccess_pm_suspend(struct kbase_device *kbdev)
{
+ int ret = 0;
+
+#if MALI_USE_CSF && defined(KBASE_PM_RUNTIME)
+ ret = kbase_pm_do_poweroff_sync(kbdev);
+ if (ret)
+ return ret;
+#else
/* Force power off the GPU and all cores (regardless of policy), only
* after the PM active count reaches zero (otherwise, we risk turning it
* off prematurely)
@@ -775,10 +936,16 @@
kbase_pm_unlock(kbdev);
- kbase_pm_wait_for_poweroff_complete(kbdev);
+ kbase_pm_wait_for_poweroff_work_complete(kbdev);
+#endif
+
+ WARN_ON(kbdev->pm.backend.gpu_powered);
+ WARN_ON(atomic_read(&kbdev->faults_pending));
if (kbdev->pm.backend.callback_power_suspend)
kbdev->pm.backend.callback_power_suspend(kbdev);
+
+ return ret;
}
void kbase_hwaccess_pm_resume(struct kbase_device *kbdev)
@@ -807,7 +974,7 @@
void kbase_pm_handle_gpu_lost(struct kbase_device *kbdev)
{
unsigned long flags;
- ktime_t end_timestamp = ktime_get();
+ ktime_t end_timestamp = ktime_get_raw();
struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;
if (!kbdev->arb.arb_if)
@@ -844,9 +1011,12 @@
/* Cancel any pending HWC dumps */
spin_lock_irqsave(&kbdev->hwcnt.lock, flags);
- kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_IDLE;
- kbdev->hwcnt.backend.triggered = 1;
- wake_up(&kbdev->hwcnt.backend.wait);
+ if (kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_DUMPING ||
+ kbdev->hwcnt.backend.state == KBASE_INSTR_STATE_FAULT) {
+ kbdev->hwcnt.backend.state = KBASE_INSTR_STATE_FAULT;
+ kbdev->hwcnt.backend.triggered = 1;
+ wake_up(&kbdev->hwcnt.backend.wait);
+ }
spin_unlock_irqrestore(&kbdev->hwcnt.lock, flags);
}
mutex_unlock(&arb_vm_state->vm_state_lock);
@@ -854,3 +1024,215 @@
}
#endif /* CONFIG_MALI_ARBITER_SUPPORT */
+
+#if MALI_USE_CSF && defined(KBASE_PM_RUNTIME)
+int kbase_pm_force_mcu_wakeup_after_sleep(struct kbase_device *kbdev)
+{
+ unsigned long flags;
+
+ lockdep_assert_held(&kbdev->pm.lock);
+
+ spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
+ /* Set the override flag to force the power up of L2 cache */
+ kbdev->pm.backend.gpu_wakeup_override = true;
+ kbase_pm_update_state(kbdev);
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+
+ return kbase_pm_wait_for_desired_state(kbdev);
+}
+
+static int pm_handle_mcu_sleep_on_runtime_suspend(struct kbase_device *kbdev)
+{
+ unsigned long flags;
+ int ret;
+
+ lockdep_assert_held(&kbdev->csf.scheduler.lock);
+ lockdep_assert_held(&kbdev->pm.lock);
+
+#ifdef CONFIG_MALI_BIFROST_DEBUG
+ /* In case of no active CSG on slot, powering up L2 could be skipped and
+ * proceed directly to suspend GPU.
+ * ToDo: firmware has to be reloaded after wake-up as no halt command
+ * has been sent when GPU was put to sleep mode.
+ */
+ if (!kbase_csf_scheduler_get_nr_active_csgs(kbdev))
+ dev_info(
+ kbdev->dev,
+ "No active CSGs. Can skip the power up of L2 and go for suspension directly");
+#endif
+
+ ret = kbase_pm_force_mcu_wakeup_after_sleep(kbdev);
+ if (ret) {
+ spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
+ dev_warn(
+ kbdev->dev,
+ "Waiting for MCU to wake up failed on runtime suspend");
+ kbdev->pm.backend.gpu_wakeup_override = false;
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+ return ret;
+ }
+
+ /* Check if a Doorbell mirror interrupt occurred meanwhile */
+ spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
+ if (kbdev->pm.backend.gpu_sleep_mode_active &&
+ kbdev->pm.backend.exit_gpu_sleep_mode) {
+ dev_dbg(kbdev->dev, "DB mirror interrupt occurred during runtime suspend after L2 power up");
+ kbdev->pm.backend.gpu_wakeup_override = false;
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+ return -EBUSY;
+ }
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+ /* Need to release the kbdev->pm.lock to avoid lock ordering issue
+ * with kctx->reg.lock, which is taken if the sync wait condition is
+ * evaluated after the CSG suspend operation.
+ */
+ kbase_pm_unlock(kbdev);
+ ret = kbase_csf_scheduler_handle_runtime_suspend(kbdev);
+ kbase_pm_lock(kbdev);
+
+ /* Power down L2 cache */
+ spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
+ kbdev->pm.backend.gpu_wakeup_override = false;
+ kbase_pm_update_state(kbdev);
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+
+ /* After re-acquiring the kbdev->pm.lock, check if the device
+ * became active (or active then idle) meanwhile.
+ */
+ if (kbdev->pm.active_count ||
+ kbdev->pm.backend.poweroff_wait_in_progress) {
+ dev_dbg(kbdev->dev,
+ "Device became active on runtime suspend after suspending Scheduler");
+ ret = -EBUSY;
+ }
+
+ if (ret)
+ return ret;
+
+ ret = kbase_pm_wait_for_desired_state(kbdev);
+ if (ret)
+ dev_warn(kbdev->dev, "Wait for power down failed on runtime suspend");
+
+ return ret;
+}
+
+int kbase_pm_handle_runtime_suspend(struct kbase_device *kbdev)
+{
+ enum kbase_mcu_state mcu_state;
+ bool exit_early = false;
+ unsigned long flags;
+ int ret = 0;
+
+ spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
+ /* This check is needed for the case where Kbase had invoked the
+ * @power_off_callback directly.
+ */
+ if (!kbdev->pm.backend.gpu_powered) {
+ dev_dbg(kbdev->dev, "GPU already powered down on runtime suspend");
+ exit_early = true;
+ }
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+
+ if (exit_early)
+ goto out;
+
+ ret = kbase_reset_gpu_try_prevent(kbdev);
+ if (ret == -ENOMEM) {
+ dev_dbg(kbdev->dev, "Quit runtime suspend as GPU is in bad state");
+ /* Finish the runtime suspend, no point in trying again as GPU is
+ * in irrecoverable bad state.
+ */
+ goto out;
+ } else if (ret) {
+ dev_dbg(kbdev->dev, "Quit runtime suspend for failing to prevent gpu reset");
+ ret = -EBUSY;
+ goto out;
+ }
+
+ kbase_csf_scheduler_lock(kbdev);
+ kbase_pm_lock(kbdev);
+
+ /*
+ * This is to handle the case where GPU device becomes active and idle
+ * very quickly whilst the runtime suspend callback is executing.
+ * This is useful for the following scenario :-
+ * - GPU goes idle and pm_callback_runtime_gpu_idle() is called.
+ * - Auto-suspend timer expires and kbase_device_runtime_suspend()
+ * is called.
+ * - GPU becomes active and pm_callback_runtime_gpu_active() calls
+ * pm_runtime_get().
+ * - Shortly after that GPU becomes idle again.
+ * - kbase_pm_handle_runtime_suspend() gets called.
+ * - pm_callback_runtime_gpu_idle() is called.
+ *
+ * We do not want to power down the GPU immediately after it goes idle.
+ * So if we notice that GPU had become active when the runtime suspend
+ * had already kicked in, we abort the runtime suspend.
+ * By aborting the runtime suspend, we defer the power down of GPU.
+ *
+ * This check also helps prevent warnings regarding L2 and MCU states
+ * inside the pm_handle_power_off() function. The warning stems from
+ * the fact that pm.lock is released before invoking Scheduler function
+ * to suspend the CSGs.
+ */
+ if (kbdev->pm.active_count ||
+ kbdev->pm.backend.poweroff_wait_in_progress) {
+ dev_dbg(kbdev->dev, "Device became active on runtime suspend");
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
+ if (kbdev->pm.backend.gpu_sleep_mode_active &&
+ kbdev->pm.backend.exit_gpu_sleep_mode) {
+ dev_dbg(kbdev->dev, "DB mirror interrupt occurred during runtime suspend before L2 power up");
+ ret = -EBUSY;
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+ goto unlock;
+ }
+
+ mcu_state = kbdev->pm.backend.mcu_state;
+ WARN_ON(!kbase_pm_is_mcu_inactive(kbdev, mcu_state));
+ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);
+
+ if (mcu_state == KBASE_MCU_IN_SLEEP) {
+ ret = pm_handle_mcu_sleep_on_runtime_suspend(kbdev);
+ if (ret)
+ goto unlock;
+ }
+
+ /* Disable interrupts and turn off the GPU clocks */
+ if (!kbase_pm_clock_off(kbdev)) {
+ dev_warn(kbdev->dev, "Failed to turn off GPU clocks on runtime suspend, MMU faults pending");
+
+ WARN_ON(!kbdev->poweroff_pending);
+ /* Previous call to kbase_pm_clock_off() would have disabled
+ * the interrupts and also synchronized with the interrupt
+ * handlers, so more fault work items can't be enqueued.
+ *
+ * Can't wait for the completion of MMU fault work items as
+ * there is a possibility of a deadlock since the fault work
+ * items would do the group termination which requires the
+ * Scheduler lock.
+ */
+ ret = -EBUSY;
+ goto unlock;
+ }
+
+ wake_up(&kbdev->pm.backend.poweroff_wait);
+ WARN_ON(kbdev->pm.backend.gpu_powered);
+ dev_dbg(kbdev->dev, "GPU power down complete");
+
+unlock:
+ kbase_pm_unlock(kbdev);
+ kbase_csf_scheduler_unlock(kbdev);
+ kbase_reset_gpu_allow(kbdev);
+out:
+ if (ret) {
+ ret = -EBUSY;
+ pm_runtime_mark_last_busy(kbdev->dev);
+ }
+
+ return ret;
+}
+#endif
--
Gitblit v1.6.2