// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note /* * * (C) COPYRIGHT 2010-2023 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 license. * * 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. * */ /* * GPU backend implementation of base kernel power management APIs */ #include #include #include #include #if !MALI_USE_CSF #include #include #include #else #include #include #endif /* !MALI_USE_CSF */ #include #include #include #include #include static void kbase_pm_gpu_poweroff_wait_wq(struct work_struct *data); static void kbase_pm_hwcnt_disable_worker(struct work_struct *data); static void kbase_pm_gpu_clock_control_worker(struct work_struct *data); int kbase_pm_runtime_init(struct kbase_device *kbdev) { struct kbase_pm_callback_conf *callbacks; callbacks = (struct kbase_pm_callback_conf *)POWER_MANAGEMENT_CALLBACKS; if (callbacks) { kbdev->pm.backend.callback_power_on = callbacks->power_on_callback; kbdev->pm.backend.callback_power_off = callbacks->power_off_callback; kbdev->pm.backend.callback_power_suspend = callbacks->power_suspend_callback; kbdev->pm.backend.callback_power_resume = callbacks->power_resume_callback; kbdev->pm.callback_power_runtime_init = callbacks->power_runtime_init_callback; kbdev->pm.callback_power_runtime_term = callbacks->power_runtime_term_callback; kbdev->pm.backend.callback_power_runtime_on = callbacks->power_runtime_on_callback; kbdev->pm.backend.callback_power_runtime_off = callbacks->power_runtime_off_callback; kbdev->pm.backend.callback_power_runtime_idle = callbacks->power_runtime_idle_callback; kbdev->pm.backend.callback_soft_reset = callbacks->soft_reset_callback; kbdev->pm.backend.callback_hardware_reset = callbacks->hardware_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; #ifdef CONFIG_MALI_HOST_CONTROLS_SC_RAILS kbdev->pm.backend.callback_power_on_sc_rails = callbacks->power_on_sc_rails_callback; kbdev->pm.backend.callback_power_off_sc_rails = callbacks->power_off_sc_rails_callback; #endif if (callbacks->power_runtime_init_callback) return callbacks->power_runtime_init_callback(kbdev); else return 0; } kbdev->pm.backend.callback_power_on = NULL; kbdev->pm.backend.callback_power_off = NULL; kbdev->pm.backend.callback_power_suspend = NULL; kbdev->pm.backend.callback_power_resume = NULL; kbdev->pm.callback_power_runtime_init = NULL; kbdev->pm.callback_power_runtime_term = NULL; kbdev->pm.backend.callback_power_runtime_on = NULL; 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_hardware_reset = NULL; kbdev->pm.backend.callback_power_runtime_gpu_idle = NULL; kbdev->pm.backend.callback_power_runtime_gpu_active = NULL; #ifdef CONFIG_MALI_HOST_CONTROLS_SC_RAILS kbdev->pm.backend.callback_power_on_sc_rails = NULL; kbdev->pm.backend.callback_power_off_sc_rails = NULL; #endif return 0; } void kbase_pm_runtime_term(struct kbase_device *kbdev) { if (kbdev->pm.callback_power_runtime_term) kbdev->pm.callback_power_runtime_term(kbdev); } void kbase_pm_register_access_enable(struct kbase_device *kbdev) { struct kbase_pm_callback_conf *callbacks; callbacks = (struct kbase_pm_callback_conf *)POWER_MANAGEMENT_CALLBACKS; if (callbacks) callbacks->power_on_callback(kbdev); #ifdef CONFIG_MALI_ARBITER_SUPPORT if (WARN_ON(kbase_pm_is_gpu_lost(kbdev))) dev_err(kbdev->dev, "Attempting to power on while GPU lost\n"); #endif kbdev->pm.backend.gpu_powered = true; } void kbase_pm_register_access_disable(struct kbase_device *kbdev) { struct kbase_pm_callback_conf *callbacks; callbacks = (struct kbase_pm_callback_conf *)POWER_MANAGEMENT_CALLBACKS; kbdev->pm.backend.gpu_powered = false; if (callbacks) callbacks->power_off_callback(kbdev); } int kbase_hwaccess_pm_init(struct kbase_device *kbdev) { int ret = 0; KBASE_DEBUG_ASSERT(kbdev != NULL); rt_mutex_init(&kbdev->pm.lock); kbase_pm_init_event_log(kbdev); kbdev->pm.backend.gpu_poweroff_wait_wq = alloc_workqueue("kbase_pm_poweroff_wait", WQ_HIGHPRI | WQ_UNBOUND, 1); if (!kbdev->pm.backend.gpu_poweroff_wait_wq) return -ENOMEM; INIT_WORK(&kbdev->pm.backend.gpu_poweroff_wait_work, kbase_pm_gpu_poweroff_wait_wq); kbdev->pm.backend.ca_cores_enabled = ~0ull; kbdev->pm.backend.gpu_powered = false; kbdev->pm.backend.gpu_ready = false; kbdev->pm.suspending = false; kbdev->pm.resuming = false; #ifdef CONFIG_MALI_ARBITER_SUPPORT kbase_pm_set_gpu_lost(kbdev, false); #endif #ifdef CONFIG_MALI_DEBUG kbdev->pm.backend.driver_ready_for_irqs = false; #endif /* CONFIG_MALI_DEBUG */ init_waitqueue_head(&kbdev->pm.backend.gpu_in_desired_state_wait); #if !MALI_USE_CSF /* Initialise the metrics subsystem */ ret = kbasep_pm_metrics_init(kbdev); if (ret) return ret; #else mutex_init(&kbdev->pm.backend.policy_change_lock); kbdev->pm.backend.policy_change_clamp_state_to_off = false; /* Due to dependency on kbase_ipa_control, the metrics subsystem can't * be initialized here. */ CSTD_UNUSED(ret); #endif init_waitqueue_head(&kbdev->pm.backend.reset_done_wait); kbdev->pm.backend.reset_done = false; init_waitqueue_head(&kbdev->pm.zero_active_count_wait); init_waitqueue_head(&kbdev->pm.resume_wait); kbdev->pm.active_count = 0; spin_lock_init(&kbdev->pm.backend.gpu_cycle_counter_requests_lock); init_waitqueue_head(&kbdev->pm.backend.poweroff_wait); if (kbase_pm_ca_init(kbdev) != 0) goto workq_fail; kbase_pm_policy_init(kbdev); if (kbase_pm_state_machine_init(kbdev) != 0) goto pm_state_machine_fail; kbdev->pm.backend.hwcnt_desired = false; kbdev->pm.backend.hwcnt_disabled = true; 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; kbdev->pm.backend.gpu_clock_slow_down_wa = false; return 0; } /* WA1: L2 always_on for GPUs being affected by GPU2017-1336 */ if (!IS_ENABLED(CONFIG_MALI_HW_ERRATA_1485982_USE_CLOCK_ALTERNATIVE)) { kbdev->pm.backend.gpu_clock_slow_down_wa = false; if (kbase_hw_has_issue(kbdev, BASE_HW_ISSUE_GPU2017_1336)) kbdev->pm.backend.l2_always_on = true; else kbdev->pm.backend.l2_always_on = false; return 0; } /* WA3: Clock slow down for GPUs being affected by GPU2017-1336 */ kbdev->pm.backend.l2_always_on = false; if (kbase_hw_has_issue(kbdev, BASE_HW_ISSUE_GPU2017_1336)) { kbdev->pm.backend.gpu_clock_slow_down_wa = true; kbdev->pm.backend.gpu_clock_suspend_freq = 0; kbdev->pm.backend.gpu_clock_slow_down_desired = true; kbdev->pm.backend.gpu_clock_slowed_down = false; INIT_WORK(&kbdev->pm.backend.gpu_clock_control_work, kbase_pm_gpu_clock_control_worker); } else kbdev->pm.backend.gpu_clock_slow_down_wa = false; return 0; pm_state_machine_fail: kbase_pm_policy_term(kbdev); kbase_pm_ca_term(kbdev); workq_fail: #if !MALI_USE_CSF kbasep_pm_metrics_term(kbdev); #endif return -EINVAL; } void kbase_pm_do_poweron(struct kbase_device *kbdev, bool is_resume) { lockdep_assert_held(&kbdev->pm.lock); /* Turn clocks and interrupts on - no-op if we haven't done a previous * kbase_pm_clock_off() */ kbase_pm_clock_on(kbdev, is_resume); if (!is_resume) { unsigned long flags; /* Force update of L2 state - if we have abandoned a power off * then this may be required to power the L2 back on. */ spin_lock_irqsave(&kbdev->hwaccess_lock, flags); kbase_pm_update_state(kbdev); spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags); } /* Update core status as required by the policy */ kbase_pm_update_cores_state(kbdev); /* NOTE: We don't wait to reach the desired state, since running atoms * will wait for that state to be reached anyway */ } 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, pm.backend.gpu_poweroff_wait_work); 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 * so that we don't deadlock with any pending workqueues. */ kbase_pm_wait_for_desired_state(kbdev); #endif kbase_pm_lock(kbdev); pm_handle_power_off(kbdev); spin_lock_irqsave(&kbdev->hwaccess_lock, flags); backend->poweroff_wait_in_progress = false; if (backend->poweron_required) { backend->poweron_required = false; kbdev->pm.backend.l2_desired = true; #if MALI_USE_CSF kbdev->pm.backend.mcu_desired = true; #endif kbase_pm_update_state(kbdev); kbase_pm_update_cores_state_nolock(kbdev); #if !MALI_USE_CSF kbase_backend_slot_update(kbdev); #endif /* !MALI_USE_CSF */ } spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags); kbase_pm_unlock(kbdev); wake_up(&kbdev->pm.backend.poweroff_wait); } static void kbase_pm_l2_clock_slow(struct kbase_device *kbdev) { #if defined(CONFIG_MALI_MIDGARD_DVFS) struct clk *clk = kbdev->clocks[0]; #endif if (!kbdev->pm.backend.gpu_clock_slow_down_wa) return; /* No suspend clock is specified */ if (WARN_ON_ONCE(!kbdev->pm.backend.gpu_clock_suspend_freq)) return; #if defined(CONFIG_MALI_DEVFREQ) /* Suspend devfreq */ devfreq_suspend_device(kbdev->devfreq); /* Keep the current freq to restore it upon resume */ kbdev->previous_frequency = kbdev->current_nominal_freq; /* Slow down GPU clock to the suspend clock*/ kbase_devfreq_force_freq(kbdev, kbdev->pm.backend.gpu_clock_suspend_freq); #elif defined(CONFIG_MALI_MIDGARD_DVFS) /* CONFIG_MALI_DEVFREQ */ if (WARN_ON_ONCE(!clk)) return; /* Stop the metrics gathering framework */ kbase_pm_metrics_stop(kbdev); /* Keep the current freq to restore it upon resume */ kbdev->previous_frequency = clk_get_rate(clk); /* Slow down GPU clock to the suspend clock*/ if (WARN_ON_ONCE(clk_set_rate(clk, kbdev->pm.backend.gpu_clock_suspend_freq))) dev_err(kbdev->dev, "Failed to set suspend freq\n"); #endif /* CONFIG_MALI_MIDGARD_DVFS */ } static void kbase_pm_l2_clock_normalize(struct kbase_device *kbdev) { #if defined(CONFIG_MALI_MIDGARD_DVFS) struct clk *clk = kbdev->clocks[0]; #endif if (!kbdev->pm.backend.gpu_clock_slow_down_wa) return; #if defined(CONFIG_MALI_DEVFREQ) /* Restore GPU clock to the previous one */ kbase_devfreq_force_freq(kbdev, kbdev->previous_frequency); /* Resume devfreq */ devfreq_resume_device(kbdev->devfreq); #elif defined(CONFIG_MALI_MIDGARD_DVFS) /* CONFIG_MALI_DEVFREQ */ if (WARN_ON_ONCE(!clk)) return; /* Restore GPU clock */ if (WARN_ON_ONCE(clk_set_rate(clk, kbdev->previous_frequency))) dev_err(kbdev->dev, "Failed to restore freq (%lu)\n", kbdev->previous_frequency); /* Restart the metrics gathering framework */ kbase_pm_metrics_start(kbdev); #endif /* CONFIG_MALI_MIDGARD_DVFS */ } static void kbase_pm_gpu_clock_control_worker(struct work_struct *data) { struct kbase_device *kbdev = container_of(data, struct kbase_device, pm.backend.gpu_clock_control_work); struct kbase_pm_device_data *pm = &kbdev->pm; struct kbase_pm_backend_data *backend = &pm->backend; unsigned long flags; bool slow_down = false, normalize = false; /* Determine if GPU clock control is required */ spin_lock_irqsave(&kbdev->hwaccess_lock, flags); if (!backend->gpu_clock_slowed_down && backend->gpu_clock_slow_down_desired) { slow_down = true; backend->gpu_clock_slowed_down = true; } else if (backend->gpu_clock_slowed_down && !backend->gpu_clock_slow_down_desired) { normalize = true; backend->gpu_clock_slowed_down = false; } spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags); /* Control GPU clock according to the request of L2 state machine. * The GPU clock needs to be lowered for safe L2 power down * and restored to previous speed at L2 power up. */ if (slow_down) kbase_pm_l2_clock_slow(kbdev); else if (normalize) kbase_pm_l2_clock_normalize(kbdev); /* Tell L2 state machine to transit to next state */ spin_lock_irqsave(&kbdev->hwaccess_lock, flags); kbase_pm_update_state(kbdev); spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags); } static void kbase_pm_hwcnt_disable_worker(struct work_struct *data) { struct kbase_device *kbdev = container_of(data, struct kbase_device, pm.backend.hwcnt_disable_work); struct kbase_pm_device_data *pm = &kbdev->pm; struct kbase_pm_backend_data *backend = &pm->backend; unsigned long flags; bool do_disable; spin_lock_irqsave(&kbdev->hwaccess_lock, flags); do_disable = !backend->hwcnt_desired && !backend->hwcnt_disabled; spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags); if (!do_disable) return; kbase_hwcnt_context_disable(kbdev->hwcnt_gpu_ctx); spin_lock_irqsave(&kbdev->hwaccess_lock, flags); do_disable = !backend->hwcnt_desired && !backend->hwcnt_disabled; if (do_disable) { /* PM state did not change while we were doing the disable, * so commit the work we just performed and continue the state * machine. */ backend->hwcnt_disabled = true; kbase_pm_update_state(kbdev); #if !MALI_USE_CSF kbase_backend_slot_update(kbdev); #endif /* !MALI_USE_CSF */ } else { /* PM state was updated while we were doing the disable, * so we need to undo the disable we just performed. */ #if MALI_USE_CSF unsigned long lock_flags; kbase_csf_scheduler_spin_lock(kbdev, &lock_flags); #endif kbase_hwcnt_context_enable(kbdev->hwcnt_gpu_ctx); #if MALI_USE_CSF kbase_csf_scheduler_spin_unlock(kbdev, lock_flags); #endif } 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; WARN_ON(kbdev->pm.active_count); ret = kbase_pm_wait_for_poweroff_work_complete(kbdev); if (ret) return ret; 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; lockdep_assert_held(&kbdev->pm.lock); spin_lock_irqsave(&kbdev->hwaccess_lock, flags); if (!kbdev->pm.backend.gpu_powered) goto unlock_hwaccess; if (kbdev->pm.backend.poweroff_wait_in_progress) goto unlock_hwaccess; #if MALI_USE_CSF kbdev->pm.backend.mcu_desired = false; #else /* Force all cores off */ kbdev->pm.backend.shaders_desired = false; #endif kbdev->pm.backend.l2_desired = false; kbdev->pm.backend.poweroff_wait_in_progress = true; kbdev->pm.backend.invoke_poweroff_wait_wq_when_l2_off = true; /* l2_desired being false should cause the state machine to * start powering off the L2. When it actually is powered off, * the interrupt handler will call kbase_pm_l2_update_state() * again, which will trigger the kbase_pm_gpu_poweroff_wait_wq. * Callers of this function will need to wait on poweroff_wait. */ kbase_pm_update_state(kbdev); unlock_hwaccess: spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags); } /** * 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) { unsigned long irq_flags; int ret; KBASE_DEBUG_ASSERT(kbdev != NULL); kbase_pm_lock(kbdev); /* A suspend won't happen during startup/insmod */ KBASE_DEBUG_ASSERT(!kbase_pm_is_suspending(kbdev)); /* Power up the GPU, don't enable IRQs as we are not ready to receive * them */ ret = kbase_pm_init_hw(kbdev, flags); if (ret) { kbase_pm_unlock(kbdev); return ret; } #if MALI_USE_CSF kbdev->pm.debug_core_mask = kbdev->gpu_props.props.raw_props.shader_present; spin_lock_irqsave(&kbdev->hwaccess_lock, irq_flags); /* Set the initial value for 'shaders_avail'. It would be later * modified only from the MCU state machine, when the shader core * allocation enable mask request has completed. So its value would * indicate the mask of cores that are currently being used by FW for * the allocation of endpoints requested by CSGs. */ kbdev->pm.backend.shaders_avail = kbase_pm_ca_get_core_mask(kbdev); spin_unlock_irqrestore(&kbdev->hwaccess_lock, irq_flags); #else kbdev->pm.debug_core_mask_all = kbdev->pm.debug_core_mask[0] = kbdev->pm.debug_core_mask[1] = kbdev->pm.debug_core_mask[2] = kbdev->gpu_props.props.raw_props.shader_present; #endif /* Pretend the GPU is active to prevent a power policy turning the GPU * 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); /* Ensure cycle counter is off */ kbdev->pm.backend.gpu_cycle_counter_requests = 0; spin_unlock_irqrestore( &kbdev->pm.backend.gpu_cycle_counter_requests_lock, irq_flags); /* We are ready to receive IRQ's now as power policy is set up, so * enable them now. */ #ifdef CONFIG_MALI_DEBUG kbdev->pm.backend.driver_ready_for_irqs = true; #endif kbase_pm_enable_interrupts(kbdev); WARN_ON(!kbdev->pm.backend.gpu_powered); /* GPU has been powered up (by kbase_pm_init_hw) and interrupts have * been enabled, so GPU is ready for use and PM state machine can be * exercised from this point onwards. */ kbdev->pm.backend.gpu_ready = true; /* Turn on the GPU and any cores needed by the policy */ #if MALI_USE_CSF /* Turn on the L2 caches, needed for firmware boot */ spin_lock_irqsave(&kbdev->hwaccess_lock, irq_flags); kbdev->pm.backend.l2_desired = true; spin_unlock_irqrestore(&kbdev->hwaccess_lock, irq_flags); #endif kbase_pm_do_poweron(kbdev, false); kbase_pm_unlock(kbdev); return 0; } void kbase_hwaccess_pm_halt(struct kbase_device *kbdev) { KBASE_DEBUG_ASSERT(kbdev != NULL); #if MALI_USE_CSF && defined(KBASE_PM_RUNTIME) WARN_ON(kbase_pm_do_poweroff_sync(kbdev)); #else rt_mutex_lock(&kbdev->pm.lock); kbase_pm_do_poweroff(kbdev); rt_mutex_unlock(&kbdev->pm.lock); kbase_pm_wait_for_poweroff_work_complete(kbdev); #endif } KBASE_EXPORT_TEST_API(kbase_hwaccess_pm_halt); void kbase_hwaccess_pm_term(struct kbase_device *kbdev) { KBASE_DEBUG_ASSERT(kbdev != NULL); KBASE_DEBUG_ASSERT(kbdev->pm.active_count == 0); KBASE_DEBUG_ASSERT(kbdev->pm.backend.gpu_cycle_counter_requests == 0); cancel_work_sync(&kbdev->pm.backend.hwcnt_disable_work); if (kbdev->pm.backend.hwcnt_disabled) { unsigned long flags; #if MALI_USE_CSF kbase_csf_scheduler_spin_lock(kbdev, &flags); kbase_hwcnt_context_enable(kbdev->hwcnt_gpu_ctx); kbase_csf_scheduler_spin_unlock(kbdev, flags); #else spin_lock_irqsave(&kbdev->hwaccess_lock, flags); kbase_hwcnt_context_enable(kbdev->hwcnt_gpu_ctx); spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags); #endif } /* Free any resources the policy allocated */ kbase_pm_state_machine_term(kbdev); kbase_pm_policy_term(kbdev); kbase_pm_ca_term(kbdev); #if !MALI_USE_CSF /* Shut down the metrics subsystem */ kbasep_pm_metrics_term(kbdev); #else if (WARN_ON(mutex_is_locked(&kbdev->pm.backend.policy_change_lock))) { mutex_lock(&kbdev->pm.backend.policy_change_lock); mutex_unlock(&kbdev->pm.backend.policy_change_lock); } mutex_destroy(&kbdev->pm.backend.policy_change_lock); #endif destroy_workqueue(kbdev->pm.backend.gpu_poweroff_wait_wq); } void kbase_pm_power_changed(struct kbase_device *kbdev) { unsigned long flags; spin_lock_irqsave(&kbdev->hwaccess_lock, flags); kbase_pm_update_state(kbdev); #if !MALI_USE_CSF kbase_backend_slot_update(kbdev); #endif /* !MALI_USE_CSF */ spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags); } #if MALI_USE_CSF void kbase_pm_set_debug_core_mask(struct kbase_device *kbdev, u64 new_core_mask) { lockdep_assert_held(&kbdev->hwaccess_lock); lockdep_assert_held(&kbdev->pm.lock); kbdev->pm.debug_core_mask = new_core_mask; kbase_pm_update_dynamic_cores_onoff(kbdev); } KBASE_EXPORT_TEST_API(kbase_pm_set_debug_core_mask); #else void kbase_pm_set_debug_core_mask(struct kbase_device *kbdev, u64 new_core_mask_js0, u64 new_core_mask_js1, u64 new_core_mask_js2) { lockdep_assert_held(&kbdev->hwaccess_lock); lockdep_assert_held(&kbdev->pm.lock); if (kbase_dummy_job_wa_enabled(kbdev)) { 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]; } kbdev->pm.debug_core_mask[0] = new_core_mask_js0; kbdev->pm.debug_core_mask[1] = new_core_mask_js1; kbdev->pm.debug_core_mask[2] = new_core_mask_js2; kbdev->pm.debug_core_mask_all = new_core_mask_js0 | new_core_mask_js1 | new_core_mask_js2; kbase_pm_update_dynamic_cores_onoff(kbdev); } #endif /* MALI_USE_CSF */ void kbase_hwaccess_pm_gpu_active(struct kbase_device *kbdev) { kbase_pm_update_active(kbdev); } void kbase_hwaccess_pm_gpu_idle(struct kbase_device *kbdev) { kbase_pm_update_active(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) */ kbase_pm_lock(kbdev); kbase_pm_do_poweroff(kbdev); #if !MALI_USE_CSF kbase_backend_timer_suspend(kbdev); #endif /* !MALI_USE_CSF */ kbase_pm_unlock(kbdev); ret = kbase_pm_wait_for_poweroff_work_complete(kbdev); if (ret) { #if !MALI_USE_CSF kbase_backend_timer_resume(kbdev); #endif /* !MALI_USE_CSF */ return ret; } #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) { kbase_pm_lock(kbdev); /* System resume callback has begun */ kbdev->pm.resuming = true; kbdev->pm.suspending = false; #ifdef CONFIG_MALI_ARBITER_SUPPORT if (kbase_pm_is_gpu_lost(kbdev)) { dev_dbg(kbdev->dev, "%s: GPU lost in progress\n", __func__); kbase_pm_unlock(kbdev); return; } #endif kbase_pm_do_poweron(kbdev, true); #if !MALI_USE_CSF kbase_backend_timer_resume(kbdev); #endif /* !MALI_USE_CSF */ kbase_pm_unlock(kbdev); } #ifdef CONFIG_MALI_ARBITER_SUPPORT void kbase_pm_handle_gpu_lost(struct kbase_device *kbdev) { unsigned long flags; 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) return; rt_mutex_lock(&kbdev->pm.lock); mutex_lock(&arb_vm_state->vm_state_lock); if (kbdev->pm.backend.gpu_powered && !kbase_pm_is_gpu_lost(kbdev)) { kbase_pm_set_gpu_lost(kbdev, true); /* GPU is no longer mapped to VM. So no interrupts will * be received and Mali registers have been replaced by * dummy RAM */ WARN(!kbase_is_gpu_removed(kbdev), "GPU is still available after GPU lost event\n"); /* Full GPU reset will have been done by hypervisor, so * cancel */ atomic_set(&kbdev->hwaccess.backend.reset_gpu, KBASE_RESET_GPU_NOT_PENDING); hrtimer_cancel(&kbdev->hwaccess.backend.reset_timer); kbase_synchronize_irqs(kbdev); /* Clear all jobs running on the GPU */ spin_lock_irqsave(&kbdev->hwaccess_lock, flags); kbdev->protected_mode = false; kbase_backend_reset(kbdev, &end_timestamp); kbase_pm_metrics_update(kbdev, NULL); kbase_pm_update_state(kbdev); spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags); /* Cancel any pending HWC dumps */ spin_lock_irqsave(&kbdev->hwcnt.lock, flags); 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); rt_mutex_unlock(&kbdev->pm.lock); } #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_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