diff --git a/drivers/platform/tegra/dce/Makefile b/drivers/platform/tegra/dce/Makefile new file mode 100644 index 00000000..db0f788a --- /dev/null +++ b/drivers/platform/tegra/dce/Makefile @@ -0,0 +1,46 @@ +# SPDX-License-Identifier: GPL-2.0 +# Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. All rights reserved. +# +# Display Controller Engine code. +# +GCOV_PROFILE := y + +ccflags-y += -I$(srctree.nvidia)/drivers/platform/tegra/dce/include +ccflags-y += -Wno-multichar +ccflags-y += -Werror +ccflags-y += -Wno-error=cpp +ifeq ($(VERSION),4) +ccflags-y += -Wextra -Wno-unused-parameter -Wno-missing-field-initializers +endif + +# Set config when build as OOT module. +ifeq ($(CONFIG_TEGRA_OOT_MODULE),m) +CONFIG_TEGRA_DCE := m +ccflags-y += -I$(srctree.nvidia)/include +endif + +obj-$(CONFIG_TEGRA_DCE) += tegra-dce.o +tegra-dce-$(CONFIG_TEGRA_DCE) += \ + dce-ast.o \ + dce-reset.o \ + dce-hsp-smb.o \ + dce-hsp-ss.o \ + dce-worker.o \ + dce-fsm.o \ + dce-init-deinit.o \ + dce-mailbox.o \ + dce-bootstrap.o \ + dce-admin.o \ + dce-ipc.o \ + dce-ipc-signal.o \ + dce-client-ipc.o \ + dce-module.o \ + dce-pm.o \ + dce-util-common.o + +ifeq ($(CONFIG_DEBUG_FS),y) +tegra-dce-$(CONFIG_TEGRA_DCE) += \ + dce-debug.o \ + dce-admin-debug.o \ + dce-debug-perf.o +endif diff --git a/drivers/platform/tegra/dce/dce-admin-debug.c b/drivers/platform/tegra/dce/dce-admin-debug.c new file mode 100644 index 00000000..e7c2985a --- /dev/null +++ b/drivers/platform/tegra/dce/dce-admin-debug.c @@ -0,0 +1,164 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. 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 +#include +#include +#include +#include +#include +#include + +/** + * dce_admin_send_cmd_set_perf_stat - Start/stop DCE perf data collection. + * + * @d - Pointer to tegra_dce struct. + * @msg - Pointer to dce_ipc_msg struct. + * @start_perf - Bool to indicate start/stop of perf data collection + * + * Return - 0 if successful + */ +int dce_admin_send_cmd_set_perf_stat(struct tegra_dce *d, + struct dce_ipc_message *msg, + bool start_perf) +{ + 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_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); + + if (start_perf == true) + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_PERF_START; + else + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_PERF_STOP; + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_err(d, "Error sending set perf msg : [%d]", ret); + goto out; + } + +out: + return ret; +} +/** + * dce_admin_send_cmd_get_perf_stat - Get DCE perf data. + * + * @d - Pointer to tegra_dce struct. + * @msg - Pointer to dce_ipc_msg struct. + * + * Return - 0 if successful + */ +int dce_admin_send_cmd_get_perf_stat(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_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_PERF_RESULTS; + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_err(d, "Error sending get perf msg : [%d]", ret); + goto out; + } + +out: + return ret; +} + +int dce_admin_send_cmd_get_perf_events(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_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_PERF_GET_EVENTS; + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_err(d, "Error sending get perf events msg : [%d]", ret); + goto out; + } + +out: + return ret; +} + +int dce_admin_send_cmd_clear_perf_events(struct tegra_dce *d, + struct dce_ipc_message *msg) +{ + int ret = -1; + struct dce_admin_ipc_cmd *req_msg; + + if (!msg || !msg->tx.data || !msg->rx.data) + goto out; + + /* return if dce bootstrap not completed */ + if (!dce_is_bootstrap_done(d)) { + dce_err(d, "Admin Bootstrap not yet done"); + goto out; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_PERF_CLEAR_EVENTS; + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_err(d, "Error sending clear perf events msg : [%d]", ret); + goto out; + } + +out: + return ret; +} diff --git a/drivers/platform/tegra/dce/dce-admin.c b/drivers/platform/tegra/dce/dce-admin.c new file mode 100644 index 00000000..274713ff --- /dev/null +++ b/drivers/platform/tegra/dce/dce-admin.c @@ -0,0 +1,670 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019-2023 NVIDIA CORPORATION & AFFILIATES. 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 +#include +#include +#include +#include +#include +#include + +/** + * dce_admin_ipc_wait - Waits for message from DCE. + * + * @d : Pointer to tegra_dce struct. + * @w_type : Requested wait type. + * + * Return : 0 if successful + */ +int dce_admin_ipc_wait(struct tegra_dce *d, u32 w_type) +{ + int ret = 0; + + ret = dce_wait_interruptible(d, DCE_WAIT_ADMIN_IPC); + if (ret) { + /** + * TODO: Add error handling for abort and retry + */ + dce_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_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_info(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_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; + + d->boot_status |= DCE_EARLY_INIT_START; + 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; + } + + d->boot_status |= DCE_EARLY_INIT_DONE; + return 0; + +err_channel_init: + dce_ipc_free_region(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_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_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 *)¶ms); + if (ret) + dce_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_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_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_wakeup_interruptible(d, 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_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_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_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_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_err(d, "Error sending get version info : [%d]", ret); + goto out; + } + dce_info(d, "version : [0x%x] err : [0x%x]", ver_info->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_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_err(d, "Error sending enter sc7 command [%d]", ret); + goto out; + } + + /* Wait for SC7 Enter done */ + ret = dce_wait_interruptible(d, DCE_WAIT_SC7_ENTER); + if (ret) { + dce_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_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_err(d, "Error sending IPC create msg for type [%u]", + i); + goto out; + } + + if (resp_msg->error != DCE_ERR_CORE_SUCCESS) { + 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; + } + + if (resp_msg->error != DCE_ERR_CORE_SUCCESS) { + 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; + + d->boot_status |= DCE_FW_ADMIN_SEQ_START; + 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_IPC_CREATE"); + goto out; + } + + ret = dce_admin_send_rm_bootstrap(d, msg); + if (ret) { + dce_err(d, "RPC failed for DCE_ADMIN_CMD_RM_BOOTSTRAP"); + goto out; + } + d->boot_status |= DCE_FW_ADMIN_SEQ_DONE; +out: + dce_admin_free_message(d, msg); + if (ret) + d->boot_status |= DCE_FW_ADMIN_SEQ_FAILED; + return ret; +} diff --git a/drivers/platform/tegra/dce/dce-ast.c b/drivers/platform/tegra/dce/dce-ast.c new file mode 100644 index 00000000..de40c2c1 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-ast.c @@ -0,0 +1,572 @@ +/* + * Copyright (c) 2019-2023, 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 +#include +#include + +#define MAX_NO_ASTS 2 +#define MAX_AST_REGIONS 1 +#define MAX_AST_STRMCTLS 2 + +#define AST_MASTER_ADDR_HI_BITS_SHIFT 32 + +/** + * dce_config_ast0_control - programs the global + * ast control register for AST0 + * + * @d : Pointer to tegra_dce struct + * + * Return : Void + */ +static void dce_config_ast0_control(struct tegra_dce *d) +{ + u32 val; + u32 def_physical; + u32 phy_stream_id = dce_get_phys_stream_id(d) << + ast_ast0_control_physstreamid_shift_v(); + + if (dce_is_physical_id_valid(d)) + def_physical = 1 << + ast_ast0_control_carveoutlock_defphysical_shift_v(); + else + def_physical = 0 << + ast_ast0_control_carveoutlock_defphysical_shift_v(); + + val = phy_stream_id | ast_ast0_control_carveoutlock_false_f() + | def_physical | ast_ast0_control_matcherrctl_decerr_f() + | ast_ast0_control_lock_false_f(); + + dce_writel(d, ast_ast0_control_r(), val); +} + +/** + * dce_config_ast1_control - programs the global + * ast control register for AST1 + * + * @d : Pointer to tegra_dce struct + * + * Return : Void + */ +static void dce_config_ast1_control(struct tegra_dce *d) +{ + u32 val; + u32 def_physical; + u32 phy_stream_id = dce_get_phys_stream_id(d) << + ast_ast1_control_physstreamid_shift_v(); + + if (dce_is_physical_id_valid(d)) + def_physical = 1 << + ast_ast1_control_carveoutlock_defphysical_shift_v(); + else + def_physical = 0 << + ast_ast1_control_carveoutlock_defphysical_shift_v(); + + val = phy_stream_id | ast_ast1_control_carveoutlock_false_f() + | def_physical | ast_ast1_control_matcherrctl_decerr_f() + | ast_ast1_control_lock_false_f(); + + dce_writel(d, ast_ast1_control_r(), val); +} + +/** + * ast_ctl_fn is an array of read-only pointers + * to a function returning void. + * + * Contains all the functions to program the global + * ast control registers defined above. + */ +static void (*const ast_ctl_fn[MAX_NO_ASTS])(struct tegra_dce *d) = { + + dce_config_ast0_control, + dce_config_ast1_control, +}; + +/** + * dce_cfg_ast0_streamid_ctl_0 - programs the ast streamid + * control register for AST0 and Control0 + * + * @d : Pointer to tegra_dce struct + * + * Return : Void + */ +static void dce_cfg_ast0_streamid_ctl_0(struct tegra_dce *d) +{ + u32 stream_id_en; + u32 dce_stream_id = dce_get_dce_stream_id(d); + + if (dce_is_physical_id_valid(d)) + stream_id_en = ast_ast0_streamid_ctl_0_enable_disable_f(); + else + stream_id_en = ast_ast0_streamid_ctl_0_enable_enable_f(); + + dce_writel(d, ast_ast0_streamid_ctl_0_r(), + (dce_stream_id << ast_ast0_streamid_ctl_0_streamid_shift_v()) | + stream_id_en); +} + +/** + * dce_cfg_ast0_streamid_ctl_1 - programs the ast streamid + * control register for AST0 and Control1 + * + * @d : Pointer to tegra_dce struct + * + * Return : Void + */ +static void dce_cfg_ast0_streamid_ctl_1(struct tegra_dce *d) +{ + u32 stream_id_en; + u32 dce_stream_id = dce_get_dce_stream_id(d); + + if (dce_is_physical_id_valid(d)) + stream_id_en = ast_ast0_streamid_ctl_1_enable_disable_f(); + else + stream_id_en = ast_ast0_streamid_ctl_1_enable_enable_f(); + + dce_writel(d, ast_ast0_streamid_ctl_1_r(), (dce_stream_id << + ast_ast0_streamid_ctl_1_streamid_shift_v()) | + stream_id_en); +} + +/** + * dce_cfg_ast1_streamid_ctl_0 - programs the ast streamid + * control register for AST1 and Control0 + * + * @d : Pointer to tegra_dce struct + * + * Return : Void + */ +static void dce_cfg_ast1_streamid_ctl_0(struct tegra_dce *d) +{ + u32 stream_id_en; + u32 dce_stream_id = dce_get_dce_stream_id(d); + + if (dce_is_physical_id_valid(d)) + stream_id_en = ast_ast1_streamid_ctl_0_enable_disable_f(); + else + stream_id_en = ast_ast1_streamid_ctl_0_enable_enable_f(); + + dce_writel(d, ast_ast1_streamid_ctl_0_r(), + (dce_stream_id << ast_ast1_streamid_ctl_0_streamid_shift_v()) | + stream_id_en); +} + +/** + * dce_cfg_ast1_streamid_ctl_1 - programs the ast streamid + * control register for AST1 and Control1 + * + * @d : Pointer to tegra_dce struct + * + * Return : Void + */ +static void dce_cfg_ast1_streamid_ctl_1(struct tegra_dce *d) +{ + u32 stream_id_en; + u32 dce_stream_id = dce_get_dce_stream_id(d); + + if (dce_is_physical_id_valid(d)) + stream_id_en = ast_ast1_streamid_ctl_1_enable_disable_f(); + else + stream_id_en = ast_ast1_streamid_ctl_1_enable_enable_f(); + + dce_writel(d, ast_ast1_streamid_ctl_1_r(), + (dce_stream_id << ast_ast1_streamid_ctl_1_streamid_shift_v()) | + stream_id_en); +} + +/** + * ast_strmidctl_fn is a 2D array of read-only pointers + * to a function returning void. + * + * Contains all the functions to program the streamId + * controls registers defined above for a given AST and Control ID + */ +static void (*const ast_strmidctl_fn[MAX_NO_ASTS][MAX_AST_STRMCTLS]) + (struct tegra_dce *d) = { + + { + dce_cfg_ast0_streamid_ctl_0, + dce_cfg_ast0_streamid_ctl_1, + }, + { + dce_cfg_ast1_streamid_ctl_0, + dce_cfg_ast1_streamid_ctl_1, + }, +}; + + +/** + * dce_set_ast0_slave_addr_32_reg0 - programs the ast slave address + * for AST0 and Region0 + * + * @d : Pointer to tegra_dce sturct. + * @addr : Address to be programmed. + * + * Return : Void + */ +static void dce_set_ast0_slave_addr_32_reg0(struct tegra_dce *d, u32 addr) +{ + dce_writel(d, ast_ast0_region_0_slave_base_lo_r(), + (addr | ast_ast0_region_0_slave_base_lo_enable_true_f()) & + ast_ast1_region_0_slave_base_lo_write_mask_v()); +} + +/** + * dce_set_ast1_slave_addr_32_reg0- programs the ast slave address + * for AST1 and Region0 + * + * @d : Pointer to tegra_dce sturct. + * @addr : Address to be programmed. + * + * Return : Void + */ +static void dce_set_ast1_slave_addr_32_reg0(struct tegra_dce *d, u32 addr) +{ + dce_writel(d, ast_ast1_region_0_slave_base_lo_r(), + (addr | ast_ast1_region_0_slave_base_lo_enable_true_f()) & + ast_ast1_region_0_slave_base_lo_write_mask_v()); +} + +/** + * ast_slave_addr_fn is a 2D array of read-only pointers + * to a function returning void. + * + * Contains all the functions to program the slave address and + * other bits in salve address registers defined above for a + * given AST and region. + */ +static void (*const ast_slave_addr_fn[MAX_NO_ASTS][MAX_AST_REGIONS]) + (struct tegra_dce *d, u32 addr) = { + + { + dce_set_ast0_slave_addr_32_reg0, + }, + { + dce_set_ast1_slave_addr_32_reg0, + }, +}; + +/** + * dce_set_ast0_master_addr_lo_reg0 - programs the lower 32 bits of ast + * master address for AST0 and Region0 + * + * @d : Pointer to tegra_dce sturct. + * @addr : Address to be programmed. + * + * Return : Void + */ +static inline void +dce_set_ast0_master_addr_lo_reg0(struct tegra_dce *d, u32 addr) +{ + dce_writel(d, ast_ast0_region_0_master_base_lo_r(), addr); +} + +/** + * dce_set_ast1_master_addr_lo_reg0 - programs the lower 32 bits of ast + * master address for AST1 and Region0 + * + * @d : Pointer to tegra_dce sturct. + * @addr : Address to be programmed. + * + * Return : Void + */ +static inline void +dce_set_ast1_master_addr_lo_reg0(struct tegra_dce *d, u32 addr) +{ + dce_writel(d, ast_ast1_region_0_master_base_lo_r(), addr); +} + +/** + * dce_set_ast1_master_addr_hi_reg0 - programs the high 32 bits of ast + * master address for AST1 and Region0 + * + * @d : Pointer to tegra_dce sturct. + * @addr : Address to be programmed. + * + * Return : Void + */ +static inline void +dce_set_ast1_master_addr_hi_reg0(struct tegra_dce *d, u32 addr) +{ + dce_writel(d, ast_ast1_region_0_master_base_hi_r(), addr); +} + +/** + * dce_set_ast0_master_addr_hi_reg0 - programs the high 32 bits of ast + * master address for AST0 and Region0 + * + * @d : Pointer to tegra_dce sturct. + * @addr : Address to be programmed. + * + * Return : Void + */ +static inline void +dce_set_ast0_master_addr_hi_reg0(struct tegra_dce *d, u32 addr) +{ + dce_writel(d, ast_ast0_region_0_master_base_hi_r(), addr); +} + +/** + * dce_set_ast0_master_addr_reg0 - programs the ast master address + * for AST0 and Region0 + * + * @d : Pointer to tegra_dce sturct. + * @addr : Address to be programmed. + * + * Return : Void + */ +static void dce_set_ast0_master_addr_reg0(struct tegra_dce *d, u64 addr) +{ + u32 ast_master_hi; + u32 ast_master_lo; + + ast_master_lo = addr & ast_ast0_region_0_master_base_lo_write_mask_v(); + dce_set_ast0_master_addr_lo_reg0(d, ast_master_lo); + + ast_master_hi = addr >> AST_MASTER_ADDR_HI_BITS_SHIFT; + dce_set_ast0_master_addr_hi_reg0(d, ast_master_hi); +} + +/** + * dce_set_ast1_master_addr_reg0 - programs the ast master address + * for AST0 and Region0 + * + * @d : Pointer to tegra_dce sturct. + * @addr : Address to be programmed. + * + * Return : Void + */ +static void dce_set_ast1_master_addr_reg0(struct tegra_dce *d, u64 addr) +{ + u32 ast_master_hi; + u32 ast_master_lo; + + ast_master_lo = addr & ast_ast0_region_0_master_base_lo_write_mask_v(); + dce_set_ast1_master_addr_lo_reg0(d, ast_master_lo); + + ast_master_hi = addr >> AST_MASTER_ADDR_HI_BITS_SHIFT; + dce_set_ast1_master_addr_hi_reg0(d, ast_master_hi); +} + +/** + * ast_master_addr_fn is a 2D array of read-only pointers + * to a function returning void. + * + * Contains all the functions to program the master address registers + * defined above for a given AST and region. + */ +static void (*const ast_master_addr_fn[MAX_NO_ASTS][MAX_AST_REGIONS]) + (struct tegra_dce *d, u64 addr) = { + + { + dce_set_ast0_master_addr_reg0, + }, + { + dce_set_ast1_master_addr_reg0, + }, +}; + +/** + * dce_get_fw_ast_reg_mask - Returns the size mask based on the fw size + * for configuring AST region + * + * @d : Pointer to tegra_dce struct + * + * If size is 64K(0x10000), mask is 0xffff. If size is 2MB(0x200000), mask is + * 0x1FFFFF. + * + * Ruturns 64 bit mask. + */ +static u64 dce_get_fw_ast_reg_mask(struct tegra_dce *d) +{ + struct dce_firmware *fw = d->fw_data; + + return fw->size - 1UL; +} + +/** + * dce_ast_cfg_reg_mask_ast0_reg0 - sets the region mask based on the size + * of the DRAM memory for AST0 and Region0. + * + * @d : pointer to tegra_dce struct + * + * Return : Void + */ +static void dce_ast_cfg_reg_mask_ast0_reg0(struct tegra_dce *d) +{ + u64 size_mask = dce_get_fw_ast_reg_mask(d); + u32 val = size_mask & ast_ast0_region_0_mask_lo_write_mask_v(); + + dce_writel(d, ast_ast0_region_0_mask_lo_r(), val); +} + +/** + * dce_ast_cfg_reg_mask_ast1_reg0 - sets the region mask based on the size + * of the DRAM memory for AST1 and Region0. + * + * @d : pointer to tegra_dce struct + * + * Return : Void + */ +static void dce_ast_cfg_reg_mask_ast1_reg0(struct tegra_dce *d) +{ + u64 size_mask = dce_get_fw_ast_reg_mask(d); + u32 val = size_mask & ast_ast1_region_0_mask_lo_write_mask_v(); + + dce_writel(d, ast_ast1_region_0_mask_lo_r(), val); +} + +/** + * ast_mask_fn is a 2D array of read-only pointers + * to a function returning void. + * + * Contains all the functions to program the mask + * register bits for a given AST and region. + */ +static void (*const ast_mask_fn[MAX_NO_ASTS][MAX_AST_REGIONS]) + (struct tegra_dce *d) = { + + { + dce_ast_cfg_reg_mask_ast0_reg0, + }, + { + dce_ast_cfg_reg_mask_ast1_reg0, + }, +}; + +/** + * dce_ast_cfg_reg_control_ast0_reg0 - Configures the ast region control + * register for AST0 and Region0 + * + * @d : Pointer to tegra_dce struct + * + * Return : Void + */ +static void dce_ast_cfg_reg_control_ast0_reg0(struct tegra_dce *d) +{ + u32 vm_index; + u32 carveout_id; + u32 use_physical_id; + + if (dce_is_physical_id_valid(d)) { + use_physical_id = 1 << + ast_ast0_region_0_control_physical_shift_v(); + vm_index = 0 << + ast_ast0_region_0_control_vmindex_shift_v(); + } else { + use_physical_id = 0 << + ast_ast0_region_0_control_physical_shift_v(); + vm_index = dce_get_fw_vm_index(d) << + ast_ast0_region_0_control_vmindex_shift_v(); + } + carveout_id = dce_get_fw_carveout_id(d) << + ast_ast0_region_0_control_carveoutid_shift_v(); + + dce_writel(d, ast_ast0_region_0_control_r(), + use_physical_id | vm_index | carveout_id | + ast_ast0_region_0_control_snoop_enable_f()); +} + +/** + * dce_ast_cfg_reg_control_ast1_reg0 - Configures the ast region control + * register for AST1 and Region0 + * + * @d : Pointer to tegra_dce struct + * + * Return : Void + */ +static void dce_ast_cfg_reg_control_ast1_reg0(struct tegra_dce *d) +{ + u32 vm_index; + u32 carveout_id; + u32 use_physical_id; + + if (dce_is_physical_id_valid(d)) { + use_physical_id = 1 << + ast_ast1_region_0_control_physical_shift_v(); + vm_index = 0 << + ast_ast1_region_0_control_vmindex_shift_v(); + } else { + use_physical_id = 0 << + ast_ast1_region_0_control_physical_shift_v(); + vm_index = dce_get_fw_vm_index(d) << + ast_ast1_region_0_control_vmindex_shift_v(); + } + + carveout_id = dce_get_fw_carveout_id(d) << + ast_ast1_region_0_control_carveoutid_shift_v(); + + + dce_writel(d, ast_ast1_region_0_control_r(), + use_physical_id | vm_index | carveout_id | + ast_ast1_region_0_control_snoop_enable_f()); +} + +/** + * ast_reg_control_fn is a 2D array of read-only pointers + * to a function returning void. + * + * Contains all the functions to program the region control + * register bits given AST and region. + */ +static void (*const ast_reg_control_fn[MAX_NO_ASTS][MAX_AST_REGIONS]) + (struct tegra_dce *d) = { + + { + dce_ast_cfg_reg_control_ast0_reg0, + }, + { + dce_ast_cfg_reg_control_ast1_reg0, + }, +}; + +/** + * dce_config_ast - Configures the a AST region for initial loading of fw + * platform data. + * + * @d : Pointer to tegra_dce struct. + * + * Return : void + */ +void dce_config_ast(struct tegra_dce *d) +{ + u8 i; + u8 j; + u32 slave_addr; + u64 master_addr; + + d->boot_status |= DCE_AST_CONFIG_START; + slave_addr = dce_get_fw_dce_addr(d); + + if (!d->fw_data) { + dce_err(d, "DCE_BOOT_FAILED: No fw_data present"); + d->boot_status |= DCE_AST_CONFIG_FAILED; + return; + } + + master_addr = d->fw_data->dma_handle; + + for (i = 0; i < MAX_NO_ASTS; i++) { + ast_ctl_fn[i](d); + + for (j = 0; j < MAX_AST_STRMCTLS; j++) + ast_strmidctl_fn[i][j](d); + + for (j = 0; j < MAX_AST_REGIONS; j++) { + ast_mask_fn[i][j](d); + ast_reg_control_fn[i][j](d); + ast_master_addr_fn[i][j](d, master_addr); + ast_slave_addr_fn[i][j](d, slave_addr); + } + } + d->boot_status |= DCE_AST_CONFIG_DONE; +} diff --git a/drivers/platform/tegra/dce/dce-bootstrap.c b/drivers/platform/tegra/dce/dce-bootstrap.c new file mode 100644 index 00000000..829f32d6 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-bootstrap.c @@ -0,0 +1,870 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION & AFFILIATES. 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 +#include +#include +#include + +/** + * dce_fw_boot_complete - Checks if dce has complelted boot. + * + * @d - Pointer to tegra_dce struct. + * + * Return : True if boot is complete + */ +inline bool dce_fw_boot_complete(struct tegra_dce *d) +{ + return !!(dce_ss_get_state(d, DCE_BOOT_SEMA) + & DCE_BOOT_COMPLETE); +} + +/** + * dce_request_fw_boot_complete - Requests DCE to raise an + * interrupt on boot completion. + * + * @d - Pointer to tegra_dce struct. + * + * Return : void + */ +inline void dce_request_fw_boot_complete(struct tegra_dce *d) +{ +#define DCE_BOOT_INIT_BPOS 31U + dce_ss_set(d, DCE_BOOT_INIT_BPOS, DCE_BOOT_SEMA); +#undef DCE_BOOT_INIT_BPOS +} + +/** + * dce_handle_boot_cmd_requested_event - callback handler function for event + * EVENT_ID_DCE_BOOT_CMD_MSG_REQUESTED + * + * @d : Pointer to tegra_dce struct. + * @params : callback params + * + * Return : 0 if successful else error code + */ +int dce_handle_boot_cmd_requested_event(struct tegra_dce *d, void *params) +{ + int ret = 0; + struct dce_mailbox_send_cmd_params *mbox_params = + (struct dce_mailbox_send_cmd_params *)params; + + dce_debug(d, "cmd:%u interface:%u", mbox_params->cmd, mbox_params->interface); + + ret = dce_handle_mailbox_send_cmd_sync(d, mbox_params->cmd, + mbox_params->interface); + + return ret; +} + +/** + * dce_handle_boot_cmd_received_event - callback handler function for event + * EVENT_ID_DCE_BOOT_CMD_MSG_RECEIVED + * + * @d : Pointer to tegra_dce struct. + * @params : callback params + * + * Return : 0 if successful else error code + */ +int dce_handle_boot_cmd_received_event(struct tegra_dce *d, void *params) +{ + dce_wakeup_interruptible(d, DCE_WAIT_BOOT_CMD); + return 0; +} + +/** + * dce_handle_boot_complete_requested_event - callback handler function for + * event EVENT_ID_DCE_BOOT_COMPLETE_REQUESTED + * + * Wait for the DCE to boot and be ready to receive commands from CCPLEX driver. + * + * @d : Pointer to tegra_dce struct. + * @params : callback params + * + * Return : 0 if successful else error code + */ +int dce_handle_boot_complete_requested_event(struct tegra_dce *d, void *params) +{ + int ret = 0; + + d->boot_status |= DCE_FW_EARLY_BOOT_START; + if (dce_fw_boot_complete(d)) { + ret = dce_fsm_post_event(d, EVENT_ID_DCE_BOOT_COMPLETE_RECEIVED, NULL); + if (ret) + dce_err(d, "failed to send DCE_BOOT_COMPLETE_RECEIVED event"); + + goto boot_done; + } + + dce_request_fw_boot_complete(d); + + dce_debug(d, "Waiting for dce fw to boot..."); + + ret = dce_wait_interruptible(d, DCE_WAIT_BOOT_COMPLETE); + if (ret) { + /** + * TODO: Add error handling for abort and retry + */ + dce_err(d, "dce boot wait was interrupted with err:%d", ret); + } + +boot_done: + if (!ret) { + dce_set_boot_complete(d, true); + d->boot_status |= DCE_FW_EARLY_BOOT_DONE; + dce_debug(d, "dce is ready to receive bootstrap commands"); + } else { + d->boot_status |= DCE_FW_EARLY_BOOT_FAILED; + } + + return ret; +} + +/** + * dce_handle_boot_complete_received_event - callback handler function for + * event EVENT_ID_DCE_BOOT_CMD_MSG_RECEIVED + * + * @d : Pointer to tegra_dce struct. + * @params : callback params + * + * Return : 0 if successful else error code + */ +int dce_handle_boot_complete_received_event(struct tegra_dce *d, void *params) +{ + dce_wakeup_interruptible(d, DCE_WAIT_BOOT_COMPLETE); + return 0; +} + +/* + * dce_start_boot_flow : Start dce bootstrap flow + * + * @d : Pointer to tegra_dce struct. + * + * Return : 0 if successful else error code + */ +int +dce_start_boot_flow(struct tegra_dce *d) +{ + int ret = 0; + + ret = dce_start_bootstrap_flow(d); + if (ret) { + dce_err(d, "DCE_BOOT_FAILED: Bootstrap flow didn't complete"); + goto exit; + } + + dce_admin_ivc_channel_reset(d); + + ret = dce_start_admin_seq(d); + if (ret) { + dce_err(d, "DCE_BOOT_FAILED: Admin flow didn't complete"); + } else { + d->boot_status |= DCE_FW_BOOT_DONE; + dce_info(d, "DCE_BOOT_DONE"); + dce_cond_broadcast_interruptible(&d->dce_bootstrap_done); + } + +exit: + if (ret) + d->boot_status |= DCE_STATUS_FAILED; + + return ret; +} + +/** + * dce_bootstrap_work_fn : execute fsm start and bootstrap flow + * + * @d : Pointer to tegra_dce struct. + * + * Return : void + */ +void dce_bootstrap_work_fn(struct tegra_dce *d) +{ + int ret = 0; + + if (d == NULL) { + dce_err(d, "tegra_dce struct is NULL"); + return; + } + + ret = dce_fsm_post_event(d, EVENT_ID_DCE_FSM_START, NULL); + if (ret) { + dce_err(d, "FSM start failed\n"); + return; + } + + ret = dce_fsm_post_event(d, EVENT_ID_DCE_BOOT_COMPLETE_REQUESTED, NULL); + if (ret) { + dce_err(d, "Error while posting DCE_BOOT_COMPLETE_REQUESTED event"); + return; + } + + ret = dce_start_boot_flow(d); + if (ret) { + dce_err(d, "DCE bootstrapping failed\n"); + return; + } +} + +/** + * dce_handle_irq_status - Handles irq status from DCE + * + * @d : Pointer to struct tegra_dce + * @status : Status received from DCE + * + * Return : Void + */ +static void dce_handle_irq_status(struct tegra_dce *d, u32 status) +{ + + if (status & DCE_IRQ_LOG_OVERFLOW) + dce_info(d, "DCE trace log overflow error received"); + + if (status & DCE_IRQ_CRASH_LOG) + dce_info(d, "DCE crash log available"); + + if (status & DCE_IRQ_ABORT) + dce_err(d, "DCE ucode abort occurred"); + + if (status & DCE_IRQ_READY) { + dce_debug(d, "DCE IRQ Ready Received"); + (void)dce_fsm_post_event(d, + EVENT_ID_DCE_BOOT_COMPLETE_RECEIVED, + NULL); + } + + if (status & DCE_IRQ_SC7_ENTERED) { + dce_info(d, "DCE can be safely powered-off now"); + (void)dce_fsm_post_event(d, + EVENT_ID_DCE_SC7_ENTERED_RECEIVED, + NULL); + } + + if (status & DCE_IRQ_LOG_READY) { + dce_info(d, "DCE trace log buffers available"); + dce_wakeup_interruptible(d, DCE_WAIT_LOG); + } + + /* + * FIXME: handle individual bits + */ + if (status & (DCE_IRQ_LOG_OVERFLOW | DCE_IRQ_CRASH_LOG | + DCE_IRQ_ABORT)) { + // Wrapper function around this ?? + // TODO: Wake All other waiting processes + (void) dce_fsm_post_event(d, EVENT_ID_DCE_ABORT_RECEIVED, NULL); + } +} + + +/** + * dce_bootstrap_handle_boot_status- Handles boot status from DCE + * + * @d : Pointer to struct tegra_dce + * @status : Status received from DCE + * + * Return : Void + */ +static void dce_bootstrap_handle_boot_status(struct tegra_dce *d, u32 status) +{ + int ret = 0; + dce_mailbox_store_interface_status(d, status, + DCE_MAILBOX_BOOT_INTERFACE); + + ret = dce_fsm_post_event(d, EVENT_ID_DCE_BOOT_CMD_MSG_RECEIVED, NULL); + if (ret) + dce_err(d, "Mbox bootstrap cmd failed"); +} + + +/** + * dce_boot_interface_isr - Isr for the CCPLEX<->DCE boot interface. + * + * @d : Pointer to tegra_dce struct. + * + * Return : Void + */ +static void dce_boot_interface_isr(struct tegra_dce *d, void *data) +{ + u32 status; + u8 interface_id = DCE_MAILBOX_BOOT_INTERFACE; + + status = dce_mailbox_get_interface_status(d, interface_id); + if (status == 0xffffffff) + return; + + switch (DCE_IRQ_GET_STATUS_TYPE(status)) { + case DCE_IRQ_STATUS_TYPE_IRQ: + dce_handle_irq_status(d, status); + break; + case DCE_IRQ_STATUS_TYPE_BOOT_CMD: + dce_bootstrap_handle_boot_status(d, status); + break; + default: + dce_info(d, "Invalid Status Received from DCE. Status: [%x]", + status); + break; + } +} + +/** + * dce_parse_boot_status_err - Parses the error sent by DCE + * + * @d : Pointer to struct tegra_dce + * @status : Status read from mailbox + * + * Return : Void + */ +static void dce_parse_boot_status_err(struct tegra_dce *d, u32 status) +{ +#define DCE_BOOT_ERR_MASK 0x7FFFFF + status &= DCE_BOOT_ERR_MASK; +#undef DCE_BOOT_ERR_MASK + + switch (status) { + case DCE_BOOT_CMD_ERR_BAD_COMMAND: + dce_info(d, "Boot Status Error : DCE_BOOT_CMD_ERR_BAD_COMMAND"); + break; + case DCE_BOOT_CMD_ERR_UNIMPLEMENTED: + dce_info(d, "Boot Status Error : DCE_BOOT_CMD_ERR_UNIMPLEMENTED"); + break; + case DCE_BOOT_CMD_ERR_IPC_SETUP: + dce_info(d, "Boot Status Error : DCE_BOOT_CMD_ERR_IPC_SETUP"); + break; + case DCE_BOOT_CMD_ERR_INVALID_NFRAMES: + dce_info(d, "Boot Status Error : DCE_BOOT_CMD_ERR_INVALID_NFRAMES"); + break; + case DCE_BOOT_CMD_ERR_IPC_CREATE: + dce_info(d, "Boot Status Error : DCE_BOOT_CMD_ERR_IPC_CREATE"); + break; + case DCE_BOOT_CMD_ERR_LOCKED: + dce_info(d, "Boot Status Error : DCE_BOOT_CMD_ERR_LOCKED"); + break; + default: + dce_info(d, "Invalid Error Status Rcvd. Status: [%x]", status); + break; + } +} + +/** + * dce_mailbox_wait_boot_interface - Waits for mailbox messages. + * + * @d : Pointer to tegra_dce + * + * Return : 0 if successful + */ +static int dce_mailbox_wait_boot_interface(struct tegra_dce *d) +{ + u32 status; + int ret; + + ret = dce_wait_interruptible(d, DCE_WAIT_BOOT_CMD); + if (ret) { + /** + * TODO: Add error handling for abort and retry + */ + dce_err(d, "dce mbox wait was interrupted with err:%d", ret); + } + + status = dce_mailbox_get_interface_status(d, + DCE_MAILBOX_BOOT_INTERFACE); + + if (status & DCE_BOOT_CMD_ERR_FLAG) { + dce_parse_boot_status_err(d, status); + dce_err(d, "Error code received on boot interface : 0x%x", + status); + return -EBADE; + } + + return 0; +} + +/** + * dce_boot_interface_init - Initializes the dce boot interface + * and the associated resources. + * + * @d : Pointer to tegra_dce struct + * + * Return : 0 if successful + */ +int dce_boot_interface_init(struct tegra_dce *d) +{ + int ret = 0; + u8 mailbox_id = DCE_MAILBOX_BOOT_INTERFACE; + + ret = dce_mailbox_init_interface(d, mailbox_id, + DCE_MBOX_BOOT_CMD, DCE_MBOX_IRQ, + dce_mailbox_wait_boot_interface, + NULL, dce_boot_interface_isr); + if (ret) { + dce_err(d, "Boot Mailbox Interface Init Failed"); + goto err_init; + } + +err_init: + return ret; +} + +/** + * dce_boot_interface_deinit - Releases the resources + * associated with dce boot. + * + * @d : Pointer to tegra_dce struct. + * + * Return : Void + */ +void dce_boot_interface_deinit(struct tegra_dce *d) +{ + dce_mailbox_deinit_interface(d, + DCE_MAILBOX_BOOT_INTERFACE); +} + +/** + * dce_send_version_cmd - Sends the "VERSION" command to dce fw + * + * @d : Pointer to tegra_dce struct + * + * Return : 0 if successful + */ +static int dce_send_version_cmd(struct tegra_dce *d) +{ + u32 val; + int ret = 0; + + val = DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_VERSION); + + ret = dce_mailbox_send_cmd_sync(d, val, DCE_MAILBOX_BOOT_INTERFACE); + + return ret; +} + +/** + * dce_send_set_sid_cmd - Sends the "SET_SID" command to dce fw + * + * @d : Pointer to tegra_dce struct + * + * Return : 0 if successful + */ +static int dce_send_set_sid_cmd(struct tegra_dce *d) +{ + u32 val; + int ret = 0; + + val = DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_SET_SID) | + DCE_BOOT_CMD_PARM_SET(0, dce_get_dce_stream_id(d)); + + ret = dce_mailbox_send_cmd_sync(d, val, DCE_MAILBOX_BOOT_INTERFACE); + + return ret; +} + +/** + * dce_send_channel_int_cmd - Sends the "CHANNEL_INIT" command to dce fw + * + * @d : Pointer to tegra_dce struct + * + * Return : 0 if successful + */ +static int dce_send_channel_int_cmd(struct tegra_dce *d) +{ + u32 val; + int ret = 0; + + val = DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_CHANNEL_INIT); + + ret = dce_mailbox_send_cmd_sync(d, val, DCE_MAILBOX_BOOT_INTERFACE); + + return ret; +} + +/** + * dce_send_set_addr_read_cmd_hi - Sends addr_hi cmd to dce fw. + * + * @d : Pointer to tegra_dce struct. + * @addr : IOVA addr to be sent. + * @rd_wr : Tells if the addr to be sent is for read or write + * interface. + * + * Return : 0 if successful + */ +static int dce_send_set_addr_cmd_hi(struct tegra_dce *d, u32 addr, u8 rd_wr) +{ + u32 val; + int ret = 0; + + val = DCE_BOOT_CMD_SET_HILO(0U, 1U) | + DCE_BOOT_CMD_SET_RDWR(0U, rd_wr) | + DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_SET_ADDR) | + DCE_BOOT_CMD_PARM_SET(0U, addr); + + ret = dce_mailbox_send_cmd_sync(d, val, DCE_MAILBOX_BOOT_INTERFACE); + + return ret; +} + +/** + * dce_send_set_addr_read_cmd_lo - Sends addr_lo cmd to dce fw. + * + * @d : Pointer to tegra_dce struct. + * @addr : IOVA addr to be sent. + * @rd_wr : Tells if the addr to be sent is for read or write + * interface. + * + * Return : 0 if successful + */ +static int dce_send_set_addr_cmd_lo(struct tegra_dce *d, u32 addr, u8 rd_wr) +{ + u32 val; + int ret = 0; + + val = DCE_BOOT_CMD_SET_HILO(0U, 0U) | + DCE_BOOT_CMD_SET_RDWR(0U, rd_wr) | + DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_SET_ADDR) | + DCE_BOOT_CMD_PARM_SET(0U, addr); + + ret = dce_mailbox_send_cmd_sync(d, val, DCE_MAILBOX_BOOT_INTERFACE); + + return ret; +} + +/** + * dce_send_set_addr_read_cmd - Sends the addresses for admin + * read interface to dce fw. + * + * @d : Pointer to tegra_dce struct + * @rd_buff : Read address + * + * Return : 0 if successful + */ +static int dce_send_set_addr_read_cmd(struct tegra_dce *d, const u64 rd_buff) +{ + int ret = 0; + +#define DCE_DATA_NBITS_SHIFT 20 + ret = dce_send_set_addr_cmd_hi(d, rd_buff >> DCE_DATA_NBITS_SHIFT, 0); + if (ret) { + dce_err(d, "Sending of SEND_ADDR for READ IOVA HI failed"); + goto err_sending; + } + + ret = dce_send_set_addr_cmd_lo(d, rd_buff, 0); + if (ret) { + dce_err(d, "Sending of SEND_ADDR for READ IOVA LO failed"); + goto err_sending; + } +#undef DCE_DATA_NBITS_SHIFT + +err_sending: + return ret; +} + +/** + * dce_send_set_addr_write_cmd - Sends the addresses for admin + * write interface to dce fw. + * + * @d : Pointer to tegra_dce struct + * @wr_buff : Write address + * + * Return : 0 if successful + */ +static int dce_send_set_addr_write_cmd(struct tegra_dce *d, const u64 wr_buff) +{ + int ret = 0; + +#define DCE_DATA_NBITS_SHIFT 20 + ret = dce_send_set_addr_cmd_hi(d, wr_buff >> DCE_DATA_NBITS_SHIFT, 1); + if (ret) { + dce_err(d, "Sending of SEND_ADDR for READ IOVA HI failed"); + goto err_sending; + } + + ret = dce_send_set_addr_cmd_lo(d, wr_buff, 1); + if (ret) { + dce_err(d, "Sending of SEND_ADDR for READ IOVA LO failed"); + goto err_sending; + } +#undef DCE_DATA_NBITS_SHIFT + +err_sending: + return ret; +} + +/** + * dce_send_get_fsize_cmd - Sends the "GET_FSIZE" command to dce fw + * + * @d : Pointer to tegra_dce struct + * + * Return : 0 if successful + */ +static int dce_send_get_fsize_cmd(struct tegra_dce *d) +{ + u32 val; + int ret = 0; + + val = DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_GET_FSIZE); + + ret = dce_mailbox_send_cmd_sync(d, val, DCE_MAILBOX_BOOT_INTERFACE); + + return ret; +} + +/** + * dce_send_set_nframes_cmd - Sends the "SET_NFRAMES" command to dce fw. + * + * @d : Pointer to tegra_dce struct + * @nframes : No. of frames + * + * Return : 0 if successful + */ +static int dce_send_set_nframes_cmd(struct tegra_dce *d, const u8 nframes) +{ + u32 val; + int ret = 0; + + val = DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_SET_NFRAMES) + | DCE_BOOT_CMD_PARM_SET(0U, nframes); + + ret = dce_mailbox_send_cmd_sync(d, val, DCE_MAILBOX_BOOT_INTERFACE); + + return ret; +} + +/** + * dce_send_set_frames_cmd - Sends the "SET_NFRAMES" command to dce fw. + * + * @d : Pointer to tegra_dce struct + * @nframes : No. of frames + * + * Retrun : 0 if successful + */ +static int dce_send_set_fsize_cmd(struct tegra_dce *d, const u32 fsize) +{ + u32 val; + int ret = 0; + + val = DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_SET_FSIZE) + | DCE_BOOT_CMD_PARM_SET(0U, fsize); + + ret = dce_mailbox_send_cmd_sync(d, val, DCE_MAILBOX_BOOT_INTERFACE); + + return ret; +} + +/** + * dce_send_channel_int_cmd - Sends the "CHANNEL_INIT" command to dce fw + * + * @d : Pointer to tegra_dce struct + * + * Return : 0 if successful + */ +static int dce_send_lock_cmd(struct tegra_dce *d) +{ + u32 val; + int ret = 0; + + val = DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_LOCK); + + ret = dce_mailbox_send_cmd_sync(d, val, DCE_MAILBOX_BOOT_INTERFACE); + + return ret; +} + +/** + * dce_bootstrap_send_ast_iova_info - Sends the iova info for AST + * channel. + * + * @d - Pointer to struct tegra_dce. + * + * Return : O if successful + */ +static int dce_bootstrap_send_ast_iova_info(struct tegra_dce *d) +{ + u64 iova; + u32 size; + int ret = 0; + + ret = dce_ipc_get_region_iova_info(d, &iova, &size); + if (ret) { + dce_err(d, "Failed to get the iova info needed for ast config"); + goto err_sending; + } + +#define DCE_DATA_NBITS_SHIFT 20 + ret = dce_mailbox_send_cmd_sync(d, + DCE_BOOT_CMD_SET_HILO(0U, 1U) | + DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_SET_AST_LENGTH) | + DCE_BOOT_CMD_PARM_SET(0U, size >> DCE_DATA_NBITS_SHIFT), + DCE_MAILBOX_BOOT_INTERFACE); + if (ret) { + dce_err(d, "Sending of bootstrap cmd SET_AST_LENGTH(HI) failed"); + goto err_sending; + } + + ret = dce_mailbox_send_cmd_sync(d, + DCE_BOOT_CMD_SET_HILO(0U, 0U) | + DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_SET_AST_LENGTH) | + DCE_BOOT_CMD_PARM_SET(0U, size), + DCE_MAILBOX_BOOT_INTERFACE); + if (ret) { + dce_err(d, "Sending of bootstrap cmd SET_AST_LENGTH(LO) failed"); + goto err_sending; + } + + ret = dce_mailbox_send_cmd_sync(d, + DCE_BOOT_CMD_SET_HILO(0U, 1U) | + DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_SET_AST_IOVA) | + DCE_BOOT_CMD_PARM_SET(0U, iova >> DCE_DATA_NBITS_SHIFT), + DCE_MAILBOX_BOOT_INTERFACE); + if (ret) { + dce_err(d, "Sending of bootstrap cmd SET_AST_IOVA(HI) failed"); + goto err_sending; + } +#undef DCE_DATA_NBITS_SHIFT + + ret = dce_mailbox_send_cmd_sync(d, + DCE_BOOT_CMD_SET_HILO(0U, 0U) | + DCE_BOOT_CMD_SET(0U, DCE_BOOT_CMD_SET_AST_IOVA) | + DCE_BOOT_CMD_PARM_SET(0U, iova), + DCE_MAILBOX_BOOT_INTERFACE); + if (ret) { + dce_err(d, "Sending of bootstrap cmd SET_AST_IOVA(LO) failed"); + goto err_sending; + } + +err_sending: + return ret; +} + +/** + * dce_bootstrap_send_admin_ivc_info - Sends the ivc related info for admin + * channel. + * + * @d - Pointer to struct tegra_dce. + * + * Return : O if successful + */ +static int dce_bootstrap_send_admin_ivc_info(struct tegra_dce *d) +{ + int ret = 0; + u32 val = 0; + + struct dce_ipc_queue_info q_info; + + ret = dce_admin_get_ipc_channel_info(d, &q_info); + if (ret) { + dce_err(d, "Failed to get the admin ivc channel info"); + goto err_sending; + } + + ret = dce_send_set_addr_read_cmd(d, (u64)(q_info.tx_iova)); + if (ret) { + dce_err(d, "Sending of bootstrap cmd set_addr_read failed"); + goto err_sending; + } + + ret = dce_send_set_addr_write_cmd(d, (u64)(q_info.rx_iova)); + if (ret) { + dce_err(d, "Sending of bootstrap cmd set_addr_write failed"); + goto err_sending; + } + + ret = dce_send_get_fsize_cmd(d); + if (ret) { + dce_err(d, "Sending of bootstrap cmd get_fsize failed"); + goto err_sending; + } + + /** + * It's assummed here that no other command is sent in between. + */ + val = dce_mailbox_get_interface_status(d, + DCE_MAILBOX_BOOT_INTERFACE); + + ret = dce_send_set_nframes_cmd(d, q_info.nframes); + if (ret) { + dce_err(d, "Sending of bootstrap cmd set_nframes failed"); + goto err_sending; + } + + ret = dce_send_set_fsize_cmd(d, q_info.frame_sz); + if (ret) { + dce_err(d, "Sending of bootstrap cmd set_fsize failed"); + goto err_sending; + } + +err_sending: + return ret; +} + +/** + * dce_start_bootstrap_flow - Starts sending the boostrap cmds to + * dce fw in the required sequence. + * + * @d : Pointer to tegra_dce struct + * + * Return : 0 if successful + */ +int dce_start_bootstrap_flow(struct tegra_dce *d) +{ + u32 val; + int ret = 0; + + d->boot_status |= DCE_FW_BOOTSTRAP_START; + ret = dce_send_version_cmd(d); + if (ret) { + dce_err(d, "Sending of bootstrap cmd VERSION failed"); + goto err_sending; + } + /** + * It's assummed here that no other command is sent in between. + */ + val = dce_mailbox_get_interface_status(d, + DCE_MAILBOX_BOOT_INTERFACE); + + ret = dce_send_set_sid_cmd(d); + if (ret) { + dce_err(d, "Sending of bootstrap cmd set_sid failed"); + goto err_sending; + } + + ret = dce_bootstrap_send_ast_iova_info(d); + if (ret) { + dce_err(d, "Sending of iova info failed"); + goto err_sending; + } + + ret = dce_bootstrap_send_admin_ivc_info(d); + if (ret) { + dce_err(d, "Sending of ivc channel info failedbootstrap cmd set_sid failed"); + goto err_sending; + } + + ret = dce_send_channel_int_cmd(d); + if (ret) { + dce_err(d, "Sending of bootstrap cmd channel_int failed"); + goto err_sending; + } + + ret = dce_send_lock_cmd(d); + if (ret) { + dce_err(d, "Sending of bootstrap cmd lock failed"); + goto err_sending; + } + + d->boot_status |= DCE_FW_BOOTSTRAP_DONE; + return 0; + +err_sending: + dce_err(d, "Bootstrap process failed"); + d->boot_status |= DCE_FW_BOOTSTRAP_FAILED; + return ret; +} diff --git a/drivers/platform/tegra/dce/dce-client-ipc.c b/drivers/platform/tegra/dce/dce-client-ipc.c new file mode 100644 index 00000000..c9faec2e --- /dev/null +++ b/drivers/platform/tegra/dce/dce-client-ipc.c @@ -0,0 +1,386 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION & AFFILIATES. 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 +#include +#include +#include + +#define DCE_IPC_HANDLES_MAX 6U +#define DCE_CLIENT_IPC_HANDLE_INVALID 0U +#define DCE_CLIENT_IPC_HANDLE_VALID ((u32)BIT(31)) + +static struct tegra_dce_client_ipc client_handles[DCE_CLIENT_IPC_TYPE_MAX]; + +static uint32_t dce_interface_type_map[DCE_CLIENT_IPC_TYPE_MAX] = { + [DCE_CLIENT_IPC_TYPE_CPU_RM] = DCE_IPC_TYPE_DISPRM, + [DCE_CLIENT_IPC_TYPE_HDCP_KMD] = DCE_IPC_TYPE_HDCP, + [DCE_CLIENT_IPC_TYPE_RM_EVENT] = DCE_IPC_TYPE_RM_NOTIFY, +}; + +static void dce_client_process_event_ipc(struct tegra_dce *d, + struct tegra_dce_client_ipc *cl); + +static inline uint32_t dce_client_get_type(uint32_t int_type) +{ + uint32_t lc = 0; + + for (lc = 0; lc < DCE_CLIENT_IPC_TYPE_MAX; lc++) + if (dce_interface_type_map[lc] == int_type) + break; + + return lc; +} + +static inline u32 client_handle_to_index(u32 handle) +{ + return (u32)(handle & ~DCE_CLIENT_IPC_HANDLE_VALID); +} + +static inline bool is_client_handle_valid(u32 handle) +{ + bool valid = false; + + if ((handle & DCE_CLIENT_IPC_HANDLE_VALID) == 0U) + goto out; + + if (client_handle_to_index(handle) >= DCE_CLIENT_IPC_TYPE_MAX) + goto out; + + valid = true; + +out: + return valid; +} + +static struct tegra_dce_client_ipc *dce_client_ipc_lookup_handle(u32 handle) +{ + struct tegra_dce_client_ipc *cl = NULL; + + if (!is_client_handle_valid(handle)) + goto out; + + cl = &client_handles[client_handle_to_index(handle)]; + +out: + return cl; +} + + +static int dce_client_ipc_handle_alloc(u32 *handle) +{ + u32 index; + int ret = -1; + + if (handle == NULL) + return ret; + + for (index = 0; index < DCE_CLIENT_IPC_TYPE_MAX; index++) { + if (client_handles[index].valid == false) { + client_handles[index].valid = true; + *handle = (u32)(index | DCE_CLIENT_IPC_HANDLE_VALID); + ret = 0; + break; + } + } + + return ret; +} + +static int dce_client_ipc_handle_free(u32 handle) +{ + struct tegra_dce *d; + struct tegra_dce_client_ipc *cl; + + if (!is_client_handle_valid(handle)) + return -EINVAL; + + cl = &client_handles[client_handle_to_index(handle)]; + + if (cl->valid == false) + return -EINVAL; + + d = cl->d; + d->d_clients[cl->type] = NULL; + memset(cl, 0, sizeof(struct tegra_dce_client_ipc)); + + return 0; +} + +static void dce_client_async_event_work(struct work_struct *data) +{ + struct tegra_dce_client_ipc *cl; + struct dce_async_work *work = container_of(data, struct dce_async_work, + async_event_work); + struct tegra_dce *d = work->d; + + cl = d->d_clients[DCE_CLIENT_IPC_TYPE_RM_EVENT]; + + dce_client_process_event_ipc(d, cl); + atomic_set(&work->in_use, 0); +} + +int tegra_dce_register_ipc_client(u32 type, + tegra_dce_client_ipc_callback_t callback_fn, + void *data, u32 *handlep) +{ + int ret; + uint32_t int_type; + struct tegra_dce *d = NULL; + struct tegra_dce_client_ipc *cl; + u32 handle = DCE_CLIENT_IPC_HANDLE_INVALID; + + if (handlep == NULL) { + dce_err(d, "Invalid handle pointer"); + ret = -EINVAL; + goto end; + } + + if (type >= DCE_CLIENT_IPC_TYPE_MAX) { + dce_err(d, "Failed to retrieve client info for type: [%u]", type); + ret = -EINVAL; + goto end; + } + + int_type = dce_interface_type_map[type]; + + d = dce_ipc_get_dce_from_ch(int_type); + if (d == NULL) { + ret = -EINVAL; + goto out; + } + + /* + * Wait for bootstrapping to complete before client IPC registration + */ +#define DCE_IPC_REGISTER_BOOT_WAIT (30U * 1000) + ret = DCE_COND_WAIT_INTERRUPTIBLE_TIMEOUT(&d->dce_bootstrap_done, + dce_is_bootstrap_done(d), + DCE_IPC_REGISTER_BOOT_WAIT); + if (ret) { + dce_info(d, "dce boot wait failed (%d)\n", ret); + goto out; + } + + ret = dce_client_ipc_handle_alloc(&handle); + if (ret) + goto out; + + cl = &client_handles[client_handle_to_index(handle)]; + + cl->d = d; + cl->type = type; + cl->data = data; + cl->handle = handle; + cl->int_type = int_type; + cl->callback_fn = callback_fn; + atomic_set(&cl->complete, 0); + + ret = dce_cond_init(&cl->recv_wait); + if (ret) { + dce_err(d, "dce condition initialization failed for int_type: [%u]", + int_type); + goto out; + } + + d->d_clients[type] = cl; + +out: + if (ret != 0) { + dce_client_ipc_handle_free(handle); + handle = DCE_CLIENT_IPC_HANDLE_INVALID; + } + + *handlep = handle; +end: + return ret; +} +EXPORT_SYMBOL(tegra_dce_register_ipc_client); + +int tegra_dce_unregister_ipc_client(u32 handle) +{ + struct tegra_dce_client_ipc *cl; + + cl = &client_handles[client_handle_to_index(handle)]; + dce_cond_destroy(&cl->recv_wait); + + return dce_client_ipc_handle_free(handle); +} +EXPORT_SYMBOL(tegra_dce_unregister_ipc_client); + +int tegra_dce_client_ipc_send_recv(u32 handle, struct dce_ipc_message *msg) +{ + int ret; + struct tegra_dce_client_ipc *cl; + + if (msg == NULL) { + ret = -1; + goto out; + } + + cl = dce_client_ipc_lookup_handle(handle); + if (cl == NULL) { + ret = -1; + goto out; + } + + ret = dce_ipc_send_message_sync(cl->d, cl->int_type, msg); + +out: + return ret; +} +EXPORT_SYMBOL(tegra_dce_client_ipc_send_recv); + +int dce_client_init(struct tegra_dce *d) +{ + int ret = 0; + uint8_t i; + struct tegra_dce_async_ipc_info *d_aipc = &d->d_async_ipc; + + d_aipc->async_event_wq = + create_singlethread_workqueue("dce-async-ipc-wq"); + + for (i = 0; i < DCE_MAX_ASYNC_WORK; i++) { + struct dce_async_work *d_work = &d_aipc->work[i]; + + INIT_WORK(&d_work->async_event_work, + dce_client_async_event_work); + d_work->d = d; + atomic_set(&d_work->in_use, 0); + } + + return ret; +} + +void dce_client_deinit(struct tegra_dce *d) +{ + struct tegra_dce_async_ipc_info *d_aipc = &d->d_async_ipc; + + flush_workqueue(d_aipc->async_event_wq); + destroy_workqueue(d_aipc->async_event_wq); +} + +int dce_client_ipc_wait(struct tegra_dce *d, u32 int_type) +{ + uint32_t type; + struct tegra_dce_client_ipc *cl; + + type = dce_client_get_type(int_type); + if (type >= DCE_CLIENT_IPC_TYPE_MAX) { + dce_err(d, "Failed to retrieve client info for int_type: [%d]", + int_type); + return -EINVAL; + } + + cl = d->d_clients[type]; + if ((cl == NULL) || (cl->int_type != int_type)) { + dce_err(d, "Failed to retrieve client info for int_type: [%d]", + int_type); + return -EINVAL; + } + +retry_wait: + DCE_COND_WAIT_INTERRUPTIBLE(&cl->recv_wait, + atomic_read(&cl->complete) == 1); + if (atomic_read(&cl->complete) != 1) + goto retry_wait; + + atomic_set(&cl->complete, 0); + + return 0; +} + +static void dce_client_process_event_ipc(struct tegra_dce *d, + struct tegra_dce_client_ipc *cl) +{ + void *msg_data = NULL; + u32 msg_length; + int ret = 0; + + if ((cl == NULL) || (cl->callback_fn == NULL)) { + dce_err(d, "Invalid arg tegra_dce_client_ipc"); + return; + } + + if (cl->type != DCE_CLIENT_IPC_TYPE_RM_EVENT) { + dce_err(d, "Invalid arg for DCE_CLIENT_IPC_TYPE_RM_EVENT type:[%u]", cl->type); + return; + } + + msg_data = dce_kzalloc(d, DCE_CLIENT_MAX_IPC_MSG_SIZE, false); + if (msg_data == NULL) { + dce_err(d, "Could not allocate msg read buffer"); + goto done; + } + msg_length = DCE_CLIENT_MAX_IPC_MSG_SIZE; + + while (dce_ipc_is_data_available(d, cl->int_type)) { + ret = dce_ipc_read_message(d, cl->int_type, msg_data, msg_length); + if (ret) { + dce_info(d, "Error in reading DCE msg for ch_type [%d]", + cl->int_type); + goto done; + } + + cl->callback_fn(cl->handle, cl->type, msg_length, msg_data, cl->data); + } + +done: + if (msg_data) + dce_kfree(d, msg_data); +} + +static void dce_client_schedule_event_work(struct tegra_dce *d) +{ + struct tegra_dce_async_ipc_info *async_work_info = &d->d_async_ipc; + uint8_t i; + + for (i = 0; i < DCE_MAX_ASYNC_WORK; i++) { + struct dce_async_work *d_work = &async_work_info->work[i]; + + if (atomic_add_unless(&d_work->in_use, 1, 1) > 0) { + queue_work(async_work_info->async_event_wq, + &d_work->async_event_work); + break; + } + } + + if (i == DCE_MAX_ASYNC_WORK) + dce_err(d, "Failed to schedule Async event Queue Full!"); +} + +void dce_client_ipc_wakeup(struct tegra_dce *d, u32 ch_type) +{ + uint32_t type; + struct tegra_dce_client_ipc *cl; + + type = dce_client_get_type(ch_type); + if (type == DCE_CLIENT_IPC_TYPE_MAX) { + dce_err(d, "Failed to retrieve client info for ch_type: [%d]", + ch_type); + return; + } + + cl = d->d_clients[type]; + if ((cl == NULL) || (cl->int_type != ch_type)) { + dce_err(d, "Failed to retrieve client info for ch_type: [%d]", + ch_type); + return; + } + + if (type == DCE_CLIENT_IPC_TYPE_RM_EVENT) + return dce_client_schedule_event_work(d); + + atomic_set(&cl->complete, 1); + dce_cond_signal_interruptible(&cl->recv_wait); +} diff --git a/drivers/platform/tegra/dce/dce-debug-perf.c b/drivers/platform/tegra/dce/dce-debug-perf.c new file mode 100644 index 00000000..bd39532c --- /dev/null +++ b/drivers/platform/tegra/dce/dce-debug-perf.c @@ -0,0 +1,549 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. 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 +#include +#include +#include +#include +#include +#include + +#define DCE_PERF_OUTPUT_FORMAT_CSV ((uint32_t)(0U)) +#define DCE_PERF_OUTPUT_FORMAT_XML ((uint32_t)(1U)) + +static uint32_t perf_output_format = DCE_PERF_OUTPUT_FORMAT_CSV; + +ssize_t dbg_dce_perf_stats_stats_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + bool start_perf; + struct dce_ipc_message *msg = NULL; + struct dce_admin_ipc_cmd *req_msg; + struct tegra_dce *d = ((struct seq_file *)file->private_data)->private; + + ret = kstrtobool_from_user(user_buf, 3ULL, &start_perf); + if (ret) { + dce_err(d, "Unable to parse start/stop for dce perf stats"); + goto out; + } + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + goto out; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + + /* + * Collect All Perfs + * Clear previous data and start collecting new perf data + * + * TODO: Add support to collect specific perf data + */ + req_msg->args.perf.perf_cmd.enable = DCE_ADMIN_PERF_STATS_ALL; + req_msg->args.perf.perf_cmd.clear = DCE_ADMIN_PERF_CLEAR_CLEAR; + + /* + * echo "1/y" (kstrtobool true) to start perf data collection + * echo "0/n" (kstrtobool false) to stop perf data collection + */ + ret = dce_admin_send_cmd_set_perf_stat(d, msg, start_perf); + if (ret) { + dce_err(d, "Failed to Set perf stat\n"); + goto out; + } + + dce_debug(d, "DCE perf stats collection %s", start_perf ? "started" : "stopped"); + +out: + dce_admin_free_message(d, msg); + return count; +} + +static void dbg_dce_perf_stats_sched_task_xml(struct seq_file *s, struct tegra_dce *d, + const struct dce_admin_task_stats *task_stat) +{ + /* Don't print task stats if task name is not set */ + if (task_stat->name == NULL) + return; + + seq_printf(s, "%s", task_stat->name); + seq_printf(s, "%llu%llu", + task_stat->init, task_stat->ready); + seq_printf(s, "%llu", + task_stat->ready_time); + seq_printf(s, "%llu%llu", + task_stat->dcache_misses, task_stat->inst_exec); + seq_printf(s, "%llu", + task_stat->mmio_req); + seq_printf(s, "sleep%llu", + task_stat->sleep.accumulate); + seq_printf(s, "%llu%llu%llu", + task_stat->sleep.iterations, + task_stat->sleep.min, + task_stat->sleep.max); + seq_printf(s, "run%llu", + task_stat->run.accumulate); + seq_printf(s, "%llu%llu%llu", + task_stat->run.iterations, + task_stat->run.min, + task_stat->run.max); + seq_printf(s, "run-context%llu", + task_stat->run_context.accumulate); + seq_printf(s, "%llu%llu%llu", + task_stat->run_context.iterations, + task_stat->run_context.min, + task_stat->run_context.max); + seq_puts(s, "\n"); +} + +static void dbg_dce_perf_stats_pm_event_xml(struct seq_file *s, + const struct dce_admin_pm_event_stats *pm_evt) +{ + seq_printf(s, "%llu%llu\n", pm_evt->start, pm_evt->end); + seq_printf(s, "%llu\n", pm_evt->count); +} + +static void dbg_dce_perf_stats_show_xml(struct seq_file *s, struct tegra_dce *d, + struct dce_admin_perf_info *perf) +{ + + const struct dce_admin_pm_event_stats *pm_evt; + const struct dce_admin_sched_stats *sched = &perf->sched; + uint32_t i; + + seq_puts(s, "\n"); + seq_puts(s, ""); + seq_printf(s, "%llu\n", perf->tcm_ready); + seq_printf(s, "%llu\n", perf->sched_start); + seq_printf(s, "%llu\n", perf->ready_time); + + /* + * dCache-misses Perf Data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_DCACHE_MISSES]; + seq_puts(s, "\n"); + dbg_dce_perf_stats_pm_event_xml(s, pm_evt); + seq_puts(s, "\n"); + + /* + * instruction execution Perf Data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_INSTR_EXEC]; + seq_puts(s, "\n"); + dbg_dce_perf_stats_pm_event_xml(s, pm_evt); + seq_puts(s, "\n"); + + /* + * instruction execution Perf Data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_MEM_REQ]; + seq_puts(s, "\n"); + dbg_dce_perf_stats_pm_event_xml(s, pm_evt); + seq_puts(s, "\n"); + + /* + * Print Scheduler Perf data and Per task data + */ + seq_puts(s, "\n"); + seq_printf(s, "%llu%llu", sched->start, sched->end); + seq_printf(s, "%llu\n", sched->context_switches); + + for (i = 0U; i < DCE_ADMIN_PERF_ACTIVE_TASKS_NUM; i += 1U) { + dbg_dce_perf_stats_sched_task_xml(s, d, + &sched->tasks[i]); + } + + seq_puts(s, ""); //sched perf data done + seq_puts(s, "\n"); +} + + +static void dbg_dce_perf_stats_sched_task_csv(struct seq_file *s, struct tegra_dce *d, + const struct dce_admin_task_stats *task_stat) +{ + /* Don't print if task_name is not set */ + if (task_stat->name == NULL) + return; + + seq_printf(s, "\"%s\",\"%llu\",\"%llu\",\"%llu\",\"%llu\",\"%llu\",\"%llu\",\"sleep\",\"%llu\",\"%llu\",\"%llu\",\"%llu\"\n", + task_stat->name, task_stat->init, task_stat->ready, + task_stat->ready_time, task_stat->dcache_misses, + task_stat->inst_exec, task_stat->mmio_req, + task_stat->sleep.accumulate, + task_stat->sleep.iterations, task_stat->sleep.min, + task_stat->sleep.max); + seq_puts(s, "\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"run\","); + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",\"%llu\"\n", + task_stat->run.accumulate, task_stat->run.iterations, + task_stat->run.min, task_stat->run.max); + seq_puts(s, "\"\",\"\",\"\",\"\",\"\",\"\",\"\",\"run-context\","); + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",\"%llu\"\n", + task_stat->run_context.accumulate, + task_stat->run_context.iterations, + task_stat->run_context.min, + task_stat->run_context.max); +} + +static void dbg_dce_perf_stats_show_csv(struct seq_file *s, struct tegra_dce *d, + struct dce_admin_perf_info *perf) +{ + const struct dce_admin_pm_event_stats *pm_evt; + const struct dce_admin_sched_stats *sched = &perf->sched; + uint32_t i; + + seq_puts(s, "\"tcm-ready\",\"sched-start\",\"signaled-ready\","); + seq_puts(s, "\"dcache:start-time\",\"dcache:end-time\",\"dcache:miss_cnt\","); + seq_puts(s, "\"inst_exec:start-time\",\"inst_exec:end-time\",\"inst_exec:count\","); + seq_puts(s, "\"mmio_req:start-time\",\"mmio_req:end-time\",\"mmio_req:count\","); + seq_puts(s, "\"sched:start-time\",\"sched:end-time\",\"sched:context_switches\"\n"); + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",", perf->tcm_ready, + perf->sched_start, perf->ready_time); + + /* + * Print Cache Perf data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_DCACHE_MISSES]; + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",", + pm_evt->start, pm_evt->end, pm_evt->count); + + /* + * Print Instruction exec Perf data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_INSTR_EXEC]; + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",", + pm_evt->start, pm_evt->end, pm_evt->count); + + /* + * Print Instruction exec Perf data + */ + pm_evt = &perf->pm_events[DCE_ADMIN_PM_EVT_MEM_REQ]; + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\",", + pm_evt->start, pm_evt->end, pm_evt->count); + /* + * Print Scheduler Perf data and Per task data + */ + seq_printf(s, "\"%llu\",\"%llu\",\"%llu\"\n", + sched->start, sched->end, + sched->context_switches); + + seq_puts(s, "\"sched:task_name\",\"sched:task-init\",\"sched:task-ready\","); + seq_puts(s, "\"sched:task-ready-time\",\"sched:task-stat:dcache-misses\","); + seq_puts(s, "\"sched:task-stat:instr_exec\",\"sched:task-stat:mem_req\","); + seq_puts(s, "\"sched:task-stat:name\","); + seq_puts(s, "\"sched:task-stat:accumulate\",\"sched:task-stat:iterations\","); + seq_puts(s, "\"sched:task-stat:min\",\"sched:task-stat:max\"\n"); + for (i = 0U; i < DCE_ADMIN_PERF_ACTIVE_TASKS_NUM; i += 1U) { + dbg_dce_perf_stats_sched_task_csv(s, d, + &sched->tasks[i]); + } +} + +static int dbg_dce_perf_stats_stats_fops_show(struct seq_file *s, void *data) +{ + int ret = 0; + struct dce_ipc_message *msg = NULL; + struct dce_admin_ipc_resp *resp_msg; + struct dce_admin_perf_info *perf; + struct tegra_dce *d = (struct tegra_dce *)s->private; + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + goto out; + } + + ret = dce_admin_send_cmd_get_perf_stat(d, msg); + if (ret) { + dce_err(d, "Failed to Get perf stat\n"); + goto out; + } + + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + perf = &resp_msg->args.perf.info.sched_stats; + + if (perf_output_format == DCE_PERF_OUTPUT_FORMAT_CSV) + dbg_dce_perf_stats_show_csv(s, d, perf); + else + dbg_dce_perf_stats_show_xml(s, d, perf); + +out: + dce_admin_free_message(d, msg); + return 0; +} + +int dbg_dce_perf_stats_stats_fops_open(struct inode *inode, + struct file *file) +{ + return single_open(file, dbg_dce_perf_stats_stats_fops_show, + inode->i_private); +} + +/* + * Debugfs nodes for displaying a help message about perf stats capture + */ +static int dbg_dce_perf_stats_help_fops_show(struct seq_file *s, void *data) +{ +/** + * Writing + * '0' to /sys/kernel/debug/tegra_dce/perf/stats/stats + * - Clear the Perf stats data + * '1' to /sys/kernel/debug/tegra_dce/perf/stats/stats + * - Start the Perf stats data + * To capture fresh Perf stats, first clear and Start. + * + * The result can be printed in csv or xml format + * + * '0' to /sys/kernel/debug/tegra_dce/perf/format + * - Data is collected in CSV format + * '1' to /sys/kernel/debug/tegra_dce/perf/format + * - Data is collected in XML format + * Default is set for CSV + * + * cat /sys/kernel/debug/tegra_dce/perf/stats/stats + * - get the perf data in selected format + * + */ + seq_printf(s, "DCE Perf Capture\n" + "----------------------\n" + " echo '0' > perf/stats/stats : Clear perf data\n" + " cat perf/stats/stats : get perf data in selected format\n" + " echo <0/1> > perf/format : get data in CSV/XML format\n"); + return 0; +} + +int dbg_dce_perf_stats_help_fops_open(struct inode *inode, + struct file *file) +{ + return single_open(file, dbg_dce_perf_stats_help_fops_show, + inode->i_private); +} + +ssize_t dbg_dce_perf_format_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + u32 format; + struct tegra_dce *d = ((struct seq_file *)file->private_data)->private; + + ret = kstrtou32_from_user(user_buf, count, 10, &format); + if (ret) { + dce_err(d, "Invalid format!"); + goto done; + } + + if (format == DCE_PERF_OUTPUT_FORMAT_CSV) + perf_output_format = DCE_PERF_OUTPUT_FORMAT_CSV; + else if (format == DCE_PERF_OUTPUT_FORMAT_XML) + perf_output_format = DCE_PERF_OUTPUT_FORMAT_XML; + else + dce_err(d, "Invalid format [%u]!", format); + +done: + return count; +} + +static int dbg_dce_perf_format_fops_show(struct seq_file *s, void *data) +{ + seq_printf(s, "Supported Formats\n" + "----------------------\n" + "CSV:0 XML:1\n" + "Current format:%s\n", + (perf_output_format == DCE_PERF_OUTPUT_FORMAT_CSV) ? "CSV" : "XML"); + return 0; +} + +int dbg_dce_perf_format_fops_open(struct inode *inode, struct file *file) +{ + return single_open(file, dbg_dce_perf_format_fops_show, + inode->i_private); +} + +/* + * Debugfs nodes for displaying a help message about perf stats capture + */ +static int dbg_dce_perf_events_help_fops_show(struct seq_file *s, void *data) +{ +/** + * Writing + * '0' to /sys/kernel/debug/tegra_dce/perf/events/events + * - Clear the Perf Events data + * 'Bit masked value' to /kernel/debug/tegra_dce/perf/events/events + * - Enable an event for the Perf Events data capture. + * Clear bit mask if the event perf is not needed to be captured. + * + * The result can be printed in csv or xml format + * + * '0' to /sys/kernel/debug/tegra_dce/perf/format + * - Data is collected in CSV format + * '1' to /sys/kernel/debug/tegra_dce/perf/format + * - Data is collected in XML format + * Default is set for CSV + * + * cat /sys/kernel/debug/tegra_dce/perf/events/events + * - get the perf event data in selected format + * - shows event name/ accumulated time/ iterations happened + * - min and max time taken to run an event iteration + */ + seq_printf(s, "DCE Perf Events Capture\n" + "----------------------\n" + " echo '0' > perf/events/events: Clear perf events data\n" + " echo 'bit masked value' > perf/events/events: Enable perf event capture\n" + " cat perf/events/events: get perf events data in selected format\n" + " echo <0/1> > perf/format: get data in CSV/XML format\n"); + return 0; +} + +int dbg_dce_perf_events_help_fops_open(struct inode *inode, + struct file *file) +{ + return single_open(file, dbg_dce_perf_events_help_fops_show, + inode->i_private); +} + +ssize_t dbg_dce_perf_events_events_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos) +{ + int ret = 0; + uint32_t events; + struct dce_ipc_message *msg = NULL; + struct dce_admin_ipc_cmd *req_msg; + struct tegra_dce *d = ((struct seq_file *)file->private_data)->private; + + /* + * Writing "0/n" will clear the events + * Write Bit Masked value to enable events + */ + ret = kstrtou32_from_user(user_buf, count, 0, &events); + if (ret) { + dce_err(d, "Unable to parse for event perf stats"); + goto out; + } + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + goto out; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + + if (events == 0x0U) { + // Clear event + req_msg->args.perf.event_cmd.clear = DCE_ADMIN_EVENT_CLEAR_CLEAR; + } else { + // Disable all Events if enable : 0x80000000 + // Elase enable only Bit masked events + req_msg->args.perf.event_cmd.clear = DCE_ADMIN_EVENT_CLEAR_RETAIN; + req_msg->args.perf.event_cmd.enable = events; + } + + ret = dce_admin_send_cmd_clear_perf_events(d, msg); + if (ret) { + dce_err(d, "Failed to Clear perf events\n"); + goto out; + } +out: + dce_admin_free_message(d, msg); + return count; +} + +static void dbg_dce_perf_events_show_csv(struct seq_file *s, struct tegra_dce *d, + const struct dce_admin_event_info *events) +{ + int i = 0; + + seq_puts(s, "\"Evt Name\",\"accumulate\",\"iterations\",\"min\",\"max\"\n"); + + for (i = 0; i < DCE_ADMIN_NUM_EVENTS; i++) { + if (events->events[i].name == NULL) + continue; + + seq_printf(s, "\"%s\",\"%llu\",\"%llu\",\"%llu\",\"%llu\"\n", + events->events[i].name, events->events[i].event.accumulate, + events->events[i].event.iterations, events->events[i].event.min, + events->events[i].event.max); + } + +} + +static void dbg_dce_perf_events_show_xml(struct seq_file *s, struct tegra_dce *d, + const struct dce_admin_event_info *events) +{ + int i = 0; + + seq_puts(s, "\n"); + seq_puts(s, ""); + + for (i = 0; i < DCE_ADMIN_NUM_EVENTS; i++) { + if (events->events[i].name == NULL) + continue; + + seq_printf(s, "%s\n", events->events[i].name); + seq_printf(s, "%llu\n", + events->events[i].event.accumulate); + seq_printf(s, "%llu\n", + events->events[i].event.iterations); + seq_printf(s, "%llu\n", events->events[i].event.min); + seq_printf(s, "%llu\n", events->events[i].event.max); + } + + seq_puts(s, "\n"); +} + +static int dbg_dce_perf_events_events_fops_show(struct seq_file *s, void *data) +{ + int ret = 0; + struct dce_ipc_message *msg = NULL; + struct dce_admin_ipc_resp *resp_msg; + struct dce_admin_event_info *events; + struct tegra_dce *d = (struct tegra_dce *)s->private; + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + goto out; + } + + ret = dce_admin_send_cmd_get_perf_events(d, msg); + if (ret) { + dce_err(d, "Failed to Get perf stat\n"); + goto out; + } + + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + events = &resp_msg->args.perf.info.events_stats; + + if (perf_output_format == DCE_PERF_OUTPUT_FORMAT_CSV) + dbg_dce_perf_events_show_csv(s, d, events); + else + dbg_dce_perf_events_show_xml(s, d, events); + +out: + dce_admin_free_message(d, msg); + return 0; +} + +int dbg_dce_perf_events_events_fops_open(struct inode *inode, + struct file *file) +{ + return single_open(file, dbg_dce_perf_events_events_fops_show, + inode->i_private); +} diff --git a/drivers/platform/tegra/dce/dce-debug.c b/drivers/platform/tegra/dce/dce-debug.c new file mode 100644 index 00000000..edc8872b --- /dev/null +++ b/drivers/platform/tegra/dce/dce-debug.c @@ -0,0 +1,845 @@ +// SPDX-License-Identifier: GPL-2.0-only +/* + * Copyright (c) 2019-2023 NVIDIA CORPORATION & AFFILIATES. 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * dbg_dce_load_fw - loads the fw to DRAM. + * + * @d : Pointer to dce struct + * + * Return : 0 if successful + */ +static int dbg_dce_load_fw(struct tegra_dce *d) +{ + const char *name = dce_get_fw_name(d); + + d->fw_data = dce_request_firmware(d, name); + if (!d->fw_data) { + dce_err(d, "FW Request Failed"); + return -EBUSY; + } + + dce_set_load_fw_status(d, true); + + return 0; +} + +/** + * dbg_dce_config_ast - Configures the ast and sets the status. + * + * @d : Pointer to dce struct + * + * Return : Void + */ +static void dbg_dce_config_ast(struct tegra_dce *d) +{ + dce_config_ast(d); + dce_set_ast_config_status(d, true); +} + +/** + * dbg_dce_reset_dce - Configures the evp in DCE cluster + * and brings dce out of reset. + * + * @d : Pointer to dce struct + * + * Return : 0 if successful + */ +static int dbg_dce_reset_dce(struct tegra_dce *d) +{ + int ret = 0; + + + ret = dce_reset_dce(d); + if (ret) { + dce_err(d, "DCE Reset Failed"); + return ret; + } + dce_set_dce_reset_status(d, true); + + return ret; + +} + +/** + * dbg_dce_boot_dce - loads the fw and configures other dce cluster + * elements for bringing dce out of reset. + * + * @d : Pointer to dce struct + * + * Return : 0 if successful + */ +static int dbg_dce_boot_dce(struct tegra_dce *d) +{ + int ret = 0; + + + ret = dbg_dce_load_fw(d); + if (ret) { + dce_err(d, "DCE Load FW Failed"); + return ret; + } + + dbg_dce_config_ast(d); + + ret = dbg_dce_reset_dce(d); + + if (ret) + dce_err(d, "DCE Reset Failed"); + + return ret; + +} + +static ssize_t dbg_dce_load_fw_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[3]; + struct tegra_dce *d = file->private_data; + + if (d->load_complete) + buf[0] = 'Y'; + else + buf[0] = 'N'; + buf[1] = '\n'; + buf[2] = 0x00; + + return simple_read_from_buffer(user_buf, count, ppos, buf, 2); +} + +static ssize_t dbg_dce_load_fw_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + int ret = 0; + char buf[32]; + int buf_size; + bool bv; + struct tegra_dce *d = file->private_data; + + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (strtobool(buf, &bv) == 0) { + if (bv == true) { + ret = dbg_dce_load_fw(d); + if (ret) + return ret; + } + } + + return count; +} + +static const struct file_operations load_firmware_fops = { + .open = simple_open, + .read = dbg_dce_load_fw_read, + .write = dbg_dce_load_fw_write, +}; + +static ssize_t dbg_dce_config_ast_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[3]; + struct tegra_dce *d = file->private_data; + + if (d->ast_config_complete) + buf[0] = 'Y'; + else + buf[0] = 'N'; + buf[1] = '\n'; + buf[2] = 0x00; + + return simple_read_from_buffer(user_buf, count, ppos, buf, 2); +} + +static ssize_t dbg_dce_config_ast_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[32]; + int buf_size; + bool bv; + struct tegra_dce *d = file->private_data; + + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (strtobool(buf, &bv) == 0) { + if (bv == true) + dbg_dce_config_ast(d); + } + + return count; +} + +static const struct file_operations config_ast_fops = { + .open = simple_open, + .read = dbg_dce_config_ast_read, + .write = dbg_dce_config_ast_write, +}; + +static ssize_t dbg_dce_reset_dce_fops_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[3]; + struct tegra_dce *d = file->private_data; + + if (d->reset_complete) + buf[0] = 'Y'; + else + buf[0] = 'N'; + buf[1] = '\n'; + buf[2] = 0x00; + + return simple_read_from_buffer(user_buf, count, ppos, buf, 2); +} + +static ssize_t dbg_dce_reset_dce_fops_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + int ret = 0; + char buf[32]; + int buf_size; + bool bv; + struct tegra_dce *d = file->private_data; + + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (strtobool(buf, &bv) == 0) { + if (bv == true) { + ret = dbg_dce_reset_dce(d); + if (ret) + return ret; + } + } + + return count; +} + +static const struct file_operations reset_dce_fops = { + .open = simple_open, + .read = dbg_dce_reset_dce_fops_read, + .write = dbg_dce_reset_dce_fops_write, +}; + +static ssize_t dbg_dce_admin_echo_fops_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + int ret = 0; + char buf[32]; + int buf_size; + u32 i, echo_count; + struct dce_ipc_message *msg = NULL; + struct dce_admin_ipc_cmd *req_msg; + struct dce_admin_ipc_resp *resp_msg; + struct tegra_dce *d = file->private_data; + + buf_size = min(count, (sizeof(buf)-1)); + ret = kstrtou32_from_user(user_buf, buf_size, 10, &echo_count); + if (ret) { + dce_err(d, "Admin msg count out of range"); + goto out; + } + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + goto out; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + + dce_info(d, "Requested %u echo messages", echo_count); + + for (i = 0; i < echo_count; i++) { + u32 resp; + + req_msg->args.echo.data = i; + ret = dce_admin_send_cmd_echo(d, msg); + if (ret) { + dce_err(d, "Admin msg failed for seq No : %u", i); + goto out; + } + + resp = resp_msg->args.echo.data; + + if (i == resp) { + dce_info(d, "Received Response:%u for request:%u", + resp, i); + } else { + dce_err(d, "Invalid response, expected:%u received:%u", + i, resp); + } + } + +out: + if (msg) + dce_admin_free_message(d, msg); + + return count; +} + +static const struct file_operations admin_echo_fops = { + .open = simple_open, + .write = dbg_dce_admin_echo_fops_write, +}; + +/* + * Debugfs nodes for displaying a help message about tests required by external + * clients (ex: MODS) + */ +static int dbg_dce_tests_external_help_fops_show(struct seq_file *s, void *data) +{ + /* TODO: Add test description? */ + seq_printf(s, "DCE External Test List\n" + "----------------------\n" + " - Test #0: MODS ALU Test\n" + " - Test #1: MODS DMA Test\n"); + + return 0; + +} + +static int dbg_dce_tests_external_help_fops_open(struct inode *inode, + struct file *file) +{ + return single_open(file, dbg_dce_tests_external_help_fops_show, + inode->i_private); +} + +static const struct file_operations tests_external_help_fops = { + .open = dbg_dce_tests_external_help_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/* Get status of last external client test run */ +static ssize_t dbg_dce_tests_external_status_fops_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + struct tegra_dce *d = file->private_data; + struct dce_device *d_dev = dce_device_from_dce(d); + char buf[15]; + ssize_t bytes_printed; + + bytes_printed = snprintf(buf, 15, "%d\n", d_dev->ext_test_status); + if (bytes_printed < 0) { + dce_err(d, "Unable to return external test status"); + buf[0] = '\0'; + bytes_printed = 0; + } + + return simple_read_from_buffer(user_buf, count, ppos, buf, bytes_printed); +} + +static const struct file_operations tests_external_status_fops = { + .open = simple_open, + .read = dbg_dce_tests_external_status_fops_read, +}; + +/* Run an external client test */ +static ssize_t dbg_dce_tests_external_run_fops_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + int ret = 0; + u32 test; + struct dce_ipc_message *msg = NULL; + struct dce_admin_ipc_cmd *req_msg = NULL; + struct dce_admin_ipc_resp *resp_msg = NULL; + struct tegra_dce *d = file->private_data; + struct dce_device *d_dev = dce_device_from_dce(d); + + ret = kstrtou32_from_user(user_buf, count, 10, &test); + if (ret) { + dce_err(d, "Invalid test number!"); + d_dev->ext_test_status = DCE_ERR_CORE_NOT_FOUND; + return -EINVAL; + } + switch (test) { + case DCE_ADMIN_EXT_TEST_ALU: + dce_info(d, "Running ALU test"); + break; + + case DCE_ADMIN_EXT_TEST_DMA: + dce_info(d, "Running DMA test"); + break; + + default: + dce_err(d, "Test(%u) not found! Check help node for valid test IDs.", + test); + d_dev->ext_test_status = DCE_ERR_CORE_NOT_FOUND; + return -EINVAL; + } + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + d_dev->ext_test_status = DCE_ERR_CORE_OTHER; + goto exit; + } + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + + req_msg->args.ext_test.test = test; + ret = dce_admin_send_cmd_ext_test(d, msg); + if (ret) { + dce_err(d, "Admin msg failed"); + d_dev->ext_test_status = DCE_ERR_CORE_IPC_IVC_ERR; + goto exit; + } + + if (resp_msg->error == DCE_ERR_CORE_SUCCESS) + dce_info(d, "Test passed!"); + else if (resp_msg->error == DCE_ERR_CORE_NOT_IMPLEMENTED) + dce_err(d, "Test not implemented!"); + else + dce_err(d, "Test failed(%d)!", (int32_t)resp_msg->error); + d_dev->ext_test_status = resp_msg->error; + +exit: + if (msg) + dce_admin_free_message(d, msg); + + return count; +} + +static const struct file_operations tests_external_run_fops = { + .open = simple_open, + .write = dbg_dce_tests_external_run_fops_write, +}; + +static ssize_t dbg_dce_boot_dce_fops_read(struct file *file, + char __user *user_buf, size_t count, loff_t *ppos) +{ + char buf[3]; + struct tegra_dce *d = file->private_data; + + if (d->ast_config_complete && + d->reset_complete && d->load_complete) + buf[0] = 'Y'; + else + buf[0] = 'N'; + buf[1] = '\n'; + buf[2] = 0x00; + + return simple_read_from_buffer(user_buf, count, ppos, buf, 2); +} + +static ssize_t dbg_dce_boot_dce_fops_write(struct file *file, + const char __user *user_buf, size_t count, loff_t *ppos) +{ + int ret = 0; + char buf[32]; + int buf_size; + bool bv; + struct tegra_dce *d = file->private_data; + + buf_size = min(count, (sizeof(buf)-1)); + if (copy_from_user(buf, user_buf, buf_size)) + return -EFAULT; + + if (strtobool(buf, &bv) == 0) { + if (bv == true) { + ret = dbg_dce_boot_dce(d); + if (ret) + return ret; + } + } + + return count; +} + +static const struct file_operations boot_dce_fops = { + .open = simple_open, + .read = dbg_dce_boot_dce_fops_read, + .write = dbg_dce_boot_dce_fops_write, +}; + +static ssize_t dbg_dce_boot_status_fops_read(struct file *file, + char __user *user_buf, + size_t count, loff_t *ppos) +{ + u8 fsb; + char buf[32]; + u32 last_status; + ssize_t len = 0; + unsigned long bitmap; + struct tegra_dce *d = file->private_data; + u32 boot_status = d->boot_status; + hsp_sema_t ss = dce_ss_get_state(d, DCE_BOOT_SEMA); + + if (ss & DCE_BOOT_COMPLETE) + goto core_boot_done; + + /* Clear BOOT_COMPLETE bit and bits set by OS */ + ss &= ~(DCE_OS_BITMASK | DCE_BOOT_COMPLETE); + bitmap = ss; + + fsb = find_first_bit(&bitmap, 32U); + if (fsb > 31U) { + dce_info(d, "dce-fw boot not started yet"); + goto core_boot_done; + } + + last_status = DCE_BIT(fsb); + + switch (last_status) { + case DCE_HALTED: + strcpy(buf, "DCE_HALTED"); + break; + case DCE_BOOT_TCM_COPY: + strcpy(buf, "TCM_COPY"); + break; + case DCE_BOOT_HW_INIT: + strcpy(buf, "HW_INIT"); + break; + case DCE_BOOT_MPU_INIT: + strcpy(buf, "MPU_INIT:"); + break; + case DCE_BOOT_CACHE_INIT: + strcpy(buf, "CACHE_INIT"); + break; + case DCE_BOOT_R5_INIT: + strcpy(buf, "R5_INIT"); + break; + case DCE_BOOT_DRIVER_INIT: + strcpy(buf, "DRIVER_INIT"); + break; + case DCE_BOOT_MAIN_STARTED: + strcpy(buf, "MAIN_STARTED"); + break; + case DCE_BOOT_TASK_INIT_START: + strcpy(buf, "TASK_INIT_STARTED"); + break; + case DCE_BOOT_TASK_INIT_DONE: + strcpy(buf, "TASK_INIT_DONE"); + break; + default: + strcpy(buf, "STATUS_UNKNOWN"); + break; + } + goto done; + +core_boot_done: + /* Clear DCE_STATUS_FAILED bit get actual failure reason*/ + boot_status &= ~DCE_STATUS_FAILED; + bitmap = boot_status; + last_status = DCE_BIT(find_first_bit(&bitmap, 32)); + + switch (last_status) { + case DCE_FW_SUSPENDED: + strcpy(buf, "DCE_FW_SUSPENDED"); + break; + case DCE_FW_BOOT_DONE: + strcpy(buf, "DCE_FW_BOOT_DONE"); + break; + case DCE_FW_ADMIN_SEQ_DONE: + strcpy(buf, "DCE_FW_ADMIN_SEQ_DONE"); + break; + case DCE_FW_ADMIN_SEQ_FAILED: + strcpy(buf, "DCE_FW_ADMIN_SEQ_FAILED"); + break; + case DCE_FW_ADMIN_SEQ_START: + strcpy(buf, "DCE_FW_ADMIN_SEQ_STARTED"); + break; + case DCE_FW_BOOTSTRAP_DONE: + strcpy(buf, "DCE_FW_BOOTSTRAP_DONE"); + break; + case DCE_FW_BOOTSTRAP_FAILED: + strcpy(buf, "DCE_FW_BOOTSTRAP_FAILED"); + break; + case DCE_FW_BOOTSTRAP_START: + strcpy(buf, "DCE_FW_BOOTSTRAP_STARTED"); + break; + case DCE_FW_EARLY_BOOT_FAILED: + strcpy(buf, "DCE_FW_EARLY_BOOT_FAILED"); + break; + case DCE_FW_EARLY_BOOT_DONE: + strcpy(buf, "DCE_FW_EARLY_BOOT_DONE"); + break; + case DCE_AST_CONFIG_DONE: + strcpy(buf, "DCE_AST_CONFIG_DONE"); + break; + case DCE_AST_CONFIG_START: + strcpy(buf, "DCE_AST_CONFIG_STARTED"); + break; + case DCE_EARLY_INIT_DONE: + strcpy(buf, "DCE_EARLY_INIT_DONE"); + break; + case DCE_EARLY_INIT_FAILED: + strcpy(buf, "DCE_EARLY_INIT_FAILED"); + break; + case DCE_EARLY_INIT_START: + strcpy(buf, "DCE_EARLY_INIT_STARTED"); + break; + default: + strcpy(buf, "STATUS_UNKNOWN"); + } + +done: + len = strlen(buf); + buf[len] = '\0'; + dce_info(d, "boot status:%s status_val:0x%x\n", buf, d->boot_status); + return simple_read_from_buffer(user_buf, count, ppos, buf, len + 1); +} + +static const struct file_operations boot_status_fops = { + .open = simple_open, + .read = dbg_dce_boot_status_fops_read, +}; + +static const struct file_operations perf_format_fops = { + .open = dbg_dce_perf_format_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = dbg_dce_perf_format_fops_write, +}; + +static const struct file_operations perf_stats_stats_fops = { + .open = dbg_dce_perf_stats_stats_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = dbg_dce_perf_stats_stats_fops_write, +}; + +static const struct file_operations perf_stats_help_fops = { + .open = dbg_dce_perf_stats_help_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +static const struct file_operations perf_events_events_fops = { + .open = dbg_dce_perf_events_events_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, + .write = dbg_dce_perf_events_events_fops_write, +}; + +static const struct file_operations perf_events_help_fops = { + .open = dbg_dce_perf_events_help_fops_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +void dce_remove_debug(struct tegra_dce *d) +{ + struct dce_device *d_dev = dce_device_from_dce(d); + + debugfs_remove(d_dev->debugfs); + + d_dev->debugfs = NULL; +} + +static int dump_hsp_regs_show(struct seq_file *s, void *unused) +{ + u8 i = 0; + struct tegra_dce *d = s->private; + + /** + * Dump Boot Semaphore Value + */ + dce_info(d, "DCE_BOOT_SEMA : 0x%x", + dce_ss_get_state(d, DCE_BOOT_SEMA)); + + /** + * Dump Shared Mailboxes Values + */ + dce_info(d, "DCE_MBOX_FROM_DCE_RM : 0x%x", + dce_smb_read(d, DCE_MBOX_FROM_DCE_RM)); + dce_info(d, "DCE_MBOX_TO_DCE_RM: 0x%x", + dce_smb_read(d, DCE_MBOX_TO_DCE_RM)); + dce_info(d, "DCE_MBOX_FROM_DCE_RM_EVENT_NOTIFY: 0x%x", + dce_smb_read(d, DCE_MBOX_FROM_DCE_RM_EVENT_NOTIFY)); + dce_info(d, "DCE_MBOX_TO_DCE_RM_EVENT_NOTIFY: 0x%x", + dce_smb_read(d, DCE_MBOX_TO_DCE_RM_EVENT_NOTIFY)); + dce_info(d, "DCE_MBOX_FROM_DCE_ADMIN: 0x%x", + dce_smb_read(d, DCE_MBOX_FROM_DCE_ADMIN)); + dce_info(d, "DCE_MBOX_BOOT_CMD: 0x%x", + dce_smb_read(d, DCE_MBOX_BOOT_CMD)); + dce_info(d, "DCE_MBOX_IRQ: 0x%x", + dce_smb_read(d, DCE_MBOX_IRQ)); + + /** + * Dump HSP IE registers Values + */ + +#define DCE_MAX_IE_REGS 5U + for (i = 0; i < DCE_MAX_IE_REGS; i++) + dce_info(d, "DCE_HSP_IE_%d : 0x%x", i, dce_hsp_ie_read(d, i)); +#undef DCE_MAX_IE_REGS + + /** + * Dump HSP IE registers Values + */ +#define DCE_MAX_SM_FULL_REGS 8U + for (i = 0; i < DCE_MAX_SM_FULL_REGS; i++) { + dce_info(d, "DCE_HSP_SM_FULL_%d : 0x%x", i, + dce_smb_read_full_ie(d, i)); + } +#undef DCE_MAX_SM_FULL_REGS + + dce_info(d, "DCE_HSP_IR : 0x%x", + dce_hsp_ir_read(d)); + return 0; +} + +static int dump_hsp_regs_open(struct inode *inode, struct file *file) +{ + return single_open(file, dump_hsp_regs_show, inode->i_private); +} + +static const struct file_operations dump_hsp_regs_fops = { + .open = dump_hsp_regs_open, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; + +/** + * dce_init_debug - Initializes the debug features of dce + * + * @d : Pointer to tegra_dce struct. + * + * Return : Void + */ +void dce_init_debug(struct tegra_dce *d) +{ + struct dentry *retval; + struct device *dev = dev_from_dce(d); + struct dce_device *d_dev = dce_device_from_dce(d); + struct dentry *debugfs_dir = NULL; + struct dentry *perf_debugfs_dir = NULL; + + d_dev->debugfs = debugfs_create_dir("tegra_dce", NULL); + if (!d_dev->debugfs) + return; + + retval = debugfs_create_file("load_fw", 0444, + d_dev->debugfs, d, &load_firmware_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("config_ast", 0444, + d_dev->debugfs, d, &config_ast_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("reset", 0444, + d_dev->debugfs, d, &reset_dce_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("boot", 0444, + d_dev->debugfs, d, &boot_dce_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("admin_echo", 0444, + d_dev->debugfs, d, &admin_echo_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("boot_status", 0444, + d_dev->debugfs, d, &boot_status_fops); + if (!retval) + goto err_handle; + + perf_debugfs_dir = debugfs_create_dir("perf", d_dev->debugfs); + if (!perf_debugfs_dir) + goto err_handle; + + retval = debugfs_create_file("format", 0644, + perf_debugfs_dir, d, &perf_format_fops); + if (!retval) + goto err_handle; + + debugfs_dir = debugfs_create_dir("stats", perf_debugfs_dir); + if (!debugfs_dir) + goto err_handle; + + retval = debugfs_create_file("stats", 0644, + debugfs_dir, d, &perf_stats_stats_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("help", 0444, + debugfs_dir, d, &perf_stats_help_fops); + if (!retval) + goto err_handle; + + debugfs_dir = debugfs_create_dir("events", perf_debugfs_dir); + if (!debugfs_dir) + goto err_handle; + + retval = debugfs_create_file("events", 0644, + debugfs_dir, d, &perf_events_events_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("help", 0444, + debugfs_dir, d, &perf_events_help_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("dump_hsp_regs", 0444, + d_dev->debugfs, d, &dump_hsp_regs_fops); + if (!retval) + goto err_handle; + + /* Tests */ + debugfs_dir = debugfs_create_dir("tests", d_dev->debugfs); + if (!debugfs_dir) + goto err_handle; + debugfs_dir = debugfs_create_dir("external", debugfs_dir); + if (!debugfs_dir) + goto err_handle; + retval = debugfs_create_file("help", 0444, + debugfs_dir, d, &tests_external_help_fops); + if (!retval) + goto err_handle; + retval = debugfs_create_file("run", 0220, + debugfs_dir, d, &tests_external_run_fops); + if (!retval) + goto err_handle; + retval = debugfs_create_file("status", 0444, + debugfs_dir, d, &tests_external_status_fops); + if (!retval) + goto err_handle; + + return; + +err_handle: + dev_err(dev, "could not create debugfs\n"); + dce_remove_debug(d); +} diff --git a/drivers/platform/tegra/dce/dce-fsm.c b/drivers/platform/tegra/dce/dce-fsm.c new file mode 100644 index 00000000..c8e35c56 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-fsm.c @@ -0,0 +1,534 @@ +/* + * Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. 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 +#include + +struct dce_event_process_struct { + enum dce_fsm_event_id_type event; + int (*fsm_event_handle)(struct tegra_dce *d, void *params); +}; + +/* + * Please update FSM design Document, whenever updating below event table + */ +static struct dce_event_process_struct event_process_table[] = { + { + .event = EVENT_ID_DCE_FSM_START, + .fsm_event_handle = dce_handle_fsm_start_event, + }, + { + .event = EVENT_ID_DCE_BOOT_COMPLETE_REQUESTED, + .fsm_event_handle = dce_handle_boot_complete_requested_event, + }, + { + .event = EVENT_ID_DCE_BOOT_COMPLETE_RECEIVED, + .fsm_event_handle = dce_handle_boot_complete_received_event, + }, + { + .event = EVENT_ID_DCE_BOOT_CMD_MSG_REQUESTED, + .fsm_event_handle = dce_handle_boot_cmd_requested_event, + }, + { + .event = EVENT_ID_DCE_BOOT_CMD_MSG_RECEIVED, + .fsm_event_handle = dce_handle_boot_cmd_received_event, + }, + { + .event = EVENT_ID_DCE_ADMIN_IPC_MSG_REQUESTED, + .fsm_event_handle = dce_admin_handle_ipc_requested_event, + }, + { + .event = EVENT_ID_DCE_ADMIN_IPC_MSG_RECEIVED, + .fsm_event_handle = dce_admin_handle_ipc_received_event, + }, + { + .event = EVENT_ID_DCE_SC7_ENTER_REQUESTED, + .fsm_event_handle = dce_pm_handle_sc7_enter_requested_event, + }, + { + .event = EVENT_ID_DCE_SC7_ENTERED_RECEIVED, + .fsm_event_handle = dce_pm_handle_sc7_enter_received_event, + }, + { + .event = EVENT_ID_DCE_SC7_EXIT_RECEIVED, + .fsm_event_handle = dce_pm_handle_sc7_exit_received_event, + }, + { + .event = EVENT_ID_DCE_LOG_REQUESTED, + .fsm_event_handle = dce_handle_event_stub, + }, + { + .event = EVENT_ID_DCE_LOG_READY_RECEIVED, + .fsm_event_handle = dce_handle_event_stub, + }, + { + .event = EVENT_ID_DCE_ABORT_RECEIVED, + .fsm_event_handle = dce_handle_event_stub, + }, + { + .event = EVENT_ID_DCE_CRASH_LOG_RECEIVED, + .fsm_event_handle = dce_handle_event_stub, + }, + { + .event = EVENT_ID_DCE_LOG_OVERFLOW_RECEIVED, + .fsm_event_handle = dce_handle_event_stub, + }, + { + .event = EVENT_ID_DCE_FSM_STOP, + .fsm_event_handle = dce_handle_event_stub, + }, +}; + +#define DCE_MAX_EVENTS_IDS (sizeof(event_process_table) / \ + sizeof(struct dce_event_process_struct)) + +/** + * dce_handle_fsm_start_event - Callback handler function for FSM_START event. + * + * @d : Pointer to tegra_dce struct. + * @params : callback params + * + * Return : 0 if successful else error code + */ +int dce_handle_fsm_start_event(struct tegra_dce *d, void *params) +{ + return 0; +} + +/** + * dce_handle_event_stub - Stub callback handler function. + * + * @d : Pointer to tegra_dce struct. + * @params : callback params + * + * Return : 0 if successful + */ +int dce_handle_event_stub(struct tegra_dce *d, void *params) +{ + return 0; +} + +/** + * dce_fsm_set_state - Set the FSM state based on event + * + * This Function is called with Mutex held. + * + * @d : Pointer to tegra_dce struct. + * @event : Event for which FSM state need to be set + * + * Return : void + * + * Please update FSM design Document, whenever updating below event states + */ +static void +dce_fsm_set_state(struct tegra_dce *d, + enum dce_fsm_event_id_type event) +{ + struct dce_fsm_info *fsm = &d->fsm_info; + + switch (event) { + case EVENT_ID_DCE_FSM_START: + fsm->c_state = STATE_DCE_FSM_IDLE; + fsm->requested_ipcs = 0; + break; + + case EVENT_ID_DCE_BOOT_COMPLETE_REQUESTED: + fsm->c_state = STATE_DCE_BOOT_WAIT; + fsm->requested_ipcs |= DCE_BIT(DCE_WAIT_BOOT_COMPLETE); + break; + + case EVENT_ID_DCE_BOOT_COMPLETE_RECEIVED: + fsm->c_state = STATE_DCE_FSM_IDLE; + fsm->requested_ipcs &= ~DCE_BIT(DCE_WAIT_BOOT_COMPLETE); + break; + + case EVENT_ID_DCE_BOOT_CMD_MSG_REQUESTED: + fsm->c_state = STATE_DCE_BOOTCMD_WFI; + fsm->requested_ipcs |= DCE_BIT(DCE_WAIT_BOOT_CMD); + break; + + case EVENT_ID_DCE_BOOT_CMD_MSG_RECEIVED: + fsm->c_state = STATE_DCE_FSM_IDLE; + fsm->requested_ipcs &= ~DCE_BIT(DCE_WAIT_BOOT_CMD); + break; + + case EVENT_ID_DCE_ADMIN_IPC_MSG_REQUESTED: + fsm->c_state = STATE_DCE_ADMIN_WFI; + fsm->requested_ipcs |= DCE_BIT(DCE_WAIT_ADMIN_IPC); + break; + + case EVENT_ID_DCE_ADMIN_IPC_MSG_RECEIVED: + fsm->c_state = STATE_DCE_FSM_IDLE; + fsm->requested_ipcs &= ~DCE_BIT(DCE_WAIT_ADMIN_IPC); + break; + + case EVENT_ID_DCE_SC7_ENTER_REQUESTED: + fsm->c_state = STATE_DCE_SC7_ENTER_WFI; + fsm->requested_ipcs |= DCE_BIT(DCE_WAIT_SC7_ENTER); + break; + + case EVENT_ID_DCE_SC7_ENTERED_RECEIVED: + fsm->c_state = STATE_DCE_SC7_ENTERED; + fsm->requested_ipcs &= ~DCE_BIT(DCE_WAIT_SC7_ENTER); + break; + + case EVENT_ID_DCE_SC7_EXIT_RECEIVED: + fsm->c_state = STATE_DCE_FSM_IDLE; + fsm->requested_ipcs = 0; + break; + + case EVENT_ID_DCE_LOG_REQUESTED: + fsm->c_state = STATE_DCE_LOG_READY_WFI; + fsm->requested_ipcs |= DCE_BIT(DCE_WAIT_LOG); + break; + + case EVENT_ID_DCE_LOG_READY_RECEIVED: + fsm->c_state = STATE_DCE_FSM_IDLE; + fsm->requested_ipcs &= ~DCE_BIT(DCE_WAIT_LOG); + break; + + case EVENT_ID_DCE_FSM_STOP: + fsm->c_state = STATE_DCE_INVALID; + break; + + case EVENT_ID_DCE_ABORT_RECEIVED: + case EVENT_ID_DCE_CRASH_LOG_RECEIVED: + case EVENT_ID_DCE_LOG_OVERFLOW_RECEIVED: + dce_debug(d, "DCE Abort received"); + fsm->c_state = STATE_DCE_ABORT; + /* + * TODO: error handling + * wake-up any waiting process avoid kernel watchdog + * trigger and crash + */ + break; + default: + dce_err(d, "INVALID EVENT [%d]", event); + } + +} + +/* + * dce_fsm_validate_event - Validate event agaist current FSM state + * + * @d : Pointer to tegra_dce struct + * @event : Posted FSM event + * + * @return : ESUCCESS if event valid else error code + * + * Please update FSM design Document, whenever updating below event validation + */ +static int +dce_fsm_validate_event(struct tegra_dce *d, + enum dce_fsm_event_id_type event) +{ + int ret = 0; + struct dce_fsm_info *fsm = &d->fsm_info; + enum dce_fsm_state curr_state; + + if (event > EVENT_ID_DCE_FSM_STOP) { + dce_err(d, "Invalid event received [%d]\n", event); + return -EINVAL; + } + + // Should we check This?? + if (fsm->initialized == false) { + dce_err(d, "FSM is not initialized yet\n"); + return -EINVAL; + } + + curr_state = fsm->c_state; + dce_debug(d, "Called for event [%d], curr_state:[%d]", event, curr_state); + switch (curr_state) { + case STATE_DCE_INVALID: + switch (event) { + case EVENT_ID_DCE_FSM_START: + ret = 0; + break; + default: + dce_err(d, "Invalid event received [%d] state:[%d]\n", + event, curr_state); + ret = -EINVAL; + break; + } + break; + case STATE_DCE_FSM_IDLE: + switch (event) { + case EVENT_ID_DCE_BOOT_COMPLETE_RECEIVED: + case EVENT_ID_DCE_BOOT_CMD_MSG_RECEIVED: + case EVENT_ID_DCE_ADMIN_IPC_MSG_RECEIVED: + case EVENT_ID_DCE_SC7_ENTERED_RECEIVED: + case EVENT_ID_DCE_LOG_READY_RECEIVED: + case EVENT_ID_DCE_FSM_START: + dce_err(d, "Invalid event received [%d] state:[%d]\n", + event, curr_state); + ret = -EINVAL; + break; + default: + ret = 0; + break; + } + break; + case STATE_DCE_BOOT_WAIT: + switch (event) { + case EVENT_ID_DCE_BOOT_COMPLETE_RECEIVED: + case EVENT_ID_DCE_ABORT_RECEIVED: + case EVENT_ID_DCE_CRASH_LOG_RECEIVED: + case EVENT_ID_DCE_SC7_ENTER_REQUESTED: // TODO: Do we need this here + case EVENT_ID_DCE_LOG_OVERFLOW_RECEIVED: + case EVENT_ID_DCE_FSM_STOP: + ret = 0; + break; + default: + dce_err(d, "Invalid event received [%d] state:[%d]\n", + event, curr_state); + ret = -EINVAL; + break; + } + break; + case STATE_DCE_BOOTCMD_WFI: + switch (event) { + case EVENT_ID_DCE_BOOT_CMD_MSG_RECEIVED: + case EVENT_ID_DCE_ABORT_RECEIVED: + case EVENT_ID_DCE_CRASH_LOG_RECEIVED: + case EVENT_ID_DCE_SC7_ENTER_REQUESTED: + case EVENT_ID_DCE_LOG_OVERFLOW_RECEIVED: + case EVENT_ID_DCE_FSM_STOP: + ret = 0; + break; + default: + dce_err(d, "Invalid event received [%d] state:[%d]\n", + event, curr_state); + ret = -EINVAL; + break; + } + break; + case STATE_DCE_ADMIN_WFI: + switch (event) { + case EVENT_ID_DCE_ADMIN_IPC_MSG_RECEIVED: + case EVENT_ID_DCE_ABORT_RECEIVED: + case EVENT_ID_DCE_CRASH_LOG_RECEIVED: + case EVENT_ID_DCE_SC7_ENTER_REQUESTED: + case EVENT_ID_DCE_LOG_OVERFLOW_RECEIVED: + case EVENT_ID_DCE_FSM_STOP: + ret = 0; + break; + default: + dce_err(d, "Invalid event received [%d] state:[%d]\n", + event, curr_state); + ret = -EINVAL; + break; + } + break; + case STATE_DCE_SC7_ENTER_WFI: + switch (event) { + case EVENT_ID_DCE_SC7_ENTERED_RECEIVED: + case EVENT_ID_DCE_ABORT_RECEIVED: + case EVENT_ID_DCE_CRASH_LOG_RECEIVED: + case EVENT_ID_DCE_LOG_OVERFLOW_RECEIVED: + case EVENT_ID_DCE_FSM_STOP: + ret = 0; + break; + default: + dce_err(d, "Invalid event received [%d] state:[%d]\n", + event, curr_state); + ret = -EINVAL; + break; + } + break; + case STATE_DCE_LOG_READY_WFI: + switch (event) { + case EVENT_ID_DCE_LOG_READY_RECEIVED: + case EVENT_ID_DCE_ABORT_RECEIVED: + case EVENT_ID_DCE_CRASH_LOG_RECEIVED: + case EVENT_ID_DCE_SC7_ENTER_REQUESTED: + case EVENT_ID_DCE_LOG_OVERFLOW_RECEIVED: + case EVENT_ID_DCE_FSM_STOP: + ret = 0; + break; + default: + dce_err(d, "Invalid event received [%d] state:[%d]\n", + event, curr_state); + ret = -EINVAL; + break; + } + break; + case STATE_DCE_SC7_ENTERED: + switch (event) { + case EVENT_ID_DCE_SC7_EXIT_RECEIVED: + ret = 0; + break; + default: + dce_err(d, "Invalid event received [%d] state:[%d]\n", + event, curr_state); + ret = -EINVAL; + break; + } + break; + default: + dce_err(d, "Invalid state:[%d] event received [%d]\n", curr_state, + event); + + } + + if (ret) { + dce_err(d, "dce event handling failed for event[%d] curr state [%d]", + event, curr_state); + goto done; + } + +done: + return ret; +} + +/* + * dce_fsm_get_event_index : Get index of event in event_process_table + * + * @d : Pointer to tegra_dce struct. + * @event : event for which index is requested + * @return : index of event in event_process_table or DCE_MAX_EVENTS_IDS + */ +static u32 +dce_fsm_get_event_index(struct tegra_dce *d, + enum dce_fsm_event_id_type event) +{ + u32 id; + + for (id = 0; id < DCE_MAX_EVENTS_IDS; id++) { + struct dce_event_process_struct *e = + &event_process_table[id]; + + if (e->event == event) + break; + } + + return id; +} + +/** + * dce_fsm_post - Post event to FSM + * + * @d : Pointer to tegra_dce struct. + * @event : Event for which FSM state need to be set. + * @data : Event specific data to pass to the callback function. + * + * Return : 0 if successful else error code. + */ +int dce_fsm_post_event(struct tegra_dce *d, + enum dce_fsm_event_id_type event, + void *data) +{ + u32 id; + int ret = 0; + enum dce_fsm_state prev_state; + struct dce_fsm_info *fsm = &d->fsm_info; + + dce_mutex_lock(&fsm->lock); + + ret = dce_fsm_validate_event(d, event); + if (ret) { + dce_mutex_unlock(&fsm->lock); + goto out; + } + + prev_state = fsm->c_state; + dce_fsm_set_state(d, event); + dce_mutex_unlock(&fsm->lock); + + /* + * call callback function with mutex unlocked + */ + id = dce_fsm_get_event_index(d, event); + if ((id < DCE_MAX_EVENTS_IDS) && + (event_process_table[id].fsm_event_handle != NULL)) { + ret = event_process_table[id].fsm_event_handle(d, data); + if (ret) { + dce_mutex_lock(&fsm->lock); + dce_err(d, "Callback failed: Resetting state old:new [%d:%d]", + prev_state, fsm->c_state); + fsm->c_state = prev_state; + dce_mutex_unlock(&fsm->lock); + } + } +out: + return ret; +} + +/** + * dce_fsm_start - Schedule a work to start the FSM + * + * @d : Pointer to tegra_dce struct. + * + * Return : void. + */ +void dce_fsm_start(struct tegra_dce *d) +{ + dce_schedule_work(&d->dce_bootstrap_work); +} + +/** + * dce_fsm_stop - Post dce_fsm_stop event + * + * @d : Pointer to tegra_dce struct. + * + * Return : void. + */ +void dce_fsm_stop(struct tegra_dce *d) +{ + dce_fsm_post_event(d, EVENT_ID_DCE_FSM_STOP, NULL); +} + +/** + * dce_fsm_init - Init the FSM + * + * @d : Pointer to tegra_dce struct. + * + * Return : 0 if successful else error code. + */ +int dce_fsm_init(struct tegra_dce *d) +{ + int ret = 0; + struct dce_fsm_info *fsm = &d->fsm_info; + + fsm->c_state = STATE_DCE_INVALID; + ret = dce_mutex_init(&fsm->lock); + if (ret) { + dce_err(d, "dce mutex initialization failed for FSM"); + return ret; + } + + dce_mutex_lock(&fsm->lock); + fsm->d = d; + fsm->initialized = true; + dce_mutex_unlock(&fsm->lock); + + return ret; +} + +/** + * dce_fsm_deinit - DeInit the FSM + * + * @d : Pointer to tegra_dce struct. + * + * Return : void + */ +void dce_fsm_deinit(struct tegra_dce *d) +{ + struct dce_fsm_info *fsm = &d->fsm_info; + + dce_mutex_lock(&fsm->lock); + fsm->initialized = false; + dce_mutex_unlock(&fsm->lock); + + dce_mutex_destroy(&fsm->lock); +} diff --git a/drivers/platform/tegra/dce/dce-hsp-smb.c b/drivers/platform/tegra/dce/dce-hsp-smb.c new file mode 100644 index 00000000..4f380486 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-hsp-smb.c @@ -0,0 +1,279 @@ +/* + * Copyright (c) 2019-2022, 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 +#include +#include + +#define DCE_MAX_NO_SMB 8 +#define DCE_MAX_HSP_IE 8 + +/** + * smb_regs is a 2D array of read-only pointers to a function returning u32. + * + * Array of functions that retrun base addresses of shared maiboxes registers + * in DCE cluster based on the mailbox id and HSP id. + */ +__weak u32 (*const smb_regs[DCE_MAX_HSP][DCE_MAX_NO_SMB])(void) = { + { + hsp_sm0_r, + hsp_sm1_r, + hsp_sm2_r, + hsp_sm3_r, + hsp_sm4_r, + hsp_sm5_r, + hsp_sm6_r, + hsp_sm7_r, + }, +}; + +/** + * smb_full_ie_regs is a 2D array of read-only pointers to a function + * returning u32. + * + * Array of functions that retrun base addresses of full IE for shared + * maiboxes registers in DCE cluster based on the mailbox id and HSP id. + */ +__weak u32 (*const smb_full_ie_regs[DCE_MAX_HSP][DCE_MAX_NO_SMB])(void) = { + { + hsp_sm0_full_int_ie_r, + hsp_sm1_full_int_ie_r, + hsp_sm2_full_int_ie_r, + hsp_sm3_full_int_ie_r, + hsp_sm4_full_int_ie_r, + hsp_sm5_full_int_ie_r, + hsp_sm6_full_int_ie_r, + hsp_sm7_full_int_ie_r, + }, +}; + +/** + * smb_empty_ie_regs is a 2D array of read-only pointers to a function + * returning u32. + * + * Array of functions that retrun base addresses of empty IE for shared + * maiboxes registers in DCE cluster based on the mailbox id and HSP id. + */ +__weak u32 (*const smb_empty_ie_regs[DCE_MAX_HSP][DCE_MAX_NO_SMB])(void) = { + { + hsp_sm0_empty_int_ie_r, + hsp_sm1_empty_int_ie_r, + hsp_sm2_empty_int_ie_r, + hsp_sm3_empty_int_ie_r, + hsp_sm4_empty_int_ie_r, + hsp_sm5_empty_int_ie_r, + hsp_sm6_empty_int_ie_r, + hsp_sm7_empty_int_ie_r, + }, +}; + +/** + * dce_smb_set - Set an u32 value to smb_#n in the DCE Cluster + * + * @d : Pointer to tegra_dce struct. + * @val : val to set. + * @id : Shared Mailbox Id. + * + * Return : Void + */ +void dce_smb_set(struct tegra_dce *d, u32 val, u8 id) +{ + u32 hsp = d->hsp_id; + + if (id >= DCE_MAX_NO_SMB || hsp >= DCE_MAX_HSP) { + dce_err(d, "Invalid Shared Mailbox ID:%u or hsp:%u", id, hsp); + return; + } + + dce_writel(d, smb_regs[hsp][id](), val); +} + +/** + * dce_smb_set_full_ie - Set an u32 value to smb_#n in the DCE Cluster + * + * @d : Pointer to tegra_dce struct. + * @en : enable if true and disable if false + * @id : Shared Mailbox Id. + * + * Return : Void + */ +void dce_smb_set_full_ie(struct tegra_dce *d, bool en, u8 id) +{ + u32 val = en ? 1U : 0U; + u32 hsp = d->hsp_id; + + if (id >= DCE_MAX_NO_SMB || hsp >= DCE_MAX_HSP) { + dce_err(d, "Invalid Shared Mailbox ID:%u or hsp:%u", id, hsp); + return; + } + + dce_writel(d, smb_full_ie_regs[hsp][id](), val); +} + +/** + * dce_smb_read_full_ie - Set an u32 value to smb_#n in the DCE Cluster + * + * @d : Pointer to tegra_dce struct. + * @id : Shared Mailbox Id. + * + * Return : u32 register value + */ +u32 dce_smb_read_full_ie(struct tegra_dce *d, u8 id) +{ + u32 hsp = d->hsp_id; + + if (id >= DCE_MAX_NO_SMB || hsp >= DCE_MAX_HSP) { + dce_err(d, "Invalid Shared Mailbox ID:%u or hsp:%u", id, hsp); + return 0xffffffff; /* TODO : Add DCE Error Numbers */ + } + + return dce_readl(d, smb_full_ie_regs[hsp][id]()); +} + +/** + * dce_smb_enable_empty_ie - Set an u32 value to smb_#n in the DCE Cluster + * + * @d : Pointer to tegra_dce struct. + * @en : enable if true and disable if false + * @id : Shared Mailbox Id. + * + * Return : Void + */ +void dce_smb_set_empty_ie(struct tegra_dce *d, bool en, u8 id) +{ + u32 val = en ? 1U : 0U; + u32 hsp = d->hsp_id; + + if (id >= DCE_MAX_NO_SMB || hsp >= DCE_MAX_HSP) { + dce_err(d, "Invalid Shared Mailbox ID:%u or hsp:%u", id, hsp); + return; + } + + dce_writel(d, smb_empty_ie_regs[hsp][id](), val); +} + +/** + * dce_smb_read - Read the u32 value from smb_#n in the DCE Cluster + * + * @d : Pointer to tegra_dce struct. + * @id : Shared Mailbox Id. + * + * Return : actual value if successful, 0xffffffff for errors scenarios + */ +u32 dce_smb_read(struct tegra_dce *d, u8 id) +{ + u32 hsp = d->hsp_id; + + if (id >= DCE_MAX_NO_SMB || hsp >= DCE_MAX_HSP) { + dce_err(d, "Invalid Shared Mailbox ID:%u or hsp:%u", id, hsp); + return 0xffffffff; /* TODO : Add DCE Error Numbers */ + } + + return dce_readl(d, smb_regs[hsp][id]()); +} + +/** + * hsp_int_ie_regs is a 2D array of read-only pointers to a + * function returning u32. + * + * Array of functions that retrun base addresses of hsp IE + * regs in DCE cluster based on the id. + */ +__weak u32 (*const hsp_int_ie_regs[DCE_MAX_HSP][DCE_MAX_HSP_IE])(void) = { + { + hsp_int_ie0_r, + hsp_int_ie1_r, + hsp_int_ie2_r, + hsp_int_ie3_r, + hsp_int_ie4_r, + hsp_int_ie5_r, + hsp_int_ie6_r, + hsp_int_ie7_r, + }, +}; + +/** + * hsp_int_ie_regs is a 1D array of read-only pointers to a + * function returning u32. + * + * Array of functions that retrun addresses of hsp IR + * regs in DCE cluster based on the id. + */ +__weak u32 (*const hsp_int_ir_regs[DCE_MAX_HSP])(void) = { + + hsp_int_ir_r, +}; + +/** + * dce_hsp_ie_read - Read the u32 value from hsp_int_ie#n + * in the DCE Cluster + * + * @d : Pointer to tegra_dce struct. + * @id : Shared IE Id. + * + * Return : actual value if successful, 0xffffffff for errors scenarios + */ +u32 dce_hsp_ie_read(struct tegra_dce *d, u8 id) +{ + u32 hsp = d->hsp_id; + + if (id >= DCE_MAX_HSP_IE || hsp >= DCE_MAX_HSP) { + dce_err(d, "Invalid Shared HSP IE ID:%u or hsp:%u", id, hsp); + return 0xffffffff; /* TODO : Add DCE Error Numbers */ + } + + return dce_readl(d, hsp_int_ie_regs[hsp][id]()); +} + +/** + * dce_hsp_ie_write - Read the u32 value from hsp_int_ie#n + * in the DCE Cluster + * + * @d : Pointer to tegra_dce struct. + * @val : Value to be written + * @id : Shared IE Id. + * + * Return : void + */ +void dce_hsp_ie_write(struct tegra_dce *d, u32 val, u8 id) +{ + u32 hsp = d->hsp_id; + + if (id >= DCE_MAX_HSP_IE || hsp >= DCE_MAX_HSP) { + dce_err(d, "Invalid Shared HSP IE ID:%u or hsp:%u", id, hsp); + return; + } + + dce_writel(d, hsp_int_ie_regs[hsp][id](), + val | dce_readl(d, hsp_int_ie_regs[hsp][id]())); +} + +/** + * dce_hsp_ir_read - Read the u32 value from hsp_int_ir + * in the DCE Cluster + * + * @d : Pointer to tegra_dce struct. + * + * Return : actual value if successful, 0xffffffff for errors scenarios + */ +u32 dce_hsp_ir_read(struct tegra_dce *d) +{ + u32 hsp = d->hsp_id; + + if (hsp >= DCE_MAX_HSP) { + dce_err(d, "Invalid HSP ID:%u", hsp); + return 0xffffffff; /* TODO : Add DCE Error Numbers */ + } + + return dce_readl(d, hsp_int_ir_regs[hsp]()); +} diff --git a/drivers/platform/tegra/dce/dce-hsp-ss.c b/drivers/platform/tegra/dce/dce-hsp-ss.c new file mode 100644 index 00000000..f2baf83d --- /dev/null +++ b/drivers/platform/tegra/dce/dce-hsp-ss.c @@ -0,0 +1,151 @@ +/* + * Copyright (c) 2019-2022, 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 +#include +#include + +#define DCE_MAX_NO_SS 4 + +/** + * ss_set_regs is a 2D array of read-only pointers to a function returning u32. + * + * Array of functions that retrun base addresses of shared semaphores set + * registers in DCE cluster based on the semaphore id and HSP id. + */ +__weak u32 (*const ss_set_regs[DCE_MAX_HSP][DCE_MAX_NO_SS])(void) = { + { + hsp_ss0_set_r, + hsp_ss1_set_r, + hsp_ss2_set_r, + hsp_ss3_set_r, + }, +}; + +/** + * ss_clear_regs is a 2D array of read-only pointers to a function + * returning u32. + * + * Array of functions that retrun base addresses of shared semaphores clear + * registers in DCE cluster based on the semaphore id and HSP id. + */ +__weak u32 (*const ss_clear_regs[DCE_MAX_HSP][DCE_MAX_NO_SS])(void) = { + { + hsp_ss0_clr_r, + hsp_ss1_clr_r, + hsp_ss2_clr_r, + hsp_ss3_clr_r, + }, +}; + +/** + * ss_state_regs is a 2D array of read-only pointers to a function + * returning u32. + * + * Array of functions that retrun base addresses of shared semaphores state + * registers in DCE cluster based on the semaphore id and HSP id. + */ +__weak u32 (*const ss_state_regs[DCE_MAX_HSP][DCE_MAX_NO_SS])(void) = { + { + hsp_ss0_state_r, + hsp_ss1_state_r, + hsp_ss2_state_r, + hsp_ss3_state_r, + }, +}; + +/** + * dce_ss_get_state - Get the state of ss_#n in the DCE Cluster + * + * @d : Pointer to tegra_dce struct. + * @id : Shared Semaphore Id. + * + * Return : u32 + */ +u32 dce_ss_get_state(struct tegra_dce *d, u8 id) +{ + u32 hsp = d->hsp_id; + + return dce_readl(d, ss_state_regs[hsp][id]()); +} + +/** + * dce_ss_set - Set an u32 value to ss_#n in the DCE Cluster + * + * @d : Pointer to tegra_dce struct. + * @bpos : bit to be set. + * @id : Shared Semaphore Id. + * + * Return : Void + */ +void dce_ss_set(struct tegra_dce *d, u8 bpos, u8 id) +{ + unsigned long val = 0U; + u32 hsp = d->hsp_id; + + if (hsp >= DCE_MAX_HSP || id >= DCE_MAX_NO_SS) { + dce_err(d, "Invalid HSP ID:%u OR SS ID:%u", hsp, id); + return; + } + + val = dce_ss_get_state(d, id); + + /** + * Debug info. please remove + */ + dce_info(d, "Current Value in SS#%d : %lx", id, val); + + /** + * TODO :Use DCE_INSERT here. + */ + dce_bitmap_set(&val, bpos, 1); + + /** + * Debug info. please remove + */ + dce_info(d, "Value after bitmap operation : %lx", val); + + dce_writel(d, ss_set_regs[hsp][id](), (u32)val); + + /** + * Debug info. please remove + */ + val = dce_ss_get_state(d, id); + dce_info(d, "Current Value in SS#%d : %lx", id, val); +} + +/** + * dce_ss_clear - Clear a bit in ss_#n in the DCE Cluster + * + * @d : Pointer to tegra_dce struct. + * @bpos : bit to be cleared. + * @id : Shared Semaphore Id. + * + * Return : Void + */ +void dce_ss_clear(struct tegra_dce *d, u8 bpos, u8 id) +{ + unsigned long val; + u32 hsp = d->hsp_id; + + if (hsp >= DCE_MAX_HSP || id >= DCE_MAX_NO_SS) { + dce_err(d, "Invalid HSP ID:%u OR SS ID:%u", hsp, id); + return; + } + + val = dce_ss_get_state(d, id); + + dce_bitmap_set(&val, bpos, 1); + + dce_writel(d, ss_clear_regs[hsp][id](), val); +} diff --git a/drivers/platform/tegra/dce/dce-init-deinit.c b/drivers/platform/tegra/dce/dce-init-deinit.c new file mode 100644 index 00000000..a4ca1997 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-init-deinit.c @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION & AFFILIATES. 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 +#include + +/** + * dce_driver_init - Initializes the various sw components + * and few hw elements dce. + * + * @d : Pointer to tegra_dce struct. + * + * Return : 0 if successful. + */ +int dce_driver_init(struct tegra_dce *d) +{ + int ret = 0; + + /** + * Set dce boot satus to false + */ + dce_set_boot_complete(d, false); + + ret = dce_boot_interface_init(d); + if (ret) { + dce_err(d, "dce boot interface init failed"); + goto err_boot_interface_init; + } + + ret = dce_admin_init(d); + if (ret) { + dce_err(d, "dce admin interface init failed"); + goto err_admin_interface_init; + } + + ret = dce_client_init(d); + if (ret) { + dce_err(d, "dce client workqueue init failed"); + goto err_client_init; + } + + ret = dce_work_cond_sw_resource_init(d); + if (ret) { + dce_err(d, "dce sw resource init failed"); + goto err_sw_init; + } + + ret = dce_fsm_init(d); + if (ret) { + dce_err(d, "dce FSM init failed"); + goto err_fsm_init; + } + + dce_fsm_start(d); + + return ret; + +err_fsm_init: + dce_work_cond_sw_resource_deinit(d); +err_sw_init: + dce_client_deinit(d); +err_client_init: + dce_admin_deinit(d); +err_admin_interface_init: + dce_boot_interface_deinit(d); +err_boot_interface_init: + d->boot_status |= DCE_STATUS_FAILED; + return ret; +} + +/** + * dce_driver_deinit - Release various sw resources + * associated with dce. + * + * @d : Pointer to tegra_dce struct. + * + * Return : Void + */ + +void dce_driver_deinit(struct tegra_dce *d) +{ + /* TODO : Reset DCE ? */ + dce_fsm_stop(d); + + dce_fsm_deinit(d); + + dce_work_cond_sw_resource_deinit(d); + + dce_client_deinit(d); + + dce_admin_deinit(d); + + dce_boot_interface_deinit(d); + + dce_release_fw(d, d->fw_data); +} diff --git a/drivers/platform/tegra/dce/dce-ipc-signal.c b/drivers/platform/tegra/dce/dce-ipc-signal.c new file mode 100644 index 00000000..88c7a242 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-ipc-signal.c @@ -0,0 +1,158 @@ +/* + * Copyright (c) 2019-2023, 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 +#include +#include +#include + +static struct dce_ipc_signal_instance *mb_signals[DCE_NUM_MBOX_REGS]; + +static void dce_ipc_mbox_notify(struct tegra_dce *d, + struct dce_ipc_signal_instance *s) +{ + if (s == NULL) { + dce_info(d, "Invalid signal instance for notification"); + return; + } + + if (s->sema_num < DCE_NUM_SEMA_REGS) + dce_ss_set(d, s->sema_bit, s->sema_num); + + dce_mailbox_set_full_interrupt(d, s->form.mbox.mb_type); +} + +static void dce_ipc_mbox_handle_signal(struct tegra_dce *d, void *data) +{ + u32 sema_val; + struct dce_ipc_channel *ch; + struct dce_ipc_signal_instance *s; + struct dce_ipc_signal_instance *cur_s; + + s = (struct dce_ipc_signal_instance *)data; + if ((s == NULL) || (s->signal == NULL) || + (s->signal->ch == NULL) || + (s->form.mbox.mb_num > DCE_NUM_MBOX_REGS)) { + dce_err(d, "Invalid signal instance in mailbox callback"); + return; + } + + for (cur_s = s; cur_s != NULL; cur_s = cur_s->next) { + if (cur_s->sema_num < DCE_NUM_SEMA_REGS) { + sema_val = dce_ss_get_state(d, cur_s->sema_num); + if ((sema_val & BIT(cur_s->sema_bit)) == 0) + continue; + } + + dce_ss_clear(d, cur_s->sema_num, BIT(cur_s->sema_bit)); + + ch = cur_s->signal->ch; + + dce_admin_ipc_handle_signal(d, ch->ch_type); + } +} + +/** + * Lock is acquired in dce-ipc before calling this API. + * Shouldn't be called from anywhere else. + */ +int dce_ipc_init_signaling(struct tegra_dce *d, struct dce_ipc_channel *ch) +{ + u8 mb_type; + u32 to_mbox; + u32 from_mbox; + int ret = 0; + struct dce_ipc_signal_instance *temp_s; + struct dce_ipc_signal_instance *to_d = &ch->signal.to_d; + struct dce_ipc_signal_instance *from_d = &ch->signal.from_d; + + ch->signal.ch = ch; + + if ((from_d == NULL) || (to_d == NULL)) { + dce_err(d, "Invalid signal instances"); + ret = -1; + goto out; + } + + mb_type = to_d->form.mbox.mb_type; + if (to_d->form.mbox.mb_type != + from_d->form.mbox.mb_type) { + dce_err(d, "Mailbox type doesn't match"); + ret = -1; + goto out; + } + + to_mbox = to_d->form.mbox.mb_num; + from_mbox = from_d->form.mbox.mb_num; + + to_d->signal = &ch->signal; + + if (to_d->type == DCE_IPC_SIGNAL_MAILBOX) { + to_d->signal->notify = dce_ipc_mbox_notify; + mb_signals[to_mbox] = to_d; + } else { + dce_info(d, "Signal type not supported : [%d]", to_d->type); + } + + from_d->signal = &ch->signal; + + if (from_d->type == DCE_IPC_SIGNAL_MAILBOX) { + if ((from_d->next != NULL) + || (from_mbox >= DCE_NUM_MBOX_REGS)) { + dce_err(d, "Invalid Signal Instance"); + ret = -1; + goto out; + } + + temp_s = mb_signals[from_mbox]; + if (temp_s != NULL) + from_d->next = temp_s; + + mb_signals[from_d->form.mbox.mb_num] = from_d; + } else { + dce_info(d, "Signal type not supported : [%d]", from_d->type); + } + + /** + * TODO : Call this API on a conditional basis. + */ + ret = dce_mailbox_init_interface(d, mb_type, + to_mbox, from_mbox, NULL, (void *)from_d, + dce_ipc_mbox_handle_signal); +out: + return ret; +} + +/** + * Lock is acquired in dce-ipc before calling this API. + * Shouldn't be called from anywhere else. + */ +void dce_ipc_deinit_signaling(struct tegra_dce *d, struct dce_ipc_channel *ch) +{ + u8 mb_type; + struct dce_ipc_signal_instance *to_d = &ch->signal.to_d; + struct dce_ipc_signal_instance *from_d = &ch->signal.from_d; + + /** + * TODO : Nullify other signal parameters as well. + */ + mb_type = to_d->form.mbox.mb_type; + if (to_d->form.mbox.mb_type != + from_d->form.mbox.mb_type) { + dce_err(d, "Mailbox type doesn't match"); + return; + } + dce_mailbox_deinit_interface(d, mb_type); + + ch->signal.ch = NULL; +} diff --git a/drivers/platform/tegra/dce/dce-ipc.c b/drivers/platform/tegra/dce/dce-ipc.c new file mode 100644 index 00000000..b06dfca7 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-ipc.c @@ -0,0 +1,891 @@ +/* + * Copyright (c) 2019-2023, 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 + +#include +#include +#include +#include +#include + +#define CREATE_TRACE_POINTS +#include + +static struct dce_ipc_channel ivc_channels[DCE_IPC_CH_KMD_TYPE_MAX] = { + [DCE_IPC_CH_KMD_TYPE_ADMIN] = { + .flags = DCE_IPC_CHANNEL_VALID + | DCE_IPC_CHANNEL_MSG_HEADER, + .ch_type = DCE_IPC_CH_KMD_TYPE_ADMIN, + .ipc_type = DCE_IPC_TYPE_ADMIN, + .signal = { + .to_d = { + .type = DCE_IPC_SIGNAL_MAILBOX, + .sema_num = DCE_NUM_SEMA_REGS, + .sema_bit = 0U, + .form = { + .mbox = { + .mb_type = DCE_MAILBOX_ADMIN_INTERFACE, + .mb_num = DCE_MBOX_TO_DCE_ADMIN, + }, + }, + .signal = NULL, + .next = NULL, + }, + .from_d = { + .type = DCE_IPC_SIGNAL_MAILBOX, + .sema_num = DCE_NUM_SEMA_REGS, + .sema_bit = 0U, + .form = { + .mbox = { + .mb_type = DCE_MAILBOX_ADMIN_INTERFACE, + .mb_num = DCE_MBOX_FROM_DCE_ADMIN, + }, + }, + .signal = NULL, + .next = NULL, + }, + }, + .q_info = { + .nframes = DCE_ADMIN_CMD_MAX_NFRAMES, + .frame_sz = DCE_ADMIN_CMD_MAX_FSIZE, + }, + }, + [DCE_IPC_CH_KMD_TYPE_RM] = { + .flags = DCE_IPC_CHANNEL_VALID + | DCE_IPC_CHANNEL_MSG_HEADER, + .ch_type = DCE_IPC_CH_KMD_TYPE_RM, + .ipc_type = DCE_IPC_TYPE_DISPRM, + .signal = { + .to_d = { + .type = DCE_IPC_SIGNAL_MAILBOX, + .sema_num = DCE_NUM_SEMA_REGS, + .sema_bit = 0U, + .form = { + .mbox = { + .mb_type = DCE_MAILBOX_DISPRM_INTERFACE, + .mb_num = DCE_MBOX_TO_DCE_RM, + }, + }, + .signal = NULL, + .next = NULL, + }, + .from_d = { + .type = DCE_IPC_SIGNAL_MAILBOX, + .sema_num = DCE_NUM_SEMA_REGS, + .sema_bit = 0U, + .form = { + .mbox = { + .mb_type = DCE_MAILBOX_DISPRM_INTERFACE, + .mb_num = DCE_MBOX_FROM_DCE_RM, + }, + }, + .signal = NULL, + .next = NULL, + }, + }, + .q_info = { + .nframes = DCE_DISPRM_CMD_MAX_NFRAMES, + .frame_sz = DCE_DISPRM_CMD_MAX_FSIZE, + }, + }, + [DCE_IPC_CH_KMD_TYPE_RM_NOTIFY] = { + .flags = DCE_IPC_CHANNEL_VALID + | DCE_IPC_CHANNEL_MSG_HEADER, + .ch_type = DCE_IPC_CH_KMD_TYPE_RM_NOTIFY, + .ipc_type = DCE_IPC_TYPE_RM_NOTIFY, + .signal = { + .to_d = { + .type = DCE_IPC_SIGNAL_MAILBOX, + .sema_num = DCE_NUM_SEMA_REGS, + .sema_bit = 0U, + .form = { + .mbox = { + .mb_type = DCE_MAILBOX_DISPRM_NOTIFY_INTERFACE, + .mb_num = DCE_MBOX_FROM_DCE_RM_EVENT_NOTIFY, + }, + }, + .signal = NULL, + .next = NULL, + }, + .from_d = { + .type = DCE_IPC_SIGNAL_MAILBOX, + .sema_num = DCE_NUM_SEMA_REGS, + .sema_bit = 0U, + .form = { + .mbox = { + .mb_type = DCE_MAILBOX_DISPRM_NOTIFY_INTERFACE, + .mb_num = DCE_MBOX_TO_DCE_RM_EVENT_NOTIFY, + }, + }, + .signal = NULL, + .next = NULL, + }, + }, + .q_info = { + .nframes = DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_NFRAMES, + .frame_sz = DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_FSIZE, + }, + }, +}; + +/** + * dce_ipc_allocate_region - Allocates IPC region for IVC + * + * @ch : Pointer to the pertinent dce_ipc_channel. + * @q_size : IVC queue size. + * + * Return : 0 if successful + */ +int dce_ipc_allocate_region(struct tegra_dce *d) +{ + unsigned long tot_q_sz; + unsigned long tot_ivc_q_sz; + struct device *dev; + struct dce_ipc_region *region; + + dev = dev_from_dce(d); + region = &d->d_ipc.region; + + tot_q_sz = ((DCE_ADMIN_CMD_MAX_NFRAMES * + tegra_ivc_align(DCE_ADMIN_CMD_MAX_FSIZE) * 2) + + (DCE_DISPRM_CMD_MAX_NFRAMES * + tegra_ivc_align(DCE_DISPRM_CMD_MAX_FSIZE) * 2) + + (DCE_ADMIN_CMD_MAX_NFRAMES * + tegra_ivc_align(DCE_ADMIN_CMD_CHAN_FSIZE) * 2) + + (DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_NFRAMES * + tegra_ivc_align(DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_FSIZE) * 2) + ); + + tot_ivc_q_sz = tegra_ivc_total_queue_size(tot_q_sz); + region->size = dce_get_nxt_pow_of_2(&tot_ivc_q_sz, 32); + region->base = dma_alloc_coherent(dev, region->size, + ®ion->iova, GFP_KERNEL | __GFP_ZERO); + if (!region->base) + return -ENOMEM; + + region->s_offset = 0; + + return 0; +} + +/** + * dce_ipc_free_region - Frees up the IPC region for IVC + * + * @d : Pointer to the tegra_dce struct. + * + * Return : Void + */ +void dce_ipc_free_region(struct tegra_dce *d) +{ + struct device *dev; + struct dce_ipc_region *region; + + dev = dev_from_dce(d); + region = &d->d_ipc.region; + + dma_free_coherent(dev, region->size, + (void *)region->base, region->iova); + + region->s_offset = 0; +} + +/** + * dce_ipc_signal_target - Generic function to signal target. + * + * @d_ivc : Pointer to struct tegra_ivc. + * + * Do not take a channel lock here. + * + * Return : Void. + */ +static void dce_ipc_signal_target(struct tegra_ivc *ivc, void *data) +{ +} + +static int _dce_ipc_wait(struct tegra_dce *d, u32 w_type, u32 ch_type) +{ + int ret = 0; + struct dce_ipc_channel *ch; + + if (ch_type >= DCE_IPC_CH_KMD_TYPE_MAX) { + dce_err(d, "Invalid Channel Type : [%d]", ch_type); + return -EINVAL; + } + + ch = d->d_ipc.ch[ch_type]; + if (ch == NULL) { + dce_err(d, "Invalid Channel Data for type : [%d]", ch_type); + ret = -EINVAL; + goto out; + } + + ch->w_type = w_type; + + dce_mutex_unlock(&ch->lock); + + if (ch_type == DCE_IPC_TYPE_ADMIN) + ret = dce_admin_ipc_wait(d, w_type); + else + ret = dce_client_ipc_wait(d, ch_type); + + dce_mutex_lock(&ch->lock); + + ch->w_type = DCE_IPC_WAIT_TYPE_INVALID; + +out: + return ret; +} + +u32 dce_ipc_get_cur_wait_type(struct tegra_dce *d, u32 ch_type) +{ + uint32_t w_type; + struct dce_ipc_channel *ch; + + if (ch_type >= DCE_IPC_CH_KMD_TYPE_MAX) { + dce_err(d, "Invalid Channel Type : [%d]", ch_type); + return -EINVAL; + } + + ch = d->d_ipc.ch[ch_type]; + if (ch == NULL) { + dce_err(d, "Invalid Channel Data for type : [%d]", ch_type); + return -EINVAL; + } + + dce_mutex_lock(&ch->lock); + + w_type = ch->w_type; + + dce_mutex_unlock(&ch->lock); + + return w_type; +} + +/** + * dce_ipc_channel_init - Initializes the underlying IPC channel to + * be used for all bi-directional messaging. + * @d : Pointer to struct tegra_dce. + * @type : Type of interface for which this channel is needed. + * + * Return : 0 if successful. + */ +int dce_ipc_channel_init(struct tegra_dce *d, u32 ch_type) +{ + u32 q_sz; + u32 msg_sz; + int ret = 0; + struct device *dev; + struct dce_ipc_region *r; + struct dce_ipc_channel *ch; + struct dce_ipc_queue_info *q_info; +#if (KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE) + struct iosys_map rx, tx; +#endif + + if (ch_type >= DCE_IPC_CH_KMD_TYPE_MAX) { + dce_err(d, "Invalid ivc channel ch_type : [%d]", ch_type); + ret = -EINVAL; + goto out; + } + + ch = &ivc_channels[ch_type]; + if (!ch) { + dce_err(d, "Invalid ivc channel for this ch_type : [%d]", + ch_type); + ret = -ENOMEM; + goto out; + } + + ret = dce_mutex_init(&ch->lock); + if (ret) { + dce_err(d, "dce lock initialization failed for mailbox"); + goto out; + } + + dce_mutex_lock(&ch->lock); + + if ((ch->flags & DCE_IPC_CHANNEL_VALID) == 0U) { + dce_info(d, "Invalid Channel State [0x%x] for ch_type [%d]", + ch->flags, ch_type); + goto out_lock_destroy; + } + + ch->d = d; + + ret = dce_ipc_init_signaling(d, ch); + if (ret) { + dce_err(d, "Signaling init failed"); + goto out_lock_destroy; + } + + q_info = &ch->q_info; + msg_sz = tegra_ivc_align(q_info->frame_sz); + q_sz = tegra_ivc_total_queue_size(msg_sz * q_info->nframes); + + r = &d->d_ipc.region; + if (!r->base) { + ret = -ENOMEM; + goto out_lock_destroy; + } + + dev = dev_from_dce(d); + +#if (KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE) + iosys_map_set_vaddr(&rx, r->base + r->s_offset); + iosys_map_set_vaddr(&tx, r->base + r->s_offset + q_sz); + + ret = tegra_ivc_init(&ch->d_ivc, NULL, &rx, r->iova + r->s_offset, &tx, + r->iova + r->s_offset + q_sz, q_info->nframes, msg_sz, + dce_ipc_signal_target, NULL); +#else + ret = tegra_ivc_init(&ch->d_ivc, NULL, r->base + r->s_offset, + r->iova + r->s_offset, r->base + r->s_offset + q_sz, + r->iova + r->s_offset + q_sz, q_info->nframes, msg_sz, + dce_ipc_signal_target, NULL); +#endif + if (ret) { + dce_err(d, "IVC creation failed"); + goto out_lock_destroy; + } + + ch->flags |= DCE_IPC_CHANNEL_INITIALIZED; + + q_info->rx_iova = r->iova + r->s_offset; + q_info->tx_iova = r->iova + r->s_offset + q_sz; + + trace_ivc_channel_init_complete(d, ch); + + d->d_ipc.ch[ch_type] = ch; + r->s_offset += (2 * q_sz); + +out_lock_destroy: + dce_mutex_unlock(&ch->lock); + if (ret) + dce_mutex_destroy(&ch->lock); +out: + return ret; +} + +/** + * dce_ivc_channel_deinit - Releases resources for a ivc channel + * + * @d : Pointer to tegra_dce struct. + * @id : Channel Id. + */ +void dce_ipc_channel_deinit(struct tegra_dce *d, u32 ch_type) +{ + struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type]; + + if (ch == NULL || (ch->flags & DCE_IPC_CHANNEL_INITIALIZED) == 0U) { + dce_info(d, "Invalid IVC Channel [%d]", ch_type); + return; + } + + dce_mutex_lock(&ch->lock); + + dce_ipc_deinit_signaling(d, ch); + + ch->flags &= ~DCE_IPC_CHANNEL_INITIALIZED; + ch->flags &= ~DCE_IPC_CHANNEL_SYNCED; + + d->d_ipc.ch[ch_type] = NULL; + + dce_mutex_unlock(&ch->lock); + + dce_mutex_destroy(&ch->lock); + +} + +struct tegra_dce *dce_ipc_get_dce_from_ch(u32 ch_type) +{ + struct tegra_dce *d = NULL; + struct dce_ipc_channel *ch = NULL; + + if (ch_type >= DCE_IPC_CH_KMD_TYPE_MAX) + goto out; + + ch = &ivc_channels[ch_type]; + + dce_mutex_lock(&ch->lock); + + d = ch->d; + + dce_mutex_unlock(&ch->lock); + +out: + return d; +} + +/** + * dce_ipc_channel_ready - Checks if channel is ready to use + * + * @d : Pointer to tegra_dce struct. + * @id : Channel Id. + * + * Return : true if channel ready to use. + */ +bool dce_ipc_channel_is_ready(struct tegra_dce *d, u32 ch_type) +{ + bool is_est; + + struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type]; + + dce_mutex_lock(&ch->lock); + + is_est = (tegra_ivc_notified(&ch->d_ivc) ? false : true); + + ch->signal.notify(d, &ch->signal.to_d); + + dce_mutex_unlock(&ch->lock); + + return is_est; +} + +/** + * dce_ipc_channel_synced - Checks if channel is in synced state + * + * @d : Pointer to tegra_dce struct. + * @id : Channel Id. + * + * Return : true if channel is in synced state. + */ +bool dce_ipc_channel_is_synced(struct tegra_dce *d, u32 ch_type) +{ + bool ret; + + struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type]; + + dce_mutex_lock(&ch->lock); + + ret = (ch->flags & DCE_IPC_CHANNEL_SYNCED) ? true : false; + + dce_mutex_unlock(&ch->lock); + + return ret; +} + +/** + * dce_ipc_channel_reset - Resets the channel and completes + * the handshake with the remote. + * + * @d : Pointer to tegra_dce struct. + * @id : Channel Id. + * + * Return : void + */ +void dce_ipc_channel_reset(struct tegra_dce *d, u32 ch_type) +{ + struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type]; + + dce_mutex_lock(&ch->lock); + + tegra_ivc_reset(&ch->d_ivc); + + trace_ivc_channel_reset_triggered(d, ch); + + ch->flags &= ~DCE_IPC_CHANNEL_SYNCED; + + ch->signal.notify(d, &ch->signal.to_d); + + dce_mutex_unlock(&ch->lock); + + do { + if (dce_ipc_channel_is_ready(d, ch_type) == true) + break; + + } while (true); + + dce_mutex_lock(&ch->lock); + + ch->flags |= DCE_IPC_CHANNEL_SYNCED; + + trace_ivc_channel_reset_complete(d, ch); + + dce_mutex_unlock(&ch->lock); +} + +/** + * dce_ipc_get_next_write_buff - waits for the next write frame. + * + * @ch : Pointer to the pertinent channel. + * + * Return : 0 if successful + */ +static int _dce_ipc_get_next_write_buff(struct dce_ipc_channel *ch) +{ +#if (KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE) + int err; + + err = tegra_ivc_write_get_next_frame(&ch->d_ivc, &ch->obuff); + if (err) { + iosys_map_clear(&ch->obuff); + return err; + } +#else + void *frame = NULL; + + frame = tegra_ivc_write_get_next_frame(&ch->d_ivc); + + if (IS_ERR(frame)) { + ch->obuff = NULL; + return -ENOMEM; + } + + ch->obuff = frame; +#endif + return 0; +} + +/** + * dce_ipc_write_channel - Writes to an ivc channel. + * + * @ch : Pointer to the pertinent channel. + * @data : Pointer to the data to be written. + * @size : Size of the data to be written. + * + * Return : 0 if successful. + */ +static int _dce_ipc_write_channel(struct dce_ipc_channel *ch, + const void *data, size_t size) +{ + struct dce_ipc_header *hdr; + + /** + * Add actual length information to the top + * of the IVC frame + */ + +#if (KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE) + if ((ch->flags & DCE_IPC_CHANNEL_MSG_HEADER) != 0U) { + iosys_map_wr_field(&ch->obuff, 0, struct dce_ipc_header, length, + size); + iosys_map_incr(&ch->obuff, sizeof(*hdr)); + } + + if (data && size > 0) + iosys_map_memcpy_to(&ch->obuff, 0, data, size); +#else + if ((ch->flags & DCE_IPC_CHANNEL_MSG_HEADER) != 0U) { + hdr = (struct dce_ipc_header *)ch->obuff; + hdr->length = (uint32_t)size; + ch->obuff = (void *)(hdr + 1U); + } + + if (data && size > 0) + memcpy(ch->obuff, data, size); +#endif + + return tegra_ivc_write_advance(&ch->d_ivc); +} + +/** + * dce_ipc_send_message - Sends messages over ipc. + * + * @d : Pointer to tegra_dce struct. + * @id : Channel Id. + * @data : Pointer to the data to be written. + * @size : Size of the data to be written. + * + * Return : 0 if successful. + */ +int dce_ipc_send_message(struct tegra_dce *d, u32 ch_type, + const void *data, size_t size) +{ + int ret = 0; + struct dce_ipc_channel *ch + = d->d_ipc.ch[ch_type]; + + dce_mutex_lock(&ch->lock); + + trace_ivc_send_req_received(d, ch); + + ret = _dce_ipc_get_next_write_buff(ch); + if (ret) { + dce_err(ch->d, "Error getting next free buf to write"); + goto out; + } + + ret = _dce_ipc_write_channel(ch, data, size); + if (ret) { + dce_err(ch->d, "Error writing to channel"); + goto out; + } + + ch->signal.notify(d, &ch->signal.to_d); + + trace_ivc_send_complete(d, ch); + +out: + dce_mutex_unlock(&ch->lock); + + return ret; +} + +/** + * dce_ipc_get_next_read_buff - waits for the next write frame. + * + * @ch : Pointer to the pertinent channel. + * + * Return : 0 if successful + */ +static int _dce_ipc_get_next_read_buff(struct dce_ipc_channel *ch) +{ +#if (KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE) + int err; + + err = tegra_ivc_read_get_next_frame(&ch->d_ivc, &ch->ibuff); + if (err) { + iosys_map_clear(&ch->ibuff); + return err; + } +#else + void *frame = NULL; + + frame = tegra_ivc_read_get_next_frame(&ch->d_ivc); + + if (IS_ERR(frame)) { + ch->ibuff = NULL; + return -ENOMEM; + } + + ch->ibuff = frame; +#endif + return 0; +} + +/** + * dce_ipc_read_channel - Writes to an ivc channel. + * + * @ch : Pointer to the pertinent channel. + * @data : Pointer to the data to be read. + * @size : Size of the data to be read. + * + * Return : 0 if successful. + */ +static int _dce_ipc_read_channel(struct dce_ipc_channel *ch, + void *data, size_t size) +{ + struct dce_ipc_header *hdr; + + /** + * Get actual length information from the top + * of the IVC frame + */ +#if (KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE) + if ((ch->flags & DCE_IPC_CHANNEL_MSG_HEADER) != 0U) { + iosys_map_wr_field(&ch->ibuff, 0, struct dce_ipc_header, length, + size); + iosys_map_incr(&ch->ibuff, sizeof(*hdr)); + } + + if (data && size > 0) + iosys_map_memcpy_from(data, &ch->ibuff, 0, size); +#else + if ((ch->flags & DCE_IPC_CHANNEL_MSG_HEADER) != 0U) { + hdr = (struct dce_ipc_header *)ch->ibuff; + size = (size_t)(hdr->length); + ch->ibuff = (void *)(hdr + 1U); + } + + if (data && size > 0) + memcpy(data, ch->ibuff, size); +#endif + + return tegra_ivc_read_advance(&ch->d_ivc); +} + +/** + * dce_ipc_read_message - Reads messages over ipc. + * + * @d : Pointer to tegra_dce struct. + * @id : Channel Id. + * @data : Pointer to the data to be read. + * @size : Size of the data to be read. + * + * Return : 0 if successful. + */ +int dce_ipc_read_message(struct tegra_dce *d, u32 ch_type, + void *data, size_t size) +{ + int ret = 0; + struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type]; + + dce_mutex_lock(&ch->lock); + + trace_ivc_receive_req_received(d, ch); + + ret = _dce_ipc_get_next_read_buff(ch); + if (ret) { + dce_debug(ch->d, "No Msg to read"); + goto out; + } + + ret = _dce_ipc_read_channel(ch, data, size); + if (ret) { + dce_err(ch->d, "Error reading from channel"); + goto out; + } + + trace_ivc_receive_req_complete(d, ch); + +out: + dce_mutex_unlock(&ch->lock); + return ret; +} + +/** + * dce_ipc_send_message_sync - Sends messages on a channel + * synchronously and waits for an ack. + * + * @d : Pointer to tegra_dce struct. + * @id : Channel Id. + * @msg : Pointer to the message to be sent/received. + * + * Return : 0 if successful + */ +int dce_ipc_send_message_sync(struct tegra_dce *d, u32 ch_type, + struct dce_ipc_message *msg) +{ + int ret = 0; + struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type]; + + ret = dce_ipc_send_message(d, ch_type, msg->tx.data, msg->tx.size); + if (ret) { + dce_err(ch->d, "Error in sending message to DCE"); + goto done; + } + + dce_mutex_lock(&ch->lock); + ret = _dce_ipc_wait(ch->d, DCE_IPC_WAIT_TYPE_RPC, ch_type); + dce_mutex_unlock(&ch->lock); + if (ret) { + dce_err(ch->d, "Error in waiting for ack"); + goto done; + } + + trace_ivc_wait_complete(d, ch); + + ret = dce_ipc_read_message(d, ch_type, msg->rx.data, msg->rx.size); + if (ret) { + dce_err(ch->d, "Error in reading DCE msg for ch_type [%d]", + ch_type); + goto done; + } +done: + return ret; +} + +/** + * dce_ipc_get_channel_info - Provides information about frames details + * + * @d : Pointer to tegra_dce struct. + * @ch_index : Channel Index. + * @q_info : Pointer to struct dce_ipc_queue_info + * + * Return : 0 if successful + */ +int dce_ipc_get_channel_info(struct tegra_dce *d, + struct dce_ipc_queue_info *q_info, u32 ch_index) +{ + struct dce_ipc_channel *ch = d->d_ipc.ch[ch_index]; + + if (ch == NULL) + return -ENOMEM; + + dce_mutex_lock(&ch->lock); + + memcpy(q_info, &ch->q_info, sizeof(ch->q_info)); + + dce_mutex_unlock(&ch->lock); + + return 0; +} + +/** + * dce_ipc_get_region_iova_info - Provides iova details for ipc region + * + * @d : Pointer to tegra_dce struct. + * @iova : Iova start address. + * @size : Iova size + * + * Return : 0 if successful + */ +int dce_ipc_get_region_iova_info(struct tegra_dce *d, u64 *iova, u32 *size) +{ + struct dce_ipc_region *r = &d->d_ipc.region; + + if (!r->base) + return -ENOMEM; + + *iova = r->iova; + *size = r->size; + + return 0; +} + +/* + * dce_ipc_handle_notification - Handles the notification from remote + * + * @d : Pointer to tegra_dce struct + * @id : Channel Index + * + * Return : True if the worker thread needs to wake up + */ +bool dce_ipc_is_data_available(struct tegra_dce *d, u32 ch_type) +{ + bool ret = false; +#if (KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE) + struct iosys_map map; +#else + void *frame; +#endif + struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type]; + + dce_mutex_lock(&ch->lock); + +#if (KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE) + if (!tegra_ivc_read_get_next_frame(&ch->d_ivc, &map)) + ret = true; +#else + frame = tegra_ivc_read_get_next_frame(&ch->d_ivc); + if (!IS_ERR(frame)) + ret = true; +#endif + + dce_mutex_unlock(&ch->lock); + + return ret; +} + +/* + * dce_ipc_get_ipc_type - Returns the ipc_type for the channel. + * + * @d : Pointer to tegra_dce struct + * @id : Channel Index + * + * Return : True if the worker thread needs to wake up + */ +uint32_t dce_ipc_get_ipc_type(struct tegra_dce *d, u32 ch_type) +{ + uint32_t ipc_type; + struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type]; + + dce_mutex_lock(&ch->lock); + + ipc_type = ch->ipc_type; + + dce_mutex_unlock(&ch->lock); + + return ipc_type; +} diff --git a/drivers/platform/tegra/dce/dce-mailbox.c b/drivers/platform/tegra/dce/dce-mailbox.c new file mode 100644 index 00000000..31476580 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-mailbox.c @@ -0,0 +1,297 @@ +/* + * Copyright (c) 2019-2022, 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 +#include +#include +#include +#include + + +#define CCPLEX_HSP_IE 1U /* TODO : Have an api to read from platform data */ +#define DCE_MAILBOX_FULL_INT_SHIFT 8U + +/** + * dce_hsp_get_irq_sources - gets the interrupt sources. + * + * @d : Pointer to tegra_dce struct. + * + * Return : bitmap for mailbox ids that triggered the irqs. + */ +static u32 dce_hsp_get_irq_sources(struct tegra_dce *d) +{ + return (dce_hsp_ie_read(d, CCPLEX_HSP_IE) & + dce_hsp_ir_read(d)); +} + +/** + * dce_mailbox_isr - Isr for mailbox irqs. + * + * @d : Pointer to tegra_dce struct. + * + * Return : Void + */ +void dce_mailbox_isr(struct tegra_dce *d) +{ + u8 i = 0; + u32 value; + struct dce_mailbox_interface *d_mb; + u32 irq_sources = dce_hsp_get_irq_sources(d); + + do { + d_mb = &d->d_mb[i]; + /** + * Get the mailbox on which the interrupt + * is received. + */ + if (irq_sources & (BIT(d_mb->r_mb) + << DCE_MAILBOX_FULL_INT_SHIFT)) { + /** + * Read and store the value. + * + * TODO : Ignore the full interrupt + * bit before storing the result. + * + */ + value = dce_smb_read(d, d_mb->r_mb); + dce_smb_set(d, 0U, d_mb->r_mb); + dce_mailbox_store_interface_status(d, value, i); + d_mb->notify(d, d_mb->notify_data); + } + i++; + } while (i < DCE_MAILBOX_MAX_INTERFACES); +} + +/** + * dce_mailbox_store_interface_status - stores the response + * received on a mailbox interface. + * @d : Pointer to tegra_dce struct. + * @v : Value to be stored. + * @id : interface id. + * + * Return :Void + */ +void dce_mailbox_store_interface_status(struct tegra_dce *d, u32 v, u8 id) +{ + struct dce_mailbox_interface *d_mb = &d->d_mb[id]; + + dce_mutex_lock(&d_mb->lock); + d_mb->ack_value = v; + d_mb->valid = true; + dce_mutex_unlock(&d_mb->lock); +} + +/** + * dce_mailbox_get_interface_status - gets the response + * received on mailbox interface. + * @d : Pointer to tegra_dce struct. + * @id : Interface id. + * + * Return : u32 value + */ +u32 dce_mailbox_get_interface_status(struct tegra_dce *d, u8 id) +{ + struct dce_mailbox_interface *d_mb = &d->d_mb[id]; + + if (d_mb->valid) + return d_mb->ack_value; + else + return 0xffffffff; +} + +/** + * dce_mailbox_invalidate_status - renders the response invalid. + * + * @d : Pointer to tegra_dce struct. + * @id : Interface id. + * + * Return : void + */ +void dce_mailbox_invalidate_status(struct tegra_dce *d, u8 id) +{ + struct dce_mailbox_interface *d_mb = &d->d_mb[id]; + + dce_mutex_lock(&d_mb->lock); + d_mb->valid = false; + dce_mutex_unlock(&d_mb->lock); +} + +/** + * dce_mailbox_write_safe - Checks if it's safe to write to + * a mailbox register. + * + * @d : Pointer to tegra_dce struct. + * @id : Mailbox ID + * + * Return : true if it's safe + */ +static bool dce_mailbox_write_safe(struct tegra_dce *d, u8 id) +{ + unsigned long val; + + val = dce_smb_read(d, id); + + return !(val & BIT(31)); +} + +/** + * dce_mailbox_set_full_interrupt - Sets the interrupt tag bit + * in the mailbox register + * @d : Pointer to tegra_dce struct. + * @id : Mailbox interface id. + * + * Return : Void + */ +void dce_mailbox_set_full_interrupt(struct tegra_dce *d, u8 id) +{ + struct dce_mailbox_interface *d_mb; + + d_mb = &d->d_mb[id]; + + dce_mutex_lock(&d_mb->lock); + + if (!dce_mailbox_write_safe(d, d_mb->s_mb)) + dce_info(d, "Intr bit set multiple times for MB : [0x%x]", + d_mb->s_mb); + + dce_smb_set(d, BIT(31), d_mb->s_mb); + + dce_mutex_unlock(&d_mb->lock); +} + +/** + * dce_handle_mailbox_send_cmd_sync - handler function for + * mailbox_send_cmd_sync + * + * @d : Pointer to tegra_dce struct. + * @cmd : The command to be sent. + * @interface : boot or admin interface + * + * + * Return : 0 if successful. + */ +int dce_handle_mailbox_send_cmd_sync(struct tegra_dce *d, u32 cmd, u32 interface) +{ + int ret = 0; + struct dce_mailbox_interface *d_mb; + + d_mb = &d->d_mb[interface]; + + dce_mutex_lock(&d_mb->lock); + + if (!dce_mailbox_write_safe(d, d_mb->s_mb)) { + dce_err(d, "Previously sent message isn't synced"); + return -1; + } + + dce_smb_set(d, cmd | BIT(31), d_mb->s_mb); + d_mb->valid = false; + + dce_mutex_unlock(&d_mb->lock); + + ret = d_mb->dce_mailbox_wait(d); + + return ret; +} + +/** + * dce_mailbox_send_cmd_sync - Sends command via mailbox and waits for ack. + * + * @d : Pointer to tegra_dce struct. + * @cmd : The command to be sent. + * @interface : boot or admin interface + * + * Return : 0 if successful. + */ +int dce_mailbox_send_cmd_sync(struct tegra_dce *d, u32 cmd, u32 interface) +{ + int ret = 0; + struct dce_mailbox_send_cmd_params params; + + params.cmd = cmd; + params.interface = interface; + + ret = dce_fsm_post_event(d, EVENT_ID_DCE_BOOT_CMD_MSG_REQUESTED, + (void *)¶ms); + if (ret) { + dce_err(d, "Unable to send msg ret :%d", ret); + goto out; + } + +out: + return ret; +} + +/** + * dce_mailbox_init_interface - Initializes the mailbox interface. + * + * @d : Pointer to tegra_dce struct. + * @id : Mailbox interface id. + * + * Return : 0 if successful + */ +int dce_mailbox_init_interface(struct tegra_dce *d, u8 id, u8 s_mb, + u8 r_mb, int (*dce_mailbox_wait)(struct tegra_dce *), + void *notify_data, void (*notify)(struct tegra_dce *, void *)) +{ + int ret; + u64 ie_wr_val; + struct dce_mailbox_interface *d_mb; + + d_mb = &d->d_mb[id]; + + ret = dce_mutex_init(&d_mb->lock); + if (ret) { + dce_err(d, "dce lock initialization failed for mailbox"); + goto err_lock_init; + } + + d_mb->valid = false; + + dce_smb_set_full_ie(d, true, r_mb); + + ie_wr_val = BIT(r_mb) << 8U; + dce_hsp_ie_write(d, ie_wr_val, CCPLEX_HSP_IE); + + d_mb->s_mb = s_mb; + d_mb->r_mb = r_mb; + + d_mb->notify = notify; + d_mb->notify_data = notify_data; + + d_mb->dce_mailbox_wait + = dce_mailbox_wait; + + return 0; + +err_lock_init: + return ret; +} + +/** + * dce_mailbox_deinit_interface - Releases the resources + * associated with boot interface. + * + * @d : Pointer to tegra_dce struct. + * + * Return : Void + */ +void dce_mailbox_deinit_interface(struct tegra_dce *d, u8 id) +{ + struct dce_mailbox_interface *d_mb; + + d_mb = &d->d_mb[id]; + + dce_mutex_destroy(&d_mb->lock); +} diff --git a/drivers/platform/tegra/dce/dce-module.c b/drivers/platform/tegra/dce/dce-module.c new file mode 100644 index 00000000..5e80a427 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-module.c @@ -0,0 +1,326 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION & AFFILIATES. 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 +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * The following platform info is needed for backdoor + * booting of dce. + */ +static const struct dce_platform_data t234_dce_platform_data = { + .stream_id = 0x08, + .phys_stream_id = 0x7f, + .fw_carveout_id = 9, + .hsp_id = 0x0, + .fw_vmindex = 0, + .fw_name = "display-t234-dce.bin", + .fw_dce_addr = 0x40000000, + .fw_info_valid = true, + .use_physical_id = false, +}; + +__weak const struct of_device_id tegra_dce_of_match[] = { + { + .compatible = "nvidia,tegra234-dce", + .data = (struct dce_platform_data *)&t234_dce_platform_data + }, + { }, +}; +MODULE_DEVICE_TABLE(of, tegra_dce_of_match); + +/** + * dce_get_pdata_dce - inline function to get the tegra_dce pointer + * from platform devicve. + * + * @pdev : Pointer to the platform device data structure. + * + * Return : Pointer pointing to tegra_dce data structure. + */ +static inline struct tegra_dce *dce_get_pdata_dce(struct platform_device *pdev) +{ + return (&((struct dce_device *)dev_get_drvdata(&pdev->dev))->d); +} + +/** + * dce_get_tegra_dce_from_dev - inline function to get the tegra_dce pointer + * from devicve struct. + * + * @pdev : Pointer to the device data structure. + * + * Return : Pointer pointing to tegra_dce data structure. + */ +static inline struct tegra_dce *dce_get_tegra_dce_from_dev(struct device *dev) +{ + return (&((struct dce_device *)dev_get_drvdata(dev))->d); +} + +/** + * dce_init_dev_data - Function to initialize the dce device data structure. + * + * @pdev : Pointer to Linux's platform device used for registering DCE. + * + * Primarily used during initialization sequence and is expected to be called + * from probe only. + * + * Return : 0 if success else the corresponding error value. + */ +static int dce_init_dev_data(struct platform_device *pdev, struct dce_platform_data *pdata) +{ + struct device *dev = &pdev->dev; + struct dce_device *d_dev = NULL; + + d_dev = devm_kzalloc(dev, sizeof(*d_dev), GFP_KERNEL); + if (!d_dev) + return -ENOMEM; + + d_dev->dev = dev; + d_dev->pdata = pdata; + d_dev->regs = of_iomap(dev->of_node, 0); + if (!d_dev->regs) { + dev_err(dev, "failed to map dce cluster IO space\n"); + return -EINVAL; + } + + dev_set_drvdata(dev, d_dev); + return 0; +} + +/** + * dce_isr - Handles dce interrupts + */ +static irqreturn_t dce_isr(int irq, void *data) +{ + struct tegra_dce *d = data; + + dce_mailbox_isr(d); + + return IRQ_HANDLED; +} + +static void dce_set_irqs(struct platform_device *pdev, bool en) +{ + int i = 0; + struct tegra_dce *d; + struct dce_device *d_dev = NULL; + + d_dev = dev_get_drvdata(&pdev->dev); + d = dce_get_pdata_dce(pdev); + + for (i = 0; i < d_dev->max_cpu_irqs; i++) { + if (en) + enable_irq(d->irq[i]); + else + disable_irq(d->irq[i]); + } +} + +/** + * dce_req_interrupts - function to initialize CPU irqs for DCE cpu driver. + * + * @pdev : Pointet to Dce Linux Platform Device. + * + * Return : 0 if success else the corresponding error value. + */ +static int dce_req_interrupts(struct platform_device *pdev) +{ + int i = 0; + int ret = 0; + int no_ints = 0; + struct tegra_dce *d; + struct dce_device *d_dev = NULL; + + d_dev = dev_get_drvdata(&pdev->dev); + d = dce_get_pdata_dce(pdev); + + no_ints = platform_irq_count(pdev); + if (no_ints == 0) { + dev_err(&pdev->dev, + "Invalid number of interrupts configured = %d", + no_ints); + return -EINVAL; + } + + d_dev->max_cpu_irqs = no_ints; + + for (i = 0; i < no_ints; i++) { + ret = platform_get_irq(pdev, i); + if (ret < 0) { + dev_err(&pdev->dev, + "Getting dce intr lines failed with ret = %d", + ret); + return ret; + } + d->irq[i] = ret; + ret = devm_request_threaded_irq(&pdev->dev, d->irq[i], + NULL, dce_isr, IRQF_ONESHOT, "tegra_dce_isr", + d); + if (ret) { + dev_err(&pdev->dev, + "failed to request irq @ with ret = %d\n", + ret); + } + disable_irq(d->irq[i]); + } + return ret; +} + +static int match_display_dev(struct device *dev, const void *data) +{ + if ((dev != NULL) && (dev->of_node != NULL)) { + if (of_device_is_compatible(dev->of_node, "nvidia,tegra234-display")) + return 1; + } + + return 0; +} + +static int tegra_dce_probe(struct platform_device *pdev) +{ + int err = 0; + struct tegra_dce *d = NULL; + struct device *dev = &pdev->dev; + struct dce_platform_data *pdata = NULL; + const struct of_device_id *match = NULL; + struct device *c_dev; + struct device_link *link; + + match = of_match_device(tegra_dce_of_match, dev); + if (!match) { + dev_info(dev, "no device match found\n"); + return -ENODEV; + } + + pdata = (struct dce_platform_data *)match->data; + + WARN_ON(!pdata); + if (!pdata) { + dev_info(dev, "no platform data\n"); + err = -ENODATA; + goto err_get_pdata; + } + + err = dce_init_dev_data(pdev, pdata); + if (err) { + dev_err(dev, "failed to init device data with err = %d\n", + err); + goto os_init_err; + } + + err = dce_req_interrupts(pdev); + if (err) { + dev_err(dev, "failed to get interrupts with err = %d\n", + err); + goto req_intr_err; + } + + d = dce_get_pdata_dce(pdev); + + /** + * TODO: Get HSP_ID from DT + */ + d->hsp_id = pdata->hsp_id; + + + err = dce_driver_init(d); + if (err) { + dce_err(d, "DCE Driver Init Failed"); + goto err_driver_init; + } + + dce_set_irqs(pdev, true); + +#ifdef CONFIG_DEBUG_FS + dce_init_debug(d); +#endif + + c_dev = bus_find_device(&platform_bus_type, NULL, NULL, match_display_dev); + if (c_dev != NULL) { + dce_info(d, "Found display consumer device"); + link = device_link_add(c_dev, dev, + DL_FLAG_PM_RUNTIME | DL_FLAG_AUTOREMOVE_SUPPLIER); + if (link == NULL) { + dce_err(d, "Failed to create device link to %s\n", dev_name(c_dev)); + return -EINVAL; + } + } + + return 0; + +req_intr_err: +os_init_err: +err_get_pdata: +err_driver_init: + return err; +} + +static int tegra_dce_remove(struct platform_device *pdev) +{ + /* TODO */ + struct tegra_dce *d = + dce_get_pdata_dce(pdev); + +#ifdef CONFIG_DEBUG_FS + dce_remove_debug(d); +#endif + + dce_set_irqs(pdev, false); + dce_driver_deinit(d); + return 0; +} + +#ifdef CONFIG_PM +static int dce_pm_suspend(struct device *dev) +{ + struct tegra_dce *d = dce_get_tegra_dce_from_dev(dev); + + return dce_pm_enter_sc7(d); +} + +static int dce_pm_resume(struct device *dev) +{ + struct tegra_dce *d = dce_get_tegra_dce_from_dev(dev); + + return dce_pm_exit_sc7(d); +} + +static const struct dev_pm_ops dce_pm_ops = { + .suspend = dce_pm_suspend, + .resume = dce_pm_resume, +}; +#endif + +static struct platform_driver tegra_dce_driver = { + .driver = { + .name = "tegra-dce", + .of_match_table = + of_match_ptr(tegra_dce_of_match), +#ifdef CONFIG_PM + .pm = &dce_pm_ops, +#endif + }, + .probe = tegra_dce_probe, + .remove = tegra_dce_remove, +}; +module_platform_driver(tegra_dce_driver); + +MODULE_DESCRIPTION("DCE Linux driver"); +MODULE_AUTHOR("NVIDIA"); +MODULE_LICENSE("GPL v2"); diff --git a/drivers/platform/tegra/dce/dce-pm.c b/drivers/platform/tegra/dce/dce-pm.c new file mode 100644 index 00000000..b53915a4 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-pm.c @@ -0,0 +1,178 @@ +/* + * Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. 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 + +#define CCPLEX_HSP_IE 1U /* TODO : Have an api to read from platform data */ + +static void dce_pm_save_state(struct tegra_dce *d) +{ + d->sc7_state.hsp_ie = dce_hsp_ie_read(d, CCPLEX_HSP_IE); +} + +static void dce_pm_restore_state(struct tegra_dce *d) +{ + uint32_t val = d->sc7_state.hsp_ie; + + dce_hsp_ie_write(d, val, CCPLEX_HSP_IE); +} + +/** + * dce_resume_work_fn : execute resume and bootstrap flow + * + * @d : Pointer to tegra_dce struct. + * + * Return : void + */ +void dce_resume_work_fn(struct tegra_dce *d) +{ + int ret = 0; + + if (d == NULL) { + dce_err(d, "tegra_dce struct is NULL"); + return; + } + + ret = dce_fsm_post_event(d, EVENT_ID_DCE_BOOT_COMPLETE_REQUESTED, NULL); + if (ret) { + dce_err(d, "Error while posting DCE_BOOT_COMPLETE_REQUESTED event"); + return; + } + + ret = dce_start_boot_flow(d); + if (ret) { + dce_err(d, "DCE bootstrapping failed\n"); + return; + } +} + +/** + * dce_handle_sc7_enter_requested_event - callback handler function for event + * EVENT_ID_DCE_SC7_ENTER_REQUESTED + * + * @d : Pointer to tegra_dce struct. + * @params : callback params + * + * Return : 0 if successful else error code + */ +int dce_pm_handle_sc7_enter_requested_event(struct tegra_dce *d, void *params) +{ + int ret = 0; + struct dce_ipc_message *msg = NULL; + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + goto out; + } + + ret = dce_admin_send_enter_sc7(d, msg); + if (ret) { + dce_err(d, "Enter SC7 failed [%d]", ret); + goto out; + } + + dce_set_boot_complete(d, false); + d->boot_status |= DCE_FW_SUSPENDED; + +out: + dce_admin_free_message(d, msg); + return ret; +} + +/** + * dce_handle_sc7_enter_received_event - callback handler function for event + * EVENT_ID_DCE_SC7_ENTER_RECEIVED + * + * @d : Pointer to tegra_dce struct. + * @params : callback params + * + * Return : 0 if successful else error code + */ +int dce_pm_handle_sc7_enter_received_event(struct tegra_dce *d, void *params) +{ + dce_wakeup_interruptible(d, DCE_WAIT_SC7_ENTER); + return 0; +} + +/** + * dce_handle_sc7_exit_received_event - callback handler function for event + * EVENT_ID_DCE_SC7_EXIT_RECEIVED + * + * @d : Pointer to tegra_dce struct. + * @params : callback params + * + * Return : 0 if successful else error code + */ +int dce_pm_handle_sc7_exit_received_event(struct tegra_dce *d, void *params) +{ + dce_schedule_work(&d->dce_resume_work); + return 0; +} + +int dce_pm_enter_sc7(struct tegra_dce *d) +{ + int ret = 0; + struct dce_ipc_message *msg = NULL; + + /* + * If Bootstrap is not yet done. Nothing to do during SC7 Enter + * Return success immediately. + */ + if (!dce_is_bootstrap_done(d)) { + dce_debug(d, "Bootstrap not done, Succeed SC7 enter\n"); + goto out; + } + + msg = dce_admin_allocate_message(d); + if (!msg) { + dce_err(d, "IPC msg allocation failed"); + ret = -1; + goto out; + } + + dce_pm_save_state(d); + + ret = dce_admin_send_prepare_sc7(d, msg); + if (ret) { + dce_err(d, "Prepare SC7 failed [%d]", ret); + ret = -1; + goto out; + } + + ret = dce_fsm_post_event(d, EVENT_ID_DCE_SC7_ENTER_REQUESTED, NULL); + if (ret) { + dce_err(d, "Error while posting SC7_ENTER event [%d]", ret); + ret = -1; + goto out; + } + +out: + dce_admin_free_message(d, msg); + return ret; +} + +int dce_pm_exit_sc7(struct tegra_dce *d) +{ + int ret = 0; + + dce_pm_restore_state(d); + + ret = dce_fsm_post_event(d, EVENT_ID_DCE_SC7_EXIT_RECEIVED, NULL); + if (ret) { + dce_err(d, "Error while posting SC7_EXIT event [%d]", ret); + goto out; + } +out: + return ret; +} diff --git a/drivers/platform/tegra/dce/dce-reset.c b/drivers/platform/tegra/dce/dce-reset.c new file mode 100644 index 00000000..9c11b29d --- /dev/null +++ b/drivers/platform/tegra/dce/dce-reset.c @@ -0,0 +1,81 @@ +/* + * 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 +#include +#include + +enum pm_controls { + FW_LOAD_HALTED, + FW_LOAD_DONE +}; + +/** + * dce_evp_set_reset_addr - Writes to the evp reset addr register. + * + * @d : Pointer to struct tegra_dce + * @addr : 32bit address + * + * Return : Void + */ +static inline void dce_evp_set_reset_addr(struct tegra_dce *d, u32 addr) +{ + dce_writel(d, evp_reset_addr_r(), addr); +} + +/** + * dce_pm_set_pm_ctrl - Writes to the reset control register. + * + * @d : Pointer to struct tegra_dce + * @val : Value to programmed to the register + * + * Return : Void + */ +static void dce_pm_set_pm_ctrl(struct tegra_dce *d, enum pm_controls val) +{ + switch (val) { + case FW_LOAD_DONE: + dce_writel(d, pm_r5_ctrl_r(), pm_r5_ctrl_fwloaddone_done_f()); + break; + case FW_LOAD_HALTED: + dce_writel(d, pm_r5_ctrl_r(), pm_r5_ctrl_fwloaddone_halted_f()); + break; + default: + break; + } +} + +/** + * dce_reset_dce - Configures the pertinent registers in + * DCE cluser to reset DCE. + * + * @d : Pointer to tegra_dce struct. + * + * Return : 0 if success + */ +int dce_reset_dce(struct tegra_dce *d) +{ + u32 fw_dce_addr; + + if (!d->fw_data) { + dce_err(d, "No fw_data present"); + return -1; + } + + fw_dce_addr = dce_get_fw_dce_addr(d); + dce_evp_set_reset_addr(d, fw_dce_addr); + + dce_pm_set_pm_ctrl(d, FW_LOAD_DONE); + + return 0; +} diff --git a/drivers/platform/tegra/dce/dce-util-common.c b/drivers/platform/tegra/dce/dce-util-common.c new file mode 100644 index 00000000..0db7800f --- /dev/null +++ b/drivers/platform/tegra/dce/dce-util-common.c @@ -0,0 +1,660 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION & AFFILIATES. 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 +#include +#include +#include +#include +#include +#include +#include + +/** + * dce_writel - Dce io function to perform MMIO writes + * + * @d : Pointer to tegra_dce struct. + * @r : register offset from dce_base. + * @v : value to be written + * + * Return : Void + */ +void dce_writel(struct tegra_dce *d, u32 r, u32 v) +{ + struct dce_device *d_dev = dce_device_from_dce(d); + + if (unlikely(!d_dev->regs)) + dce_err(d, "DCE Register Space not IOMAPed to CPU"); + else + writel(v, d_dev->regs + r); +} + +/** + * dce_readl - Dce io function to perform MMIO reads + * + * @d : Pointer to tegra_dce struct. + * @r : register offset from dce_base. + * + * Return : the read value + */ +u32 dce_readl(struct tegra_dce *d, u32 r) +{ + u32 v = 0xffffffff; + + struct dce_device *d_dev = dce_device_from_dce(d); + + if (unlikely(!d_dev->regs)) + dce_err(d, "DCE Register Space not IOMAPed to CPU"); + else + v = readl(d_dev->regs + r); + /*TODO : Add error check here */ + return v; +} + +/** + * dce_writel_check - Performs MMIO writes and checks if the writes + * are actaully correct. + * + * @d : Pointer to tegra_dce struct. + * @r : register offset from dce_base. + * @v : value to be written + * + * Return : Void + */ +void dce_writel_check(struct tegra_dce *d, u32 r, u32 v) +{ + /* TODO : Write and read back to check */ +} + +/** + * dce_io_exists - Dce io function to check if the registers are mapped + * to CPU correctly + * + * @d : Pointer to tegra_dce struct. + * + * Return : True if mapped. + */ +bool dce_io_exists(struct tegra_dce *d) +{ + struct dce_device *d_dev = dce_device_from_dce(d); + + return d_dev->regs != NULL; +} + +/** + * dce_io_valid_regs - Dce io function to check if the requested offset is + * within the range of CPU mapped MMIO range. + * + * @d : Pointer to tegra_dce struct. + * @r : register offset from dce_base. + * + * Return : True if offset within range. + */ +bool dce_io_valid_reg(struct tegra_dce *d, u32 r) +{ + /* TODO : Implement range check here. Returning true for now*/ + return true; +} + +/** + * dce_kzalloc - Function to allocate contiguous kernel memory + * + * @d : Pointer to tegra_dce struct. + * @size_t : Size of the memory to be allocated + * @dma_flag: True if allocated memory should be DMAable + * + * Return : CPU Mapped Address if successful else NULL. + */ +void *dce_kzalloc(struct tegra_dce *d, size_t size, bool dma_flag) +{ + void *alloc; + gfp_t flags = GFP_KERNEL; + + if (dma_flag) + flags |= __GFP_DMA; + + alloc = kzalloc(size, flags); + + return alloc; +} + +/** + * dce_kfree - Frees an alloc from dce_kzalloc + * + * @d : Pointer to tegra_dce struct. + * @addr : Address of the object to free. + * + * Return : void + */ +void dce_kfree(struct tegra_dce *d, void *addr) +{ + kfree(addr); +} + +/** + * dce_request_firmware - Reads the fw into memory. + * + * @d : Pointer to tegra_dce struct. + * @fw_name : Name of the fw. + * + * Return : Pointer to dce_firmware if successful else NULL. + */ +struct dce_firmware *dce_request_firmware(struct tegra_dce *d, + const char *fw_name) +{ + struct device *dev = dev_from_dce(d); + struct dce_firmware *fw; + const struct firmware *l_fw; + + fw = dce_kzalloc(d, sizeof(*fw), false); + if (!fw) + return NULL; + + if (request_firmware(&l_fw, fw_name, dev) < 0) { + dce_err(d, "FW Request Failed"); + goto err; + } + + if (!l_fw) + goto err; + + /* Make sure the address is aligned to 4K */ + fw->size = l_fw->size; + + fw->size = ALIGN(fw->size + SZ_4K, SZ_4K); + /** + * BUG : Currently overwriting all alignment logic above to blinldy + * allocate 2MB FW virtual space. Ideally it should be as per the + * actual size of the fw. + */ + fw->size = SZ_32M; + + fw->data = dma_alloc_coherent(dev, fw->size, + (dma_addr_t *)&fw->dma_handle, + GFP_KERNEL); + if (!fw->data) + goto err_release; + + memcpy((u8 *)fw->data, (u8 *)l_fw->data, l_fw->size); + + release_firmware(l_fw); + + return fw; + +err_release: + release_firmware(l_fw); +err: + dce_kfree(d, fw); + return NULL; +} + +/** + * dce_release_firmware - Reads the fw into memory. + * + * @d : Pointer to tegra_dce struct. + * @fw : Pointer to dce_firmware. + * + * Return : void + */ +void dce_release_fw(struct tegra_dce *d, struct dce_firmware *fw) +{ + struct device *dev = dev_from_dce(d); + + if (!fw) + return; + + dma_free_coherent(dev, fw->size, + (void *)fw->data, + (dma_addr_t)fw->dma_handle); + + dce_kfree(d, fw); +} + +/** + * dce_get_phys_stream_id - Gets the physical stream ID to be programmed from + * platform data. + * + * @d : Pointer to tegra_dce struct. + * + * Return : Stream ID Value + */ +u8 dce_get_phys_stream_id(struct tegra_dce *d) +{ + return pdata_from_dce(d)->phys_stream_id; +} + +/** + * dce_get_dce_stream_id - Gets the dce stream ID to be programmed from + * platform data. + * + * @d : Pointer to tegra_dce struct. + * + * Return : Stream ID Value + */ +u8 dce_get_dce_stream_id(struct tegra_dce *d) +{ + return pdata_from_dce(d)->stream_id; +} + +/** + * dce_get_fw_vm_index - Gets the VMIndex for the fw region to be + * programmed from platform data. + * + * @d : Pointer to tegra_dce struct. + * + * Return : VMIndex + */ +u8 dce_get_fw_vm_index(struct tegra_dce *d) +{ + return pdata_from_dce(d)->fw_vmindex; +} + +/** + * dce_get_fw_carveout_id- Gets the carveout ID for the fw region to be + * programmed from platform data. + * + * @d : Pointer to tegra_dce struct. + * + * Return : Carveout Id + */ +u8 dce_get_fw_carveout_id(struct tegra_dce *d) +{ + return pdata_from_dce(d)->fw_carveout_id; +} + +/** + * dce_is_physical_id_valid - Checks if the DCE can use physical stream ID. + * + * @d : Pointer to tegra_dce struct. + * + * Return : True if SMMU is disabled. + */ +bool dce_is_physical_id_valid(struct tegra_dce *d) +{ + return pdata_from_dce(d)->use_physical_id; +} + +/** + * dce_get_fw_dce_addr - Gets the 32bit address to be used for + * loading the fw before being converted + * by AST into a 40-bit address. + * + * @d : Pointer to tegra_dce struct. + * + * Return : 32bit address + */ +u32 dce_get_fw_dce_addr(struct tegra_dce *d) +{ + return pdata_from_dce(d)->fw_dce_addr; +} + +/** + * dce_get_fw_phy_addr - Gets the 40bit address to be used by AST + * for loading the fw after converting + * the 32bit incoming address. + * + * @d : Pointer to tegra_dce struct. + * + * This API is to be used only if the memory is being allocated + * via kzalloc or friends. Do not use this if memory is + * allocated via dma apis. + * + * Return : 64bit address + */ +u64 dce_get_fw_phy_addr(struct tegra_dce *d, struct dce_firmware *fw) +{ + /* Caller should make sure that *fw is valid since this func is + * not expected to return any error. + */ + return (u64)virt_to_phys((void *)fw->data); +} + +/** + * dce_get_fw_name - Gets the dce fw name from platform data. + * + * @d : Pointer to tegra_dce struct. + * + * Return : fw_name + */ +const char *dce_get_fw_name(struct tegra_dce *d) +{ + return pdata_from_dce(d)->fw_name; +} + +static void dce_print(const char *func_name, int line, + enum dce_log_type type, const char *log) +{ +#define DCE_LOG_FMT "dce: %15s:%-4d %s\n" + + switch (type) { + case DCE_DEBUG: + pr_debug(DCE_LOG_FMT, func_name, line, log); + break; + case DCE_INFO: + pr_info(DCE_LOG_FMT, func_name, line, log); + break; + case DCE_WARNING: + pr_warn(DCE_LOG_FMT, func_name, line, log); + break; + case DCE_ERROR: + pr_err(DCE_LOG_FMT, func_name, line, log); + break; + } +#undef DCE_LOG_FMT +} + +__printf(5, 6) +void dce_log_msg(struct tegra_dce *d, const char *func_name, int line, + enum dce_log_type type, const char *fmt, ...) +{ + +#define BUF_LEN 100 + + char log[BUF_LEN]; + va_list args; + + va_start(args, fmt); + (void) vsnprintf(log, BUF_LEN, fmt, args); + va_end(args); + + dce_print(func_name, line, type, log); +} + +/** + * dce_cond_init - Initialize a condition variable + * + * @cond - The condition variable to initialize + * + * Initialize a condition variable before using it. + */ +int dce_cond_init(struct dce_cond *cond) +{ + init_waitqueue_head(&cond->wq); + cond->initialized = true; + + return 0; +} + +/** + * dce_cond_destroy - Destroy a condition variable + * + * @cond - The condition variable to destroy + */ +void dce_cond_destroy(struct dce_cond *cond) +{ + cond->initialized = false; +} + +/** + * dce_cond_signal - Signal a condition variable + * + * @cond - The condition variable to signal + * + * Wake up a waiter for a condition variable to check if its condition has been + * satisfied. + * + * The waiter is using an uninterruptible wait. + */ +void dce_cond_signal(struct dce_cond *cond) +{ + WARN_ON(!cond->initialized); + + wake_up(&cond->wq); +} + +/** + * dce_cond_signal_interruptible - Signal a condition variable + * + * @cond - The condition variable to signal + * + * Wake up a waiter for a condition variable to check if its condition has been + * satisfied. + * + * The waiter is using an interruptible wait. + */ +void dce_cond_signal_interruptible(struct dce_cond *cond) +{ + WARN_ON(!cond->initialized); + + wake_up_interruptible(&cond->wq); +} + +/** + * dce_cond_broadcast - Signal all waiters of a condition variable + * + * @cond - The condition variable to signal + * + * Wake up all waiters for a condition variable to check if their conditions + * have been satisfied. + * + * The waiters are using an uninterruptible wait. + */ +int dce_cond_broadcast(struct dce_cond *cond) +{ + if (!cond->initialized) + return -EINVAL; + + wake_up_all(&cond->wq); + + return 0; +} + +/** + * dce_cond_broadcast_interruptible - Signal all waiters of a condition + * variable + * + * @cond - The condition variable to signal + * + * Wake up all waiters for a condition variable to check if their conditions + * have been satisfied. + * + * The waiters are using an interruptible wait. + */ +int dce_cond_broadcast_interruptible(struct dce_cond *cond) +{ + if (!cond->initialized) + return -EINVAL; + + wake_up_interruptible_all(&cond->wq); + + return 0; +} + +/** + * dce_thread_proxy - Function to be passed to kthread. + * + * @thread_data : Pointer to actual dce_thread struct + * + * Return : Ruturns the return value of the function to be run. + */ +static int dce_thread_proxy(void *thread_data) +{ + struct dce_thread *thread = thread_data; + int ret = thread->fn(thread->data); + + thread->running = false; + return ret; +} + +/** + * dce_thread_create - Create and run a new thread. + * + * @thread - thread structure to use + * @data - data to pass to threadfn + * @threadfn - Thread function + * @name - name of the thread + * + * Create a thread and run threadfn in it. The thread stays alive as long as + * threadfn is running. As soon as threadfn returns the thread is destroyed. + * + * threadfn needs to continuously poll dce_thread_should_stop() to determine + * if it should exit. + */ +int dce_thread_create(struct dce_thread *thread, + void *data, + int (*threadfn)(void *data), const char *name) +{ + struct task_struct *task = kthread_create(dce_thread_proxy, + thread, name); + if (IS_ERR(task)) + return PTR_ERR(task); + + thread->task = task; + thread->fn = threadfn; + thread->data = data; + thread->running = true; + wake_up_process(task); + return 0; +}; + +/** + * dce_thread_stop - Destroy or request to destroy a thread + * + * @thread - thread to stop + * + * Request a thread to stop by setting dce_thread_should_stop() to + * true and wait for thread to exit. + */ +void dce_thread_stop(struct dce_thread *thread) +{ + /* + * Threads waiting on wq's should have dce_thread_should_stop() + * as one of its wakeup condition. This allows the thread to be woken + * up when kthread_stop() is invoked and does not require an additional + * callback to wakeup the sleeping thread. + */ + if (thread->task) { + kthread_stop(thread->task); + thread->task = NULL; + } +}; + +/** + * dce_thread_should_stop - Query if thread should stop + * + * @thread + * + * Return true if thread should exit. Can be run only in the thread's own + * context and with the thread as parameter. + */ +bool dce_thread_should_stop(struct dce_thread *thread) +{ + return kthread_should_stop(); +}; + +/** + * dce_thread_is_running - Query if thread is running + * + * @thread + * + * Return true if thread is started. + */ +bool dce_thread_is_running(struct dce_thread *thread) +{ + return READ_ONCE(thread->running); +}; + +/** + * dce_thread_join - join a thread to reclaim resources + * after it has exited + * + * @thread - thread to join + * + */ +void dce_thread_join(struct dce_thread *thread) +{ + while (READ_ONCE(thread->running)) + usleep_range(10000, 20000); +}; + +/** + * dce_get_nxt_pow_of_2 : get next power of 2 number for a given number + * + * @addr : Address of given number + * @nbits : bits in given number + * + * Return : unsigned long next power of 2 value + */ +unsigned long dce_get_nxt_pow_of_2(unsigned long *addr, u8 nbits) +{ + u8 l_bit = 0; + u8 bit_index = 0; + unsigned long val; + + val = *addr; + if (val == 0) + return 0; + + bit_index = find_first_bit(addr, nbits); + + while (bit_index && (bit_index < nbits)) { + l_bit = bit_index; + bit_index = find_next_bit(addr, nbits, bit_index + 1); + } + + if (BIT(l_bit) < val) { + l_bit += 1UL; + val = BIT(l_bit); + } + + return val; +} + +/* + * dce_schedule_work : schedule work in global highpri workqueue + * + * @work : dce work to be scheduled + * + * Return : void + */ +void dce_schedule_work(struct dce_work_struct *work) +{ + queue_work(system_highpri_wq, &work->work); +} + +/* + * dce_work_handle_fn : handler function for scheduled dce-work + * + * @work : Pointer to the scheduled work + * + * Return : void + */ +static void dce_work_handle_fn(struct work_struct *work) +{ + struct dce_work_struct *dce_work = container_of(work, + struct dce_work_struct, + work); + + if (dce_work->dce_work_fn != NULL) + dce_work->dce_work_fn(dce_work->d); +} + +/* + * dce_init_work : Init dce work structure + * + * @d : Pointer to tegra_dce struct. + * @work : Pointer to dce work structure + * @work_fn : worker function to be called + * + * Return : 0 if successful + */ +int dce_init_work(struct tegra_dce *d, + struct dce_work_struct *work, + void (*work_fn)(struct tegra_dce *d)) +{ + work->d = d; + work->dce_work_fn = work_fn; + + INIT_WORK(&work->work, dce_work_handle_fn); + + return 0; +} diff --git a/drivers/platform/tegra/dce/dce-worker.c b/drivers/platform/tegra/dce/dce-worker.c new file mode 100644 index 00000000..0299c192 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-worker.c @@ -0,0 +1,162 @@ +/* + * Copyright (c) 2019-2023, NVIDIA CORPORATION & AFFILIATES. 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 +#include +#include +#include +#include +#include + +/* + * dce_wait_interruptible : Wait for a given condition + * + * @d : Pointer to tegra_dce struct. + * @msg_id : index of wait condition + * + * Return : 0 if successful else error code + */ +int dce_wait_interruptible(struct tegra_dce *d, u32 msg_id) +{ + struct dce_wait_cond *wait; + + if (msg_id >= DCE_MAX_WAIT) { + dce_err(d, "Invalid wait requested %u", msg_id); + return -EINVAL; + } + + wait = &d->ipc_waits[msg_id]; + + /* + * It is possible that we received the ACK from DCE even before we + * start waiting. But that should not be an issue as wait->complete + * Will be "1" and we immediately exit from the wait. + */ + DCE_COND_WAIT_INTERRUPTIBLE(&wait->cond_wait, + atomic_read(&wait->complete) == 1); + + if (atomic_read(&wait->complete) != 1) + return -EINTR; + + /* + * Clear wait->complete as soon as we exit from wait (consume the wake call) + * So that when the next dce_wait_interruptible is called, it doesn't see old + * wait->complete state. + */ + atomic_set(&wait->complete, 0); + return 0; +} + +/* + * dce_wakeup_interruptible : Wakeup waiting task on given condition + * + * @d : Pointer to tegra_dce struct. + * @msg_id : index of wait condition + * + * Return : void + */ +void dce_wakeup_interruptible(struct tegra_dce *d, u32 msg_id) +{ + struct dce_wait_cond *wait; + + if (msg_id >= DCE_MAX_WAIT) { + dce_err(d, "Invalid wait requested %u", msg_id); + return; + } + + wait = &d->ipc_waits[msg_id]; + + /* + * Set wait->complete to "1", so if the wait is called even after + * "dce_cond_signal_interruptible", it'll see the complete variable + * as "1" and exit the wait immediately. + */ + atomic_set(&wait->complete, 1); + dce_cond_signal_interruptible(&wait->cond_wait); +} + +/** + * dce_work_cond_sw_resource_init : Init dce workqueues related resources + * + * @d : Pointer to tegra_dce struct. + * + * Return : 0 if successful else error code + */ +int dce_work_cond_sw_resource_init(struct tegra_dce *d) +{ + int ret = 0; + int i; + + ret = dce_init_work(d, &d->dce_bootstrap_work, dce_bootstrap_work_fn); + if (ret) { + dce_err(d, "Bootstrap work init failed"); + goto exit; + } + + ret = dce_init_work(d, &d->dce_resume_work, dce_resume_work_fn); + if (ret) { + dce_err(d, "resume work init failed"); + goto exit; + } + + if (dce_cond_init(&d->dce_bootstrap_done)) { + dce_err(d, "dce boot wait condition init failed"); + ret = -1; + goto exit; + } + + for (i = 0; i < DCE_MAX_WAIT; i++) { + struct dce_wait_cond *wait = &d->ipc_waits[i]; + + if (dce_cond_init(&wait->cond_wait)) { + dce_err(d, "dce wait condition %d init failed", i); + ret = -1; + goto init_error; + } + + atomic_set(&wait->complete, 0); + } + + return 0; +init_error: + while (i >= 0) { + struct dce_wait_cond *wait = &d->ipc_waits[i]; + + dce_cond_destroy(&wait->cond_wait); + i--; + } + dce_cond_destroy(&d->dce_bootstrap_done); +exit: + return ret; +} + +/** + * dce_work_cond_sw_resource_deinit : de-init dce workqueues related resources + * + * @d : Pointer to tegra_dce struct. + * + * Return : void + */ +void dce_work_cond_sw_resource_deinit(struct tegra_dce *d) +{ + int i; + + for (i = 0; i < DCE_MAX_WAIT; i++) { + struct dce_wait_cond *wait = &d->ipc_waits[i]; + + dce_cond_destroy(&wait->cond_wait); + atomic_set(&wait->complete, 0); + } + + dce_cond_destroy(&d->dce_bootstrap_done); +} diff --git a/drivers/platform/tegra/dce/include/dce-client-ipc-internal.h b/drivers/platform/tegra/dce/include/dce-client-ipc-internal.h new file mode 100644 index 00000000..ff82b5fb --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-client-ipc-internal.h @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2019-2022, 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. + */ + +#ifndef DCE_CLIENT_IPC_INTERNAL_H +#define DCE_CLIENT_IPC_INTERNAL_H + +#include + +/** + * struct tegra_dce_client_ipc - Data Structure to hold client specific ipc + * data pertaining to IPC type + * + * @valid : Tells if the client ipc data held by data structure is valid + * @data : Pointer to any specific data passed by client during registration + * for corresponding IPC type + * @type : Corresponding IPC type as defined in CPU driver + * @handle : Corresponding handle allocated for client during registration + * @int_type : IPC interface type for above IPC type as defined in CPU driver + * @d : pointer to OS agnostic dce struct. Stores all runtime info for dce + * cluster elements + * @recv_wait : condition variable used for IPC synchronization + * @complete : atomic variable used for IPC synchronization + * @callback_fn : function pointer to the callback function passed by the + * client during registration + */ +struct tegra_dce_client_ipc { + bool valid; + void *data; + uint32_t type; + uint32_t handle; + uint32_t int_type; + struct tegra_dce *d; + struct dce_cond recv_wait; + atomic_t complete; + tegra_dce_client_ipc_callback_t callback_fn; +}; + +#define DCE_MAX_ASYNC_WORK 8 +struct dce_async_work { + struct tegra_dce *d; + struct work_struct async_event_work; + atomic_t in_use; +}; + +/** + * @async_event_wq - Workqueue to process async events from DCE + */ +struct tegra_dce_async_ipc_info { + struct workqueue_struct *async_event_wq; + struct dce_async_work work[DCE_MAX_ASYNC_WORK]; +}; + +void dce_client_ipc_wakeup(struct tegra_dce *d, u32 ch_type); + +int dce_client_ipc_wait(struct tegra_dce *d, u32 ch_type); + +int dce_client_init(struct tegra_dce *d); + +void dce_client_deinit(struct tegra_dce *d); + +#endif diff --git a/drivers/platform/tegra/dce/include/dce-cond.h b/drivers/platform/tegra/dce/include/dce-cond.h new file mode 100644 index 00000000..d4a1daf5 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-cond.h @@ -0,0 +1,133 @@ +/* + * Copyright (c) 2019-2022, 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. + */ + +#ifndef DCE_COND_H +#define DCE_COND_H + +#include +#include + +struct dce_cond { + bool initialized; + wait_queue_head_t wq; +}; + +/** + * DCE_COND_WAIT - Wait for a condition to be true + * + * @c - The condition variable to sleep on + * @condition - The condition that needs to be true + * + * Wait for a condition to become true. + */ +#define DCE_COND_WAIT(c, condition) \ +({\ + int ret = 0; \ + wait_event((c)->wq, condition); \ + ret;\ +}) + +/** + * DCE_COND_WAIT_INTERRUPTIBLE - Wait for a condition to be true + * + * @c - The condition variable to sleep on + * @condition - The condition that needs to be true + * + * Wait for a condition to become true. Returns -ERESTARTSYS + * on signal. + */ +#define DCE_COND_WAIT_INTERRUPTIBLE(c, condition) \ +({ \ + int ret = 0; \ + ret = wait_event_interruptible((c)->wq, condition); \ + ret; \ +}) + +/** + * DCE_COND_WAIT_TIMEOUT - Wait for a condition to be true + * + * @c - The condition variable to sleep on + * @condition - The condition that needs to be true + * @timeout_ms - Timeout in milliseconds, or 0 for infinite wait. + * This parameter must be a u32. Since this is a macro, this is + * enforced by assigning a typecast NULL pointer to a u32 tmp + * variable which will generate a compiler warning (or error if + * the warning is configured as an error). + * + * Wait for a condition to become true. Returns -ETIMEOUT if + * the wait timed out with condition false. + */ +#define DCE_COND_WAIT_TIMEOUT(c, condition, timeout_ms) \ +({\ + int ret = 0; \ + /* This is the assignment to enforce a u32 for timeout_ms */ \ + u32 *tmp = (typeof(timeout_ms) *)NULL; \ + (void)tmp; \ + if (timeout_ms > 0U) { \ + long _ret = wait_event_timeout((c)->wq, condition, \ + msecs_to_jiffies(timeout_ms)); \ + if (_ret == 0) \ + ret = -ETIMEDOUT; \ + } else { \ + wait_event((c)->wq, condition); \ + } \ + ret;\ +}) + +/** + * DCE_COND_WAIT_INTERRUPTIBLE_TIMEOUT - Wait for a condition to be true + * + * @c - The condition variable to sleep on + * @condition - The condition that needs to be true + * @timeout_ms - Timeout in milliseconds, or 0 for infinite wait. + * This parameter must be a u32. Since this is a macro, this is + * enforced by assigning a typecast NULL pointer to a u32 tmp + * variable which will generate a compiler warning (or error if + * the warning is configured as an error). + * + * Wait for a condition to become true. Returns -ETIMEOUT if + * the wait timed out with condition false or -ERESTARTSYS on + * signal. + */ +#define DCE_COND_WAIT_INTERRUPTIBLE_TIMEOUT(c, condition, timeout_ms) \ +({ \ + int ret = 0; \ + /* This is the assignment to enforce a u32 for timeout_ms */ \ + u32 *tmp = (typeof(timeout_ms) *)NULL; \ + (void)tmp; \ + if (timeout_ms > 0U) { \ + long _ret = wait_event_interruptible_timeout((c)->wq, \ + condition, msecs_to_jiffies(timeout_ms)); \ + if (_ret == 0) \ + ret = -ETIMEDOUT; \ + else if (_ret == -ERESTARTSYS) \ + ret = -ERESTARTSYS; \ + } else { \ + ret = wait_event_interruptible((c)->wq, condition); \ + } \ + ret; \ +}) + +int dce_cond_init(struct dce_cond *cond); + +void dce_cond_signal(struct dce_cond *cond); + +void dce_cond_signal_interruptible(struct dce_cond *cond); + +int dce_cond_broadcast(struct dce_cond *cond); + +int dce_cond_broadcast_interruptible(struct dce_cond *cond); + +void dce_cond_destroy(struct dce_cond *cond); + +#endif diff --git a/drivers/platform/tegra/dce/include/dce-debug-perf.h b/drivers/platform/tegra/dce/include/dce-debug-perf.h new file mode 100644 index 00000000..60bef8a4 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-debug-perf.h @@ -0,0 +1,42 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION & AFFILIATES. 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. + */ + +#ifndef DCE_DEBUG_PERF_H +#define DCE_DEBUG_PERF_H + +#include +#include + +int dbg_dce_perf_stats_stats_fops_open(struct inode *inode, + struct file *file); +ssize_t dbg_dce_perf_stats_stats_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos); + +int dbg_dce_perf_stats_help_fops_open(struct inode *inode, + struct file *file); + +ssize_t dbg_dce_perf_format_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos); +int dbg_dce_perf_format_fops_open(struct inode *inode, struct file *file); + +int dbg_dce_perf_events_events_fops_open(struct inode *inode, + struct file *file); +ssize_t dbg_dce_perf_events_events_fops_write(struct file *file, + const char __user *user_buf, + size_t count, loff_t *ppos); +int dbg_dce_perf_events_help_fops_open(struct inode *inode, + struct file *file); +#endif diff --git a/drivers/platform/tegra/dce/include/dce-fsm.h b/drivers/platform/tegra/dce/include/dce-fsm.h new file mode 100644 index 00000000..8fcf2806 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-fsm.h @@ -0,0 +1,104 @@ +/* + * Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. 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. + */ + +#ifndef DCE_FSM_H +#define DCE_FSM_H + +#include +#include + +/** + * enum dce_fsm_event_id_type - IDs to be used to convey various + * events throughout the life cycle of dce. + */ +enum dce_fsm_event_id_type { + EVENT_ID_DCE_INVALID = -1, + EVENT_ID_DCE_FSM_START = 0, + EVENT_ID_DCE_BOOT_COMPLETE_REQUESTED, + EVENT_ID_DCE_BOOT_COMPLETE_RECEIVED, + EVENT_ID_DCE_BOOT_CMD_MSG_REQUESTED, + EVENT_ID_DCE_BOOT_CMD_MSG_RECEIVED, + EVENT_ID_DCE_ADMIN_IPC_MSG_REQUESTED, + EVENT_ID_DCE_ADMIN_IPC_MSG_RECEIVED, + EVENT_ID_DCE_SC7_ENTER_REQUESTED, + EVENT_ID_DCE_SC7_ENTERED_RECEIVED, + EVENT_ID_DCE_SC7_EXIT_RECEIVED, + EVENT_ID_DCE_LOG_REQUESTED, + EVENT_ID_DCE_LOG_READY_RECEIVED, + EVENT_ID_DCE_ABORT_RECEIVED, + EVENT_ID_DCE_CRASH_LOG_RECEIVED, + EVENT_ID_DCE_LOG_OVERFLOW_RECEIVED, + EVENT_ID_DCE_FSM_STOP, +}; + +/** + * dce_fsm_state - Different states of dce. + */ +enum dce_fsm_state { + STATE_DCE_INVALID = -1, + STATE_DCE_FSM_IDLE = 0, + STATE_DCE_BOOT_WAIT, + STATE_DCE_BOOTCMD_WFI, + STATE_DCE_ADMIN_WFI, + STATE_DCE_SC7_ENTER_WFI, + STATE_DCE_SC7_ENTERED, + STATE_DCE_LOG_READY_WFI, + STATE_DCE_ABORT, +}; + +/** + * struct dce_fsm_info - contains info used by FSM + * @d : Pointer to tegra_dce struct + * @initialized : If FSM is initialized + * @c_state : Current state of FSM + * @lock : Mutex to protect FSM operations + * @requested_ipcs : Indicate what IPCs/requests are currently running. + * In case we support more than one FSM request at a time. + */ +struct dce_fsm_info { + struct tegra_dce *d; + bool initialized; + enum dce_fsm_state c_state; + struct dce_mutex lock; + u32 requested_ipcs; +}; + +/** + * struct dce_mailbox_send_cmd_params - contains params for callback function + * @cmd : u32 command to be send + * @interface : interface id on which command need to be sent + */ +struct dce_mailbox_send_cmd_params { + u32 cmd; + u32 interface; +}; + +/** + * struct dce_admin_send_msg_params - contains params for callback function + * @msg : Pointer to dce_ipc_msg + */ +struct dce_admin_send_msg_params { + struct dce_ipc_message *msg; +}; + +int dce_fsm_init(struct tegra_dce *d); +void dce_fsm_start(struct tegra_dce *d); +void dce_fsm_stop(struct tegra_dce *d); +void dce_fsm_deinit(struct tegra_dce *d); +int dce_fsm_post_event(struct tegra_dce *d, + enum dce_fsm_event_id_type event, + void *data); + +int dce_handle_fsm_start_event(struct tegra_dce *d, void *params); +int dce_handle_event_stub(struct tegra_dce *d, void *params); +#endif diff --git a/drivers/platform/tegra/dce/include/dce-hsp.h b/drivers/platform/tegra/dce/include/dce-hsp.h new file mode 100644 index 00000000..a03effe7 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-hsp.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#ifndef DCE_HSP_H +#define DCE_HSP_H + +#include + +struct tegra_dce; + +/** + * DCE HSP Shared Semaphore Utility functions. Description + * can be found with function definitions. + */ +u32 dce_ss_get_state(struct tegra_dce *d, u8 id); +void dce_ss_set(struct tegra_dce *d, u8 bpos, u8 id); +void dce_ss_clear(struct tegra_dce *d, u8 bpos, u8 id); + +/** + * DCE HSP Shared Mailbox Utility functions. Description + * can be found with function definitions. + */ +void dce_smb_set(struct tegra_dce *d, u32 val, u8 id); +void dce_smb_set_full_ie(struct tegra_dce *d, bool en, u8 id); +u32 dce_smb_read_full_ie(struct tegra_dce *d, u8 id); +void dce_smb_set_empty_ie(struct tegra_dce *d, bool en, u8 id); +u32 dce_smb_read(struct tegra_dce *d, u8 id); +u32 dce_hsp_ie_read(struct tegra_dce *d, u8 id); +void dce_hsp_ie_write(struct tegra_dce *d, u32 val, u8 id); +u32 dce_hsp_ir_read(struct tegra_dce *d); + +#endif diff --git a/drivers/platform/tegra/dce/include/dce-ipc.h b/drivers/platform/tegra/dce/include/dce-ipc.h new file mode 100644 index 00000000..a54dc8a6 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-ipc.h @@ -0,0 +1,198 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019-2023 NVIDIA CORPORATION & AFFILIATES. 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. + */ + +#ifndef DCE_IPC_H +#define DCE_IPC_H + +#include +#include +#include +#include +#include +#include +#include + +#define DCE_IPC_CHANNEL_TYPE_ADMIN 0U +#define DCE_IPC_CHANNEL_TYPE_CPU_CLIENTS 1U + +#define DCE_IPC_MAX_IVC_CHANNELS 4U + +/** + * TODO : Move the DispRM max to a config file + */ +#define DCE_DISPRM_CMD_MAX_NFRAMES 1U +#define DCE_DISPRM_CMD_MAX_FSIZE 4096U +#define DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_NFRAMES 4U +#define DCE_DISPRM_EVENT_NOTIFY_CMD_MAX_FSIZE 4096U +#define DCE_ADMIN_CMD_MAX_FSIZE 2048U + +#define DCE_IPC_WAIT_TYPE_INVALID 0U +#define DCE_IPC_WAIT_TYPE_RPC 1U + +#define DCE_IPC_CHANNEL_VALID BIT(0) +#define DCE_IPC_CHANNEL_INITIALIZED BIT(1) +#define DCE_IPC_CHANNEL_SYNCED BIT(2) +#define DCE_IPC_CHANNEL_MSG_HEADER BIT(15) + +#define DCE_IPC_CH_KMD_TYPE_ADMIN 0U +#define DCE_IPC_CH_KMD_TYPE_RM 1U +#define DCE_IPC_CH_KMD_TYPE_HDCP 2U +#define DCE_IPC_CH_KMD_TYPE_RM_NOTIFY 3U +#define DCE_IPC_CH_KMD_TYPE_MAX 4U +/** + * struct dce_ipc_signal - Stores ivc channel details + * + * @d : Pointer to struct tegra_dce. + * @ibuff : Pointer to the input data buffer. + * @obuff : Pointer to the output data buffer. + * @d_ivc : Pointer to the ivc data structure. + */ +struct dce_ipc_mailbox { + u8 mb_type; + u32 mb_num; +}; + +/** + * TODO : Use linux doorbell driver + */ +struct dce_ipc_doorbell { + u32 db_num; + u32 db_bit; +}; + +struct dce_ipc_signal_instance { + u32 type; + u32 sema_num; + u32 sema_bit; + union { + struct dce_ipc_mailbox mbox; + struct dce_ipc_doorbell db; + } form; + struct dce_ipc_signal *signal; + struct dce_ipc_signal_instance *next; +}; + +typedef void (*dce_ipc_signal_notify)(struct tegra_dce *d, + struct dce_ipc_signal_instance *signal); + +struct dce_ipc_signal { + struct dce_ipc_channel *ch; + dce_ipc_signal_notify notify; + struct dce_ipc_signal_instance to_d; + struct dce_ipc_signal_instance from_d; +}; + +int dce_ipc_signal_init(struct dce_ipc_channel *chan); + + +/** + * struct dce_ipc_region - Contains ivc region specific memory info. + * + * @size : total IVC region size. + * @tx : transmit region info. + * @rx : receive region info. + */ +struct dce_ipc_region { + u32 s_offset; + dma_addr_t iova; + unsigned long size; + void *base; +}; + +struct dce_ipc_queue_info { + u8 nframes; + u32 frame_sz; + dma_addr_t rx_iova; + dma_addr_t tx_iova; +}; + +/** + * struct dce_ipc_channel - Stores ivc channel details + * + * @d : Pointer to struct tegra_dce. + * @ibuff : Pointer to the input data buffer. + * @obuff : Pointer to the output data buffer. + * @d_ivc : Pointer to the ivc data structure. + */ +struct dce_ipc_channel { + u32 flags; + u32 w_type; + u32 ch_type; + u32 ipc_type; +#if (KERNEL_VERSION(6, 2, 0) <= LINUX_VERSION_CODE) + struct iosys_map ibuff; + struct iosys_map obuff; +#else + void *ibuff; + void *obuff; +#endif + struct tegra_ivc d_ivc; + struct tegra_dce *d; + struct dce_mutex lock; + struct dce_ipc_signal signal; + struct dce_ipc_queue_info q_info; +}; + +/** + * struct dce_ipc - Stores ipc data + * + * @region - Store data about ivc region in DRAM + * @ch - Array of pointers to store dce ivc channel info + */ +struct dce_ipc { + struct dce_ipc_region region; + struct dce_ipc_channel *ch[DCE_IPC_MAX_IVC_CHANNELS]; +}; + +int dce_ipc_send_message(struct tegra_dce *d, + u32 ch_type, const void *data, size_t size); + +int dce_ipc_read_message(struct tegra_dce *d, + u32 ch_type, void *data, size_t size); + +int dce_ipc_send_message_sync(struct tegra_dce *d, + u32 ch_type, struct dce_ipc_message *msg); + +int dce_ipc_get_channel_info(struct tegra_dce *d, + struct dce_ipc_queue_info *q_info, u32 ch_index); + +void dce_ipc_free_region(struct tegra_dce *d); + +int dce_ipc_allocate_region(struct tegra_dce *d); + +struct tegra_dce *dce_ipc_get_dce_from_ch(u32 ch_type); + +int dce_ipc_channel_init(struct tegra_dce *d, u32 ch_type); + +void dce_ipc_channel_deinit(struct tegra_dce *d, u32 ch_type); + +void dce_ipc_channel_reset(struct tegra_dce *d, u32 ch_type); + +uint32_t dce_ipc_get_ipc_type(struct tegra_dce *d, u32 ch_type); + +bool dce_ipc_channel_is_ready(struct tegra_dce *d, u32 ch_type); + +bool dce_ipc_channel_is_synced(struct tegra_dce *d, u32 ch_type); + +u32 dce_ipc_get_cur_wait_type(struct tegra_dce *d, u32 ch_type); + +bool dce_ipc_is_data_available(struct tegra_dce *d, u32 ch_type); + +int dce_ipc_get_region_iova_info(struct tegra_dce *d, u64 *iova, u32 *size); + +int dce_ipc_init_signaling(struct tegra_dce *d, struct dce_ipc_channel *ch); + +void dce_ipc_deinit_signaling(struct tegra_dce *d, struct dce_ipc_channel *ch); + +#endif diff --git a/drivers/platform/tegra/dce/include/dce-lock.h b/drivers/platform/tegra/dce/include/dce-lock.h new file mode 100644 index 00000000..f4b1cc04 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-lock.h @@ -0,0 +1,70 @@ +/* + * 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. + */ + +#ifndef DCE_LOCK_H +#define DCE_LOCK_H + +struct dce_mutex { + struct mutex mutex; +}; + +/** + * dce_mutex_init - Initialize dce_mutex in Linux style + * + * mutex : pointer to the mutext to be initialized. + * + * Return : 0 if successful + */ +static inline int dce_mutex_init(struct dce_mutex *mutex) +{ + mutex_init(&mutex->mutex); + return 0; +}; + +/** + * dce_mutex_lock - Acquire the dce_mutex in Linux style + * + * mutex : pointer to the mutext to be acquired. + * + * Return : void + */ +static inline void dce_mutex_lock(struct dce_mutex *mutex) +{ + mutex_lock(&mutex->mutex); +}; + +/** + * dce_mutex_unlock - Release the dce_mutex in Linux style + * + * mutex : pointer to the mutext to be released. + * + * Return : void + */ +static inline void dce_mutex_unlock(struct dce_mutex *mutex) +{ + mutex_unlock(&mutex->mutex); +}; + +/** + * dce_mutex_destroy - Destroy the dce_mutex in Linux style + * + * mutex : pointer to the mutext to be destroyed. + * + * Return : 0 if successful + */ +static inline void dce_mutex_destroy(struct dce_mutex *mutex) +{ + mutex_destroy(&mutex->mutex); +}; + +#endif diff --git a/drivers/platform/tegra/dce/include/dce-log.h b/drivers/platform/tegra/dce/include/dce-log.h new file mode 100644 index 00000000..52bc8e07 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-log.h @@ -0,0 +1,82 @@ +/* + * Copyright (c) 2019-2022, 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. + */ + +#ifndef DCE_LOG_H +#define DCE_LOG_H + +struct tegra_dce; + +enum dce_log_type { + DCE_ERROR, + DCE_WARNING, + DCE_INFO, + DCE_DEBUG, +}; + +/* + * Each OS must implement these functions. They handle the OS specific nuances + * of printing data to a UART, log, whatever. + */ +__printf(5, 6) +void dce_log_msg(struct tegra_dce *d, const char *func_name, int line, + enum dce_log_type type, const char *fmt, ...); + +/** + * dce_err - Print an error + * + * @d - Pointer to tegra_dce. + * @fmt - A format string (printf style). + * @arg... - Arguments for the format string. + * + * Uncondtionally print an error message. + */ +#define dce_err(d, fmt, arg...) \ + dce_log_msg(d, __func__, __LINE__, DCE_ERROR, fmt, ##arg) + +/** + * dce_warn - Print a warning + * + * @d - Pointer to tegra_dce. + * @fmt - A format string (printf style). + * @arg... - Arguments for the format string. + * + * Uncondtionally print a warming message. + */ +#define dce_warn(d, fmt, arg...) \ + dce_log_msg(d, __func__, __LINE__, DCE_WARNING, fmt, ##arg) + +/** + * dce_info - Print an info message + * + * @d - Pointer to tegra_dce. + * @fmt - A format string (printf style). + * @arg... - Arguments for the format string. + * + * Unconditionally print an information message. + */ +#define dce_info(d, fmt, arg...) \ + dce_log_msg(d, __func__, __LINE__, DCE_INFO, fmt, ##arg) + +/** + * dce_debug - Print a debug message + * + * @d - Pointer to tegra_dce. + * @fmt - A format string (printf style). + * @arg... - Arguments for the format string. + * + * print a debug message. + */ +#define dce_debug(d, fmt, arg...) \ + dce_log_msg(d, __func__, __LINE__, DCE_DEBUG, fmt, ##arg) + +#endif diff --git a/drivers/platform/tegra/dce/include/dce-mailbox.h b/drivers/platform/tegra/dce/include/dce-mailbox.h new file mode 100644 index 00000000..b2150265 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-mailbox.h @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2019-2022, 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. + */ + +#ifndef DCE_MAILBOX_H +#define DCE_MAILBOX_H + +struct tegra_dce; + +#define DCE_MAILBOX_BOOT_INTERFACE 0U +#define DCE_MAILBOX_ADMIN_INTERFACE 1U +#define DCE_MAILBOX_DISPRM_INTERFACE 2U +#define DCE_MAILBOX_DISPRM_NOTIFY_INTERFACE 3U +#define DCE_MAILBOX_MAX_INTERFACES 4U + +/** + * struct dce_mailbox_interface - Contains dce mailbox interface state info + * + * @lock : dce_mutext for this mailbox interface. + * @state : Stores the current status of the mailbox interface. + * @ack_value : Stores the response received from dce f/w on an interface. + * @s_mb : mailbox used to send commands to DCE CCPLEX for this interface. + * @r_mb : mailbox used to receive commands from DCE for this interface. + * @valid : true if the stored status is valid. + */ +struct dce_mailbox_interface { + u8 s_mb; + u8 r_mb; + int state; + bool valid; + void *notify_data; + struct dce_mutex lock; + unsigned int ack_value; + int (*dce_mailbox_wait)(struct tegra_dce *); + void (*notify)(struct tegra_dce *, void *); +}; + + +u32 dce_mailbox_get_interface_status(struct tegra_dce *d, u8 id); + +void dce_mailbox_store_interface_status(struct tegra_dce *d, + u32 v, u8 id); + +void dce_mailbox_invalidate_status(struct tegra_dce *d, u8 id); + +void dce_mailbox_isr(struct tegra_dce *d); + +void dce_mailbox_set_full_interrupt(struct tegra_dce *d, u8 id); + +int dce_mailbox_send_cmd_sync(struct tegra_dce *d, u32 cmd, u32 interface); +int dce_handle_mailbox_send_cmd_sync(struct tegra_dce *d, u32 cmd, u32 interface); + +int dce_mailbox_init_interface(struct tegra_dce *d, u8 id, u8 s_mb, + u8 r_mb, int (*dce_mailbox_wait)(struct tegra_dce *), + void *notify_data, void (*notify)(struct tegra_dce *, void *)); + +void dce_mailbox_deinit_interface(struct tegra_dce *d, u8 id); + +#endif diff --git a/drivers/platform/tegra/dce/include/dce-pm.h b/drivers/platform/tegra/dce/include/dce-pm.h new file mode 100644 index 00000000..6cb4b3b1 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-pm.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022-2023, NVIDIA CORPORATION & AFFILIATES. 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. + */ + +#ifndef DCE_PM_H +#define DCE_PM_H + +#include + +struct dce_sc7_state { + uint32_t hsp_ie; +}; + +int dce_pm_enter_sc7(struct tegra_dce *d); +int dce_pm_exit_sc7(struct tegra_dce *d); +void dce_resume_work_fn(struct tegra_dce *d); +int dce_pm_handle_sc7_enter_requested_event(struct tegra_dce *d, void *params); +int dce_pm_handle_sc7_enter_received_event(struct tegra_dce *d, void *params); +int dce_pm_handle_sc7_exit_received_event(struct tegra_dce *d, void *params); + +#endif diff --git a/drivers/platform/tegra/dce/include/dce-regs.h b/drivers/platform/tegra/dce/include/dce-regs.h new file mode 100644 index 00000000..a5930394 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-regs.h @@ -0,0 +1,25 @@ +/* + * 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. + */ + +#ifndef DCE_REGS_H +#define DCE_REGS_H + + +#include +#include +#include +#include + +#define DCE_MAX_HSP 2 + +#endif /* DCE_REGS_H */ diff --git a/drivers/platform/tegra/dce/include/dce-thread.h b/drivers/platform/tegra/dce/include/dce-thread.h new file mode 100644 index 00000000..62183ecf --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-thread.h @@ -0,0 +1,42 @@ +/* + * 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. + */ + +#ifndef DCE_THREAD_H +#define DCE_THREAD_H + +#include + +struct task_struct; + +struct dce_thread { + struct task_struct *task; + bool running; + int (*fn)(void *); + void *data; +}; + +struct dce_thread; + +int dce_thread_create(struct dce_thread *thread, + void *data, + int (*threadfn)(void *data), const char *name); + +void dce_thread_stop(struct dce_thread *thread); + +bool dce_thread_should_stop(struct dce_thread *thread); + +bool dce_thread_is_running(struct dce_thread *thread); + +void dce_thread_join(struct dce_thread *thread); + +#endif diff --git a/drivers/platform/tegra/dce/include/dce-util-common.h b/drivers/platform/tegra/dce/include/dce-util-common.h new file mode 100644 index 00000000..788ebb3f --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-util-common.h @@ -0,0 +1,64 @@ +/* + * Copyright (c) 2019-2022, 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. + */ + +#ifndef DCE_UTIL_COMMON_H +#define DCE_UTIL_COMMON_H + +#include +#include +#include +#include + +/** + * This file contains all dce common fucntions and data strutcures which are + * abstarcted out from the operating system. The underlying OS layer will + * implement the pertinent low level details. This design is to make sure that + * dce cpu driver can be leveraged across multiple OSes if the neede arises. + */ + +struct tegra_dce; + +void dce_writel(struct tegra_dce *d, u32 r, u32 v); + +u32 dce_readl(struct tegra_dce *d, u32 r); + +void dce_writel_check(struct tegra_dce *d, u32 r, u32 v); + +bool dce_io_exists(struct tegra_dce *d); + +bool dce_io_valid_reg(struct tegra_dce *d, u32 r); + +struct dce_firmware *dce_request_firmware(struct tegra_dce *d, + const char *fw_name); + +void dce_release_fw(struct tegra_dce *d, struct dce_firmware *fw); + +void *dce_kzalloc(struct tegra_dce *d, size_t size, bool dma_flag); + +void dce_kfree(struct tegra_dce *d, void *addr); + +unsigned long dce_get_nxt_pow_of_2(unsigned long *addr, u8 nbits); + +static inline void dce_bitmap_set(unsigned long *map, + unsigned int start, unsigned int len) +{ + bitmap_set(map, start, (int)len); +} + +static inline void dce_bitmap_clear(unsigned long *map, + unsigned int start, unsigned int len) +{ + bitmap_clear(map, start, (int)len); +} + +#endif diff --git a/drivers/platform/tegra/dce/include/dce-worker.h b/drivers/platform/tegra/dce/include/dce-worker.h new file mode 100644 index 00000000..d8a2abe5 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-worker.h @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2019-2022, NVIDIA CORPORATION & AFFILIATES. 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. + */ + +#ifndef DCE_WORKER_H +#define DCE_WORKER_H + +#include +#include +#include + +struct tegra_dce; + +#define DCE_WAIT_BOOT_COMPLETE 0 +#define DCE_WAIT_BOOT_CMD 1 +#define DCE_WAIT_ADMIN_IPC 2 +#define DCE_WAIT_SC7_ENTER 3 +#define DCE_WAIT_LOG 4 +#define DCE_MAX_WAIT 5 + +struct dce_wait_cond { + atomic_t complete; + struct dce_cond cond_wait; +}; + +int dce_work_cond_sw_resource_init(struct tegra_dce *d); +void dce_work_cond_sw_resource_deinit(struct tegra_dce *d); +void dce_schedule_boot_complete_wait_worker(struct tegra_dce *d); +int dce_wait_interruptible(struct tegra_dce *d, u32 msg_id); +void dce_wakeup_interruptible(struct tegra_dce *d, u32 msg_id); + +#endif diff --git a/drivers/platform/tegra/dce/include/dce-workqueue.h b/drivers/platform/tegra/dce/include/dce-workqueue.h new file mode 100644 index 00000000..0c02a107 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-workqueue.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2022, 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. + */ + +#ifndef DCE_WORKQUEUE_H +#define DCE_WORKQUEUE_H + +#include + +struct dce_work_struct { + struct tegra_dce *d; + struct work_struct work; + void (*dce_work_fn)(struct tegra_dce *d); +}; + +int dce_init_work(struct tegra_dce *d, + struct dce_work_struct *work, + void (*work_fn)(struct tegra_dce *d)); +void dce_schedule_work(struct dce_work_struct *work); + +#endif diff --git a/drivers/platform/tegra/dce/include/dce.h b/drivers/platform/tegra/dce/include/dce.h new file mode 100644 index 00000000..2a0460b7 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce.h @@ -0,0 +1,460 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* + * Copyright (c) 2019-2023 NVIDIA CORPORATION & AFFILIATES. 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. + */ + +#ifndef TEGRA_DCE_H +#define TEGRA_DCE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DCE_MAX_CPU_IRQS 4 + +/** + * DCE Boot Status : DCE Driver Init States + * + * @DCE_EARLY_INIT_* : Driver Init Before Bootstrap + * @DCE_AST_CONFIG_* : Used When DCE-CPU Driver Loads the Firmware + */ +#define DCE_EARLY_INIT_START DCE_BIT(31) +#define DCE_EARLY_INIT_FAILED DCE_BIT(30) +#define DCE_EARLY_INIT_DONE DCE_BIT(29) +#define DCE_AST_CONFIG_START DCE_BIT(28) +#define DCE_AST_CONFIG_FAILED DCE_BIT(27) +#define DCE_AST_CONFIG_DONE DCE_BIT(26) +/** + * DCE Boot Status: FW Boot States + */ +#define DCE_FW_EARLY_BOOT_START DCE_BIT(16) +#define DCE_FW_EARLY_BOOT_FAILED DCE_BIT(15) +#define DCE_FW_EARLY_BOOT_DONE DCE_BIT(14) +#define DCE_FW_BOOTSTRAP_START DCE_BIT(13) +#define DCE_FW_BOOTSTRAP_FAILED DCE_BIT(12) +#define DCE_FW_BOOTSTRAP_DONE DCE_BIT(11) +#define DCE_FW_ADMIN_SEQ_START DCE_BIT(10) +#define DCE_FW_ADMIN_SEQ_FAILED DCE_BIT(9) +#define DCE_FW_ADMIN_SEQ_DONE DCE_BIT(8) +#define DCE_FW_SUSPENDED DCE_BIT(2) +#define DCE_FW_BOOT_DONE DCE_BIT(1) +#define DCE_STATUS_FAILED DCE_BIT(0) +#define DCE_STATUS_UNKNOWN ((u32)(0)) + +struct tegra_dce; + +/** + * struct dce_platform_data - Data Structure to hold platform specific DCE + * cluster data. + */ +struct dce_platform_data { + /** + * @fw_dce_addr : Stores the firmware address that DCE sees before being + * converted by AST. + */ + u32 fw_dce_addr; + /** + * fw_image_size : Stores the max size of DCE fw. + */ + u32 fw_img_size; + /** + * @fw_info_valid : Tells if the above address and size info are valid. + * CPU driver will use this info just for debug purpose. + */ + bool fw_info_valid; + /** + * @no_of_asts : Stores max no. of ASTs in the DCE Cluster + */ + u8 no_of_asts; + /** + * phys_stream_id : Physical stream ID to be programmed for debug + * purpose only. + */ + u32 phys_stream_id; + /** + * stream_id : Stream ID to program the ASTs in debug mode + * only. + */ + u8 stream_id; + /** + * hsp_id - HSP instance id used for dce communication + */ + u32 hsp_id; + /** + * fw_vmindex : VMIndex to program the AST region to read FW in debug + * mode only. + */ + u8 fw_vmindex; + /** + * fw_carveout_id : Carveout ID to program the AST region to read FW in + * debug mode only. + */ + u8 fw_carveout_id; + /** + * @fw_name : Stores dce fw name + */ + const char *fw_name; + /** + * @use_physical_id : Use physical streamid + */ + bool use_physical_id; +}; + +/** + * struct dce_firmware - Contains dce firmware info + * + * @data : u8 pointer to hold the fw data + * @size : size of the fw + * @dma_handle : stores the dma_handle for firmware + */ +struct dce_firmware { + u8 *data; + size_t size; + u64 dma_handle; +}; + +/** + * struct tegra_dce - Primary OS independent tegra dce structure to hold dce + * cluster's and it's element's runtime info. + */ +struct tegra_dce { + /** + * @irq - Array of irqs to be handled by cpu from dce cluster. + */ + u32 irq[DCE_MAX_CPU_IRQS]; + /** + * @fsm_info - Data Structure to manage dce FSM states. + */ + struct dce_fsm_info fsm_info; + /** + * dce_bootstrap_work : dce work to be executed to start FSM flow + */ + struct dce_work_struct dce_bootstrap_work; + /** + * dce_resume_work : dce work to executed dce resume flow + */ + struct dce_work_struct dce_resume_work; + /** + * dce_sc7_state : structure to save/restore state during sc7 enter/exit + */ + struct dce_sc7_state sc7_state; + /** + * dce_wait_info - Data structure to manage wait for different event types + */ + struct dce_wait_cond ipc_waits[DCE_MAX_WAIT]; + /** + * dce_bootstrap_done - Data structure to manage wait for boot done + */ + struct dce_cond dce_bootstrap_done; + /** + * @d_mb - Stores the current status of dce mailbox interfaces. + */ + struct dce_mailbox_interface d_mb[DCE_MAILBOX_MAX_INTERFACES]; + /** + * @d_ipc - Stores the ipc related data between CPU and DCE. + */ + struct dce_ipc d_ipc; + /** + * @d_clients - Stores all dce clients data. + */ + struct tegra_dce_client_ipc *d_clients[DCE_CLIENT_IPC_TYPE_MAX]; + + /** + * @d_async_ipc_info - stores data to handle async events + */ + struct tegra_dce_async_ipc_info d_async_ipc; + /** + * @hsp_id - HSP instance id used for dce communication + */ + u32 hsp_id; + /** + * @boot_status - u32 variable to store dce's boot status. + */ + u32 boot_status; + /** + * @boot_complete - Boolean variable to store dce's boot status. + */ + bool boot_complete; + /** + * @ast_config_complete - Boolean variable to store dce's ast + * configuration status. + */ + bool ast_config_complete; + /** + * @reset_complete - Boolean variable to store dce's reset status. + */ + bool reset_complete; + /** + * @load_complete - Boolean variable to store dce's fw load status. + */ + bool load_complete; + /** + * @log_level - Stores the log level for dce cpu prints. + */ + u32 log_level; + /** + * @fw_data - Stores info regardign firmware to be used runtime. + */ + struct dce_firmware *fw_data; +}; + +/** + * struct dce_device - DCE data structure for storing + * linux device specific info. + */ +struct dce_device { + /** + * @d : OS agnostic dce struct. Stores all runitme info for dce cluster + * elements. + */ + struct tegra_dce d; + /** + * @dev : Pointer to DCE Cluster's Linux device struct. + */ + struct device *dev; + /** + * @pdata : Pointer to dce platform data struct. + */ + struct dce_platform_data *pdata; + /** + * @max_cpu_irqs : stores maximum no. os irqs from DCE cluster to CPU + * for this platform. + */ + u8 max_cpu_irqs; + /** + * @regs : Stores the cpu-mapped base address of DCE Cluster. Will be + * used for MMIO transactions to DCE elements. + */ + void __iomem *regs; +#ifdef CONFIG_DEBUG_FS + /** + * @debugfs : Debugfs node for DCE Linux device. + */ + struct dentry *debugfs; + /** + * @ext_test_status : Return code for external client tests run via + * debugfs + */ + s32 ext_test_status; +#endif +}; + +/** + * dce_device_from_dce - inline function to get linux os data from the + * os agnostic struct tegra_dc + * @d : Pointer to the os agnostic tegra_dce data structure. + * + * Return : pointer to struct dce_device + */ +static inline struct dce_device *dce_device_from_dce(struct tegra_dce *d) +{ + return container_of(d, struct dce_device, d); +} + +/** + * dev_from_dce - inline function to get linux device from the + * os agnostic struct tegra_dc + * @d : Pointer to the os agnostic tegra_dce data structure. + * + * Return : pointer to struct device + */ +static inline struct device *dev_from_dce(struct tegra_dce *d) +{ + return dce_device_from_dce(d)->dev; +} + +/** + * pdata_from_dce - inline function to get dce platform data from + * the os agnostic struct tegra_dc. + * + * @d : Pointer to the os agnostic tegra_dce data structure. + * + * Return : pointer to struct device + */ +static inline struct dce_platform_data *pdata_from_dce(struct tegra_dce *d) +{ + return ((struct dce_device *)dev_get_drvdata(dev_from_dce(d)))->pdata; +} + +/** + * dce_set_boot_complete - updates the current dce boot complete status. + * + * @d : Pointer to tegra_dce struct. + * @val : true or false. + * + * Return : void + */ +static inline void dce_set_boot_complete(struct tegra_dce *d, bool val) +{ + d->boot_complete = val; + if (!val) + d->boot_status &= (~DCE_FW_BOOT_DONE); +} + +/** + * dce_is_bootcmds_done - Checks if dce bootstrap bootcmds done. + * + * Chekc if all the mailbox boot commands are completed + * + * @d - Pointer to tegra_dce struct. + * + * Return : True if bootcmds are completed + */ +static inline bool dce_is_bootcmds_done(struct tegra_dce *d) +{ + return (d->boot_status & DCE_FW_BOOTSTRAP_DONE) ? true : false; +} + +/** + * dce_is_bootstrap_done - check if dce bootstrap is done. + * + * @d : Pointer to tegra_dce struct. + * + * Return : true if bootstrap done else false + */ +static inline bool dce_is_bootstrap_done(struct tegra_dce *d) +{ + return (d->boot_status & DCE_FW_BOOT_DONE) ? true : false; +} + +/** + * dce_set_ast_config_status - updates the current status of ast configuration. + * + * @d : Pointer to tegra_dce struct. + * @val : true or false. + * + * Return : void + */ +static inline void dce_set_ast_config_status(struct tegra_dce *d, bool val) +{ + d->ast_config_complete = val; +} + +/** + * dce_set_dce_reset_status - updates the current status of dce reset. + * + * @d : Pointer to tegra_dce struct. + * @val : true or false. + * + * Return : void + */ +static inline void dce_set_dce_reset_status(struct tegra_dce *d, bool val) +{ + d->reset_complete = val; +} + +/** + * dce_set_load_fw_status - updates the current status of fw loading. + * + * @d : Pointer to tegra_dce struct. + * @val : true or false stating fw load is complete or incomplete respectiveely. + * + * Return : void + */ +static inline void dce_set_load_fw_status(struct tegra_dce *d, bool val) +{ + d->load_complete = val; +} + +/** + * Common Utility Functions. Description can be found with + * function definitions. + */ +u8 dce_get_phys_stream_id(struct tegra_dce *d); +u8 dce_get_dce_stream_id(struct tegra_dce *d); +u8 dce_get_fw_vm_index(struct tegra_dce *d); +u8 dce_get_fw_carveout_id(struct tegra_dce *d); +bool dce_is_physical_id_valid(struct tegra_dce *d); + +u32 dce_get_fw_dce_addr(struct tegra_dce *d); +u64 dce_get_fw_phy_addr(struct tegra_dce *d, struct dce_firmware *fw); +const char *dce_get_fw_name(struct tegra_dce *d); + +int dce_driver_init(struct tegra_dce *d); +void dce_driver_deinit(struct tegra_dce *d); + +int dce_start_boot_flow(struct tegra_dce *d); +void dce_bootstrap_work_fn(struct tegra_dce *d); +int dce_start_bootstrap_flow(struct tegra_dce *d); +int dce_boot_interface_init(struct tegra_dce *d); +void dce_boot_interface_deinit(struct tegra_dce *d); +int dce_handle_boot_cmd_requested_event(struct tegra_dce *d, void *params); +int dce_handle_boot_cmd_received_event(struct tegra_dce *d, void *params); +int dce_handle_boot_complete_requested_event(struct tegra_dce *d, void *params); +int dce_handle_boot_complete_received_event(struct tegra_dce *d, void *params); + +int dce_admin_init(struct tegra_dce *d); +void dce_admin_deinit(struct tegra_dce *d); +int dce_start_admin_seq(struct tegra_dce *d); +struct dce_ipc_message + *dce_admin_allocate_message(struct tegra_dce *d); +void dce_admin_free_message(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_send_msg(struct tegra_dce *d, + struct dce_ipc_message *msg); +void dce_admin_ivc_channel_reset(struct tegra_dce *d); +int dce_admin_get_ipc_channel_info(struct tegra_dce *d, + struct dce_ipc_queue_info *q_info); +int dce_admin_send_cmd_echo(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_send_cmd_ext_test(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_send_prepare_sc7(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_send_enter_sc7(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_handle_ipc_requested_event(struct tegra_dce *d, void *params); +int dce_admin_handle_ipc_received_event(struct tegra_dce *d, void *params); +int dce_admin_send_cmd_get_perf_stat(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_send_cmd_set_perf_stat(struct tegra_dce *d, + struct dce_ipc_message *msg, + bool start_perf); +int dce_admin_send_cmd_stop_perf_stat(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_send_cmd_get_perf_events(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_send_cmd_clear_perf_events(struct tegra_dce *d, + struct dce_ipc_message *msg); +int dce_admin_ipc_wait(struct tegra_dce *d, u32 w_type); +void dce_admin_ipc_handle_signal(struct tegra_dce *d, u32 ch_type); + +bool dce_fw_boot_complete(struct tegra_dce *d); +void dce_request_fw_boot_complete(struct tegra_dce *d); + +/** + * Functions to be used in debug mode only. + * + * TODO : Have sanity checks for these not to be + * used in non-debug mode. + */ +void dce_config_ast(struct tegra_dce *d); +int dce_reset_dce(struct tegra_dce *d); + +#ifdef CONFIG_DEBUG_FS +void dce_init_debug(struct tegra_dce *d); +void dce_remove_debug(struct tegra_dce *d); +#endif + +#endif diff --git a/drivers/platform/tegra/dce/include/hw/hw_ast_dce.h b/drivers/platform/tegra/dce/include/hw/hw_ast_dce.h new file mode 100644 index 00000000..2497a3fa --- /dev/null +++ b/drivers/platform/tegra/dce/include/hw/hw_ast_dce.h @@ -0,0 +1,517 @@ +/* + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/* + * Function naming determines intended use: + * + * _r(void) : Returns the offset for register . + * + * _o(void) : Returns the offset for element . + * + * _w(void) : Returns the word offset for word (4 byte) element . + * + * __s(void) : Returns size of field of register in bits. + * + * __f(u32 v) : Returns a value based on 'v' which has been shifted + * and masked to place it at field of register . This value + * can be |'d with others to produce a full register value for + * register . + * + * __m(void) : Returns a mask for field of register . This + * value can be ~'d and then &'d to clear the value of field for + * register . + * + * ___f(void) : Returns the constant value after being shifted + * to place it at field of register . This value can be |'d + * with others to produce a full register value for . + * + * __v(u32 r) : Returns the value of field from a full register + * value 'r' after being shifted to place its LSB at bit 0. + * This value is suitable for direct comparison with other unshifted + * values appropriate for use in field of register . + * + * ___v(void) : Returns the constant value for defined for + * field of register . This value is suitable for direct + * comparison with unshifted values appropriate for use in field + * of register . + */ +#ifndef HW_AST_DCE_H +#define HW_AST_DCE_H + +static inline u32 ast_ast0_control_r(void) +{ + return 0x40000U; +} +static inline u32 ast_ast0_control_write_mask_v(void) +{ + return 0xffdf83e7U; +} +static inline u32 ast_ast0_control_apbovron_shift_v(void) +{ + return 0x0000001fU; +} +static inline u32 ast_ast0_control_nicovron_shift_v(void) +{ + return 0x0000001eU; +} +static inline u32 ast_ast0_control_physstreamid_shift_v(void) +{ + return 0x00000016U; +} +static inline u32 ast_ast0_control_carveoutlock_true_f(void) +{ + return 0x100000U; +} +static inline u32 ast_ast0_control_carveoutlock_false_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast0_control_carveoutlock_defphysical_shift_v(void) +{ + return 0x00000013U; +} +static inline u32 ast_ast0_control_carveoutlock_defvmindex_shift_v(void) +{ + return 0x0000000fU; +} +static inline u32 ast_ast0_control_carveoutlock_defcarveoutid_shift_v(void) +{ + return 0x00000005U; +} +static inline u32 ast_ast0_control_defsnoop_enable_f(void) +{ + return 0x4U; +} +static inline u32 ast_ast0_control_defsnoop_disable_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast0_control_matcherrctl_no_decerr_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast0_control_matcherrctl_decerr_f(void) +{ + return 0x2U; +} +static inline u32 ast_ast0_control_lock_true_f(void) +{ + return 0x1U; +} +static inline u32 ast_ast0_control_lock_false_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast0_error_status_r(void) +{ + return 0x40004U; +} +static inline u32 ast_ast0_error_addr_lo_r(void) +{ + return 0x40008U; +} +static inline u32 ast_ast0_error_addr_hi_r(void) +{ + return 0x4000cU; +} +static inline u32 ast_ast0_streamid_ctl_0_r(void) +{ + return 0x40020U; +} +static inline u32 ast_ast0_streamid_ctl_0_write_mask_v(void) +{ + return 0x0000ff01U; +} +static inline u32 ast_ast0_streamid_ctl_0_streamid_shift_v(void) +{ + return 0x00000008U; +} +static inline u32 ast_ast0_streamid_ctl_0_enable_enable_f(void) +{ + return 0x1U; +} +static inline u32 ast_ast0_streamid_ctl_0_enable_disable_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast0_streamid_ctl_1_r(void) +{ + return 0x40024U; +} +static inline u32 ast_ast0_streamid_ctl_1_write_mask_v(void) +{ + return 0x0000ff01U; +} +static inline u32 ast_ast0_streamid_ctl_1_streamid_shift_v(void) +{ + return 0x00000008U; +} +static inline u32 ast_ast0_streamid_ctl_1_enable_enable_f(void) +{ + return 0x1U; +} +static inline u32 ast_ast0_streamid_ctl_1_enable_disable_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast0_region_0_slave_base_lo_r(void) +{ + return 0x40100U; +} +static inline u32 ast_ast0_region_0_slave_base_lo_slvbase_shift_v(void) +{ + return 0x0000000cU; +} +static inline u32 ast_ast0_region_0_slave_base_lo_write_mask_v(void) +{ + return 0xfffff001U; +} +static inline u32 ast_ast0_region_0_slave_base_lo_enable_true_f(void) +{ + return 0x1U; +} +static inline u32 ast_ast0_region_0_slave_base_lo_enable_false_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast0_region_0_slave_base_hi_r(void) +{ + return 0x40104U; +} +static inline u32 ast_ast0_region_0_mask_lo_r(void) +{ + return 0x40108U; +} +static inline u32 ast_ast0_region_0_mask_lo_write_mask_v(void) +{ + return 0xfffff000U; +} +static inline u32 ast_ast0_region_0_mask_hi_r(void) +{ + return 0x4010cU; +} +static inline u32 ast_ast0_region_0_mask_hi_write_mask_v(void) +{ + return 0xffffffffU; +} +static inline u32 ast_ast0_region_0_master_base_lo_r(void) +{ + return 0x40110U; +} +static inline u32 ast_ast0_region_0_master_base_lo_write_mask_v(void) +{ + return 0xfffff000U; +} +static inline u32 ast_ast0_region_0_master_base_hi_r(void) +{ + return 0x40114U; +} +static inline u32 ast_ast0_region_0_control_r(void) +{ + return 0x40118U; +} +static inline u32 ast_ast0_region_0_control_write_mask_v(void) +{ + return 0x000f83e5U; +} +static inline u32 ast_ast0_region_0_control_physical_shift_v(void) +{ + return 0x00000013U; +} +static inline u32 ast_ast0_region_0_control_vmindex_shift_v(void) +{ + return 0x0000000fU; +} +static inline u32 ast_ast0_region_0_control_carveoutid_shift_v(void) +{ + return 0x00000005U; +} +static inline u32 ast_ast0_region_0_control_snoop_enable_f(void) +{ + return 0x4U; +} +static inline u32 ast_ast0_region_0_control_snoop_disable_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast0_region_0_control_lock_true_f(void) +{ + return 0x1U; +} +static inline u32 ast_ast0_region_0_control_lock_false_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast0_region_1_slave_base_lo_r(void) +{ + return 0x40120U; +} +static inline u32 ast_ast0_region_1_slave_base_hi_r(void) +{ + return 0x40124U; +} +static inline u32 ast_ast0_region_1_mask_lo_r(void) +{ + return 0x40128U; +} +static inline u32 ast_ast0_region_1_mask_hi_r(void) +{ + return 0x4012cU; +} +static inline u32 ast_ast0_region_1_master_base_lo_r(void) +{ + return 0x40130U; +} +static inline u32 ast_ast0_region_1_master_base_hi_r(void) +{ + return 0x40134U; +} +static inline u32 ast_ast0_region_1_control_r(void) +{ + return 0x40138U; +} +static inline u32 ast_ast1_control_r(void) +{ + return 0x50000U; +} +static inline u32 ast_ast1_control_write_mask_v(void) +{ + return 0xffdf83e7U; +} +static inline u32 ast_ast1_control_apbovron_shift_v(void) +{ + return 0x0000001fU; +} +static inline u32 ast_ast1_control_nicovron_shift_v(void) +{ + return 0x0000001eU; +} +static inline u32 ast_ast1_control_physstreamid_shift_v(void) +{ + return 0x00000016U; +} +static inline u32 ast_ast1_control_carveoutlock_true_f(void) +{ + return 0x100000U; +} +static inline u32 ast_ast1_control_carveoutlock_false_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast1_control_carveoutlock_defphysical_shift_v(void) +{ + return 0x00000013U; +} +static inline u32 ast_ast1_control_carveoutlock_defvmindex_shift_v(void) +{ + return 0x0000000fU; +} +static inline u32 ast_ast1_control_carveoutlock_defcarveoutid_shift_v(void) +{ + return 0x00000005U; +} +static inline u32 ast_ast1_control_defsnoop_enable_f(void) +{ + return 0x4U; +} +static inline u32 ast_ast1_control_defsnoop_disable_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast1_control_matcherrctl_no_decerr_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast1_control_matcherrctl_decerr_f(void) +{ + return 0x2U; +} +static inline u32 ast_ast1_control_lock_true_f(void) +{ + return 0x1U; +} +static inline u32 ast_ast1_control_lock_false_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast1_error_status_r(void) +{ + return 0x50004U; +} +static inline u32 ast_ast1_error_addr_lo_r(void) +{ + return 0x50008U; +} +static inline u32 ast_ast1_error_addr_hi_r(void) +{ + return 0x5000cU; +} +static inline u32 ast_ast1_streamid_ctl_0_r(void) +{ + return 0x50020U; +} +static inline u32 ast_ast1_streamid_ctl_0_write_mask_v(void) +{ + return 0x0000ff01U; +} +static inline u32 ast_ast1_streamid_ctl_0_streamid_shift_v(void) +{ + return 0x00000008U; +} +static inline u32 ast_ast1_streamid_ctl_0_enable_enable_f(void) +{ + return 0x1U; +} +static inline u32 ast_ast1_streamid_ctl_0_enable_disable_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast1_streamid_ctl_1_r(void) +{ + return 0x50024U; +} +static inline u32 ast_ast1_streamid_ctl_1_write_mask_v(void) +{ + return 0x0000ff01U; +} +static inline u32 ast_ast1_streamid_ctl_1_streamid_shift_v(void) +{ + return 0x00000008U; +} +static inline u32 ast_ast1_streamid_ctl_1_enable_enable_f(void) +{ + return 0x1U; +} +static inline u32 ast_ast1_streamid_ctl_1_enable_disable_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast1_region_0_slave_base_lo_r(void) +{ + return 0x50100U; +} +static inline u32 ast_ast1_region_0_slave_base_lo_slvbase_shift_v(void) +{ + return 0x0000000cU; +} +static inline u32 ast_ast1_region_0_slave_base_lo_write_mask_v(void) +{ + return 0xfffff001U; +} +static inline u32 ast_ast1_region_0_slave_base_lo_enable_true_f(void) +{ + return 0x1U; +} +static inline u32 ast_ast1_region_0_slave_base_lo_enable_false_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast1_region_0_slave_base_hi_r(void) +{ + return 0x50104U; +} +static inline u32 ast_ast1_region_0_mask_lo_r(void) +{ + return 0x50108U; +} +static inline u32 ast_ast1_region_0_mask_lo_write_mask_v(void) +{ + return 0xfffff000U; +} +static inline u32 ast_ast1_region_0_mask_hi_r(void) +{ + return 0x5010cU; +} +static inline u32 ast_ast1_region_0_mask_hi_write_mask_v(void) +{ + return 0xffffffffU; +} +static inline u32 ast_ast1_region_0_master_base_lo_r(void) +{ + return 0x50110U; +} +static inline u32 ast_ast1_region_0_master_base_lo_write_mask_v(void) +{ + return 0xfffff000U; +} +static inline u32 ast_ast1_region_0_master_base_hi_r(void) +{ + return 0x50114U; +} +static inline u32 ast_ast1_region_0_control_r(void) +{ + return 0x50118U; +} +static inline u32 ast_ast1_region_0_control_write_mask_v(void) +{ + return 0x000f83e5U; +} +static inline u32 ast_ast1_region_0_control_physical_shift_v(void) +{ + return 0x00000013U; +} +static inline u32 ast_ast1_region_0_control_vmindex_shift_v(void) +{ + return 0x0000000fU; +} +static inline u32 ast_ast1_region_0_control_carveoutid_shift_v(void) +{ + return 0x00000005U; +} +static inline u32 ast_ast1_region_0_control_snoop_enable_f(void) +{ + return 0x4U; +} +static inline u32 ast_ast1_region_0_control_snoop_disable_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast1_region_0_control_lock_true_f(void) +{ + return 0x1U; +} +static inline u32 ast_ast1_region_0_control_lock_false_f(void) +{ + return 0x0U; +} +static inline u32 ast_ast1_region_1_slave_base_lo_r(void) +{ + return 0x50120U; +} +static inline u32 ast_ast1_region_1_slave_base_hi_r(void) +{ + return 0x50124U; +} +static inline u32 ast_ast1_region_1_mask_lo_r(void) +{ + return 0x50128U; +} +static inline u32 ast_ast1_region_1_mask_hi_r(void) +{ + return 0x5012cU; +} +static inline u32 ast_ast1_region_1_master_base_lo_r(void) +{ + return 0x50130U; +} +static inline u32 ast_ast1_region_1_master_base_hi_r(void) +{ + return 0x50134U; +} +static inline u32 ast_ast1_region_1_control_r(void) +{ + return 0x50138U; +} +#endif diff --git a/drivers/platform/tegra/dce/include/hw/hw_evp_dce.h b/drivers/platform/tegra/dce/include/hw/hw_evp_dce.h new file mode 100644 index 00000000..8970cbf8 --- /dev/null +++ b/drivers/platform/tegra/dce/include/hw/hw_evp_dce.h @@ -0,0 +1,85 @@ +/* + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/* + * Function naming determines intended use: + * + * _r(void) : Returns the offset for register . + * + * _o(void) : Returns the offset for element . + * + * _w(void) : Returns the word offset for word (4 byte) element . + * + * __s(void) : Returns size of field of register in bits. + * + * __f(u32 v) : Returns a value based on 'v' which has been shifted + * and masked to place it at field of register . This value + * can be |'d with others to produce a full register value for + * register . + * + * __m(void) : Returns a mask for field of register . This + * value can be ~'d and then &'d to clear the value of field for + * register . + * + * ___f(void) : Returns the constant value after being shifted + * to place it at field of register . This value can be |'d + * with others to produce a full register value for . + * + * __v(u32 r) : Returns the value of field from a full register + * value 'r' after being shifted to place its LSB at bit 0. + * This value is suitable for direct comparison with other unshifted + * values appropriate for use in field of register . + * + * ___v(void) : Returns the constant value for defined for + * field of register . This value is suitable for direct + * comparison with unshifted values appropriate for use in field + * of register . + */ +#ifndef HW_EVP_DCE_H +#define HW_EVP_DCE_H + +static inline u32 evp_reset_addr_r(void) +{ + return 0x20U; +} +static inline u32 evp_undef_addr_r(void) +{ + return 0x4U; +} +static inline u32 evp_swi_addr_r(void) +{ + return 0x28U; +} +static inline u32 evp_prefetch_abort_addr_r(void) +{ + return 0x2cU; +} +static inline u32 evp_data_abort_addr_r(void) +{ + return 0x30U; +} +static inline u32 evp_rsvd_addr_r(void) +{ + return 0x34U; +} +static inline u32 evp_irq_addr_r(void) +{ + return 0x38U; +} +static inline u32 evp_fiq_addr_r(void) +{ + return 0x3cU; +} +#endif diff --git a/drivers/platform/tegra/dce/include/hw/hw_hsp_dce.h b/drivers/platform/tegra/dce/include/hw/hw_hsp_dce.h new file mode 100644 index 00000000..6433a00f --- /dev/null +++ b/drivers/platform/tegra/dce/include/hw/hw_hsp_dce.h @@ -0,0 +1,233 @@ +/* + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/* + * Function naming determines intended use: + * + * _r(void) : Returns the offset for register . + * + * _o(void) : Returns the offset for element . + * + * _w(void) : Returns the word offset for word (4 byte) element . + * + * __s(void) : Returns size of field of register in bits. + * + * __f(u32 v) : Returns a value based on 'v' which has been shifted + * and masked to place it at field of register . This value + * can be |'d with others to produce a full register value for + * register . + * + * __m(void) : Returns a mask for field of register . This + * value can be ~'d and then &'d to clear the value of field for + * register . + * + * ___f(void) : Returns the constant value after being shifted + * to place it at field of register . This value can be |'d + * with others to produce a full register value for . + * + * __v(u32 r) : Returns the value of field from a full register + * value 'r' after being shifted to place its LSB at bit 0. + * This value is suitable for direct comparison with other unshifted + * values appropriate for use in field of register . + * + * ___v(void) : Returns the constant value for defined for + * field of register . This value is suitable for direct + * comparison with unshifted values appropriate for use in field + * of register . + */ +#ifndef HW_HSP_DCE_H +#define HW_HSP_DCE_H + +static inline u32 hsp_int_ie0_r(void) +{ + return 0x150100U; +} +static inline u32 hsp_int_ie1_r(void) +{ + return 0x150104U; +} +static inline u32 hsp_int_ie2_r(void) +{ + return 0x150108U; +} +static inline u32 hsp_int_ie3_r(void) +{ + return 0x15010cU; +} +static inline u32 hsp_int_ie4_r(void) +{ + return 0x150110U; +} +static inline u32 hsp_int_ie5_r(void) +{ + return 0x150114U; +} +static inline u32 hsp_int_ie6_r(void) +{ + return 0x150118U; +} +static inline u32 hsp_int_ie7_r(void) +{ + return 0x15011cU; +} +static inline u32 hsp_int_ir_r(void) +{ + return 0x150304U; +} +static inline u32 hsp_sm0_r(void) +{ + return 0x160000U; +} +static inline u32 hsp_sm0_full_int_ie_r(void) +{ + return 0x160004U; +} +static inline u32 hsp_sm0_empty_int_ie_r(void) +{ + return 0x160008U; +} +static inline u32 hsp_sm1_r(void) +{ + return 0x168000U; +} +static inline u32 hsp_sm1_full_int_ie_r(void) +{ + return 0x168004U; +} +static inline u32 hsp_sm1_empty_int_ie_r(void) +{ + return 0x168008U; +} +static inline u32 hsp_sm2_r(void) +{ + return 0x170000U; +} +static inline u32 hsp_sm2_full_int_ie_r(void) +{ + return 0x170004U; +} +static inline u32 hsp_sm2_empty_int_ie_r(void) +{ + return 0x170008U; +} +static inline u32 hsp_sm3_r(void) +{ + return 0x178000U; +} +static inline u32 hsp_sm3_full_int_ie_r(void) +{ + return 0x178004U; +} +static inline u32 hsp_sm3_empty_int_ie_r(void) +{ + return 0x178008U; +} +static inline u32 hsp_sm4_r(void) +{ + return 0x180000U; +} +static inline u32 hsp_sm4_full_int_ie_r(void) +{ + return 0x180004U; +} +static inline u32 hsp_sm4_empty_int_ie_r(void) +{ + return 0x180008U; +} +static inline u32 hsp_sm5_r(void) +{ + return 0x188000U; +} +static inline u32 hsp_sm5_full_int_ie_r(void) +{ + return 0x188004U; +} +static inline u32 hsp_sm5_empty_int_ie_r(void) +{ + return 0x188008U; +} +static inline u32 hsp_sm6_r(void) +{ + return 0x190000U; +} +static inline u32 hsp_sm6_full_int_ie_r(void) +{ + return 0x190004U; +} +static inline u32 hsp_sm6_empty_int_ie_r(void) +{ + return 0x190008U; +} +static inline u32 hsp_sm7_r(void) +{ + return 0x198000U; +} +static inline u32 hsp_sm7_full_int_ie_r(void) +{ + return 0x198004U; +} +static inline u32 hsp_sm7_empty_int_ie_r(void) +{ + return 0x198008U; +} +static inline u32 hsp_ss0_state_r(void) +{ + return 0x1a0000U; +} +static inline u32 hsp_ss0_set_r(void) +{ + return 0x1a0004U; +} +static inline u32 hsp_ss0_clr_r(void) +{ + return 0x1a0008U; +} +static inline u32 hsp_ss1_state_r(void) +{ + return 0x1b0000U; +} +static inline u32 hsp_ss1_set_r(void) +{ + return 0x1b0004U; +} +static inline u32 hsp_ss1_clr_r(void) +{ + return 0x1b0008U; +} +static inline u32 hsp_ss2_state_r(void) +{ + return 0x1c0000U; +} +static inline u32 hsp_ss2_set_r(void) +{ + return 0x1c0004U; +} +static inline u32 hsp_ss2_clr_r(void) +{ + return 0x1c0008U; +} +static inline u32 hsp_ss3_state_r(void) +{ + return 0x1d0000U; +} +static inline u32 hsp_ss3_set_r(void) +{ + return 0x1d0004U; +} +static inline u32 hsp_ss3_clr_r(void) +{ + return 0x1d0008U; +} +#endif diff --git a/drivers/platform/tegra/dce/include/hw/hw_pm_dce.h b/drivers/platform/tegra/dce/include/hw/hw_pm_dce.h new file mode 100644 index 00000000..a06ffa48 --- /dev/null +++ b/drivers/platform/tegra/dce/include/hw/hw_pm_dce.h @@ -0,0 +1,65 @@ +/* + * 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. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ +/* + * Function naming determines intended use: + * + * _r(void) : Returns the offset for register . + * + * _o(void) : Returns the offset for element . + * + * _w(void) : Returns the word offset for word (4 byte) element . + * + * __s(void) : Returns size of field of register in bits. + * + * __f(u32 v) : Returns a value based on 'v' which has been shifted + * and masked to place it at field of register . This value + * can be |'d with others to produce a full register value for + * register . + * + * __m(void) : Returns a mask for field of register . This + * value can be ~'d and then &'d to clear the value of field for + * register . + * + * ___f(void) : Returns the constant value after being shifted + * to place it at field of register . This value can be |'d + * with others to produce a full register value for . + * + * __v(u32 r) : Returns the value of field from a full register + * value 'r' after being shifted to place its LSB at bit 0. + * This value is suitable for direct comparison with other unshifted + * values appropriate for use in field of register . + * + * ___v(void) : Returns the constant value for defined for + * field of register . This value is suitable for direct + * comparison with unshifted values appropriate for use in field + * of register . + */ +#ifndef HW_PM_DCE_H +#define HW_PM_DCE_H + +static inline u32 pm_r5_ctrl_r(void) +{ + return 0x1f0040U; +} +static inline u32 pm_r5_ctrl_fwloaddone_halted_f(void) +{ + return 0x0U; +} +static inline u32 pm_r5_ctrl_fwloaddone_done_f(void) +{ + return 0x2U; +} +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-admin-cmds.h b/drivers/platform/tegra/dce/include/interface/dce-admin-cmds.h new file mode 100644 index 00000000..3aed6230 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-admin-cmds.h @@ -0,0 +1,183 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2018-2023 NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_ADMIN_CMDS_H +#define DCE_ADMIN_CMDS_H + +#include +#include + +/* + * Version of the ADMIN command interface. + * + * This MUST be updated any time any changes are made to the ADMIN + * commands. + * + * To keep things simple, this value should be incremented by 1 each + * time changes are made. + */ +#define DCE_ADMIN_VERSION 3 + +#define DCE_ADMIN_CMD_SIZE sizeof(struct dce_admin_ipc_cmd) +#define DCE_ADMIN_RESP_SIZE sizeof(struct dce_admin_ipc_resp) + +#define DCE_ADMIN_CMD_CHAN_FSIZE max(DCE_ADMIN_CMD_SIZE, \ + DCE_ADMIN_RESP_SIZE) + +#define DCE_ADMIN_CMD_MAX_NFRAMES 4 + +#define DCE_ADMIN_CMD_VERSION 0x00U // returns version of interface +#define DCE_ADMIN_CMD_HOST_VERSION 0x01U // host supplied version +#define DCE_ADMIN_CMD_GET_FW_VERSION 0x02U // return FW version info +#define DCE_ADMIN_CMD_ECHO 0x03U // echo data back to CCPLEX +#define DCE_ADMIN_CMD_MEM_MAP 0x04U // map a region of memory +#define DCE_ADMIN_CMD_MEM_INFO 0x05U // return info about a region +#define DCE_ADMIN_CMD_IPC_INFO 0x06U // return IPC chan info +#define DCE_ADMIN_CMD_IPC_CREATE 0x07U // create an IPC channel +#define DCE_ADMIN_CMD_PREPARE_SC7 0x08U // prepare to enter SC7 +#define DCE_ADMIN_CMD_ENTER_SC7 0x09U // enter SC7 +#define DCE_ADMIN_CMD_SET_LOGGING 0x0AU // set logging level +#define DCE_ADMIN_CMD_GET_LOG_INFO 0x0BU // get current log info +#define DCE_ADMIN_CMD_LOCK_CHANGES 0x0CU // lock creating new channels + // and changing memory areas +#define DCE_ADMIN_CMD_CODE_COVERAGE_START 0x0DU // start collecting code + // coverage data +#define DCE_ADMIN_CMD_CODE_COVERAGE_STOP 0x0EU // stop collecting code + // coverage data +#define DCE_ADMIN_CMD_PERF_START 0x0FU // start collecting perf data +#define DCE_ADMIN_CMD_PERF_STOP 0x10U // stop collecting perf data +#define DCE_ADMIN_CMD_INT_TEST_START 0x11U // start internal tests +#define DCE_ADMIN_CMD_INT_TEST_STOP 0x12U // stop internal tests and return status +#define DCE_ADMIN_CMD_EXT_TEST 0x13U // run external test (blocking call) +#define DCE_ADMIN_CMD_DEBUG 0x14U // debug command +#define DCE_ADMIN_CMD_RM_BOOTSTRAP 0x15U // tell RM to "bootstrap" +#define DCE_ADMIN_CMD_PERF_RESULTS 0x16U // copy out the perf results +#define DCE_ADMIN_CMD_PERF_GET_EVENTS 0x17U // get perf events +#define DCE_ADMIN_CMD_PERF_CLEAR_EVENTS 0x18U // clear perf events + +#define DCE_ADMIN_CMD_NEXT 0x19U // must be last command ID + 1 + +struct dce_admin_version_info { + uint32_t version; +}; + +struct dce_admin_fw_version_info { + uint32_t bootstrap_interface; + uint32_t admin_interface; + uint32_t driver_headers; + uint32_t core_interface; + uint8_t fw_version[4]; + uint32_t gcid_revision; + uint8_t safertos_major; + uint8_t safertos_minor; +}; + +struct dce_admin_echo { + uint32_t data; +}; + +enum dce_admin_ext_test { + DCE_ADMIN_EXT_TEST_ALU = 0U, + DCE_ADMIN_EXT_TEST_DMA = 1U, +}; +struct dce_admin_ext_test_args { + enum dce_admin_ext_test test; +}; + +struct dce_admin_log_args { + uint32_t log_enable; + uint32_t log_level; +}; + +struct dce_admin_mem_args { + uint32_t region; + dce_iova iova; + uint32_t length; + uint32_t sid; +}; + +struct dce_admin_ipc_info_args { + uint32_t type; +}; + +struct dce_admin_ipc_signal { + uint32_t signal_type; + union { + uint32_t mailbox; + struct { + uint32_t doorbell_num; + uint32_t doorbell_bit_num; + } doorbell; + } signal; + uint32_t semaphore; + uint32_t semaphore_bit_num; +}; + +struct dce_admin_ipc_info { + uint32_t type; + uint32_t flags; + uint32_t mem_region; + dce_iova rd_iova; + dce_iova wr_iova; + uint32_t fsize; + uint32_t n_frames; + struct dce_admin_ipc_signal signal_from_dce; + struct dce_admin_ipc_signal signal_to_dce; +}; + +struct dce_admin_ipc_create_args { + uint32_t type; + dce_iova rd_iova; + dce_iova wr_iova; + uint32_t fsize; + uint32_t n_frames; +}; + +struct dce_admin_ipc_cmd { + uint32_t cmd; + union { + struct dce_admin_version_info version; + struct dce_admin_echo echo; + struct dce_admin_ext_test_args ext_test; + struct dce_admin_log_args log; + struct dce_admin_ipc_info_args ipc_info; + struct dce_admin_mem_args mem_map; + struct dce_admin_ipc_create_args ipc_create; + struct dce_admin_perf_args perf; + } args; +}; + +struct dce_admin_ipc_resp { + uint32_t error; + union { + struct dce_admin_version_info version; + struct dce_admin_echo echo; + struct dce_admin_log_args log; + struct dce_admin_ipc_info ipc; + struct dce_admin_mem_args mem_info; + struct dce_admin_fw_version_info fw_version; + struct dce_admin_perf_stats_info perf; + } args; +}; + +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-admin-perf-stats.h b/drivers/platform/tegra/dce/include/interface/dce-admin-perf-stats.h new file mode 100644 index 00000000..0c2a9c2c --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-admin-perf-stats.h @@ -0,0 +1,138 @@ +/* SPDX-License-Identifier: MIT */ +/* + * Copyright (c) 2023 NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_ADMIN_PERF_STATS_H +#define DCE_ADMIN_PERF_STATS_H + +/* + * Values for stats field + */ +#define DCE_ADMIN_PERF_STATS_SCHED 0x0001 +#define DCE_ADMIN_PERF_STATS_CACHE 0x0002 +#define DCE_ADMIN_PERF_STATS_IEXEC 0x0003 +#define DCE_ADMIN_PERF_STATS_MMIO 0x0004 +#define DCE_ADMIN_PERF_STATS_ALL 0xFFFF + +/* + * Values for clear field + */ +#define DCE_ADMIN_PERF_CLEAR_RETAIN 0U +#define DCE_ADMIN_PERF_CLEAR_CLEAR 1U + +#define DCE_ADMIN_EVENT_CLEAR_RETAIN 0U +#define DCE_ADMIN_EVENT_CLEAR_CLEAR 1U + +struct dce_admin_perf_cmd_args { + uint32_t enable; // Enable stats capture through Bit-Mask + uint32_t clear; // Clear all event stats +}; + +struct dce_admin_perf_args { + struct dce_admin_perf_cmd_args perf_cmd; + struct dce_admin_perf_cmd_args event_cmd; +}; + +/* + * Task related defines for perf stats + */ +#define DCE_ADMIN_PERF_ADMIN_TASK 0U +#define DCE_ADMIN_PERF_RM_TASK 1U +#define DCE_ADMIN_PERF_PRINT_TASK 2U +#define DCE_ADMIN_PERF_TCU_TASK 3U +#define DCE_ADMIN_PERF_IDLE_TASK 4U +// Change the active task number on adding more tasks +#define DCE_ADMIN_PERF_ACTIVE_TASKS_NUM 5U +// Max number of tasks in perf stats capture +#define DCE_ADMIN_PERF_NUM_TASKS 10U +// Task name length +#define DCE_ADMIN_TASK_NAME_LEN 16U + +struct dce_admin_stat_avg { + uint64_t accumulate; // accumulated delta times + uint64_t iterations; + uint64_t min; + uint64_t max; +}; + +struct dce_admin_task_stats { + const char name[DCE_ADMIN_TASK_NAME_LEN]; // task name + uint64_t init; // how long to run init code + uint64_t ready; // how long for task to be ready + uint64_t ready_time; // time when task became ready + uint64_t dcache_misses; // dcaches missed count accumulated within this task + uint64_t inst_exec; // instruction execution accumulated + uint64_t mmio_req; // memory request accumulated + struct dce_admin_stat_avg sleep; + struct dce_admin_stat_avg run; + struct dce_admin_stat_avg run_context; // running context +}; + +struct dce_admin_sched_stats { + uint64_t start; // time collection started + uint64_t end; // time collection stopped + uint64_t context_switches; // overall context switches + struct dce_admin_task_stats tasks[DCE_ADMIN_PERF_NUM_TASKS]; +}; + +/* + * Performance monitor events and counters + */ +#define DCE_ADMIN_PM_EVT_DCACHE_MISSES 0U +#define DCE_ADMIN_PM_EVT_INSTR_EXEC 1U +#define DCE_ADMIN_PM_EVT_MEM_REQ 2U +#define DCE_ADMIN_PM_EVT_COUNTERS 3U + +struct dce_admin_pm_event_stats { + uint64_t start; // time collection started + uint64_t end; // time collection stopped + uint64_t count; // Event counter accumulated +}; + +struct dce_admin_perf_info { + uint64_t tcm_ready; // tcm is ready/copied + uint64_t sched_start; // scheduler started + uint64_t ready_time; // signaled ready + struct dce_admin_pm_event_stats pm_events[DCE_ADMIN_PM_EVT_COUNTERS]; + struct dce_admin_sched_stats sched; +}; + +#define DCE_ADMIN_EVENT_NAME_LEN 32U // max length of an event name +#define DCE_ADMIN_NUM_EVENTS 20U // s.b. same as DCE_NUM_EVENTS + +struct dce_admin_event { + char name[DCE_ADMIN_EVENT_NAME_LEN]; + struct dce_admin_stat_avg event; +}; + +struct dce_admin_event_info { + struct dce_admin_event events[DCE_ADMIN_NUM_EVENTS]; +}; + + +struct dce_admin_perf_stats_info { + union { + struct dce_admin_perf_info sched_stats; + struct dce_admin_event_info events_stats; + } info; +}; +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-bitops.h b/drivers/platform/tegra/dce/include/interface/dce-bitops.h new file mode 100644 index 00000000..11f8daab --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-bitops.h @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_BITOPS_H +#define DCE_BITOPS_H + +#define DCE_BIT(_b_) (((uint32_t)1U) << (_b_)) + +#define DCE_MASK(_msb_, _lsb_) \ + (((DCE_BIT(_msb_) - (uint32_t)1U) | \ + DCE_BIT(_msb_)) & (~(DCE_BIT(_lsb_) - (uint32_t)1U))) + +#define DCE_EXTRACT(_x_, _msb_, _lsb_, _type_) \ + ((_type_)((_type_)((_x_) & DCE_MASK(_msb_, _lsb_)) >> (_lsb_))) + +#define DCE_INSERT(_x_, _msb_, _lsb_, _value_) \ + ((((uint32_t)_x_) & DCE_MASK(_msb_, _lsb_)) | \ + ((((uint32_t)_value_) << _lsb_) & DCE_MASK(_msb_, _lsb_))) + +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-boot-cmds.h b/drivers/platform/tegra/dce/include/interface/dce-boot-cmds.h new file mode 100644 index 00000000..fca9093a --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-boot-cmds.h @@ -0,0 +1,123 @@ +/* + * Copyright (c) 2018-2022, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_BOOT_CMDS_H +#define DCE_BOOT_CMDS_H + +#include + +/* + * Version of the bootstrap command interface. + * + * This MUST be updated any time any changes are made to the + * bootstrap commands. + * + * To keep things simple, this value should be incremented by 1 + * each time changes are made. + */ +#define DCE_BOOT_CMD_VERSION_NUM 2 + +/* + * Defines the various bootstrap commands to DCE. + * + * These commands are relatively simple and are mainly used to + * communicate with DCE during initialization. + * + * The fundamental layout of a command is: + * Bit(s) Field Description + * 31:31 GO Signals to the DCE that a command is to be + * processed + * 30:27 COMMAND Identifies the command that the DCE is to + * process + * 26 RESERVED should be 0 + * 25 HILO 0 = PARM is 19:0 of address + * 1 = PARM is 39:20 of address + * 24 RDWR 0 = read header + * 1 = write header + * 23:20 RESERVED should be 0 + * 19:0 PARM Parameter to the command + * + * Once the command has been processed and the CCPLEX receives an interrupt + * from DCE, the mailbox used will contain any information about the result + * of the command. + * + * The commands are: + * + * DCE_BOOT_CMD_VERSION returns the version of the interface + * DCE_BOOT_CMD_SET_SID sets the SID for a buffer + * DCE_BOOT_CMD_CHANNEL_INIT initialize an IVC channel + * DCE_BOOT_CMD_SET_ADDR set the channel address + * DCE_BOOT_CMD_GET_FSIZE get the size of the frame + * DCE_BOOT_CMD_SET_NFRAMES set the number of frames + * DCE_BOOT_CMD_RESET causes DCE to reset to its initial state. + * This does not cause DCE to reboot. It mearly + * indicates that all of memory buffers for IPC + * will be ignored and the CCPLEX will have to + * re-establish the memory again. + * + * DCE_BOOT_CMD_LOCK locks the admin command interface. Requires + * a full reset of DCE to unlock. + */ +#define DCE_BOOT_CMD_GO DCE_BIT(31) +#define DCE_BOOT_CMD_SET(_x_, _v_) DCE_INSERT(_x_, 30, 27, _v_) +#define DCE_BOOT_CMD_GET(_x_) DCE_EXTRACT(_x_, 30, 27, uint32_t) +#define DCE_BOOT_CMD_SET_HILO(_x_, _v_) DCE_INSERT(_x_, 25, 25, _v_) +#define DCE_BOOT_CMD_GET_HILO(_x_) DCE_EXTRACT(_x_, 25, 25, uint32_t) +#define DCE_BOOT_CMD_SET_RDWR(_x_, _v_) DCE_INSERT(_x_, 24, 24, _v_) +#define DCE_BOOT_CMD_GET_RDWR(_x_) DCE_EXTRACT(_x_, 24, 24, uint32_t) +#define DCE_BOOT_CMD_PARM_SET(_x_, _v_) DCE_INSERT(_x_, 19, 0, _v_) +#define DCE_BOOT_CMD_PARM_GET(_x_) DCE_EXTRACT(_x_, 19, 0, uint32_t) + +/* + * Commands + */ +#define DCE_BOOT_CMD_VERSION (0x00U) +#define DCE_BOOT_CMD_SET_SID (0x01U) +#define DCE_BOOT_CMD_CHANNEL_INIT (0x02U) +#define DCE_BOOT_CMD_SET_ADDR (0x03U) +#define DCE_BOOT_CMD_GET_FSIZE (0x04U) +#define DCE_BOOT_CMD_SET_NFRAMES (0x05U) +#define DCE_BOOT_CMD_RESET (0x06U) +#define DCE_BOOT_CMD_LOCK (0x07U) +#define DCE_BOOT_CMD_SET_AST_LENGTH (0x08U) +#define DCE_BOOT_CMD_SET_AST_IOVA (0x09U) +#define DCE_BOOT_CMD_SET_FSIZE (0x0AU) +#define DCE_BOOT_CMD_UNUSED_11 (0x0BU) +#define DCE_BOOT_CMD_UNUSED_12 (0x0CU) +#define DCE_BOOT_CMD_UNUSED_13 (0x0DU) +#define DCE_BOOT_CMD_UNUSED_14 (0x0EU) +#define DCE_BOOT_CMD_UNUSED_15 (0x0FU) +#define DCE_BOOT_CMD_NEXT (0x10U) + +/* + * Boot Command Errors + */ +#define DCE_BOOT_CMD_ERR_FLAG DCE_BIT(23) +#define DCE_BOOT_CMD_NO_ERROR 0U +#define DCE_BOOT_CMD_ERR_BAD_COMMAND (1U | DCE_BOOT_CMD_ERR_FLAG) +#define DCE_BOOT_CMD_ERR_UNIMPLEMENTED (2U | DCE_BOOT_CMD_ERR_FLAG) +#define DCE_BOOT_CMD_ERR_IPC_SETUP (3U | DCE_BOOT_CMD_ERR_FLAG) +#define DCE_BOOT_CMD_ERR_INVALID_NFRAMES (4U | DCE_BOOT_CMD_ERR_FLAG) +#define DCE_BOOT_CMD_ERR_IPC_CREATE (5U | DCE_BOOT_CMD_ERR_FLAG) +#define DCE_BOOT_CMD_ERR_LOCKED (6U | DCE_BOOT_CMD_ERR_FLAG) + +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-core-interface-errors.h b/drivers/platform/tegra/dce/include/interface/dce-core-interface-errors.h new file mode 100644 index 00000000..2d0ee32e --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-core-interface-errors.h @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2022, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_CORE_INTERFACE_ERRORS_H +#define DCE_CORE_INTERFACE_ERRORS_H + +#define DCE_ERR_CORE_SUCCESS 0U + +#define DCE_ERR_CORE_NOT_IMPLEMENTED 1U +#define DCE_ERR_CORE_SC7_SEQUENCE 2U +#define DCE_ERR_CORE_RD_MEM_MAP 3U +#define DCE_ERR_CORE_WR_MEM_MAP 4U +#define DCE_ERR_CORE_IVC_INIT 5U +#define DCE_ERR_CORE_MEM_NOT_FOUND 6U +#define DCE_ERR_CORE_MEM_NOT_MAPPED 7U + +#define DCE_ERR_CORE_VMINDEX_INVALID 8U +#define DCE_ERR_CORE_VMINDEX_NO_AST_BASE 9U + +#define DCE_ERR_CORE_MEM_ALREADY_MAPPED 10U + +#define DCE_ERR_CORE_BAD_ADMIN_CMD 11U +#define DCE_ERR_CORE_INTERFACE_LOCKED 12U +#define DCE_ERR_CORE_INTERFACE_INCOMPATIBLE 13U + +#define DCE_ERR_CORE_MEM_SIZE 14U + +#define DCE_ERR_CORE_NULL_PTR 15U + +#define DCE_ERR_CORE_TIMER_INVALID 16U +#define DCE_ERR_CORE_TIMER_EXPIRED 17U + +#define DCE_ERR_CORE_IPC_BAD_TYPE 18U +#define DCE_ERR_CORE_IPC_NO_HANDLES 19U +#define DCE_ERR_CORE_IPC_BAD_CHANNEL 20U +#define DCE_ERR_CORE_IPC_CHAN_REGISTERED 21U +#define DCE_ERR_CORE_IPC_BAD_HANDLE 22U +#define DCE_ERR_CORE_IPC_MSG_TOO_LARGE 23U +#define DCE_ERR_CORE_IPC_NO_BUFFERS 24U +#define DCE_ERR_CORE_IPC_BAD_HEADER 25U +#define DCE_ERR_CORE_IPC_IVC_INIT 26U +#define DCE_ERR_CORE_IPC_NO_DATA 27U +#define DCE_ERR_CORE_IPC_INVALID_SIGNAL 28U +#define DCE_ERR_CORE_IPC_IVC_ERR 29U + +#define DCE_ERR_CORE_MEM_BAD_REGION 30U + +#define DCE_ERR_CORE_GPIO_INVALID_ID 31U +#define DCE_ERR_CORE_GPIO_NO_SPACE 32U + +#define DCE_ERR_CORE_RM_BOOTSTRAP 40U + +#define DCE_ERR_CORE_IPC_SIGNAL_REGISTERED 50U + +#define DCE_ERR_CORE_NOT_FOUND 60U +#define DCE_ERR_CORE_NOT_INITIALIZED 61U + +#define DCE_ERR_CORE_OTHER 9999U + +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-core-interface-ipc-types.h b/drivers/platform/tegra/dce/include/interface/dce-core-interface-ipc-types.h new file mode 100644 index 00000000..75ff1d00 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-core-interface-ipc-types.h @@ -0,0 +1,46 @@ +/* + * Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_CORE_INTERFACE_IPC_TYPES_H +#define DCE_CORE_INTERFACE_IPC_TYPES_H + + +/* + * Because this file is to be shared between DCE Core, RM and driver, + * it must not include or reference anything that is not contained + * in the following header files: + * stdint.h + * stddef.h + * stdbool.h + */ + +typedef uint32_t dce_ipc_type_t; + +#define DCE_IPC_TYPE_ADMIN ((dce_ipc_type_t)0U) +#define DCE_IPC_TYPE_DISPRM ((dce_ipc_type_t)1U) +#define DCE_IPC_TYPE_HDCP ((dce_ipc_type_t)2U) +#define DCE_IPC_TYPE_RM_NOTIFY ((dce_ipc_type_t)3U) +#define DCE_IPC_TYPE_BPMP ((dce_ipc_type_t)4U) +#define DCE_IPC_TYPE_MAX ((dce_ipc_type_t)5U) + + +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-driver-header-version.h b/drivers/platform/tegra/dce/include/interface/dce-driver-header-version.h new file mode 100644 index 00000000..994a86f6 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-driver-header-version.h @@ -0,0 +1,37 @@ +/* + * Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_DRIVER_HEADER_VERSION_H +#define DCE_DRIVER_HEADER_VERSION_H + +/* + * Version of the headers shared between the driver and DCE FW. + * + * This MUST be updated any time any changes are made to the headers + * that are shared between the driver and DCE FW. + * + * To keep things simple, this value should be incremented by 1 each + * time changes are made. + */ +#define DCE_DRIVER_HEADER_VERSION 2 + +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-interface.h b/drivers/platform/tegra/dce/include/interface/dce-interface.h new file mode 100644 index 00000000..5e0bf580 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-interface.h @@ -0,0 +1,125 @@ +/* + * Copyright (c) 2016-2022, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_INTERFACE_H +#define DCE_INTERFACE_H + +#include + +/* + * XXX: TODO + * + * These should be defined in terms of the HW registers + */ +#define DCE_NUM_SEMA_REGS 4 +#define DCE_NUM_MBOX_REGS 8 + +/* + * Symbolic definitions of the semaphore registers + */ +typedef uint32_t hsp_sema_t; + +#define DCE_BOOT_SEMA (hsp_sema_t)0U + +/* + * Definitions for DCE_BOOT_SEMA + * + * Used to communicate bits of information between the OS and DCE + */ + +/* + * Bits set by the OS and examined by the R5 + */ +#define DCE_BOOT_INT DCE_BIT(31) // interrupt when DCE is ready +#define DCE_WAIT_DEBUG DCE_BIT(30) // wait in debug loop +#define DCE_SC7_RESUME DCE_BIT(29) // resume using saved SC7 state + // rather than a full restart +#define DCE_OS_BITMASK (DCE_BOOT_INT | DCE_WAIT_DEBUG | DCE_SC7_RESUME) + +/* + * Bits set by the R5 and examined by the OS + */ +#define DCE_BOOT_TCM_COPY DCE_BIT(15) // uCode has copied to TCM +#define DCE_BOOT_HW_INIT DCE_BIT(14) // hardware init complete +#define DCE_BOOT_MPU_INIT DCE_BIT(13) // MPU initialized +#define DCE_BOOT_CACHE_INIT DCE_BIT(12) // cache initialized +#define DCE_BOOT_R5_INIT DCE_BIT(11) // R5 initialized +#define DCE_BOOT_DRIVER_INIT DCE_BIT(10) // driver init complete +#define DCE_BOOT_MAIN_STARTED DCE_BIT(9) // main started +#define DCE_BOOT_TASK_INIT_START DCE_BIT(8) // task initialization started +#define DCE_BOOT_TASK_INIT_DONE DCE_BIT(7) // task initialization complete + +#define DCE_HALTED DCE_BIT(1) // uCode has halted +#define DCE_BOOT_COMPLETE DCE_BIT(0) // uCode boot has completed + +/* + * Symbolic definitions of the doorbell registers + */ + typedef uint32_t hsp_db_t; + +/* + * Symbolic definitions of the mailbox registers (rather than using 0-7) + */ +typedef uint32_t hsp_mbox_t; + +#define DCE_MBOX_FROM_DCE_RM (hsp_mbox_t)0U // signal from RM IPC +#define DCE_MBOX_TO_DCE_RM (hsp_mbox_t)1U // signal to RM IPC +#define DCE_MBOX_FROM_DCE_RM_EVENT_NOTIFY (hsp_mbox_t)2U // signal to DCE for event notification +#define DCE_MBOX_TO_DCE_RM_EVENT_NOTIFY (hsp_mbox_t)3U // signal from DCE for event notification IPC +#define DCE_MBOX_FROM_DCE_ADMIN (hsp_mbox_t)4U // signal from DCE ADMIN IPC +#define DCE_MBOX_TO_DCE_ADMIN (hsp_mbox_t)5U // signal to ADMIN IPC +#define DCE_MBOX_BOOT_CMD (hsp_mbox_t)6U // boot commands +#define DCE_MBOX_IRQ (hsp_mbox_t)7U // general interrupt/status +/* + * Generic interrupts & status from the DCE are reported in DCE_MBOX_IRQ + */ +#define DCE_IRQ_PENDING DCE_BIT(31)// interrupt is pending + +#define DCE_IRQ_GET_STATUS_TYPE(_x_) DCE_EXTRACT(_x_, 30, 27, uint32_t) +#define DCE_IRQ_SET_STATUS_TYPE(_x_) DCE_INSERT(0U, 30, 27, _x_) + +#define DCE_IRQ_STATUS_TYPE_IRQ 0x0 // irq status +#define DCE_IRQ_STATUS_TYPE_BOOT_CMD 0x1 // boot command status + +#define NUM_DCE_IRQ_STATUS_TYPES 2 + +#define DCE_IRQ_GET_STATUS(_x_) DCE_EXTRACT(_x_, 23, 0, uint32_t) +#define DCE_IRQ_SET_STATUS(_x_) DCE_INSERT(0U, 23, 0, _x_) + +/* + * Bits in status field when IRQ_STATUS_TYPE == IRQ_STATUS_TYPE_IRQ + */ +#define DCE_IRQ_READY DCE_BIT(23) // DCE is ready +#define DCE_IRQ_LOG_OVERFLOW DCE_BIT(22) // trace log overflow +#define DCE_IRQ_LOG_READY DCE_BIT(21) // trace log buffers available +#define DCE_IRQ_CRASH_LOG DCE_BIT(20) // crash log available +#define DCE_IRQ_ABORT DCE_BIT(19) // uCode abort occurred +#define DCE_IRQ_SC7_ENTERED DCE_BIT(18) // DCE state saved + // can be powered off + +/* + * MBOX contents for IPC are the same for all of the mailboxes that are + * used for signaling IPC. Not all values will be useful for all mailboxes. + */ +#define DCE_IPC_IRQ_PENDING DCE_BIT(31) // interrupt is pending + +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-ipc-header.h b/drivers/platform/tegra/dce/include/interface/dce-ipc-header.h new file mode 100644 index 00000000..ca907f91 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-ipc-header.h @@ -0,0 +1,30 @@ +/* + * Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_IPC_HEADER_H +#define DCE_IPC_HEADER_H + +struct dce_ipc_header { + uint32_t length; +}; + +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-ipc-state.h b/drivers/platform/tegra/dce/include/interface/dce-ipc-state.h new file mode 100644 index 00000000..b0d93be5 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-ipc-state.h @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_IPC_STATE_H +#define DCE_IPC_STATE_H + +#include + +/* + * Flags used to denote the state of IPC data structures + */ +typedef uint32_t dce_ipc_flags_t; + +#define DCE_IPC_FL_VALID ((dce_ipc_flags_t)DCE_BIT(0)) +#define DCE_IPC_FL_REGISTERED ((dce_ipc_flags_t)DCE_BIT(1)) +#define DCE_IPC_FL_INIT ((dce_ipc_flags_t)DCE_BIT(2)) +#define DCE_IPC_FL_READY ((dce_ipc_flags_t)DCE_BIT(3)) +#define DCE_IPC_FL_RM_ALLOWED ((dce_ipc_flags_t)DCE_BIT(4)) +#define DCE_IPC_FL_MSG_HEADER ((dce_ipc_flags_t)DCE_BIT(15)) + +/* + * Different types of signal mechanisms + */ +typedef uint32_t dce_ipc_signal_type_t; + +#define DCE_IPC_SIGNAL_MAILBOX ((dce_ipc_signal_type_t)0U) +#define DCE_IPC_SIGNAL_DOORBELL ((dce_ipc_signal_type_t)1U) + +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-memory-ids.h b/drivers/platform/tegra/dce/include/interface/dce-memory-ids.h new file mode 100644 index 00000000..28418125 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-memory-ids.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_MEMORY_IDS_H +#define DCE_MEMORY_IDS_H + +/* + * Defines the varous memory IDs used for mapping memory regions + * for DCE. + * + * XXX: TODO + * Rename some of the IDs to better represent what they're used for + */ + +#define DCE_MAP_DRAM_ID 0U // FW DRAM +#define DCE_MAP_BPMP_ID 1U // BPMP communications area +#define DCE_MAP_CONFIG_DATA_ID 2U // device tree +#define DCE_MAP_IPC_ID 3U // memory region for IPC +#define DCE_MAP_MSG_ID 4U // extra: rename at some point +#define DCE_MAP_UTILITY_ID 5U // extra: rename at some point +#define DCE_MAP_RM_ID 6U // RM communications area +#define DCE_MAP_RM_DATA_ID 7U // extra RM data area + +#endif diff --git a/drivers/platform/tegra/dce/include/interface/dce-types.h b/drivers/platform/tegra/dce/include/interface/dce-types.h new file mode 100644 index 00000000..e254bf0b --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-types.h @@ -0,0 +1,28 @@ +/* + * Copyright (c) 2020-2022, NVIDIA CORPORATION. All rights reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a + * copy of this software and associated documentation files (the "Software"), + * to deal in the Software without restriction, including without limitation + * the rights to use, copy, modify, merge, publish, distribute, sublicense, + * and/or sell copies of the Software, and to permit persons to whom the + * Software is furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL + * THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING + * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER + * DEALINGS IN THE SOFTWARE. + */ + +#ifndef DCE_TYPES_H +#define DCE_TYPES_H + +typedef uint64_t dce_iova; + +#endif diff --git a/include/linux/platform/tegra/dce/dce-client-ipc.h b/include/linux/platform/tegra/dce/dce-client-ipc.h new file mode 100644 index 00000000..341dc0ab --- /dev/null +++ b/include/linux/platform/tegra/dce/dce-client-ipc.h @@ -0,0 +1,86 @@ +/* + * 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. + */ + +#ifndef TEGRA_DCE_CLIENT_IPC_H +#define TEGRA_DCE_CLIENT_IPC_H + +#define DCE_CLIENT_IPC_TYPE_CPU_RM 0U +#define DCE_CLIENT_IPC_TYPE_HDCP_KMD 1U +#define DCE_CLIENT_IPC_TYPE_RM_EVENT 2U +#define DCE_CLIENT_IPC_TYPE_MAX 3U + +#define DCE_CLIENT_MAX_IPC_MSG_SIZE 4096 + +/** + * struct dce_ipc_message - Contains necessary info for an ipc msg. + * + * @tx : transmit message info + * @rx : receive message info + */ +struct dce_ipc_message { + struct { + void *data; + size_t size; + } tx; + struct { + void *data; + size_t size; + } rx; +}; + +/* + * tegra_dce_client_ipc_callback_t - callback function to notify the + * client when the CPU Driver receives an IPC from DCE for the client. + * + * @client_id: id that represents the corresponding client. + * @interface_type: Interface on which the IPC was received. + * @msg_length: Length of the message. + * @msg_data: Message data. + * @usr_ctx: Any user context if present. + */ +typedef void (*tegra_dce_client_ipc_callback_t)(u32 handle, + u32 interface_type, u32 msg_length, + void *msg_data, void *usr_ctx); + +/* + * tegra_dce_register_ipc_client() - used by clients to register with dce driver + * @interface_type: Interface for which this client is expected to send rpcs and + * receive callbacks. + * @callback_fn: callback function to be called by DCE driver on receiving IPCs + * from DCE on this interface. + * @usr_ctx: Any user context if present. + * + * Return: valid client_id if no errors else corresponding error value. + */ +int tegra_dce_register_ipc_client(u32 interface_type, + tegra_dce_client_ipc_callback_t callback_fn, + void *usr_ctx, u32 *handlep); + +/* + * tegra_dce_unregister_client() - used by clients to unregister to dce driver + * @client_id : ointer to client's data + * + * Return: 0 if no errors else corresponding error value. + */ +int tegra_dce_unregister_ipc_client(u32 handle); + +/* + * tegra_dce_client_ipc_send_recv() - used by clients to send rpcs to dce + * @client_id : client_id registered with dce driver + * @msg : message to be sent and received + * + * Return: 0 if no errors else corresponding error value. + */ +int tegra_dce_client_ipc_send_recv(u32 handle, struct dce_ipc_message *msg); + +#endif diff --git a/include/trace/events/dce_events.h b/include/trace/events/dce_events.h new file mode 100644 index 00000000..276d597d --- /dev/null +++ b/include/trace/events/dce_events.h @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2020-2022, NVIDIA CORPORATION, All rights reserved. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * 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 Lesser General Public + * License along with this program. If not, see + * . + */ + +#undef TRACE_SYSTEM +#define TRACE_SYSTEM dce_events + +#if !defined(_TRACE_DCE_EVENTS_H) || defined(TRACE_HEADER_MULTI_READ) +#define _TRACE_DCE_EVENTS_H + +#include +#include +#include + +DECLARE_EVENT_CLASS(dce_ipc_events_notifier, + TP_PROTO(struct tegra_dce *d, struct dce_ipc_channel *ch), + TP_ARGS(d, ch), + TP_STRUCT__entry( + __field_struct(struct dce_ipc_channel *, ch) + __field_struct(struct tegra_dce *, d) + ), + TP_fast_assign( + __entry->ch = ch; + __entry->d = d; + ), + TP_printk("Channel Type = [%u], Flags = [0x%x], Wait Type = [%u], Write Pos = [%u], Read Pos = [%u], Frame Size = [%u], " + "No of Frames = [%u], Rx Iova = [0x%llx], Tx Iova = [0x%llx], Region Current Offset = [%u], Region Iova Base = [0x%llx], " + "Region Size = [%lu] Region Base Address = [0x%p]", + __entry->ch->ch_type, __entry->ch->flags, __entry->ch->w_type, + __entry->ch->d_ivc.tx.position, __entry->ch->d_ivc.rx.position, + __entry->ch->q_info.frame_sz, __entry->ch->q_info.nframes, + __entry->ch->q_info.rx_iova, __entry->ch->q_info.tx_iova, + __entry->d->d_ipc.region.s_offset, __entry->d->d_ipc.region.iova, + __entry->d->d_ipc.region.size, __entry->d->d_ipc.region.base) +); + +DEFINE_EVENT(dce_ipc_events_notifier, ivc_channel_init_complete, + TP_PROTO(struct tegra_dce *d, struct dce_ipc_channel *ch), + TP_ARGS(d, ch) +); + +DEFINE_EVENT(dce_ipc_events_notifier, ivc_channel_reset_triggered, + TP_PROTO(struct tegra_dce *d, struct dce_ipc_channel *ch), + TP_ARGS(d, ch) +); + +DEFINE_EVENT(dce_ipc_events_notifier, ivc_channel_reset_complete, + TP_PROTO(struct tegra_dce *d, struct dce_ipc_channel *ch), + TP_ARGS(d, ch) +); + +DEFINE_EVENT(dce_ipc_events_notifier, ivc_send_req_received, + TP_PROTO(struct tegra_dce *d, struct dce_ipc_channel *ch), + TP_ARGS(d, ch) +); + +DEFINE_EVENT(dce_ipc_events_notifier, ivc_send_complete, + TP_PROTO(struct tegra_dce *d, struct dce_ipc_channel *ch), + TP_ARGS(d, ch) +); + +DEFINE_EVENT(dce_ipc_events_notifier, ivc_wait_complete, + TP_PROTO(struct tegra_dce *d, struct dce_ipc_channel *ch), + TP_ARGS(d, ch) +); + +DEFINE_EVENT(dce_ipc_events_notifier, ivc_receive_req_received, + TP_PROTO(struct tegra_dce *d, struct dce_ipc_channel *ch), + TP_ARGS(d, ch) +); + +DEFINE_EVENT(dce_ipc_events_notifier, ivc_receive_req_complete, + TP_PROTO(struct tegra_dce *d, struct dce_ipc_channel *ch), + TP_ARGS(d, ch) +); + +#endif /* _TRACE_DCE_EVENTS_H */ + +/* This part must be outside protection */ +#include