Files
linux-nv-oot/drivers/platform/tegra/dce/dce-admin.c
Arun Swain 606f03fbf2 platform: tegra: dce: add dce kernel driver
For T23x, we have a separate R5 based cluster
named as Display Controller Engine(DCE) to run
our Display RM code. This driver will run on CPU
with the following functionality:

Via debugfs for test and bring-up purposes:
1. Reads the DCE firmware image into DRAM.
2. Sets up DCE AST to cover the DCE firmware image.
3. Sets up R5 reset vector to point to DCE firmware
entry point
4. Brings DCE out of reset
5. Dumps various regsiters for debug

In production env:
1. Manages interrupts to CPU from DCE
2. Uses bootstrap command interface to define Admin
IPC
3. Locks down bootstrap command interface
4. Uses Admin IPC to define message IPC
5. Uses Admin IPC to define message IPC payload area
6. Uses Admin IPC to set IPC channels
6. Uses Admin IPC to define crashdump area
(optional)
7. Provides IPC interfaces for any DCE Client running
on CCPLEX including Display RM.
8. Uses Admin IPC to set logging level (optional)

This patch puts a framework in place with the
following features :
1. Firmware Loading
2. AST Configuration
3. DCE Reset with EVP Programming
4. Logging Infra
5. Debugfs Support
6. Interrupt Handling
7. Mailbox Programming
8. IPC Programming
9. DCE Client Interface
10. Ftrace Support for debug purposes

Change-Id: Idd28cd9254706c7313f531fcadaa7024a5b344e7
Signed-off-by: Arun Swain <arswain@nvidia.com>
Reviewed-on: https://git-master.nvidia.com/r/c/linux-t23x/+/2289865
Reviewed-by: automaticguardword <automaticguardword@nvidia.com>
Reviewed-by: Mahesh Kumar <mahkumar@nvidia.com>
Reviewed-by: Santosh Galma <galmar@nvidia.com>
Reviewed-by: Mitch Luban <mluban@nvidia.com>
Reviewed-by: mobile promotions <svcmobile_promotions@nvidia.com>
GVS: Gerrit_Virtual_Submit
Tested-by: Mahesh Kumar <mahkumar@nvidia.com>
Tested-by: mobile promotions <svcmobile_promotions@nvidia.com>
2023-04-14 19:23:43 +00:00

506 lines
11 KiB
C

/*
* Copyright (c) 2019-2020, NVIDIA CORPORATION. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope 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.
*/
#include <dce.h>
#include <dce-mailbox.h>
#include <dce-util-common.h>
#include <dce-client-ipc-internal.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, u32 w_type)
{
int ret = 0;
enum dce_worker_event_id_type event;
switch (w_type) {
case DCE_IPC_WAIT_TYPE_SYNC:
event = EVENT_ID_DCE_IPC_MESSAGE_SENT;
break;
case DCE_IPC_WAIT_TYPE_RPC:
event = EVENT_ID_DCE_IPC_MESSAGE_SENT;
break;
default:
dce_err(d, "Invalid wait type [%d]", w_type);
break;
}
dce_worker_thread_wait(d, event);
if (dce_worker_get_state(d)
== STATE_DCE_WORKER_ABORTED)
ret = -1;
return ret;
}
/**
* dce_admin_interface_isr - Isr for the CCPLEX<->DCE admin interface
*
* @d : Pointer to tegra_de struct.
*
* Return : Void
*/
static void dce_admin_wakeup_ipc(struct tegra_dce *d)
{
enum dce_worker_event_id_type event = EVENT_ID_DCE_IPC_SIGNAL_RECEIVED;
dce_worker_thread_wakeup(d, 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_cl = false;
bool wakeup_needed = false;
/**
* TODO : Get rid of prints
*/
dce_info(d, "Signal Received : check if channel is ready [%d]",
ch_type);
if (!(dce_ipc_channel_is_ready(d, ch_type))) {
/**
* The ivc channel is not ready yet. Exit
* and wait for another signal from target.
*/
goto process_wakeup;
}
/**
* TODO : Get rid of prints
*/
dce_info(d, "Signal Received : Channel in established state already : [%d]",
ch_type);
wakeup_needed = (DCE_IPC_WAIT_TYPE_SYNC ==
dce_ipc_get_cur_wait_type(d, ch_type));
if (wakeup_needed) {
/**
* Handshake successful, wake up the
* dce worker thread since it's waiting
* for the synchronization to complete.
*/
goto process_wakeup;
}
/**
* TODO : Get rid of prints
*/
dce_info(d, "Signal Received : Channel synchronized already : [%d]",
ch_type);
/**
* 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)
wakeup_cl = (ch_type != DCE_IPC_CH_KMD_TYPE_ADMIN);
/**
* TODO : Get rid of prints
*/
dce_info(d, "Signal Received : Data available : [%d] for channel: [%d]",
wakeup_needed, ch_type);
process_wakeup:
if (!wakeup_needed) {
dce_info(d, "Spurious signal on channel: [%d]. Ignored...",
ch_type);
return;
}
if (!wakeup_cl)
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_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(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(d, loop_cnt);
if (ret) {
dce_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;
ret = dce_ipc_allocate_region(d);
if (ret) {
dce_err(d, "IPC region allocation failed");
goto err_ipc_reg_alloc;
}
ret = dce_admin_channel_init(d);
if (ret) {
dce_err(d, "Channel Initialization Failed");
goto err_channel_init;
}
return 0;
err_channel_init:
dce_ipc_free_region(d);
err_ipc_reg_alloc:
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_deinit(d);
dce_ipc_free_region(d);
dce_mailbox_deinit_interface(d,
DCE_MAILBOX_ADMIN_INTERFACE);
}
/**
* dce_admin_allocate_message - Allocates memory for a message
* on admin interface.
* @d : Pointer tegra_dce struct.
*
* Return : Allocated msg if successful.
*/
struct dce_ipc_message *dce_admin_allocate_message(struct tegra_dce *d)
{
struct dce_ipc_message *msg;
msg = dce_kzalloc(d, sizeof(*msg), false);
if (!msg) {
dce_err(d, "Insufficient memory for admin msg");
goto err_alloc_msg;
}
msg->tx.data = dce_kzalloc(d, DCE_ADMIN_CMD_SIZE, false);
if (!msg->tx.data) {
dce_err(d, "Insufficient memory for admin msg");
goto err_alloc_tx;
}
msg->rx.data = dce_kzalloc(d, DCE_ADMIN_RESP_SIZE, false);
if (!msg->rx.data) {
dce_err(d, "Insufficient memory for admin msg");
goto err_alloc_rx;
}
msg->tx.size = DCE_ADMIN_CMD_SIZE;
msg->rx.size = DCE_ADMIN_RESP_SIZE;
return msg;
err_alloc_rx:
dce_kfree(d, msg->tx.data);
err_alloc_tx:
dce_kfree(d, msg);
err_alloc_msg:
return NULL;
}
/**
* dce_admin_free_message - Frees memory allocated for a message
* on admin interface.
*
* @d : Pointer to tegra_dce struct.
* @msg : Pointer to allocated message.
*
* Return : Void.
*/
void dce_admin_free_message(struct tegra_dce *d,
struct dce_ipc_message *msg)
{
if (!msg || !msg->tx.data || !msg->rx.data)
return;
dce_kfree(d, msg->tx.data);
dce_kfree(d, msg->rx.data);
dce_kfree(d, msg);
}
/**
* dce_admin_send_cmd_version_cmd - Sends DCE_ADMIN_CMD_VERSION cmd.
*
* @d - Pointer to tegra_dce struct.
*
* Return - 0 if successful
*/
int dce_admin_send_msg(struct tegra_dce *d, struct dce_ipc_message *msg)
{
int ret = 0;
ret = dce_ipc_send_message_sync(d, DCE_IPC_CHANNEL_TYPE_ADMIN, msg);
if (ret)
dce_err(d, "Error sending admin message on admin interface");
return ret;
}
/**
* 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;
}
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_err(d, "Error sending get version info : [%d]", ret);
goto out;
}
dce_info(d, "error value : [0x%x]", resp_msg->error);
dce_info(d, "version number : [0x%x]", ver_info->version);
out:
/**
* TODO : Add more error handling here
*/
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_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;
/**
* TODO : Get rid of prints
*/
dce_err(d, "value of q_struct for type [%d]", ipc_info->type);
dce_err(d, "ch->q_info.nframes : [%u]", ipc_info->n_frames);
dce_err(d, "ch->q_info.frame_sz: [%u]", ipc_info->fsize);
dce_err(d, "ch->q_info.rx_iova: [0x%llx]", ipc_info->wr_iova);
dce_err(d, "ch->q_info.tx_iova: [0x%llx]", ipc_info->rd_iova);
ret = dce_admin_send_msg(d, msg);
if (ret) {
dce_err(d, "Error sending IPC create msg for type [%u]",
i);
goto out;
}
dce_info(d, "error value : [0x%x]", resp_msg->error);
if (resp_msg->error) {
dce_err(d, "IPC create for type [%u] failed", i);
goto out;
}
dce_ipc_channel_reset(d, i);
dce_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_err(d, "Error sending rm bootstrap cmd: [%d]", ret);
goto out;
}
dce_info(d, "error value : [0x%x]", resp_msg->error);
if (resp_msg->error) {
dce_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_allocate_message(d);
if (!msg)
return -1;
ret = dce_admin_send_cmd_ver(d, msg);
if (ret) {
dce_err(d, "RPC failed for DCE_ADMIN_CMD_VERSION");
goto out;
}
ret = dce_admin_setup_clients_ipc(d, msg);
if (ret) {
dce_err(d, "RPC failed for DCE_ADMIN_CMD_VERSION");
goto out;
}
ret = dce_admin_send_rm_bootstrap(d, msg);
if (ret) {
dce_err(d, "RPC failed for DCE_ADMIN_CMD_VERSION");
goto out;
}
out:
dce_admin_free_message(d, msg);
return ret;
}