// SPDX-License-Identifier: GPL-2.0-only
/*
 * SPDX-FileCopyrightText: Copyright (c) 2019-2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
 */


#include <dce.h>
#include <dce-mailbox.h>
#include <dce-os-utils.h>
#include <dce-client-ipc-internal.h>
#include <interface/dce-core-interface-errors.h>
#include <interface/dce-interface.h>
#include <interface/dce-admin-cmds.h>

/**
 * dce_admin_ipc_wait - Waits for message from DCE.
 *
 * @d :  Pointer to tegra_dce struct.
 *
 * Return : 0 if successful
 */
int dce_admin_ipc_wait(struct tegra_dce *d)
{
	int ret = 0;

	ret = dce_wait_cond_wait_interruptible(d, &d->ipc_waits[DCE_WAIT_ADMIN_IPC], true, 0);
	if (ret) {
		/**
		 * TODO: Add error handling for abort and retry
		 */
		dce_os_err(d, "Admin IPC wait was interrupted with err:%d", ret);
		goto out;
	}

out:
	return ret;
}

/**
 * dce_admin_wakeup_ipc - wakeup process, waiting for Admin RPC
 *
 * @d :  Pointer to tegra_de struct.
 *
 * Return : Void
 */
static void dce_admin_wakeup_ipc(struct tegra_dce *d)
{
	int ret;

	ret = dce_fsm_post_event(d, EVENT_ID_DCE_ADMIN_IPC_MSG_RECEIVED, NULL);
	if (ret)
		dce_os_err(d, "Error while posting ADMIN_IPC_MSG_RECEIVED event");
}

/**
 * dce_admin_ipc_handle_signal - Isr for the CCPLEX<->DCE admin interface
 *
 * @d :  Pointer to tegra_de struct.
 *
 * Return : Void
 */
void dce_admin_ipc_handle_signal(struct tegra_dce *d, u32 ch_type)
{
	bool wakeup_needed = false;

	if (!dce_ipc_channel_is_synced(d, ch_type)) {
		/**
		 * The ivc channel is not ready yet. Exit
		 * and wait for another signal from target.
		 */
		return;
	}

	/**
	 * Channel already in sync with remote. Check if data
	 * is available to read.
	 */
	wakeup_needed = dce_ipc_is_data_available(d, ch_type);

	if (!wakeup_needed) {
		dce_os_debug(d, "Spurious signal on channel: [%d]. Ignored...",
			 ch_type);
		return;
	}

	if (ch_type == DCE_IPC_CH_KMD_TYPE_ADMIN) {
		dce_admin_wakeup_ipc(d);
	} else {
		dce_client_ipc_wakeup(d, ch_type);
	}
}

/**
 * dce_admin_ivc_channel_reset - Resets the admin ivc channel
 *
 * @d : Pointer to tegra_dce struct.
 *
 * Return : Void.
 */
void dce_admin_ivc_channel_reset(struct tegra_dce *d)
{
	dce_ipc_channel_reset(d, DCE_IPC_CH_KMD_TYPE_ADMIN);
}

/**
 * dce_admin_channel_client_get_buff_count - Get admin channel client buffer count.
 *
 * @d : Pointer to tegra_dce struct.
 * @cl_id : Admin channel client ID.
 *
 * Return : Admin channel client buffer count if client is valid,
 *		0 otherwise.
 */
static u32 dce_admin_channel_client_get_buff_count(struct tegra_dce *d, u32 cl_id)
{
	u32 count = 0;

	if (d == NULL)
		dce_os_warn(d, "Invalid tegra DCE struct");

	switch (cl_id) {
		case DCE_ADMIN_CH_CL_ADMIN_BUFF: {
			count = DCE_ADMIN_CH_CL_ADMIN_BUFF_COUNT;
			break;
		}
		case DCE_ADMIN_CH_CL_PM_BUFF: {
			count = DCE_ADMIN_CH_CL_PM_BUFF_COUNT;
			break;
		}
		case DCE_ADMIN_CH_CL_DBG_BUFF: {
			count = DCE_ADMIN_CH_CL_DBG_BUFF_COUNT;
			break;
		}
		case DCE_ADMIN_CH_CL_DBG_PERF_BUFF: {
			count = DCE_ADMIN_CH_CL_DBG_PERF_BUFF_COUNT;
			break;
		}
		default: {
			count = 0;
			break;
		}
	}

	return count;
}

/**
 * dce_admin_channel_client_buffers_deinit - De-init admin channel client buffers.
 *
 * @d : Pointer to tegra_dce struct.
 * @ch_id : Admin channel client buffer ID.
 *
 * Return : Void.
 *
 * Note: This function shall not be called concurrently with any of the following
 *	functions for the same client:
 *	- dce_admin_channel_client_buffers_init()
 *	- dce_{get/put}_admin_channel_client_buffer()
 */
void dce_admin_channel_client_buffers_deinit(struct tegra_dce *d, u32 cl_id)
{
	struct dce_admin_ch_cl_buff **cl_buff_arr = NULL;
	u32 cl_buff_count = 0;
	u32 buff_idx = 0;

	if (cl_id >= DCE_ADMIN_CH_CL_MAX) {
		dce_os_err(d, "Invalid client ID [%u]", cl_id);
		goto fail;
	}

	cl_buff_arr = d->admin_ch_cl_buff[cl_id];

	// Nothing to do for this client if the array is not allocated.
	if (!cl_buff_arr) {
		dce_os_err(d, "Trying to free unallocated buffers for client ID [%u]", cl_id);
		goto fail;
	}

	// Get number of buffers for this client.
	cl_buff_count = dce_admin_channel_client_get_buff_count(d, cl_id);

	// De-init and free actual buffer structs
	for (buff_idx = 0; buff_idx < cl_buff_count; buff_idx++) {
		struct dce_admin_ch_cl_buff *buff = cl_buff_arr[buff_idx];
		struct dce_ipc_message *pmsg = NULL;

		// Nothing to do if this buffer is not allocated.
		if (!buff)
			continue;

		// Free DCE IPC message tx and rx buffers.
		pmsg = &buff->msg;

		if (!pmsg->tx.data)
			dce_os_kfree(d, pmsg->tx.data);

		if (!pmsg->rx.data)
			dce_os_kfree(d, pmsg->rx.data);

		pmsg->tx.data = NULL;
		pmsg->rx.data = NULL;
		pmsg->tx.size = 0;
		pmsg->rx.size = 0;

		// Free the buffer.
		dce_os_kfree(d, buff);
		cl_buff_arr[buff_idx] = NULL;
	}


	// Finally, free admin channel client buffer array for this client.
	dce_os_kfree(d, cl_buff_arr);
	d->admin_ch_cl_buff[cl_id] = NULL;

fail:
	return;
}

/**
 * dce_admin_channel_client_buffers_init - Init admin channel client buffers.
 *
 * @d : Pointer tegra_dce struct.
 * @ch_id : Admin channel client buffer ID.
 *
 * Return : 0 on success, non-zero error otherwise.
 *
 * Notes:
 * 1) Since we call dce_admin_channel_client_buffers_deinit() if anything fails
 *	in this function, we need to be careful with the order of how we
 *	set allocated pointers in this function.
 *	- dce_admin_channel_client_buffers_deinit() will free any data that's set.
 *	- If this not done carefully then we will have memory leaks.
 *
 * 2) This function shall not be called concurrently with any of the following
 *	functions for the same client:
 *	- dce_admin_channel_client_buffers_deinit()
 *	- dce_{get/put}_admin_channel_client_buffer()
 */
int dce_admin_channel_client_buffers_init(struct tegra_dce *d, u32 cl_id)
{
	int ret = -1;
	struct dce_admin_ch_cl_buff **cl_buff_arr = NULL;
	u32 cl_buff_count = 0;
	u32 buff_idx = 0;

	if (cl_id >= DCE_ADMIN_CH_CL_MAX) {
		dce_os_err(d, "Invalid client ID [%u]", cl_id);
		goto fail;
	}

	// Get number of buffers for this client.
	cl_buff_count = dce_admin_channel_client_get_buff_count(d, cl_id);

	// Set to NULL so deinit works as intended.
	d->admin_ch_cl_buff[cl_id] = NULL;

	// Allocate admin channel client buffer array for this client.
	cl_buff_arr = dce_os_kzalloc(d, sizeof(*cl_buff_arr) * cl_buff_count, false);
	if (!cl_buff_arr) {
		dce_os_err(d, "Insufficient memory for admin client [%u] buff array",
				cl_id);
		goto fail;
	}

	// Set the allocated buffer array for this client.
	d->admin_ch_cl_buff[cl_id] = cl_buff_arr;

	// Finally allocate and init actual buffer structs.
	for (buff_idx = 0; buff_idx < cl_buff_count; buff_idx++) {
		struct dce_admin_ch_cl_buff *buff = NULL;
		struct dce_ipc_message *pmsg = NULL;
		dce_os_atomic_t *p_in_use = NULL;

		// Set to NULL so deinit works as intended.
		cl_buff_arr[buff_idx] = NULL;

		buff = dce_os_kzalloc(d, sizeof(*buff), false);
		if (!buff) {
			dce_os_err(d, "Insufficient memory for admin client id [%u] buff idx [%u]",
					cl_id, buff_idx);
			goto fail;
		}

		// Set the allocated buffer.
		cl_buff_arr[buff_idx] = buff;

		// Allocate DCE IPC message tx and rx buffers.
		pmsg = &buff->msg;

		// Set to NULL so deinit works as intended.
		pmsg->tx.data = NULL;

		pmsg->tx.data = dce_os_kzalloc(d, DCE_ADMIN_CMD_SIZE, false);
		if (!pmsg->tx.data) {
			dce_os_err(d, "Insufficient memory for admin msg");
			goto fail;
		}

		// Set to NULL so deinit works as intended.
		pmsg->rx.data = NULL;

		pmsg->rx.data = dce_os_kzalloc(d, DCE_ADMIN_RESP_SIZE, false);
		if (!pmsg->rx.data) {
			dce_os_err(d, "Insufficient memory for admin msg");
			goto fail;
		}

		pmsg->tx.size = DCE_ADMIN_CMD_SIZE;
		pmsg->rx.size = DCE_ADMIN_RESP_SIZE;

		// Init in_use atomic variable.
		p_in_use = &buff->in_use;
		dce_os_atomic_set(p_in_use, 0);
	}

	ret = 0;

fail:
	if (ret != 0)
		dce_admin_channel_client_buffers_deinit(d, cl_id);

	return ret;
}

/**
 * dce_admin_channel_client_buffer_get - Get dce admin msg buffer pointer
 *					for client.
 *
 * @d : Pointer to tegra_dce struct.
 * @cl_id : Admin channel client ID.
 * @flags: This is reserved argument for future use.
 *	Must be set to 0.
 *
 * Return : Pointer to admin message buffer or
 *		NULL if buffer is not available at the moment.
 *
 * Notes:
 * This function shouldn't be called concurrently with below
 * functions with same client id:
 *	- dce_admin_channel_client_buffer_get()
 *	- dce_admin_channel_client_buffers_{init/deinit}()
 *	- dce_admin_channel_client_buffer_put()
 */
struct dce_ipc_message *dce_admin_channel_client_buffer_get(
	struct tegra_dce *d, u32 cl_id, u32 flags)
{
	struct dce_ipc_message *pmsg = NULL;
	u32 cl_buff_count = 0;
	struct dce_admin_ch_cl_buff **cl_buff_arr = NULL;
	u32 buff_idx = 0;

	if (flags != 0) {
		dce_os_err(d, "flags=[%u] must be set to 0 for client [%u]",
			flags, cl_id);
		goto done;
	}

	if (cl_id >= DCE_ADMIN_CH_CL_MAX) {
		dce_os_err(d, "Invalid admin channel client ID [%u]", cl_id);
		goto done;
	}

	cl_buff_arr = d->admin_ch_cl_buff[cl_id];
	if (!cl_buff_arr) {
		dce_os_err(d, "Invalid client buff arr [%u]", cl_id);
		goto done;
	}

	cl_buff_count = dce_admin_channel_client_get_buff_count(d, cl_id);

	for (buff_idx = 0; buff_idx < cl_buff_count; buff_idx++) {
		struct dce_admin_ch_cl_buff *buff = cl_buff_arr[buff_idx];
		dce_os_atomic_t *p_in_use = NULL;
		u32 in_use = 0;

		if (!buff) {
			dce_os_err(d, "Invalid client [%u] buff [%u]", cl_id, buff_idx);
			goto done;
		}

		p_in_use = &buff->in_use;

		// Continue to next buffer if this one is in use.
		in_use = dce_os_atomic_read(p_in_use);
		if (in_use == 1)
			continue;

		// If this buffer is available then mark it as in use.
		dce_os_atomic_set(p_in_use, 1);

		// Return buffer to the caller.
		pmsg = &buff->msg;

		// Ensure that this buffer is valid.
		if (!pmsg->tx.data || !pmsg->rx.data) {
			// This should never happen so fail.
			pmsg = NULL;
		}

		// Search complete.
		goto done;
	}

done:
	return pmsg;
}

/**
 * dce_admin_channel_client_buffer_get - Release admin channel client buffer.
 *
 * @d : Pointer to tegra_dce struct.
 *
 * Return : void
 *
 * Notes:
 * This function shouldn't be called concurrently with below
 * functions with same client id:
 *	- dce_admin_channel_client_buffer_put()
 *	- dce_admin_channel_client_buffers_{init/deinit}()
 *	- dce_admin_channel_client_buffer_get()
 */
void dce_admin_channel_client_buffer_put(
	struct tegra_dce *d, struct dce_ipc_message *pmsg)
{
	struct dce_admin_ch_cl_buff *buff = NULL;
	dce_os_atomic_t *p_in_use = NULL;
	u32 in_use = 0;

	if (!d || !pmsg) {
		dce_os_err(d, "Invalid input");
		goto done;
	}

	buff = container_of(pmsg, struct dce_admin_ch_cl_buff, msg);
	if (!buff) {
		dce_os_err(d, "Invalid buffer");
		goto done;
	}

	p_in_use = &buff->in_use;

	// Verify that buffer is in use
	in_use = dce_os_atomic_read(p_in_use);
	if (in_use == 0) {
		dce_os_err(d, "Buffer not in use");
		goto done;
	}

	// Release buffer
	dce_os_atomic_set(p_in_use, 0);

done:
	return;
}

/**
 * dce_admin_channel_deinit - Cleans up the channel resources.
 *
 * @d : Pointer to tegra_dce struct
 *
 * Return : Void
 */
static void dce_admin_channel_deinit(struct tegra_dce *d)
{
	u32 loop_cnt;

	for (loop_cnt = 0; loop_cnt < DCE_IPC_CH_KMD_TYPE_MAX; loop_cnt++)
		dce_ipc_channel_deinit_unlocked(d, loop_cnt);
}


/**
 * dce_admin_channel_init - Initializes the admin ivc interface
 *
 * @d : Pointer to tegra_dce.
 *
 * Return : 0 if successful.
 */
static int dce_admin_channel_init(struct tegra_dce *d)
{
	int ret = 0;
	u32 loop_cnt;

	for (loop_cnt = 0; loop_cnt < DCE_IPC_CH_KMD_TYPE_MAX; loop_cnt++) {
		ret = dce_ipc_channel_init_unlocked(d, loop_cnt);
		if (ret) {
			dce_os_err(d, "Channel init failed for type : [%d]",
				loop_cnt);
			goto out;
		}
	}

out:
	if (ret)
		dce_admin_channel_deinit(d);
	return ret;
}

/**
 * dce_admin_init - Sets up resources managed by admin.
 *
 * @d : Pointer to tegra_dce struct.
 *
 * Return : 0 if successful
 */
int dce_admin_init(struct tegra_dce *d)
{
	int ret = 0;

	d->boot_status |= DCE_EARLY_INIT_START;
	ret = dce_os_ipc_init_region_info(d);
	if (ret) {
		dce_os_err(d, "IPC region allocation failed");
		goto err_ipc_reg_alloc;
	}

	ret = dce_admin_channel_init(d);
	if (ret) {
		dce_os_err(d, "Channel Initialization Failed");
		goto err_channel_init;
	}

	ret = dce_admin_channel_client_buffers_init(d, DCE_ADMIN_CH_CL_ADMIN_BUFF);
	if (ret) {
		dce_os_err(d, "Admin channel client buffers init failed: Admin");
		goto err_channel_clients_buff_init;
	}

	d->boot_status |= DCE_EARLY_INIT_DONE;
	return 0;

err_channel_clients_buff_init:
	dce_admin_channel_deinit(d);
err_channel_init:
	dce_os_ipc_deinit_region_info(d);
err_ipc_reg_alloc:
	d->boot_status |= DCE_EARLY_INIT_FAILED;
	return ret;
}

/**
 * dce_admin_deinit - Releases the resources
 *					associated with admin interface.
 *
 * @d : Pointer to tegra_dce struct.
 *
 * Return : Void
 */
void dce_admin_deinit(struct tegra_dce *d)
{
	dce_admin_channel_client_buffers_deinit(d, DCE_ADMIN_CH_CL_ADMIN_BUFF);

	dce_admin_channel_deinit(d);

	dce_os_ipc_deinit_region_info(d);

	dce_mailbox_deinit_interface(d,
			DCE_MAILBOX_ADMIN_INTERFACE);
}

/**
 * dce_admin_send_msg - Sends messages on Admin Channel
 *				synchronously and waits for an ack.
 *
 * @d : Pointer to tegra_dce struct.
 * @msg : Pointer to allocated message.
 *
 * Return : 0 if successful
 */
int dce_admin_send_msg(struct tegra_dce *d, struct dce_ipc_message *msg)
{
	int ret = 0;
	struct dce_admin_send_msg_params params;

	params.msg = msg;

	ret = dce_fsm_post_event(d,
				 EVENT_ID_DCE_ADMIN_IPC_MSG_REQUESTED,
				 (void *)&params);
	if (ret)
		dce_os_err(d, "Unable to send msg invalid FSM state");

	return ret;
}

/**
 * dce_admin_send_msg - Sends messages on Admin Channel
 *				synchronously and waits for an ack.
 *
 * @d : Pointer to tegra_dce struct.
 * @msg : Pointer to allocated message.
 *
 * Return : 0 if successful
 */
int dce_admin_handle_ipc_requested_event(struct tegra_dce *d, void *params)
{
	int ret = 0;
	struct dce_ipc_message *msg;
	struct dce_admin_send_msg_params *admin_params =
			(struct dce_admin_send_msg_params *)params;

	/*
	 * Do not handle admin IPC if boot commands are not completed
	 */
	if (!dce_is_bootcmds_done(d)) {
		dce_os_err(d, "Boot commands are not yet completed\n");
		ret = -EINVAL;
		goto out;
	}

	/* Error check on msg */
	msg = admin_params->msg;

	ret = dce_ipc_send_message_sync(d, DCE_IPC_CHANNEL_TYPE_ADMIN, msg);
	if (ret)
		dce_os_err(d, "Error sending admin message on admin interface");

out:
	return ret;
}

int dce_admin_handle_ipc_received_event(struct tegra_dce *d, void *params)
{
	DCE_WARN_ON_NOT_NULL(params);

	dce_wait_cond_signal_interruptible(d, &d->ipc_waits[DCE_WAIT_ADMIN_IPC]);
	return 0;
}

/**
 * dce_admin_get_ipc_channel_info - Provides channel's
 *                                      buff details
 *
 * @d - Pointer to tegra_dce struct.
 * @q_info : Pointer to struct dce_ipc_queue_info
 *
 * Return - 0 if successful
 */
int dce_admin_get_ipc_channel_info(struct tegra_dce *d,
					struct dce_ipc_queue_info *q_info)
{
	int ret;
	u8 channel_id = DCE_IPC_CHANNEL_TYPE_ADMIN;

	ret = dce_ipc_get_channel_info(d, q_info, channel_id);

	return ret;
}

/**
 * dce_admin_send_cmd_echo - Sends DCE_ADMIN_CMD_ECHO cmd.
 *
 * @d - Pointer to tegra_dce struct.
 * @msg - Pointer to dce_ipc_msg struct.
 *
 * Return - 0 if successful
 */
int dce_admin_send_cmd_echo(struct tegra_dce *d,
			    struct dce_ipc_message *msg)
{
	int ret = -1;
	struct dce_admin_ipc_cmd *req_msg;
	struct dce_admin_ipc_resp *resp_msg;

	if (!msg || !msg->tx.data || !msg->rx.data)
		goto out;

	/* return if dce bootstrap not completed */
	if (!dce_is_bootstrap_done(d)) {
		dce_os_err(d, "Admin Bootstrap not yet done");
		goto out;
	}

	req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data);
	resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data);

	req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_ECHO;

	ret = dce_admin_send_msg(d, msg);
	if ((ret) || (resp_msg->error != DCE_ERR_CORE_SUCCESS)) {
		dce_os_err(d, "Error sending echo msg : [%d]", ret);
		ret = ret ? ret : resp_msg->error;
		goto out;
	}

out:
	return ret;
}

/**
 * dce_admin_send_cmd_ext_test - Sends DCE_ADMIN_CMD_EXT_TEST cmd.
 *
 * @d - Pointer to tegra_dce struct.
 * @msg - Pointer to dce_ipc_msg struct.
 *
 * Return - 0 if successful
 */
int dce_admin_send_cmd_ext_test(struct tegra_dce *d,
				struct dce_ipc_message *msg)
{
	int ret = -1;
	struct dce_admin_ipc_cmd *req_msg;
	struct dce_admin_ipc_resp *resp_msg;

	if (!msg || !msg->tx.data || !msg->rx.data)
		goto out;

	/* return if dce bootstrap not completed */
	if (!dce_is_bootstrap_done(d)) {
		dce_os_err(d, "Admin Bootstrap not yet done");
		goto out;
	}

	req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data);
	resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data);

	req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_EXT_TEST;

	ret = dce_admin_send_msg(d, msg);
	if (ret) {
		dce_os_err(d, "Error sending test msg : [%d]", ret);
		goto out;
	}

out:
	return ret;
}

/**
 * dce_admin_send_cmd_ver - Sends DCE_ADMIN_CMD_VERSION cmd.
 *
 * @d - Pointer to tegra_dce struct.
 * @msg - Pointer to dce_ipc_msg struct.
 *
 * Return - 0 if successful
 */
static int dce_admin_send_cmd_ver(struct tegra_dce *d,
		struct dce_ipc_message *msg)
{
	int ret = 0;
	struct dce_admin_ipc_cmd *req_msg;
	struct dce_admin_ipc_resp *resp_msg;
	struct dce_admin_version_info *ver_info;

	req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data);
	resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data);

	req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_VERSION;
	ver_info = (struct dce_admin_version_info *)(&resp_msg->args.version);

	ret = dce_admin_send_msg(d, msg);
	if (ret) {
		dce_os_err(d, "Error sending get version info : [%d]", ret);
		goto out;
	}

	if (resp_msg->error != DCE_ERR_CORE_SUCCESS) {
		dce_os_err(d, "Error in handling DCE_ADMIN_CMD_VERSION on DCE\n");
		ret = resp_msg->error;
		goto out;
	}

	dce_os_info(d, "version : dcefw:[0x%x] dcekmd:[0x%x] err : [0x%x]",
		 ver_info->version, DCE_ADMIN_VERSION, resp_msg->error);

out:
	/**
	 * TODO : Add more error handling here
	 */
	return ret;
}

/**
 * dce_admin_send_prepare_sc7 - Sends DCE_ADMIN_CMD_PREPARE_SC7 cmd.
 *
 * @d - Pointer to tegra_dce struct.
 * @msg - Pointer to dce_ipc_msg struct.
 *
 * Return - 0 if successful
 */
int dce_admin_send_prepare_sc7(struct tegra_dce *d,
			    struct dce_ipc_message *msg)
{
	int ret = -1;
	struct dce_admin_ipc_cmd *req_msg;
	struct dce_admin_ipc_resp *resp_msg;

	if (!msg || !msg->tx.data || !msg->rx.data)
		goto out;

	req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data);
	resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data);

	req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_PREPARE_SC7;

	ret = dce_admin_send_msg(d, msg);
	if (ret) {
		dce_os_err(d, "Error sending prepare sc7 command [%d]", ret);
		goto out;
	}

out:
	return ret;
}

/**
 * dce_admin_send_enter_sc7 - Sends DCE_ADMIN_CMD_ENTER_SC7 cmd.
 *
 * @d - Pointer to tegra_dce struct.
 * @msg - Pointer to dce_ipc_msg struct.
 *
 * Return - 0 if successful
 */
int dce_admin_send_enter_sc7(struct tegra_dce *d,
			     struct dce_ipc_message *msg)
{
	int ret = -1;
	struct dce_admin_ipc_cmd *req_msg;
	struct dce_admin_ipc_resp *resp_msg;

	if (!msg || !msg->tx.data || !msg->rx.data)
		goto out;

	req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data);
	resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data);

	req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_ENTER_SC7;

	ret = dce_ipc_send_message(d, DCE_IPC_CHANNEL_TYPE_ADMIN, msg->tx.data, msg->tx.size);
	if (ret) {
		dce_os_err(d, "Error sending enter sc7 command [%d]", ret);
		goto out;
	}

	/* Wait for SC7 Enter done */
	ret = dce_wait_cond_wait_interruptible(d, &d->ipc_waits[DCE_WAIT_SC7_ENTER], true, 0);
	if (ret) {
		dce_os_err(d, "SC7 Enter wait was interrupted with err:%d", ret);
		goto out;
	}

out:
	return ret;
}

static int dce_admin_setup_clients_ipc(struct tegra_dce *d,
		struct dce_ipc_message *msg)
{
	uint32_t i;
	int ret = 0;
	struct dce_admin_ipc_cmd *req_msg;
	struct dce_admin_ipc_resp *resp_msg;
	struct dce_ipc_queue_info q_info;
	struct dce_admin_ipc_create_args *ipc_info;

	req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data);
	resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data);

	req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_IPC_CREATE;
	ipc_info = (struct dce_admin_ipc_create_args *)
					(&req_msg->args.ipc_create);

	for (i = 0; i < DCE_IPC_CH_KMD_TYPE_MAX; i++) {
		if (i == DCE_IPC_CH_KMD_TYPE_ADMIN)
			continue;
		ret = dce_ipc_get_channel_info(d, &q_info, i);
		if (ret) {
			dce_os_info(d, "Get queue info failed for [%u]", i);
			ret = 0;
			continue;
		}

		ipc_info->type = dce_ipc_get_ipc_type(d, i);
		ipc_info->rd_iova = q_info.tx_iova;
		ipc_info->wr_iova = q_info.rx_iova;
		ipc_info->fsize = q_info.frame_sz;
		ipc_info->n_frames = q_info.nframes;

		ret = dce_admin_send_msg(d, msg);
		if (ret) {
			dce_os_err(d, "Error sending IPC create msg for type [%u]",
				i);
			goto out;
		}

		if (resp_msg->error != DCE_ERR_CORE_SUCCESS) {
			dce_os_err(d, "IPC create for type [%u] failed", i);
			goto out;
		}

		dce_ipc_channel_reset(d, i);
		dce_os_info(d, "Channel Reset Complete for Type [%u] ...", i);
	}

out:
	/**
	 * TODO : Add more error handling here
	 */
	return ret;
}

static int dce_admin_send_rm_bootstrap(struct tegra_dce *d,
		struct dce_ipc_message *msg)
{
	int ret = 0;
	struct dce_admin_ipc_cmd *req_msg;
	struct dce_admin_ipc_resp *resp_msg;
	struct dce_admin_version_info *ver_info;

	req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data);
	resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data);

	req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_RM_BOOTSTRAP;
	ver_info = (struct dce_admin_version_info *)(&resp_msg->args.version);

	ret = dce_admin_send_msg(d, msg);
	if (ret) {
		dce_os_err(d, "Error sending rm bootstrap cmd: [%d]", ret);
		goto out;
	}

	if (resp_msg->error != DCE_ERR_CORE_SUCCESS) {
		dce_os_err(d, "Error in handling rm bootstrap cmd on dce: [0x%x]",
			resp_msg->error);
		ret = -EINVAL;
	}

out:
	/**
	 * TODO : Add more error handling here
	 */
	return ret;
}

int dce_start_admin_seq(struct tegra_dce *d)
{
	int ret = 0;
	struct dce_ipc_message *msg;

	msg = dce_admin_channel_client_buffer_get(d, DCE_ADMIN_CH_CL_ADMIN_BUFF,
			0 /* reserved flags */);
	if (!msg)
		return -1;

	d->boot_status |= DCE_FW_ADMIN_SEQ_START;
	ret = dce_admin_send_cmd_ver(d, msg);
	if (ret) {
		dce_os_err(d, "RPC failed for DCE_ADMIN_CMD_VERSION");
		goto out;
	}

	ret = dce_admin_setup_clients_ipc(d, msg);
	if (ret) {
		dce_os_err(d, "RPC failed for DCE_ADMIN_CMD_IPC_CREATE");
		goto out;
	}

	ret = dce_admin_send_rm_bootstrap(d, msg);
	if (ret) {
		dce_os_err(d, "RPC failed for DCE_ADMIN_CMD_RM_BOOTSTRAP");
		goto out;
	}
	d->boot_status |= DCE_FW_ADMIN_SEQ_DONE;
out:
	if (msg)
		dce_admin_channel_client_buffer_put(d, msg);

	if (ret)
		d->boot_status |= DCE_FW_ADMIN_SEQ_FAILED;

	return ret;
}
