// SPDX-License-Identifier: GPL-2.0 WITH Linux-syscall-note
/*
 *
 * (C) COPYRIGHT 2019-2024 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.
 *
 */

/**
 * DOC: Mali arbiter power manager state machine and APIs
 */

#include <mali_kbase.h>
#include <mali_kbase_pm.h>
#include <backend/gpu/mali_kbase_irq_internal.h>
#include <backend/gpu/mali_kbase_pm_internal.h>
#include <tl/mali_kbase_tracepoints.h>
#include <mali_kbase_gpuprops.h>

#if MALI_USE_CSF
#include <csf/mali_kbase_csf_scheduler.h>
#endif

/* A dmesg warning will occur if the GPU is not granted
 * after the following time (in milliseconds) has ellapsed.
 */
#define GPU_REQUEST_TIMEOUT 1000
#define KHZ_TO_HZ 1000

#define MAX_L2_SLICES_MASK 0xFF

/* Maximum time in ms, before deferring probe incase
 * GPU_GRANTED message is not received
 */
static int gpu_req_timeout = 1;
module_param(gpu_req_timeout, int, 0644);
MODULE_PARM_DESC(
	gpu_req_timeout,
	"On a virtualized platform, if the GPU is not granted within this time(ms) kbase will defer the probe");

static void kbase_arbiter_pm_vm_wait_gpu_assignment(struct kbase_device *kbdev);
static inline bool kbase_arbiter_pm_vm_gpu_assigned_locked(struct kbase_device *kbdev);

/**
 * kbase_arbiter_pm_vm_state_str() - Helper function to get string
 *                                   for kbase VM state.(debug)
 * @state: kbase VM state
 *
 * Return: string representation of Kbase_vm_state
 */
static inline const char *kbase_arbiter_pm_vm_state_str(enum kbase_vm_state state)
{
	switch (state) {
	case KBASE_VM_STATE_INITIALIZING:
		return "KBASE_VM_STATE_INITIALIZING";
	case KBASE_VM_STATE_INITIALIZING_WITH_GPU:
		return "KBASE_VM_STATE_INITIALIZING_WITH_GPU";
	case KBASE_VM_STATE_SUSPENDED:
		return "KBASE_VM_STATE_SUSPENDED";
	case KBASE_VM_STATE_STOPPED:
		return "KBASE_VM_STATE_STOPPED";
	case KBASE_VM_STATE_STOPPED_GPU_REQUESTED:
		return "KBASE_VM_STATE_STOPPED_GPU_REQUESTED";
	case KBASE_VM_STATE_STARTING:
		return "KBASE_VM_STATE_STARTING";
	case KBASE_VM_STATE_IDLE:
		return "KBASE_VM_STATE_IDLE";
	case KBASE_VM_STATE_ACTIVE:
		return "KBASE_VM_STATE_ACTIVE";
	case KBASE_VM_STATE_STOPPING_IDLE:
		return "KBASE_VM_STATE_STOPPING_IDLE";
	case KBASE_VM_STATE_STOPPING_ACTIVE:
		return "KBASE_VM_STATE_STOPPING_ACTIVE";
	case KBASE_VM_STATE_SUSPEND_PENDING:
		return "KBASE_VM_STATE_SUSPEND_PENDING";
	case KBASE_VM_STATE_SUSPEND_WAIT_FOR_GRANT:
		return "KBASE_VM_STATE_SUSPEND_WAIT_FOR_GRANT";
	default:
		return "[UnknownState]";
	}
}

/**
 * kbase_arbiter_pm_vm_event_str() - Helper function to get string
 *                                   for kbase VM event.(debug)
 * @evt: kbase VM state
 *
 * Return: String representation of Kbase_arbif_event
 */
static inline const char *kbase_arbiter_pm_vm_event_str(enum kbase_arbif_evt evt)
{
	switch (evt) {
	case KBASE_VM_GPU_INITIALIZED_EVT:
		return "KBASE_VM_GPU_INITIALIZED_EVT";
	case KBASE_VM_GPU_STOP_EVT:
		return "KBASE_VM_GPU_STOP_EVT";
	case KBASE_VM_GPU_GRANTED_EVT:
		return "KBASE_VM_GPU_GRANTED_EVT";
	case KBASE_VM_GPU_LOST_EVT:
		return "KBASE_VM_GPU_LOST_EVT";
	case KBASE_VM_OS_SUSPEND_EVENT:
		return "KBASE_VM_OS_SUSPEND_EVENT";
	case KBASE_VM_OS_RESUME_EVENT:
		return "KBASE_VM_OS_RESUME_EVENT";
	case KBASE_VM_GPU_IDLE_EVENT:
		return "KBASE_VM_GPU_IDLE_EVENT";
	case KBASE_VM_REF_EVENT:
		return "KBASE_VM_REF_EVENT";
	default:
		return "[UnknownEvent]";
	}
}

/**
 * kbase_arbiter_pm_vm_set_state() - Sets new kbase_arbiter_vm_state
 * @kbdev: The kbase device structure for the device
 * @new_state: kbase VM new state
 *
 * This function sets the new state for the VM
 */
static void kbase_arbiter_pm_vm_set_state(struct kbase_device *kbdev, enum kbase_vm_state new_state)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;

	dev_dbg(kbdev->dev, "VM set_state %s -> %s",
		kbase_arbiter_pm_vm_state_str(arb_vm_state->vm_state),
		kbase_arbiter_pm_vm_state_str(new_state));

	lockdep_assert_held(&arb_vm_state->vm_state_lock);
	arb_vm_state->vm_state = new_state;
	if (new_state != KBASE_VM_STATE_INITIALIZING_WITH_GPU &&
	    new_state != KBASE_VM_STATE_INITIALIZING)
		KBASE_KTRACE_ADD(kbdev, ARB_VM_STATE, NULL, new_state);
	wake_up(&arb_vm_state->vm_state_wait);
}

/**
 * kbase_arbiter_pm_suspend_wq() - suspend work queue of the driver.
 * @data: work queue
 *
 * Suspends work queue of the driver, when VM is in SUSPEND_PENDING or
 * STOPPING_IDLE or STOPPING_ACTIVE state
 */
static void kbase_arbiter_pm_suspend_wq(struct work_struct *data)
{
	struct kbase_arbiter_vm_state *arb_vm_state =
		container_of(data, struct kbase_arbiter_vm_state, vm_suspend_work);
	struct kbase_device *kbdev = arb_vm_state->kbdev;

	mutex_lock(&arb_vm_state->vm_state_lock);
	dev_dbg(kbdev->dev, ">%s\n", __func__);
	if (arb_vm_state->vm_state == KBASE_VM_STATE_STOPPING_IDLE ||
	    arb_vm_state->vm_state == KBASE_VM_STATE_STOPPING_ACTIVE ||
	    arb_vm_state->vm_state == KBASE_VM_STATE_SUSPEND_PENDING) {
		mutex_unlock(&arb_vm_state->vm_state_lock);
		dev_dbg(kbdev->dev, ">kbase_pm_driver_suspend\n");
		WARN_ON_ONCE(kbase_pm_driver_suspend(kbdev));
		dev_dbg(kbdev->dev, "<kbase_pm_driver_suspend\n");
		mutex_lock(&arb_vm_state->vm_state_lock);
	}
	mutex_unlock(&arb_vm_state->vm_state_lock);
	dev_dbg(kbdev->dev, "<%s\n", __func__);
}

/**
 * kbase_arbiter_pm_resume_wq() -Kbase resume work queue.
 * @data: work item
 *
 * Resume work queue of the driver when VM is in STARTING state,
 * else if its in STOPPING_ACTIVE will request a stop event.
 */
static void kbase_arbiter_pm_resume_wq(struct work_struct *data)
{
	struct kbase_arbiter_vm_state *arb_vm_state =
		container_of(data, struct kbase_arbiter_vm_state, vm_resume_work);
	struct kbase_device *kbdev = arb_vm_state->kbdev;

	mutex_lock(&arb_vm_state->vm_state_lock);
	dev_dbg(kbdev->dev, ">%s\n", __func__);
	arb_vm_state->vm_arb_starting = true;
	if (arb_vm_state->vm_state == KBASE_VM_STATE_STARTING) {
		mutex_unlock(&arb_vm_state->vm_state_lock);
		dev_dbg(kbdev->dev, ">kbase_pm_driver_resume\n");
		kbase_pm_driver_resume(kbdev, true);
		dev_dbg(kbdev->dev, "<kbase_pm_driver_resume\n");
		mutex_lock(&arb_vm_state->vm_state_lock);
	} else if (arb_vm_state->vm_state == KBASE_VM_STATE_STOPPING_ACTIVE) {
		kbase_arbiter_pm_vm_stopped(kbdev);
	}
	arb_vm_state->vm_arb_starting = false;
	mutex_unlock(&arb_vm_state->vm_state_lock);
	KBASE_TLSTREAM_TL_ARBITER_STARTED(kbdev, kbdev);
	KBASE_KTRACE_ADD(kbdev, ARB_GPU_STARTED, NULL, 0);
	dev_dbg(kbdev->dev, "<%s\n", __func__);
}

/**
 * request_timer_callback() - Issue warning on request timer expiration
 * @timer: Request hr timer data
 *
 * Called when the Arbiter takes too long to grant the GPU after a
 * request has been made.  Issues a warning in dmesg.
 *
 * Return: Always returns HRTIMER_NORESTART
 */
static enum hrtimer_restart request_timer_callback(struct hrtimer *timer)
{
	struct kbase_arbiter_vm_state *arb_vm_state =
		container_of(timer, struct kbase_arbiter_vm_state, vm_request_timer);

	KBASE_DEBUG_ASSERT(arb_vm_state);
	KBASE_DEBUG_ASSERT(arb_vm_state->kbdev);

	dev_warn(arb_vm_state->kbdev->dev,
		 "Still waiting for GPU to be granted from Arbiter after %d ms\n",
		 GPU_REQUEST_TIMEOUT);
	return HRTIMER_NORESTART;
}

/**
 * start_request_timer() - Start a timer after requesting GPU
 * @kbdev: The kbase device structure for the device
 *
 * Start a timer to track when kbase is waiting for the GPU from the
 * Arbiter.  If the timer expires before GPU is granted, a warning in
 * dmesg will be issued.
 */
static void start_request_timer(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;

	hrtimer_start(&arb_vm_state->vm_request_timer, HR_TIMER_DELAY_MSEC(GPU_REQUEST_TIMEOUT),
		      HRTIMER_MODE_REL);
}

/**
 * cancel_request_timer() - Stop the request timer
 * @kbdev: The kbase device structure for the device
 *
 * Stops the request timer once GPU has been granted.  Safe to call
 * even if timer is no longer running.
 */
static void cancel_request_timer(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;

	hrtimer_cancel(&arb_vm_state->vm_request_timer);
}

/**
 * kbase_arbiter_pm_early_init() - Initialize arbiter for VM
 *                                 Paravirtualized use.
 * @kbdev: The kbase device structure for the device
 *
 * Initialize the arbiter and other required resources during the runtime
 * and request the GPU for the VM for the first time.
 *
 * Return: 0 if success, or a Linux error code
 */
int kbase_arbiter_pm_early_init(struct kbase_device *kbdev)
{
	int err;
	struct kbase_arbiter_vm_state *arb_vm_state = NULL;

	arb_vm_state = kzalloc(sizeof(struct kbase_arbiter_vm_state), GFP_KERNEL);
	if (arb_vm_state == NULL)
		return -ENOMEM;

	arb_vm_state->kbdev = kbdev;
	arb_vm_state->vm_state = KBASE_VM_STATE_INITIALIZING;

	mutex_init(&arb_vm_state->vm_state_lock);
	init_waitqueue_head(&arb_vm_state->vm_state_wait);
	arb_vm_state->vm_arb_wq = alloc_ordered_workqueue("kbase_vm_arb_wq", WQ_HIGHPRI);
	if (!arb_vm_state->vm_arb_wq) {
		dev_err(kbdev->dev, "Failed to allocate vm_arb workqueue\n");
		kfree(arb_vm_state);
		return -ENOMEM;
	}
	INIT_WORK(&arb_vm_state->vm_suspend_work, kbase_arbiter_pm_suspend_wq);
	INIT_WORK(&arb_vm_state->vm_resume_work, kbase_arbiter_pm_resume_wq);
	arb_vm_state->vm_arb_starting = false;
	atomic_set(&kbdev->pm.gpu_users_waiting, 0);
	hrtimer_init(&arb_vm_state->vm_request_timer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
	arb_vm_state->vm_request_timer.function = request_timer_callback;
	kbdev->pm.arb_vm_state = arb_vm_state;

	err = kbase_arbif_init(kbdev);
	if (err) {
		if (err != -EPERM)
			dev_err(kbdev->dev, "Failed to initialise arbif module. (err = %d)", err);

		goto arbif_init_fail;
	}

	if (kbase_has_arbiter(kbdev)) {
		kbase_arbif_gpu_request(kbdev);
		dev_dbg(kbdev->dev, "Waiting for initial GPU assignment...\n");

		err = wait_event_timeout(arb_vm_state->vm_state_wait,
					 arb_vm_state->vm_state ==
						 KBASE_VM_STATE_INITIALIZING_WITH_GPU,
					 msecs_to_jiffies((unsigned int)gpu_req_timeout));

		if (!err) {
			dev_err(kbdev->dev,
				"Kbase probe Deferred after waiting %d ms to receive GPU_GRANT\n",
				gpu_req_timeout);

			err = -ENODEV;
			goto arbif_timeout;
		}

		dev_dbg(kbdev->dev, "Waiting for initial GPU assignment - done\n");
	}
	return 0;

arbif_timeout:
	kbase_arbiter_pm_early_term(kbdev);
	return err;

arbif_init_fail:
	destroy_workqueue(arb_vm_state->vm_arb_wq);
	kfree(arb_vm_state);
	kbdev->pm.arb_vm_state = NULL;
	return err;
}

/**
 * kbase_arbiter_pm_early_term() - Shutdown arbiter and free resources
 * @kbdev: The kbase device structure for the device
 *
 * Clean up all the resources
 */
void kbase_arbiter_pm_early_term(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;

	if (arb_vm_state == NULL)
		return;

	if (!kbase_has_arbiter(kbdev))
		return;

	kbase_arbiter_pm_release_interrupts(kbdev);

	cancel_request_timer(kbdev);
	mutex_lock(&arb_vm_state->vm_state_lock);
	if (arb_vm_state->vm_state > KBASE_VM_STATE_STOPPED_GPU_REQUESTED) {
		kbase_pm_set_gpu_lost(kbdev, false);
		kbase_arbif_gpu_stopped(kbdev, false);
	}
	mutex_unlock(&arb_vm_state->vm_state_lock);
	destroy_workqueue(arb_vm_state->vm_arb_wq);
	kbase_arbif_destroy(kbdev);
	arb_vm_state->vm_arb_wq = NULL;
	kfree(kbdev->pm.arb_vm_state);
	kbdev->pm.arb_vm_state = NULL;
}

void kbase_arbiter_pm_release_interrupts(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;

	mutex_lock(&arb_vm_state->vm_state_lock);
	if (arb_vm_state->interrupts_installed == true) {
		arb_vm_state->interrupts_installed = false;
		kbase_release_interrupts(kbdev);
	}
	mutex_unlock(&arb_vm_state->vm_state_lock);
}

int kbase_arbiter_pm_install_interrupts(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;
	int err = 0;

	mutex_lock(&arb_vm_state->vm_state_lock);
	if (arb_vm_state->interrupts_installed == false) {
		arb_vm_state->interrupts_installed = true;
		err = kbase_install_interrupts(kbdev);
	} else {
		dev_dbg(kbdev->dev, "%s: interrupts installed already", __func__);
	}
	mutex_unlock(&arb_vm_state->vm_state_lock);
	return err;
}

/**
 * kbase_arbiter_pm_vm_stopped() - Handle stop state for the VM
 * @kbdev: The kbase device structure for the device
 *
 * Handles a stop state for the VM
 */
void kbase_arbiter_pm_vm_stopped(struct kbase_device *kbdev)
{
	bool request_gpu = false;
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;

	lockdep_assert_held(&arb_vm_state->vm_state_lock);

	if (atomic_read(&kbdev->pm.gpu_users_waiting) > 0 &&
	    arb_vm_state->vm_state == KBASE_VM_STATE_STOPPING_IDLE)
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_STOPPING_ACTIVE);

	dev_dbg(kbdev->dev, "%s %s\n", __func__,
		kbase_arbiter_pm_vm_state_str(arb_vm_state->vm_state));

	/*
	 * Release the interrupts on external arb_if to address Xen requirements.
	 * Interrupts are not released with internal arb_if as the IRQs are required
	 * to handle messaging to/from Arbiter/Resource Group.
	 */
	if (arb_vm_state->interrupts_installed
	) {
		arb_vm_state->interrupts_installed = false;
		kbase_release_interrupts(kbdev);
	}

	switch (arb_vm_state->vm_state) {
	case KBASE_VM_STATE_STOPPING_ACTIVE:
		request_gpu = true;
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_STOPPED_GPU_REQUESTED);
		break;
	case KBASE_VM_STATE_STOPPING_IDLE:
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_STOPPED);
		break;
	case KBASE_VM_STATE_SUSPEND_PENDING:
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_SUSPENDED);
		break;
	default:
		dev_warn(kbdev->dev, "unexpected pm_stop VM state %u", arb_vm_state->vm_state);
		break;
	}

	kbase_pm_set_gpu_lost(kbdev, false);
	kbase_arbif_gpu_stopped(kbdev, request_gpu);
	if (request_gpu)
		start_request_timer(kbdev);
}

void kbase_arbiter_set_max_config(struct kbase_device *kbdev, uint32_t max_l2_slices,
				  uint32_t max_core_mask)
{
	struct kbase_arbiter_vm_state *arb_vm_state;
	struct max_config_props max_config;

	if (!kbdev)
		return;

	/* Mask the max_l2_slices as it is stored as 8 bits into kbase */
	max_config.l2_slices = max_l2_slices & MAX_L2_SLICES_MASK;
	max_config.core_mask = max_core_mask;
	arb_vm_state = kbdev->pm.arb_vm_state;

	mutex_lock(&arb_vm_state->vm_state_lock);
	/* Just set the max_props in kbase during initialization. */
	if (arb_vm_state->vm_state == KBASE_VM_STATE_INITIALIZING)
		kbase_gpuprops_set_max_config(kbdev, &max_config);
	else
		dev_dbg(kbdev->dev, "Unexpected max_config on VM state %s",
			kbase_arbiter_pm_vm_state_str(arb_vm_state->vm_state));

	mutex_unlock(&arb_vm_state->vm_state_lock);
}

int kbase_arbiter_pm_gpu_assigned(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state;
	int result = -EINVAL;

	if (!kbdev)
		return result;

	/* If there is no Arbiter, then there is no virtualization
	 * and current VM always has access to GPU.
	 */
	if (!kbase_has_arbiter(kbdev))
		return 1;

	/* First check the GPU_LOST state */
	kbase_pm_lock(kbdev);
	if (kbase_pm_is_gpu_lost(kbdev)) {
		kbase_pm_unlock(kbdev);
		return 0;
	}
	kbase_pm_unlock(kbdev);

	/* Then the arbitration state machine */
	arb_vm_state = kbdev->pm.arb_vm_state;

	mutex_lock(&arb_vm_state->vm_state_lock);
	switch (arb_vm_state->vm_state) {
	case KBASE_VM_STATE_INITIALIZING:
	case KBASE_VM_STATE_SUSPENDED:
	case KBASE_VM_STATE_STOPPED:
	case KBASE_VM_STATE_STOPPED_GPU_REQUESTED:
	case KBASE_VM_STATE_SUSPEND_WAIT_FOR_GRANT:
		result = 0;
		break;
	default:
		result = 1;
		break;
	}
	mutex_unlock(&arb_vm_state->vm_state_lock);

	return result;
}

/**
 * kbase_arbiter_pm_vm_gpu_start() - Handles the start state of the VM
 * @kbdev: The kbase device structure for the device
 *
 * Handles the start state of the VM
 */
static void kbase_arbiter_pm_vm_gpu_start(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;
	bool freq_updated = false;

	lockdep_assert_held(&arb_vm_state->vm_state_lock);
	mutex_lock(&kbdev->arb.arb_freq.arb_freq_lock);
	if (kbdev->arb.arb_freq.freq_updated) {
		kbdev->arb.arb_freq.freq_updated = false;
		freq_updated = true;
	}
	mutex_unlock(&kbdev->arb.arb_freq.arb_freq_lock);

	cancel_request_timer(kbdev);
	switch (arb_vm_state->vm_state) {
	case KBASE_VM_STATE_INITIALIZING:
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_INITIALIZING_WITH_GPU);
		break;
	case KBASE_VM_STATE_STOPPED_GPU_REQUESTED:
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_STARTING);
		arb_vm_state->interrupts_installed = true;
		/*
		 * Re-install interrupts that were released for external arb_if to
		 * address Xen requirements. Interrupts are not released with internal
		 * arb_if as the IRQs are required to handle messaging to/from
		 * Arbiter/Resource Group.
		 */
		{
			kbase_install_interrupts(kbdev);
		}
		/*
		 * GPU GRANTED received while in stop can be a result of a
		 * repartitioning.
		 */
		kbase_gpuprops_req_curr_config_update(kbdev);
		/* curr_config will be updated while resuming the PM. */
		queue_work(arb_vm_state->vm_arb_wq, &arb_vm_state->vm_resume_work);
		break;
	case KBASE_VM_STATE_SUSPEND_WAIT_FOR_GRANT:
		kbase_pm_set_gpu_lost(kbdev, false);
		kbase_arbif_gpu_stopped(kbdev, false);
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_SUSPENDED);
		break;
	default:
		/*
		 * GPU_GRANTED can be received when there is a frequency update
		 * Only show a warning if received in an unexpected state
		 * without a frequency update
		 */
		if (!freq_updated)
			dev_warn(kbdev->dev, "GPU_GRANTED when not expected - state %s\n",
				 kbase_arbiter_pm_vm_state_str(arb_vm_state->vm_state));
		break;
	}
}

/**
 * kbase_arbiter_pm_vm_gpu_stop() - Handles the stop state of the VM
 * @kbdev: The kbase device structure for the device
 *
 * Handles the start state of the VM
 */
static void kbase_arbiter_pm_vm_gpu_stop(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;

	lockdep_assert_held(&arb_vm_state->vm_state_lock);
	if (arb_vm_state->vm_state == KBASE_VM_STATE_INITIALIZING_WITH_GPU) {
		mutex_unlock(&arb_vm_state->vm_state_lock);
		kbase_arbiter_pm_vm_wait_gpu_assignment(kbdev);
		mutex_lock(&arb_vm_state->vm_state_lock);
	}

	switch (arb_vm_state->vm_state) {
	case KBASE_VM_STATE_IDLE:
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_STOPPING_IDLE);
		queue_work(arb_vm_state->vm_arb_wq, &arb_vm_state->vm_suspend_work);
		break;
	case KBASE_VM_STATE_ACTIVE:
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_STOPPING_ACTIVE);
		queue_work(arb_vm_state->vm_arb_wq, &arb_vm_state->vm_suspend_work);
		break;
	case KBASE_VM_STATE_STARTING:
		dev_dbg(kbdev->dev, "Got GPU_STOP event while STARTING.");
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_STOPPING_ACTIVE);
		if (arb_vm_state->vm_arb_starting)
			queue_work(arb_vm_state->vm_arb_wq, &arb_vm_state->vm_suspend_work);
		break;
	case KBASE_VM_STATE_SUSPEND_PENDING:
		/* Suspend finishes with a stop so nothing else to do */
		break;
	default:
		dev_warn(kbdev->dev, "GPU_STOP when not expected - state %s\n",
			 kbase_arbiter_pm_vm_state_str(arb_vm_state->vm_state));
		break;
	}
}

/**
 * kbase_gpu_lost() - Kbase signals GPU is lost on a lost event signal
 * @kbdev: The kbase device structure for the device
 *
 * On GPU lost event signals GPU_LOST to the aribiter
 */
static void kbase_gpu_lost(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;
	bool handle_gpu_lost = false;

	lockdep_assert_held(&arb_vm_state->vm_state_lock);

	switch (arb_vm_state->vm_state) {
	case KBASE_VM_STATE_STARTING:
	case KBASE_VM_STATE_ACTIVE:
	case KBASE_VM_STATE_IDLE:
		dev_warn(kbdev->dev, "GPU lost in state %s",
			 kbase_arbiter_pm_vm_state_str(arb_vm_state->vm_state));
		kbase_arbiter_pm_vm_gpu_stop(kbdev);
		handle_gpu_lost = true;
		break;
	case KBASE_VM_STATE_STOPPING_IDLE:
	case KBASE_VM_STATE_STOPPING_ACTIVE:
	case KBASE_VM_STATE_SUSPEND_PENDING:
		dev_dbg(kbdev->dev, "GPU lost while stopping");
		handle_gpu_lost = true;
		break;
	case KBASE_VM_STATE_SUSPENDED:
	case KBASE_VM_STATE_STOPPED:
	case KBASE_VM_STATE_STOPPED_GPU_REQUESTED:
		dev_dbg(kbdev->dev, "GPU lost while already stopped");
		break;
	case KBASE_VM_STATE_SUSPEND_WAIT_FOR_GRANT:
		dev_dbg(kbdev->dev, "GPU lost while waiting to suspend");
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_SUSPENDED);
		break;
	default:
		break;
	}
	if (handle_gpu_lost) {
		/* Releasing the VM state lock here is safe because
		 * we are guaranteed to be in either STOPPING_IDLE,
		 * STOPPING_ACTIVE or SUSPEND_PENDING at this point.
		 * The only transitions that are valid from here are to
		 * STOPPED, STOPPED_GPU_REQUESTED or SUSPENDED which can
		 * only happen at the completion of the GPU lost handling.
		 */
		mutex_unlock(&arb_vm_state->vm_state_lock);
		kbase_pm_handle_gpu_lost(kbdev);
		mutex_lock(&arb_vm_state->vm_state_lock);
	}
}

/**
 * kbase_arbiter_pm_vm_os_suspend_ready_state() - checks if VM is ready
 *			to be moved to suspended state.
 * @kbdev: The kbase device structure for the device
 *
 * Return: True if its ready to be suspended else False.
 */
static inline bool kbase_arbiter_pm_vm_os_suspend_ready_state(struct kbase_device *kbdev)
{
	switch (kbdev->pm.arb_vm_state->vm_state) {
	case KBASE_VM_STATE_SUSPENDED:
	case KBASE_VM_STATE_STOPPED:
	case KBASE_VM_STATE_IDLE:
	case KBASE_VM_STATE_ACTIVE:
		return true;
	default:
		return false;
	}
}

/**
 * kbase_arbiter_pm_vm_os_prepare_suspend() - Prepare OS to be in suspend state
 *                             until it receives the grant message from arbiter
 * @kbdev: The kbase device structure for the device
 *
 * Prepares OS to be in suspend state until it receives GRANT message
 * from Arbiter asynchronously. This function assumes there is an active Arbiter.
 */
static void kbase_arbiter_pm_vm_os_prepare_suspend(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;
	enum kbase_vm_state prev_state;

	lockdep_assert_held(&arb_vm_state->vm_state_lock);
	if (kbdev->pm.arb_vm_state->vm_state == KBASE_VM_STATE_SUSPENDED)
		return;
	/* Block suspend OS function until we are in a stable state
	 * with vm_state_lock
	 */
	while (!kbase_arbiter_pm_vm_os_suspend_ready_state(kbdev)) {
		prev_state = arb_vm_state->vm_state;
		switch (arb_vm_state->vm_state) {
		case KBASE_VM_STATE_STOPPING_ACTIVE:
		case KBASE_VM_STATE_STOPPING_IDLE:
			kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_SUSPEND_PENDING);
			break;
		case KBASE_VM_STATE_STOPPED_GPU_REQUESTED:
			kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_SUSPEND_WAIT_FOR_GRANT);
			break;
		case KBASE_VM_STATE_STARTING:
			if (!arb_vm_state->vm_arb_starting) {
				kbase_arbiter_pm_vm_set_state(kbdev,
							      KBASE_VM_STATE_SUSPEND_PENDING);
				kbase_arbiter_pm_vm_stopped(kbdev);
			}
			break;
		default:
			break;
		}
		mutex_unlock(&arb_vm_state->vm_state_lock);
		wait_event(arb_vm_state->vm_state_wait, arb_vm_state->vm_state != prev_state);
		mutex_lock(&arb_vm_state->vm_state_lock);
	}

	switch (arb_vm_state->vm_state) {
	case KBASE_VM_STATE_STOPPED:
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_SUSPENDED);
		break;
	case KBASE_VM_STATE_IDLE:
	case KBASE_VM_STATE_ACTIVE:
		kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_SUSPEND_PENDING);
		mutex_unlock(&arb_vm_state->vm_state_lock);
		/* Ensure resume has completed fully before starting suspend */
		flush_work(&arb_vm_state->vm_resume_work);
		WARN_ON_ONCE(kbase_pm_driver_suspend(kbdev));
		mutex_lock(&arb_vm_state->vm_state_lock);
		break;
	case KBASE_VM_STATE_SUSPENDED:
		break;
	default:
		KBASE_DEBUG_ASSERT_MSG(false, "Unexpected state to suspend");
		break;
	}
}

/**
 * kbase_arbiter_pm_vm_os_resume() - Resume OS function once it receives
 *                                   a grant message from arbiter
 * @kbdev: The kbase device structure for the device
 *
 * Resume OS function once it receives GRANT message
 * from Arbiter asynchronously.
 */
static void kbase_arbiter_pm_vm_os_resume(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;

	lockdep_assert_held(&arb_vm_state->vm_state_lock);
	KBASE_DEBUG_ASSERT_MSG(arb_vm_state->vm_state == KBASE_VM_STATE_SUSPENDED,
			       "Unexpected state to resume");

	kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_STOPPED_GPU_REQUESTED);
	kbase_arbif_gpu_request(kbdev);
	start_request_timer(kbdev);

	/* Release lock and block resume OS function until we have
	 * asynchronously received the GRANT message from the Arbiter and
	 * fully resumed
	 */
	mutex_unlock(&arb_vm_state->vm_state_lock);
	kbase_arbiter_pm_vm_wait_gpu_assignment(kbdev);
	flush_work(&arb_vm_state->vm_resume_work);
	mutex_lock(&arb_vm_state->vm_state_lock);
}

/**
 * kbase_arbiter_pm_vm_event() - Dispatch VM event to the state machine.
 * @kbdev: The kbase device structure for the device
 * @evt: VM event
 *
 * The state machine function. Receives events and transitions states
 * according the event received and the current state
 */
void kbase_arbiter_pm_vm_event(struct kbase_device *kbdev, enum kbase_arbif_evt evt)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;

	if (!kbase_has_arbiter(kbdev))
		return;

	mutex_lock(&arb_vm_state->vm_state_lock);
	dev_dbg(kbdev->dev, "%s %s\n", __func__, kbase_arbiter_pm_vm_event_str(evt));
	if (arb_vm_state->vm_state != KBASE_VM_STATE_INITIALIZING_WITH_GPU &&
	    arb_vm_state->vm_state != KBASE_VM_STATE_INITIALIZING)
		KBASE_KTRACE_ADD(kbdev, ARB_VM_EVT, NULL, evt);
	switch (evt) {
	case KBASE_VM_GPU_GRANTED_EVT:
		kbase_arbiter_pm_vm_gpu_start(kbdev);
		break;
	case KBASE_VM_GPU_STOP_EVT:
		kbase_arbiter_pm_vm_gpu_stop(kbdev);
		break;
	case KBASE_VM_GPU_LOST_EVT:
		dev_dbg(kbdev->dev, "KBASE_ARBIF_GPU_LOST_EVT!");
		kbase_gpu_lost(kbdev);
		break;
	case KBASE_VM_OS_SUSPEND_EVENT:
		kbase_arbiter_pm_vm_os_prepare_suspend(kbdev);
		break;
	case KBASE_VM_OS_RESUME_EVENT:
		kbase_arbiter_pm_vm_os_resume(kbdev);
		break;
	case KBASE_VM_GPU_IDLE_EVENT:
		switch (arb_vm_state->vm_state) {
		case KBASE_VM_STATE_ACTIVE:
			kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_IDLE);
			kbase_arbif_gpu_idle(kbdev);
			break;
		default:
			break;
		}
		break;

	case KBASE_VM_REF_EVENT:
		switch (arb_vm_state->vm_state) {
		case KBASE_VM_STATE_STARTING:
		case KBASE_VM_STATE_IDLE:
			kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_ACTIVE);
			kbase_arbif_gpu_active(kbdev);
			break;
		case KBASE_VM_STATE_STOPPING_IDLE:
			kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_STOPPING_ACTIVE);
			break;
		default:
			break;
		}
		break;

	case KBASE_VM_GPU_INITIALIZED_EVT:
		switch (arb_vm_state->vm_state) {
		case KBASE_VM_STATE_INITIALIZING_WITH_GPU:
			lockdep_assert_held(&kbdev->pm.lock);
			if (kbdev->pm.active_count > 0) {
				kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_ACTIVE);
				kbase_arbif_gpu_active(kbdev);
			} else {
				kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_IDLE);
				kbase_arbif_gpu_idle(kbdev);
			}
			break;
		default:
			break;
		}
		break;

	default:
		dev_err(kbdev->dev, "Got Unknown Event!");
		break;
	}
	mutex_unlock(&arb_vm_state->vm_state_lock);
}

KBASE_EXPORT_TEST_API(kbase_arbiter_pm_vm_event);

/**
 * kbase_arbiter_pm_vm_wait_gpu_assignment() - VM wait for a GPU assignment.
 * @kbdev: The kbase device structure for the device
 *
 * VM waits for a GPU assignment.
 */
static void kbase_arbiter_pm_vm_wait_gpu_assignment(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;

	dev_dbg(kbdev->dev, "Waiting for GPU assignment...\n");
	wait_event(arb_vm_state->vm_state_wait,
		   arb_vm_state->vm_state == KBASE_VM_STATE_IDLE ||
			   arb_vm_state->vm_state == KBASE_VM_STATE_ACTIVE);
	dev_dbg(kbdev->dev, "Waiting for GPU assignment - done\n");
}

/**
 * kbase_arbiter_pm_vm_gpu_assigned_locked() - Check if VM holds VM state lock
 * @kbdev: The kbase device structure for the device
 *
 * Checks if the virtual machine holds VM state lock.
 *
 * Return: true if GPU is assigned, else false.
 */
static inline bool kbase_arbiter_pm_vm_gpu_assigned_locked(struct kbase_device *kbdev)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;

	lockdep_assert_held(&arb_vm_state->vm_state_lock);
	return (arb_vm_state->vm_state == KBASE_VM_STATE_IDLE ||
		arb_vm_state->vm_state == KBASE_VM_STATE_ACTIVE);
}

/**
 * kbase_arbiter_pm_ctx_active_handle_suspend() - Handle suspend operation for
 *                                                arbitration mode
 * @kbdev: The kbase device structure for the device
 * @suspend_handler: The handler code for how to handle a suspend
 *                   that might occur
 * @sched_lock_held: Flag variable that tells whether the caller grabs the
 *                   scheduler lock or not
 *
 * This function handles a suspend event from the driver,
 * communicating with the arbiter and waiting synchronously for the GPU
 * to be granted again depending on the VM state. Returns immediately
 * with success if there is no Arbiter.
 *
 * Return: 0 on success else 1 suspend handler isn not possible.
 */
int kbase_arbiter_pm_ctx_active_handle_suspend(struct kbase_device *kbdev,
					       enum kbase_pm_suspend_handler suspend_handler,
					       bool sched_lock_held)
{
	struct kbase_arbiter_vm_state *arb_vm_state = kbdev->pm.arb_vm_state;
	int res = 0;

#if !MALI_USE_CSF
	CSTD_UNUSED(sched_lock_held);
#endif

	if (!kbase_has_arbiter(kbdev))
		return res;

	mutex_lock(&arb_vm_state->vm_state_lock);
	while (!kbase_arbiter_pm_vm_gpu_assigned_locked(kbdev)) {
		/* Update VM state since we have GPU work to do */
		if (arb_vm_state->vm_state == KBASE_VM_STATE_STOPPING_IDLE)
			kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_STOPPING_ACTIVE);
		else if (arb_vm_state->vm_state == KBASE_VM_STATE_STOPPED) {
			kbase_arbiter_pm_vm_set_state(kbdev, KBASE_VM_STATE_STOPPED_GPU_REQUESTED);
			kbase_arbif_gpu_request(kbdev);
			start_request_timer(kbdev);
		} else if (arb_vm_state->vm_state == KBASE_VM_STATE_INITIALIZING_WITH_GPU)
			break;

		if (suspend_handler != KBASE_PM_SUSPEND_HANDLER_NOT_POSSIBLE) {
			/* In case of GPU lost, even if
			 * active_count > 0, we no longer have GPU
			 * access
			 */
			if (kbase_pm_is_gpu_lost(kbdev))
				res = 1;

			switch (suspend_handler) {
			case KBASE_PM_SUSPEND_HANDLER_DONT_INCREASE:
				res = 1;
				break;
			case KBASE_PM_SUSPEND_HANDLER_DONT_REACTIVATE:
				if (kbdev->pm.active_count == 0)
					res = 1;
				break;
			case KBASE_PM_SUSPEND_HANDLER_VM_GPU_GRANTED:
				break;
			default:
				WARN(1, "Unknown suspend_handler\n");
				res = 1;
				break;
			}
			break;
		}

		/* Need to synchronously wait for GPU assignment */
		atomic_inc(&kbdev->pm.gpu_users_waiting);
		mutex_unlock(&arb_vm_state->vm_state_lock);
		kbase_pm_unlock(kbdev);
#if MALI_USE_CSF
		if (sched_lock_held)
			kbase_csf_scheduler_unlock(kbdev);
#endif
		kbase_arbiter_pm_vm_wait_gpu_assignment(kbdev);
#if MALI_USE_CSF
		if (sched_lock_held)
			kbase_csf_scheduler_lock(kbdev);
#endif
		kbase_pm_lock(kbdev);
		mutex_lock(&arb_vm_state->vm_state_lock);
		atomic_dec(&kbdev->pm.gpu_users_waiting);
	}
	mutex_unlock(&arb_vm_state->vm_state_lock);

	return res;
}

/**
 * kbase_arbiter_pm_update_gpu_freq() - Updates GPU clock frequency received
 * from arbiter.
 * @arb_freq: Pointer to struchture holding GPU clock frequenecy data
 * @freq: New frequency value in KHz
 */
void kbase_arbiter_pm_update_gpu_freq(struct kbase_arbiter_freq *arb_freq, uint32_t freq)
{
	struct kbase_gpu_clk_notifier_data ndata;

	mutex_lock(&arb_freq->arb_freq_lock);
	if (arb_freq->arb_freq != freq) {
		ndata.new_rate = (unsigned long)freq * KHZ_TO_HZ;
		ndata.old_rate = (unsigned long)arb_freq->arb_freq * KHZ_TO_HZ;
		ndata.gpu_clk_handle = arb_freq;
		arb_freq->arb_freq = freq;
		arb_freq->freq_updated = true;
		if (arb_freq->nb)
			arb_freq->nb->notifier_call(arb_freq->nb, POST_RATE_CHANGE, &ndata);
	}

	mutex_unlock(&arb_freq->arb_freq_lock);
}

/**
 * get_arb_gpu_clk() - Enumerate a GPU clock on the given index
 * @kbdev: kbase_device pointer
 * @index: GPU clock index
 *
 * Return: Pointer to structure holding GPU clock frequency data reported from
 * arbiter, only index 0 is valid.
 */
static void *get_arb_gpu_clk(struct kbase_device *kbdev, unsigned int index)
{
	if (index == 0)
		return &kbdev->arb.arb_freq;
	return NULL;
}

/**
 * get_arb_gpu_clk_rate() - Get the current rate of GPU clock frequency value
 * @kbdev:          kbase_device pointer
 * @gpu_clk_handle: Handle unique to the enumerated GPU clock
 *
 * Return: The GPU clock frequency value saved when gpu is granted from arbiter
 */
static unsigned long get_arb_gpu_clk_rate(struct kbase_device *kbdev, void *gpu_clk_handle)
{
	uint32_t freq;
	struct kbase_arbiter_freq *arb_dev_freq = (struct kbase_arbiter_freq *)gpu_clk_handle;

	mutex_lock(&arb_dev_freq->arb_freq_lock);
	/* Convert from KHz to Hz */
	freq = arb_dev_freq->arb_freq * KHZ_TO_HZ;
	mutex_unlock(&arb_dev_freq->arb_freq_lock);
	return freq;
}

/**
 * arb_gpu_clk_notifier_register() - Register a clock rate change notifier.
 * @kbdev:           kbase_device pointer
 * @gpu_clk_handle:  Handle unique to the enumerated GPU clock
 * @nb:              notifier block containing the callback function pointer
 *
 * This function registers a callback function that is invoked whenever the
 * frequency of the clock corresponding to @gpu_clk_handle changes.
 *
 * Return: 0 on success, negative error code otherwise.
 */
static int arb_gpu_clk_notifier_register(struct kbase_device *kbdev, void *gpu_clk_handle,
					 struct notifier_block *nb)
{
	int ret = 0;
	struct kbase_arbiter_freq *arb_dev_freq = (struct kbase_arbiter_freq *)gpu_clk_handle;

	if (!arb_dev_freq->nb)
		arb_dev_freq->nb = nb;
	else
		ret = -EBUSY;

	return ret;
}

/**
 * arb_gpu_clk_notifier_unregister() - Unregister clock rate change notifier
 * @kbdev:           kbase_device pointer
 * @gpu_clk_handle:  Handle unique to the enumerated GPU clock
 * @nb:              notifier block containing the callback function pointer
 *
 * This function pointer is used to unregister a callback function that
 * was previously registered to get notified of a frequency change of the
 * clock corresponding to @gpu_clk_handle.
 */
static void arb_gpu_clk_notifier_unregister(struct kbase_device *kbdev, void *gpu_clk_handle,
					    struct notifier_block *nb)
{
	struct kbase_arbiter_freq *arb_dev_freq = (struct kbase_arbiter_freq *)gpu_clk_handle;
	if (arb_dev_freq->nb == nb) {
		arb_dev_freq->nb = NULL;
	} else {
		dev_err(kbdev->dev, "%s - notifier did not match\n", __func__);
	}
}

struct kbase_clk_rate_trace_op_conf arb_clk_rate_trace_ops = {
	.get_gpu_clk_rate = get_arb_gpu_clk_rate,
	.enumerate_gpu_clk = get_arb_gpu_clk,
	.gpu_clk_notifier_register = arb_gpu_clk_notifier_register,
	.gpu_clk_notifier_unregister = arb_gpu_clk_notifier_unregister
};
