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

#include <mali_kbase.h>
#include <linux/slab.h>
#include <csf/mali_kbase_csf_registers.h>
#include <csf/mali_kbase_csf_firmware.h>
#include <backend/gpu/mali_kbase_pm_internal.h>
#include <linux/mali_kbase_debug_coresight_csf.h>
#include <debug/backend/mali_kbase_debug_coresight_internal_csf.h>

static const char *coresight_state_to_string(enum kbase_debug_coresight_csf_state state)
{
	switch (state) {
	case KBASE_DEBUG_CORESIGHT_CSF_DISABLED:
		return "DISABLED";
	case KBASE_DEBUG_CORESIGHT_CSF_ENABLED:
		return "ENABLED";
	default:
		break;
	}

	return "UNKNOWN";
}

static bool validate_reg_addr(struct kbase_debug_coresight_csf_client *client,
			      struct kbase_device *kbdev, u32 reg_addr, u8 op_type)
{
	int i;

	if (reg_addr & 0x3) {
		dev_err(kbdev->dev, "Invalid operation %d: reg_addr (0x%x) not 32bit aligned",
			op_type, reg_addr);
		return false;
	}

	for (i = 0; i < client->nr_ranges; i++) {
		struct kbase_debug_coresight_csf_address_range *range = &client->addr_ranges[i];

		if ((range->start <= reg_addr) && (reg_addr <= range->end))
			return true;
	}

	dev_err(kbdev->dev, "Invalid operation %d: reg_addr (0x%x) not in client range", op_type,
		reg_addr);

	return false;
}

static bool validate_op(struct kbase_debug_coresight_csf_client *client,
			struct kbase_debug_coresight_csf_op *op)
{
	struct kbase_device *kbdev;
	u32 reg;

	if (!op)
		return false;

	if (!client)
		return false;

	kbdev = (struct kbase_device *)client->drv_data;

	switch (op->type) {
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_NOP:
		return true;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_WRITE_IMM:
		if (validate_reg_addr(client, kbdev, op->op.write_imm.reg_addr, op->type))
			return true;

		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_WRITE_IMM_RANGE:
		for (reg = op->op.write_imm_range.reg_start; reg <= op->op.write_imm_range.reg_end;
		     reg += sizeof(u32)) {
			if (!validate_reg_addr(client, kbdev, reg, op->type))
				return false;
		}

		return true;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_WRITE:
		if (!op->op.write.ptr) {
			dev_err(kbdev->dev, "Invalid operation %d: ptr not set", op->type);
			break;
		}

		if (validate_reg_addr(client, kbdev, op->op.write.reg_addr, op->type))
			return true;

		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_READ:
		if (!op->op.read.ptr) {
			dev_err(kbdev->dev, "Invalid operation %d: ptr not set", op->type);
			break;
		}

		if (validate_reg_addr(client, kbdev, op->op.read.reg_addr, op->type))
			return true;

		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_POLL:
		if (validate_reg_addr(client, kbdev, op->op.poll.reg_addr, op->type))
			return true;

		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_BIT_AND:
		fallthrough;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_BIT_OR:
		fallthrough;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_BIT_XOR:
		fallthrough;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_BIT_NOT:
		if (op->op.bitw.ptr != NULL)
			return true;

		dev_err(kbdev->dev, "Invalid bitwise operation pointer");

		break;
	default:
		dev_err(kbdev->dev, "Invalid operation %d", op->type);
		break;
	}

	return false;
}

static bool validate_seq(struct kbase_debug_coresight_csf_client *client,
			 struct kbase_debug_coresight_csf_sequence *seq)
{
	struct kbase_debug_coresight_csf_op *ops = seq->ops;
	int nr_ops = seq->nr_ops;
	int i;

	for (i = 0; i < nr_ops; i++) {
		if (!validate_op(client, &ops[i]))
			return false;
	}

	return true;
}

static int execute_op(struct kbase_device *kbdev, struct kbase_debug_coresight_csf_op *op)
{
	int result = -EINVAL;
	u32 reg;

	dev_dbg(kbdev->dev, "Execute operation %d", op->type);

	switch (op->type) {
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_NOP:
		result = 0;
		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_WRITE_IMM:
		result = kbase_csf_firmware_mcu_register_write(kbdev, op->op.write.reg_addr,
							       op->op.write_imm.val);
		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_WRITE_IMM_RANGE:
		for (reg = op->op.write_imm_range.reg_start; reg <= op->op.write_imm_range.reg_end;
		     reg += sizeof(u32)) {
			result = kbase_csf_firmware_mcu_register_write(kbdev, reg,
								       op->op.write_imm_range.val);
			if (!result)
				break;
		}
		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_WRITE:
		result = kbase_csf_firmware_mcu_register_write(kbdev, op->op.write.reg_addr,
							       *op->op.write.ptr);
		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_READ:
		result = kbase_csf_firmware_mcu_register_read(kbdev, op->op.read.reg_addr,
							      op->op.read.ptr);
		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_POLL:
		result = kbase_csf_firmware_mcu_register_poll(kbdev, op->op.poll.reg_addr,
							      op->op.poll.mask, op->op.poll.val);
		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_BIT_AND:
		*op->op.bitw.ptr &= op->op.bitw.val;
		result = 0;
		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_BIT_OR:
		*op->op.bitw.ptr |= op->op.bitw.val;
		result = 0;
		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_BIT_XOR:
		*op->op.bitw.ptr ^= op->op.bitw.val;
		result = 0;
		break;
	case KBASE_DEBUG_CORESIGHT_CSF_OP_TYPE_BIT_NOT:
		*op->op.bitw.ptr = ~(*op->op.bitw.ptr);
		result = 0;
		break;
	default:
		dev_err(kbdev->dev, "Invalid operation %d", op->type);
		break;
	}

	return result;
}

static int coresight_config_enable(struct kbase_device *kbdev,
				   struct kbase_debug_coresight_csf_config *config)
{
	bool glb_init_request_pending;
	unsigned long flags;
	int ret = 0;
	int i;

	spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
	glb_init_request_pending = kbdev->csf.glb_init_request_pending;
	spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);

	if (!config || glb_init_request_pending)
		return -EINVAL;

	if (config->state == KBASE_DEBUG_CORESIGHT_CSF_ENABLED)
		return ret;

	for (i = 0; config->enable_seq && !ret && i < config->enable_seq->nr_ops; i++)
		ret = execute_op(kbdev, &config->enable_seq->ops[i]);

	if (!ret) {
		dev_dbg(kbdev->dev, "Coresight config (0x%pK) state transition: %s to %s", config,
			coresight_state_to_string(config->state),
			coresight_state_to_string(KBASE_DEBUG_CORESIGHT_CSF_ENABLED));
		config->state = KBASE_DEBUG_CORESIGHT_CSF_ENABLED;
	}

	/* Always assign the return code during config enable.
	 * It gets propagated when calling config disable.
	 */
	config->error = ret;

	return ret;
}

static int coresight_config_disable(struct kbase_device *kbdev,
				    struct kbase_debug_coresight_csf_config *config)
{
	bool glb_init_request_pending;
	unsigned long flags;
	int ret = 0;
	int i;

	spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
	glb_init_request_pending = kbdev->csf.glb_init_request_pending;
	spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);

	if (!config || glb_init_request_pending)
		return -EINVAL;

	if (config->state == KBASE_DEBUG_CORESIGHT_CSF_DISABLED)
		return ret;

	for (i = 0; config->disable_seq && !ret && i < config->disable_seq->nr_ops; i++)
		ret = execute_op(kbdev, &config->disable_seq->ops[i]);

	if (!ret) {
		dev_dbg(kbdev->dev, "Coresight config (0x%pK) state transition: %s to %s", config,
			coresight_state_to_string(config->state),
			coresight_state_to_string(KBASE_DEBUG_CORESIGHT_CSF_DISABLED));
		config->state = KBASE_DEBUG_CORESIGHT_CSF_DISABLED;
	} else {
		/* Only assign the error if ret is not 0.
		 * As we don't want to overwrite an error from config enable
		 */
		if (!config->error)
			config->error = ret;
	}

	return ret;
}

void *kbase_debug_coresight_csf_register(void *drv_data,
					 struct kbase_debug_coresight_csf_address_range *ranges,
					 int nr_ranges)
{
	struct kbase_debug_coresight_csf_client *client, *client_entry;
	struct kbase_device *kbdev;
	unsigned long flags;
	int k;

	if (unlikely(!drv_data)) {
		pr_err("NULL drv_data");
		return NULL;
	}

	kbdev = (struct kbase_device *)drv_data;

	if (unlikely(!ranges)) {
		dev_err(kbdev->dev, "NULL ranges");
		return NULL;
	}

	if (unlikely(!nr_ranges)) {
		dev_err(kbdev->dev, "nr_ranges is 0");
		return NULL;
	}

	for (k = 0; k < nr_ranges; k++) {
		if (ranges[k].end < ranges[k].start) {
			dev_err(kbdev->dev, "Invalid address ranges 0x%08x - 0x%08x",
				ranges[k].start, ranges[k].end);
			return NULL;
		}
	}

	client = kzalloc(sizeof(struct kbase_debug_coresight_csf_client), GFP_KERNEL);

	if (!client)
		return NULL;

	spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);
	list_for_each_entry(client_entry, &kbdev->csf.coresight.clients, link) {
		struct kbase_debug_coresight_csf_address_range *client_ranges =
			client_entry->addr_ranges;
		int i;

		for (i = 0; i < client_entry->nr_ranges; i++) {
			int j;

			for (j = 0; j < nr_ranges; j++) {
				if ((ranges[j].start < client_ranges[i].end) &&
				    (client_ranges[i].start < ranges[j].end)) {
					spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);
					kfree(client);
					dev_err(kbdev->dev,
						"Client with range 0x%08x - 0x%08x already present at address range 0x%08x - 0x%08x",
						client_ranges[i].start, client_ranges[i].end,
						ranges[j].start, ranges[j].end);

					return NULL;
				}
			}
		}
	}

	client->drv_data = drv_data;
	client->addr_ranges = ranges;
	client->nr_ranges = nr_ranges;
	list_add(&client->link, &kbdev->csf.coresight.clients);
	spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);

	return client;
}
EXPORT_SYMBOL(kbase_debug_coresight_csf_register);

void kbase_debug_coresight_csf_unregister(void *client_data)
{
	struct kbase_debug_coresight_csf_client *client;
	struct kbase_debug_coresight_csf_config *config_entry;
	struct kbase_device *kbdev;
	unsigned long flags;
	bool retry = true;

	if (unlikely(!client_data)) {
		pr_err("NULL client");
		return;
	}

	client = (struct kbase_debug_coresight_csf_client *)client_data;

	kbdev = (struct kbase_device *)client->drv_data;
	if (unlikely(!kbdev)) {
		pr_err("NULL drv_data in client");
		return;
	}

	/* check for active config from client */
	spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);
	list_del_init(&client->link);

	while (retry && !list_empty(&kbdev->csf.coresight.configs)) {
		retry = false;
		list_for_each_entry(config_entry, &kbdev->csf.coresight.configs, link) {
			if (config_entry->client == client) {
				spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);
				kbase_debug_coresight_csf_config_free(config_entry);
				spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);
				retry = true;
				break;
			}
		}
	}
	spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);

	kfree(client);
}
EXPORT_SYMBOL(kbase_debug_coresight_csf_unregister);

void *kbase_debug_coresight_csf_config_create(
	void *client_data, struct kbase_debug_coresight_csf_sequence *enable_seq,
	struct kbase_debug_coresight_csf_sequence *disable_seq, bool pre_post)
{
	struct kbase_debug_coresight_csf_client *client;
	struct kbase_debug_coresight_csf_config *config;
	struct kbase_device *kbdev;

	if (unlikely(!client_data)) {
		pr_err("NULL client");
		return NULL;
	}

	client = (struct kbase_debug_coresight_csf_client *)client_data;

	kbdev = (struct kbase_device *)client->drv_data;
	if (unlikely(!kbdev)) {
		pr_err("NULL drv_data in client");
		return NULL;
	}

	if (enable_seq) {
		if (!validate_seq(client, enable_seq)) {
			dev_err(kbdev->dev, "Invalid enable_seq");
			return NULL;
		}
	}

	if (disable_seq) {
		if (!validate_seq(client, disable_seq)) {
			dev_err(kbdev->dev, "Invalid disable_seq");
			return NULL;
		}
	}

	config = kzalloc(sizeof(struct kbase_debug_coresight_csf_config), GFP_KERNEL);
	if (WARN_ON(!config))
		return NULL;

	config->client = client;
	config->enable_seq = enable_seq;
	config->disable_seq = disable_seq;
	config->error = 0;
	config->is_pre_post_all = pre_post;
	config->state = KBASE_DEBUG_CORESIGHT_CSF_DISABLED;

	INIT_LIST_HEAD(&config->link);

	return config;
}
EXPORT_SYMBOL(kbase_debug_coresight_csf_config_create);

void kbase_debug_coresight_csf_config_free(void *config_data)
{
	struct kbase_debug_coresight_csf_config *config;

	if (unlikely(!config_data)) {
		pr_err("NULL config");
		return;
	}

	config = (struct kbase_debug_coresight_csf_config *)config_data;

	kbase_debug_coresight_csf_config_disable(config);

	kfree(config);
}
EXPORT_SYMBOL(kbase_debug_coresight_csf_config_free);

int kbase_debug_coresight_csf_config_enable(void *config_data)
{
	struct kbase_debug_coresight_csf_config *config;
	struct kbase_debug_coresight_csf_client *client;
	struct kbase_device *kbdev;
	struct kbase_debug_coresight_csf_config *config_entry;
	unsigned long flags;
	int ret = 0;

	if (unlikely(!config_data)) {
		pr_err("NULL config");
		return -EINVAL;
	}

	config = (struct kbase_debug_coresight_csf_config *)config_data;
	client = (struct kbase_debug_coresight_csf_client *)config->client;

	if (unlikely(!client)) {
		pr_err("NULL client in config");
		return -EINVAL;
	}

	kbdev = (struct kbase_device *)client->drv_data;
	if (unlikely(!kbdev)) {
		pr_err("NULL drv_data in client");
		return -EINVAL;
	}

	/* Check to prevent double entry of config */
	spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);
	list_for_each_entry(config_entry, &kbdev->csf.coresight.configs, link) {
		if (config_entry == config) {
			spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);
			dev_err(kbdev->dev, "Config already enabled");
			return -EINVAL;
		}
	}
	spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);

	kbase_csf_scheduler_lock(kbdev);
	kbase_csf_scheduler_spin_lock(kbdev, &flags);

	/* Check the state of Scheduler to confirm the desired state of MCU */
	if (((kbdev->csf.scheduler.state != SCHED_SUSPENDED) &&
	     (kbdev->csf.scheduler.state != SCHED_SLEEPING) &&
	     !kbase_csf_scheduler_protected_mode_in_use(kbdev)) ||
	    kbase_pm_get_policy(kbdev) == &kbase_pm_always_on_policy_ops) {
		kbase_csf_scheduler_spin_unlock(kbdev, flags);
		/* Wait for MCU to reach the stable ON state */
		ret = kbase_pm_wait_for_desired_state(kbdev);

		if (ret)
			dev_err(kbdev->dev,
				"Wait for PM state failed when enabling coresight config");
		else
			ret = coresight_config_enable(kbdev, config);

		kbase_csf_scheduler_spin_lock(kbdev, &flags);
	}

	/* Add config to next enable sequence */
	if (!ret) {
		spin_lock(&kbdev->csf.coresight.lock);
		list_add(&config->link, &kbdev->csf.coresight.configs);
		spin_unlock(&kbdev->csf.coresight.lock);
	}

	kbase_csf_scheduler_spin_unlock(kbdev, flags);
	kbase_csf_scheduler_unlock(kbdev);

	return ret;
}
EXPORT_SYMBOL(kbase_debug_coresight_csf_config_enable);

int kbase_debug_coresight_csf_config_disable(void *config_data)
{
	struct kbase_debug_coresight_csf_config *config, *pre_post_config;
	struct kbase_debug_coresight_csf_client *client;
	struct kbase_device *kbdev;
	struct kbase_debug_coresight_csf_config *config_entry;
	bool found_in_list = false;
	bool config_deleted = false;
	unsigned long flags;
	int ret = 0;

	if (unlikely(!config_data)) {
		pr_err("NULL config");
		return -EINVAL;
	}

	config = (struct kbase_debug_coresight_csf_config *)config_data;

	/* Exit early if not enabled prior */
	if (list_empty(&config->link))
		return ret;

	client = (struct kbase_debug_coresight_csf_client *)config->client;

	if (unlikely(!client)) {
		pr_err("NULL client in config");
		return -EINVAL;
	}

	kbdev = (struct kbase_device *)client->drv_data;
	if (unlikely(!kbdev)) {
		pr_err("NULL drv_data in client");
		return -EINVAL;
	}

	/* Check if the config is in the correct list */
	spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);
	list_for_each_entry(config_entry, &kbdev->csf.coresight.configs, link) {
		if (config_entry == config) {
			found_in_list = true;
			break;
		}
	}
	spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);

	if (!found_in_list) {
		dev_err(kbdev->dev, "Config looks corrupted");
		return -EINVAL;
	}

	kbase_csf_scheduler_lock(kbdev);
	kbase_csf_scheduler_spin_lock(kbdev, &flags);

	/* Check the state of Scheduler to confirm the desired state of MCU */
	if (((kbdev->csf.scheduler.state != SCHED_SUSPENDED) &&
	     (kbdev->csf.scheduler.state != SCHED_SLEEPING) &&
	     !kbase_csf_scheduler_protected_mode_in_use(kbdev)) ||
	    kbase_pm_get_policy(kbdev) == &kbase_pm_always_on_policy_ops) {
		kbase_csf_scheduler_spin_unlock(kbdev, flags);
		/* Wait for MCU to reach the stable ON state */
		ret = kbase_pm_wait_for_desired_state(kbdev);

		if (ret)
			dev_err(kbdev->dev,
				"Wait for PM state failed when disabling coresight config");
		else
			ret = coresight_config_disable(kbdev, config);

		kbase_csf_scheduler_spin_lock(kbdev, &flags);

		if (!ret) {
			spin_lock(&kbdev->csf.coresight.lock);
			list_del_init(&config->link);
			config_deleted = true;

			/* Check if post disable config exists. It has to be explicitly
			 * executed as the last one.
			 */
			if (list_count_nodes(&kbdev->csf.coresight.configs) == 1) {
				pre_post_config = list_first_entry(
					&kbdev->csf.coresight.configs,
					struct kbase_debug_coresight_csf_config, link);
				if (pre_post_config->is_pre_post_all) {
					spin_unlock(&kbdev->csf.coresight.lock);
					kbase_csf_scheduler_spin_unlock(kbdev, flags);

					ret = coresight_config_disable(kbdev, pre_post_config);

					kbase_csf_scheduler_spin_lock(kbdev, &flags);
					spin_lock(&kbdev->csf.coresight.lock);
					list_del_init(&pre_post_config->link);
				}
			}
			spin_unlock(&kbdev->csf.coresight.lock);
		}
	} else if (kbdev->pm.backend.mcu_state == KBASE_MCU_OFF) {
		/* MCU is OFF, so the disable sequence was already executed.
		 *
		 * Propagate any error that would have occurred during the enable
		 * or disable sequence.
		 *
		 * This is done as part of the disable sequence, since the call from
		 * client is synchronous.
		 */
		ret = config->error;
	}

	/* Remove config from next disable sequence */
	if (!config_deleted) {
		spin_lock(&kbdev->csf.coresight.lock);
		list_del_init(&config->link);
		spin_unlock(&kbdev->csf.coresight.lock);
	}

	kbase_csf_scheduler_spin_unlock(kbdev, flags);
	kbase_csf_scheduler_unlock(kbdev);

	return ret;
}
EXPORT_SYMBOL(kbase_debug_coresight_csf_config_disable);

static void coresight_config_enable_all(struct work_struct *data)
{
	struct kbase_device *kbdev =
		container_of(data, struct kbase_device, csf.coresight.enable_work);
	struct kbase_debug_coresight_csf_config *config_entry;
	unsigned long flags;

	spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);

	/* Check whether pre_post_config exists and enable it as the first one */
	list_for_each_entry(config_entry, &kbdev->csf.coresight.configs, link) {
		if (config_entry->is_pre_post_all) {
			spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);
			if (coresight_config_enable(kbdev, config_entry))
				dev_err(kbdev->dev, "enable pre_post_config (0x%pK) failed",
					config_entry);
			spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);
			break;
		}
	}

	list_for_each_entry(config_entry, &kbdev->csf.coresight.configs, link) {
		if (config_entry->is_pre_post_all)
			continue;

		spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);
		if (coresight_config_enable(kbdev, config_entry))
			dev_err(kbdev->dev, "enable config (0x%pK) failed", config_entry);
		spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);
	}

	spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);

	spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
	kbase_pm_update_state(kbdev);
	spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);

	wake_up_all(&kbdev->csf.coresight.event_wait);
}

static void coresight_config_disable_all(struct work_struct *data)
{
	struct kbase_device *kbdev =
		container_of(data, struct kbase_device, csf.coresight.disable_work);
	struct kbase_debug_coresight_csf_config *config_entry;
	struct kbase_debug_coresight_csf_config *pre_post_config = NULL;
	unsigned long flags;

	spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);

	list_for_each_entry(config_entry, &kbdev->csf.coresight.configs, link) {
		/* If pre_post_config exists, disable it as the last one */
		if (!pre_post_config && config_entry->is_pre_post_all) {
			pre_post_config = config_entry;
			continue;
		}

		spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);
		if (coresight_config_disable(kbdev, config_entry))
			dev_err(kbdev->dev, "disable config (0x%pK) failed", config_entry);
		spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);
	}

	spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);

	if (pre_post_config) {
		if (coresight_config_disable(kbdev, pre_post_config))
			dev_err(kbdev->dev, "disable pre_post_config (0x%pK) failed",
				pre_post_config);
	}

	spin_lock_irqsave(&kbdev->hwaccess_lock, flags);
	kbase_pm_update_state(kbdev);
	spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);

	wake_up_all(&kbdev->csf.coresight.event_wait);
}

void kbase_debug_coresight_csf_disable_pmode_enter(struct kbase_device *kbdev)
{
	unsigned long flags;

	dev_dbg(kbdev->dev, "Coresight state %s before protected mode enter",
		coresight_state_to_string(KBASE_DEBUG_CORESIGHT_CSF_ENABLED));

	lockdep_assert_held(&kbdev->csf.scheduler.lock);

	kbase_pm_lock(kbdev);
	spin_lock_irqsave(&kbdev->hwaccess_lock, flags);

	kbdev->csf.coresight.disable_on_pmode_enter = true;
	kbdev->csf.coresight.enable_on_pmode_exit = false;
	kbase_pm_update_state(kbdev);

	spin_unlock_irqrestore(&kbdev->hwaccess_lock, flags);

	kbase_pm_wait_for_desired_state(kbdev);

	kbase_pm_unlock(kbdev);
}

void kbase_debug_coresight_csf_enable_pmode_exit(struct kbase_device *kbdev)
{
	dev_dbg(kbdev->dev, "Coresight state %s after protected mode exit",
		coresight_state_to_string(KBASE_DEBUG_CORESIGHT_CSF_DISABLED));

	lockdep_assert_held(&kbdev->hwaccess_lock);

	WARN_ON(kbdev->csf.coresight.disable_on_pmode_enter);

	kbdev->csf.coresight.enable_on_pmode_exit = true;
	kbase_pm_update_state(kbdev);
}

void kbase_debug_coresight_csf_state_request(struct kbase_device *kbdev,
					     enum kbase_debug_coresight_csf_state state)
{
	if (unlikely(!kbdev))
		return;

	if (unlikely(!kbdev->csf.coresight.workq))
		return;

	dev_dbg(kbdev->dev, "Coresight state %s requested", coresight_state_to_string(state));

	switch (state) {
	case KBASE_DEBUG_CORESIGHT_CSF_DISABLED:
		queue_work(kbdev->csf.coresight.workq, &kbdev->csf.coresight.disable_work);
		break;
	case KBASE_DEBUG_CORESIGHT_CSF_ENABLED:
		queue_work(kbdev->csf.coresight.workq, &kbdev->csf.coresight.enable_work);
		break;
	default:
		dev_err(kbdev->dev, "Invalid Coresight state %d", state);
		break;
	}
}

bool kbase_debug_coresight_csf_state_check(struct kbase_device *kbdev,
					   enum kbase_debug_coresight_csf_state state)
{
	struct kbase_debug_coresight_csf_config *config_entry;
	unsigned long flags;
	bool success = true;

	dev_dbg(kbdev->dev, "Coresight check for state: %s", coresight_state_to_string(state));

	spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);

	list_for_each_entry(config_entry, &kbdev->csf.coresight.configs, link) {
		if (state != config_entry->state) {
			success = false;
			break;
		}
	}

	spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);

	return success;
}
KBASE_EXPORT_TEST_API(kbase_debug_coresight_csf_state_check);

bool kbase_debug_coresight_csf_state_wait(struct kbase_device *kbdev,
					  enum kbase_debug_coresight_csf_state state)
{
	const long wait_timeout =
		kbase_csf_timeout_in_jiffies(kbase_get_timeout_ms(kbdev, CSF_FIRMWARE_TIMEOUT));
	struct kbase_debug_coresight_csf_config *config_entry, *next_config_entry;
	unsigned long flags;
	bool success = true;

	dev_dbg(kbdev->dev, "Coresight wait for state: %s", coresight_state_to_string(state));

	spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);

	list_for_each_entry_safe(config_entry, next_config_entry, &kbdev->csf.coresight.configs,
				 link) {
		const enum kbase_debug_coresight_csf_state prev_state = config_entry->state;
		long remaining;

		spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);
		remaining = wait_event_timeout(kbdev->csf.coresight.event_wait,
					       state == config_entry->state, wait_timeout);
		spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);

		if (!remaining) {
			success = false;
			dev_err(kbdev->dev,
				"Timeout waiting for Coresight state transition %s to %s",
				coresight_state_to_string(prev_state),
				coresight_state_to_string(state));
		}
	}

	spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);

	return success;
}
KBASE_EXPORT_TEST_API(kbase_debug_coresight_csf_state_wait);

int kbase_debug_coresight_csf_init(struct kbase_device *kbdev)
{
	kbdev->csf.coresight.workq = alloc_ordered_workqueue("Mali CoreSight workqueue", 0);
	if (kbdev->csf.coresight.workq == NULL)
		return -ENOMEM;

	INIT_LIST_HEAD(&kbdev->csf.coresight.clients);
	INIT_LIST_HEAD(&kbdev->csf.coresight.configs);
	INIT_WORK(&kbdev->csf.coresight.enable_work, coresight_config_enable_all);
	INIT_WORK(&kbdev->csf.coresight.disable_work, coresight_config_disable_all);
	init_waitqueue_head(&kbdev->csf.coresight.event_wait);
	spin_lock_init(&kbdev->csf.coresight.lock);

	kbdev->csf.coresight.disable_on_pmode_enter = false;
	kbdev->csf.coresight.enable_on_pmode_exit = false;

	return 0;
}

void kbase_debug_coresight_csf_term(struct kbase_device *kbdev)
{
	struct kbase_debug_coresight_csf_client *client_entry, *next_client_entry;
	struct kbase_debug_coresight_csf_config *config_entry, *next_config_entry;
	unsigned long flags;

	kbdev->csf.coresight.disable_on_pmode_enter = false;
	kbdev->csf.coresight.enable_on_pmode_exit = false;

	cancel_work_sync(&kbdev->csf.coresight.enable_work);
	cancel_work_sync(&kbdev->csf.coresight.disable_work);
	destroy_workqueue(kbdev->csf.coresight.workq);
	kbdev->csf.coresight.workq = NULL;

	spin_lock_irqsave(&kbdev->csf.coresight.lock, flags);

	list_for_each_entry_safe(config_entry, next_config_entry, &kbdev->csf.coresight.configs,
				 link) {
		list_del_init(&config_entry->link);
		kfree(config_entry);
	}

	list_for_each_entry_safe(client_entry, next_client_entry, &kbdev->csf.coresight.clients,
				 link) {
		list_del_init(&client_entry->link);
		kfree(client_entry);
	}

	spin_unlock_irqrestore(&kbdev->csf.coresight.lock, flags);
}
