diff --git a/drivers/platform/tegra/dce/Makefile b/drivers/platform/tegra/dce/Makefile new file mode 100644 index 00000000..68b26fa8 --- /dev/null +++ b/drivers/platform/tegra/dce/Makefile @@ -0,0 +1,31 @@ +# +# Display Controller Engine code. +# +GCOV_PROFILE := y + +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 +obj-$(CONFIG_TEGRA_DCE) += tegra-dce.o + +tegra-dce-y += \ + dce-ast.o \ + dce-reset.o \ + dce-hsp-smb.o \ + dce-hsp-ss.o \ + dce-worker.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-util-common.o \ + dce-debug.o + +ccflags-y += -I$(srctree.nvidia-t23x)/drivers/platform/tegra/dce/include diff --git a/drivers/platform/tegra/dce/dce-admin.c b/drivers/platform/tegra/dce/dce-admin.c new file mode 100644 index 00000000..f4413b4c --- /dev/null +++ b/drivers/platform/tegra/dce/dce-admin.c @@ -0,0 +1,505 @@ +/* + * 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 +#include +#include +#include + +/** + * dce_admin_ipc_wait - Waits for message from DCE. + * + * @d : Pointer to tegra_dce struct. + * + * Return : 0 if successful + */ +int dce_admin_ipc_wait(struct tegra_dce *d, u32 w_type) +{ + int ret = 0; + enum dce_worker_event_id_type event; + + switch (w_type) { + case DCE_IPC_WAIT_TYPE_SYNC: + event = EVENT_ID_DCE_IPC_MESSAGE_SENT; + break; + case DCE_IPC_WAIT_TYPE_RPC: + event = EVENT_ID_DCE_IPC_MESSAGE_SENT; + break; + default: + dce_err(d, "Invalid wait type [%d]", w_type); + break; + } + + dce_worker_thread_wait(d, event); + + if (dce_worker_get_state(d) + == STATE_DCE_WORKER_ABORTED) + ret = -1; + + return ret; +} + +/** + * dce_admin_interface_isr - Isr for the CCPLEX<->DCE admin interface + * + * @d : Pointer to tegra_de struct. + * + * Return : Void + */ +static void dce_admin_wakeup_ipc(struct tegra_dce *d) +{ + enum dce_worker_event_id_type event = EVENT_ID_DCE_IPC_SIGNAL_RECEIVED; + + dce_worker_thread_wakeup(d, event); +} + +/** + * dce_admin_ipc_handle_signal - Isr for the CCPLEX<->DCE admin interface + * + * @d : Pointer to tegra_de struct. + * + * Return : Void + */ +void dce_admin_ipc_handle_signal(struct tegra_dce *d, u32 ch_type) +{ + bool wakeup_cl = false; + bool wakeup_needed = false; + + /** + * TODO : Get rid of prints + */ + dce_info(d, "Signal Received : check if channel is ready [%d]", + ch_type); + + if (!(dce_ipc_channel_is_ready(d, ch_type))) { + /** + * The ivc channel is not ready yet. Exit + * and wait for another signal from target. + */ + goto process_wakeup; + } + /** + * TODO : Get rid of prints + */ + dce_info(d, "Signal Received : Channel in established state already : [%d]", + ch_type); + + wakeup_needed = (DCE_IPC_WAIT_TYPE_SYNC == + dce_ipc_get_cur_wait_type(d, ch_type)); + if (wakeup_needed) { + /** + * Handshake successful, wake up the + * dce worker thread since it's waiting + * for the synchronization to complete. + */ + goto process_wakeup; + } + /** + * TODO : Get rid of prints + */ + dce_info(d, "Signal Received : Channel synchronized already : [%d]", + ch_type); + + /** + * Channel already in sync with remote. Check if data + * is available to read. + */ + wakeup_needed = dce_ipc_is_data_available(d, ch_type); + if (wakeup_needed) + wakeup_cl = (ch_type != DCE_IPC_CH_KMD_TYPE_ADMIN); + + /** + * TODO : Get rid of prints + */ + dce_info(d, "Signal Received : Data available : [%d] for channel: [%d]", + wakeup_needed, ch_type); + +process_wakeup: + if (!wakeup_needed) { + dce_info(d, "Spurious signal on channel: [%d]. Ignored...", + ch_type); + return; + } + if (!wakeup_cl) + dce_admin_wakeup_ipc(d); + else + dce_client_ipc_wakeup(d, ch_type); +} + +/** + * dce_admin_ivc_channel_reset - Resets the admin ivc channel + * + * @d : Pointer to tegra_dce struct. + * + * Return : Void. + */ +void dce_admin_ivc_channel_reset(struct tegra_dce *d) +{ + dce_ipc_channel_reset(d, DCE_IPC_CH_KMD_TYPE_ADMIN); +} + +/** + * dce_admin_channel_deinit - Cleans up the channel resources. + * + * @d : Pointer to tegra_dce struct + * + * Return : Void + */ +static void dce_admin_channel_deinit(struct tegra_dce *d) +{ + u32 loop_cnt; + + for (loop_cnt = 0; loop_cnt < DCE_IPC_CH_KMD_TYPE_MAX; loop_cnt++) + dce_ipc_channel_deinit(d, loop_cnt); +} + + +/** + * dce_admin_channel_init - Initializes the admin ivc interface + * + * @d : Pointer to tegra_dce. + * + * Return : 0 if successful. + */ +static int dce_admin_channel_init(struct tegra_dce *d) +{ + int ret = 0; + u32 loop_cnt; + + for (loop_cnt = 0; loop_cnt < DCE_IPC_CH_KMD_TYPE_MAX; loop_cnt++) { + ret = dce_ipc_channel_init(d, loop_cnt); + if (ret) { + dce_err(d, "Channel init failed for type : [%d]", + loop_cnt); + goto out; + } + } + +out: + if (ret) + dce_admin_channel_deinit(d); + return ret; +} + +/** + * dce_admin_init - Sets up resources managed by admin. + * + * @d : Pointer to tegra_dce struct. + * + * Return : 0 if successful + */ +int dce_admin_init(struct tegra_dce *d) +{ + int ret = 0; + + ret = dce_ipc_allocate_region(d); + if (ret) { + dce_err(d, "IPC region allocation failed"); + goto err_ipc_reg_alloc; + } + + ret = dce_admin_channel_init(d); + if (ret) { + dce_err(d, "Channel Initialization Failed"); + goto err_channel_init; + } + + return 0; + +err_channel_init: + dce_ipc_free_region(d); +err_ipc_reg_alloc: + return ret; +} + +/** + * dce_admin_deinit - Releases the resources + * associated with admin interface. + * + * @d : Pointer to tegra_dce struct. + * + * Return : Void + */ +void dce_admin_deinit(struct tegra_dce *d) +{ + dce_admin_channel_deinit(d); + + dce_ipc_free_region(d); + + dce_mailbox_deinit_interface(d, + DCE_MAILBOX_ADMIN_INTERFACE); +} + +/** + * dce_admin_allocate_message - Allocates memory for a message + * on admin interface. + * @d : Pointer tegra_dce struct. + * + * Return : Allocated msg if successful. + */ +struct dce_ipc_message *dce_admin_allocate_message(struct tegra_dce *d) +{ + struct dce_ipc_message *msg; + + msg = dce_kzalloc(d, sizeof(*msg), false); + if (!msg) { + dce_err(d, "Insufficient memory for admin msg"); + goto err_alloc_msg; + } + + msg->tx.data = dce_kzalloc(d, DCE_ADMIN_CMD_SIZE, false); + if (!msg->tx.data) { + dce_err(d, "Insufficient memory for admin msg"); + goto err_alloc_tx; + } + + msg->rx.data = dce_kzalloc(d, DCE_ADMIN_RESP_SIZE, false); + if (!msg->rx.data) { + dce_err(d, "Insufficient memory for admin msg"); + goto err_alloc_rx; + } + + msg->tx.size = DCE_ADMIN_CMD_SIZE; + msg->rx.size = DCE_ADMIN_RESP_SIZE; + + return msg; + +err_alloc_rx: + dce_kfree(d, msg->tx.data); +err_alloc_tx: + dce_kfree(d, msg); +err_alloc_msg: + return NULL; +} + +/** + * dce_admin_free_message - Frees memory allocated for a message + * on admin interface. + * + * @d : Pointer to tegra_dce struct. + * @msg : Pointer to allocated message. + * + * Return : Void. + */ +void dce_admin_free_message(struct tegra_dce *d, + struct dce_ipc_message *msg) +{ + if (!msg || !msg->tx.data || !msg->rx.data) + return; + + dce_kfree(d, msg->tx.data); + dce_kfree(d, msg->rx.data); + dce_kfree(d, msg); +} + +/** + * dce_admin_send_cmd_version_cmd - Sends DCE_ADMIN_CMD_VERSION cmd. + * + * @d - Pointer to tegra_dce struct. + * + * Return - 0 if successful + */ +int dce_admin_send_msg(struct tegra_dce *d, struct dce_ipc_message *msg) +{ + int ret = 0; + + ret = dce_ipc_send_message_sync(d, DCE_IPC_CHANNEL_TYPE_ADMIN, msg); + if (ret) + dce_err(d, "Error sending admin message on admin interface"); + + return ret; +} + +/** + * dce_admin_get_ipc_channel_info - Provides channel's + * buff details + * + * @d - Pointer to tegra_dce struct. + * @q_info : Pointer to struct dce_ipc_queue_info + * + * Return - 0 if successful + */ +int dce_admin_get_ipc_channel_info(struct tegra_dce *d, + struct dce_ipc_queue_info *q_info) +{ + int ret; + u8 channel_id = DCE_IPC_CHANNEL_TYPE_ADMIN; + + ret = dce_ipc_get_channel_info(d, q_info, channel_id); + + return ret; +} + +static int dce_admin_send_cmd_ver(struct tegra_dce *d, + struct dce_ipc_message *msg) +{ + int ret = 0; + struct dce_admin_ipc_cmd *req_msg; + struct dce_admin_ipc_resp *resp_msg; + struct dce_admin_version_info *ver_info; + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_VERSION; + ver_info = (struct dce_admin_version_info *)(&resp_msg->args.version); + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_err(d, "Error sending get version info : [%d]", ret); + goto out; + } + dce_info(d, "error value : [0x%x]", resp_msg->error); + dce_info(d, "version number : [0x%x]", ver_info->version); + +out: + /** + * TODO : Add more error handling here + */ + return ret; +} + +static int dce_admin_setup_clients_ipc(struct tegra_dce *d, + struct dce_ipc_message *msg) +{ + uint32_t i; + int ret = 0; + struct dce_admin_ipc_cmd *req_msg; + struct dce_admin_ipc_resp *resp_msg; + struct dce_ipc_queue_info q_info; + struct dce_admin_ipc_create_args *ipc_info; + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_IPC_CREATE; + ipc_info = (struct dce_admin_ipc_create_args *) + (&req_msg->args.ipc_create); + + for (i = 0; i < DCE_IPC_CH_KMD_TYPE_MAX; i++) { + if (i == DCE_IPC_CH_KMD_TYPE_ADMIN) + continue; + ret = dce_ipc_get_channel_info(d, &q_info, i); + if (ret) { + dce_info(d, "Get queue info failed for [%u]", i); + ret = 0; + continue; + } + + ipc_info->type = dce_ipc_get_ipc_type(d, i); + ipc_info->rd_iova = q_info.tx_iova; + ipc_info->wr_iova = q_info.rx_iova; + ipc_info->fsize = q_info.frame_sz; + ipc_info->n_frames = q_info.nframes; + + /** + * TODO : Get rid of prints + */ + dce_err(d, "value of q_struct for type [%d]", ipc_info->type); + dce_err(d, "ch->q_info.nframes : [%u]", ipc_info->n_frames); + dce_err(d, "ch->q_info.frame_sz: [%u]", ipc_info->fsize); + dce_err(d, "ch->q_info.rx_iova: [0x%llx]", ipc_info->wr_iova); + dce_err(d, "ch->q_info.tx_iova: [0x%llx]", ipc_info->rd_iova); + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_err(d, "Error sending IPC create msg for type [%u]", + i); + goto out; + } + + dce_info(d, "error value : [0x%x]", resp_msg->error); + if (resp_msg->error) { + dce_err(d, "IPC create for type [%u] failed", i); + goto out; + } + + dce_ipc_channel_reset(d, i); + dce_info(d, "Channel Reset Complete for Type [%u] ...", i); + } + +out: + /** + * TODO : Add more error handling here + */ + return ret; +} + +static int dce_admin_send_rm_bootstrap(struct tegra_dce *d, + struct dce_ipc_message *msg) +{ + int ret = 0; + struct dce_admin_ipc_cmd *req_msg; + struct dce_admin_ipc_resp *resp_msg; + struct dce_admin_version_info *ver_info; + + req_msg = (struct dce_admin_ipc_cmd *)(msg->tx.data); + resp_msg = (struct dce_admin_ipc_resp *) (msg->rx.data); + + req_msg->cmd = (uint32_t)DCE_ADMIN_CMD_RM_BOOTSTRAP; + ver_info = (struct dce_admin_version_info *)(&resp_msg->args.version); + + ret = dce_admin_send_msg(d, msg); + if (ret) { + dce_err(d, "Error sending rm bootstrap cmd: [%d]", ret); + goto out; + } + + dce_info(d, "error value : [0x%x]", resp_msg->error); + + if (resp_msg->error) { + dce_err(d, "Error in handling rm bootstrap cmd on dce: [0x%x]", + resp_msg->error); + ret = -EINVAL; + } + +out: + /** + * TODO : Add more error handling here + */ + return ret; +} + +int dce_start_admin_seq(struct tegra_dce *d) +{ + int ret = 0; + struct dce_ipc_message *msg; + + msg = dce_admin_allocate_message(d); + if (!msg) + return -1; + + ret = dce_admin_send_cmd_ver(d, msg); + if (ret) { + dce_err(d, "RPC failed for DCE_ADMIN_CMD_VERSION"); + goto out; + } + + ret = dce_admin_setup_clients_ipc(d, msg); + if (ret) { + dce_err(d, "RPC failed for DCE_ADMIN_CMD_VERSION"); + goto out; + } + + ret = dce_admin_send_rm_bootstrap(d, msg); + if (ret) { + dce_err(d, "RPC failed for DCE_ADMIN_CMD_VERSION"); + goto out; + } +out: + dce_admin_free_message(d, msg); + return ret; +} diff --git a/drivers/platform/tegra/dce/dce-ast.c b/drivers/platform/tegra/dce/dce-ast.c new file mode 100644 index 00000000..18d58dc2 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-ast.c @@ -0,0 +1,602 @@ +/* + * 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 + +#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_info(d, "AST_AST0_CONTROL_R : 0x%x", + dce_readl(d, ast_ast0_control_r())); +} + +/** + * 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); + dce_info(d, "AST_AST1_CONTROL_R : 0x%x", + dce_readl(d, ast_ast1_control_r())); +} + +/** + * 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_info(d, "AST_AST0_STREAMID_CTL_0_R : 0x%x", + dce_readl(d, ast_ast0_streamid_ctl_0_r())); +} + +/** + * 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_info(d, "AST_AST0_STREAMID_CTL_1_R : 0x%x", + dce_readl(d, ast_ast0_streamid_ctl_1_r())); +} + +/** + * 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_info(d, "AST_AST1_STREAMID_CTL_0_R : 0x%x", + dce_readl(d, ast_ast1_streamid_ctl_0_r())); +} + +/** + * 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); + dce_info(d, "AST_AST1_STREAMID_CTL_1_R : 0x%x", + dce_readl(d, ast_ast1_streamid_ctl_1_r())); +} + +/** + * 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_info(d, "AST_AST0_REGION_0_SLAVE_BASE_LO_R : 0x%x", + dce_readl(d, ast_ast0_region_0_slave_base_lo_r())); +} + +/** + * 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()); + dce_info(d, "AST_AST1_REGION_0_SLAVE_BASE_LO_R : 0x%x", + dce_readl(d, ast_ast1_region_0_slave_base_lo_r())); +} + +/** + * 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_info(d, "AST_AST0_REGION_0_MASTER_BASE_LO_R : 0x%x", + dce_readl(d, ast_ast0_region_0_master_base_lo_r())); +} + +/** + * 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_info(d, "AST_AST1_REGION_0_MASTER_BASE_LO_R : 0x%x", + dce_readl(d, ast_ast1_region_0_master_base_lo_r())); +} + +/** + * 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_info(d, "AST_AST1_REGION_0_MASTER_BASE_HI_R : 0x%x", + dce_readl(d, ast_ast1_region_0_master_base_hi_r())); +} + +/** + * 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_info(d, "AST_AST0_REGION_0_MASTER_BASE_HI_R : 0x%x", + dce_readl(d, ast_ast0_region_0_master_base_hi_r())); +} + +/** + * 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. + */ +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_info(d, "AST_AST0_REGION_0_MASK_LO_R : 0x%x", + dce_readl(d, ast_ast0_region_0_mask_lo_r())); +} + +/** + * 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); + dce_info(d, "AST_AST1_REGION_0_MASK_LO_R : 0x%x", + dce_readl(d, ast_ast1_region_0_mask_lo_r())); +} + +/** + * 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_info(d, "AST_AST0_REGION_0_CONTROL_R : 0x%x", + dce_readl(d, ast_ast0_region_0_control_r())); +} + +/** + * 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()); + dce_info(d, "AST_AST1_REGION_0_CONTROL_R : 0x%x", + dce_readl(d, ast_ast1_region_0_control_r())); +} + +/** + * 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; + + slave_addr = dce_get_fw_dce_addr(d); + + if (!d->fw_data) { + dce_err(d, "No fw_data present"); + return; + } + + master_addr = d->fw_data->dma_handle; + dce_info(d, "Value of master address 0x%llx\n", master_addr); + + 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); + } + } +} diff --git a/drivers/platform/tegra/dce/dce-bootstrap.c b/drivers/platform/tegra/dce/dce-bootstrap.c new file mode 100644 index 00000000..272ba6e7 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-bootstrap.c @@ -0,0 +1,752 @@ +/* + * 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 +#include +#include + +/** + * dce_boot_complete - Checks if dce has complelted boot. + * + * @d - Pointer to tegra_dce struct. + * + * Return : True if boot is complete + */ +static inline bool dce_boot_complete(struct tegra_dce *d) +{ + return !!(dce_ss_get_state(d, DCE_BOOT_SEMA) + & DCE_BOOT_COMPLETE); +} + +/** + * dce_boot_poll_boot_complete - Poll until dce boot is complete. + * + * @d : Pointer to tegra_dce struct. + * + * Return : 0 if successful + */ +static int dce_boot_poll_boot_complete(struct tegra_dce *d) +{ + int ret = 0; + + while (!dce_boot_complete(d)) { + dce_worker_thread_wait(d, + EVENT_ID_DCE_BOOT_COMPLETE_IRQ_REQ_SET); + } + + if (dce_worker_get_state(d) == STATE_DCE_WORKER_ABORTED) + ret = -1; + + return ret; +} + +/** + * dce_req_boot_irq_sync - Requests DCE to raise an + * interrupt on boot completion. + * + * @d - Pointer to tegra_dce struct. + * + * Return : 0 if sucessful. + */ +static int dce_req_boot_irq_sync(struct tegra_dce *d) +{ + int ret = 0; + +#define DCE_BOOT_INIT_BPOS 31U + dce_ss_set(d, DCE_BOOT_INIT_BPOS, DCE_BOOT_SEMA); +#undef DCE_BOOT_INIT_BPOS + + dce_info(d, "Waiting on dce fw to boot..."); + + ret = dce_boot_poll_boot_complete(d); + if (ret) + dce_err(d, "DCE Boot Complete Poll Interrupted"); + + return ret; +} + +/** + * dce_wait_boot_complete - Wait for the DCE to boot and be ready to receive + * commands from CCPLEX driver. + * + * @d : Pointer to tegra_dce struct. + * + * Return : 0 if successful + */ +int dce_wait_boot_complete(struct tegra_dce *d) +{ + int ret = 0; + + if (dce_boot_complete(d)) + goto boot_done; + + ret = dce_req_boot_irq_sync(d); + +boot_done: + if (!ret) { + dce_set_boot_complete(d, true); + dce_info(d, "dce is ready to receive bootstrap commands"); + } + return ret; +} + +/** + * dce_handle_irq_status - Handles irq status from DCE + * + * @d : Pointer to struct tegra_dce + * @status : Status received from DCE + * + * Return : Void + */ +void dce_handle_irq_status(struct tegra_dce *d, u32 status) +{ + enum dce_worker_event_id_type event; + + if (status & DCE_IRQ_LOG_OVERFLOW) + dce_info(d, "DCE trace log overflow error received"); + + if (status & DCE_IRQ_LOG_READY) + dce_info(d, "DCE trace log buffers available"); + + 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_SC7_ENTERED) + dce_info(d, "DCE can be safely powered-off now"); + + event = EVENT_ID_DCE_INTERFACE_ERROR_RECEIVED; + + if (status & DCE_IRQ_READY) { + dce_info(d, "DCE IRQ Ready Received"); + event = EVENT_ID_DCE_BOOT_COMPLETE_IRQ_RECEIVED; + } + + dce_worker_thread_wakeup(d, event); +} + + +/** + * dce_bootstrap_handle_boot_status- Handles boot status from DCE + * + * @d : Pointer to struct tegra_dce + * @status : Status received from DCE + * + * Return : Void + */ +void dce_bootstrap_handle_boot_status(struct tegra_dce *d, u32 status) +{ + enum dce_worker_event_id_type event; + + event = EVENT_ID_DCE_IPC_SIGNAL_RECEIVED; + + dce_info(d, "Boot Cmd Resp Received. Status: [%x]", status); + + dce_mailbox_store_interface_status(d, status, + DCE_MAILBOX_BOOT_INTERFACE); + + dce_worker_thread_wakeup(d, event); +} + + +/** + * 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; + enum dce_worker_event_id_type event; + + event = EVENT_ID_DCE_IPC_MESSAGE_SENT; + + dce_worker_thread_wait(d, event); + + status = dce_mailbox_get_interface_status(d, + DCE_MAILBOX_BOOT_INTERFACE); + + if (dce_worker_get_state(d) == STATE_DCE_WORKER_ABORTED) + return -EINTR; + + 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; + } + + + dce_err(d, "value of q_struct:"); + dce_err(d, "ch->q_info.nframes : %u", q_info.nframes); + dce_err(d, "ch->q_info.frame_sz: %u", q_info.frame_sz); + dce_err(d, "ch->q_info.rx_iova: [0x%llx]", q_info.rx_iova); + dce_err(d, "ch->q_info.tx_iova: [0x%llx]", q_info.tx_iova); + + 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); + dce_info(d, "Frame size received: [%u]", DCE_BOOT_CMD_GET(val)); + + + 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; + + 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); + dce_info(d, "Version received: [%u]", DCE_BOOT_CMD_GET(val)); + + 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; + } + + return 0; + +err_sending: + dce_err(d, "Bootstrap process 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..5a50f363 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-client-ipc.c @@ -0,0 +1,259 @@ +/* + * 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 +#include + +#define DCE_IPC_HANDLES_MAX 6U +#define DCE_CLIENT_IPC_HANDLE_INVALID 0U +#define DCE_CLIENT_IPC_HANDLE_VALID ((u32)BIT(31)) + +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, +}; + +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; +} + +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; +} + +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; + struct tegra_dce_client_ipc *cl; + u32 handle = DCE_CLIENT_IPC_HANDLE_INVALID; + + int_type = dce_interface_type_map[type]; + + d = dce_ipc_get_dce_from_ch(int_type); + if (d == NULL) { + ret = -EINVAL; + goto out; + } + + if (handlep == NULL) { + dce_err(d, "Invalid handle pointer"); + ret = -EINVAL; + 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->int_type = int_type; + cl->callback_fn = callback_fn; + + d->d_clients[type] = cl; + init_completion(&cl->recv_wait); + +out: + if (ret != 0) { + dce_client_ipc_handle_free(handle); + handle = DCE_CLIENT_IPC_HANDLE_INVALID; + } + + *handlep = handle; + + return ret; +} +EXPORT_SYMBOL(tegra_dce_register_ipc_client); + +int tegra_dce_unregister_ipc_client(u32 handle) +{ + 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); + +static int dce_client_ipc_wait_rpc(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; + } + + wait_for_completion(&cl->recv_wait); + + return 0; +} + +int dce_client_ipc_wait(struct tegra_dce *d, u32 w_type, u32 ch_type) +{ + int ret = 0; + + switch (w_type) { + case DCE_IPC_WAIT_TYPE_SYNC: + ret = dce_admin_ipc_wait(d, w_type); + break; + case DCE_IPC_WAIT_TYPE_RPC: + ret = dce_client_ipc_wait_rpc(d, ch_type); + break; + default: + dce_err(d, "Invalid wait type [%d]", w_type); + break; + } + + return ret; +} + +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; + } + + complete(&cl->recv_wait); +} diff --git a/drivers/platform/tegra/dce/dce-debug.c b/drivers/platform/tegra/dce/dce-debug.c new file mode 100644 index 00000000..bfff504c --- /dev/null +++ b/drivers/platform/tegra/dce/dce-debug.c @@ -0,0 +1,418 @@ +/* + * 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 +#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_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, +}; + +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; +} + +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_BPMP: 0x%x", + dce_smb_read(d, DCE_MBOX_FROM_BPMP)); + dce_info(d, "DCE_MBOX_TO_BPMP: 0x%x", + dce_smb_read(d, DCE_MBOX_TO_BPMP)); + 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); + + 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_dce", 0444, + d_dev->debugfs, d, &reset_dce_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_file("boot_dce", 0444, + d_dev->debugfs, d, &boot_dce_fops); + if (!retval) + goto err_handle; + + retval = debugfs_create_bool("boot_status", 0644, + d_dev->debugfs, &d->boot_complete); + 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; + + return; + +err_handle: + dev_err(dev, "could not create debugfs\n"); + dce_remove_debug(d); +} 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..8d67ce6f --- /dev/null +++ b/drivers/platform/tegra/dce/dce-hsp-smb.c @@ -0,0 +1,244 @@ +/* + * 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 + +#define DCE_MAX_NO_SMB 8 +#define DCE_MAX_HSP_IE 8 + +/** + * smb_regs is a 1D 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. + */ +static u32 (*const smb_regs[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 1D 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. + */ +static u32 (*const smb_full_ie_regs[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 1D 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. + */ +static u32 (*const smb_empty_ie_regs[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) +{ + if (id >= DCE_MAX_NO_SMB) { + dce_err(d, "Invalid Shared Mailbox ID"); + return; + } + + dce_writel(d, smb_regs[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; + + if (id >= DCE_MAX_NO_SMB) { + dce_err(d, "Invalid Shared Mailbox ID"); + return; + } + + dce_writel(d, smb_full_ie_regs[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) +{ + if (id >= DCE_MAX_NO_SMB) { + dce_err(d, "Invalid Shared Mailbox ID"); + return 0xffffffff; /* TODO : Add DCE Error Numbers */ + } + + return dce_readl(d, smb_full_ie_regs[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; + + if (id >= DCE_MAX_NO_SMB) { + dce_err(d, "Invalid Shared Mailbox ID"); + return; + } + + dce_writel(d, smb_empty_ie_regs[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) +{ + if (id >= DCE_MAX_NO_SMB) { + dce_err(d, "Invalid Shared Mailbox ID : %x", id); + return 0xffffffff; /* TODO : Add DCE Error Numbers */ + } + + return dce_readl(d, smb_regs[id]()); +} + +/** + * hsp_int_ie_regs is a 1D 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. + */ +static u32 (*const hsp_int_ie_regs[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, +}; + +/** + * 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) +{ + if (id >= DCE_MAX_HSP_IE) { + dce_err(d, "Invalid Shared HSP IE ID"); + return 0xffffffff; /* TODO : Add DCE Error Numbers */ + } + + return dce_readl(d, hsp_int_ie_regs[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) +{ + if (id >= DCE_MAX_HSP_IE) { + dce_err(d, "Invalid Shared HSP IE ID"); + return; + } + + dce_writel(d, hsp_int_ie_regs[id](), + val | dce_readl(d, hsp_int_ie_regs[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) +{ + return dce_readl(d, hsp_int_ir_r()); +} 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..db42345e --- /dev/null +++ b/drivers/platform/tegra/dce/dce-hsp-ss.c @@ -0,0 +1,144 @@ +/* + * 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 + +#define DCE_MAX_NO_SS 4 + +/** + * ss_set_regs is a 1D 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. + */ +static u32 (*const ss_set_regs[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 1D 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. + */ +static u32 (*const ss_clear_regs[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 1D 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. + */ +static u32 (*const ss_state_regs[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) +{ + return dce_readl(d, ss_state_regs[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; + + if (id >= DCE_MAX_NO_SS) { + dce_err(d, "Invalid Shared Semaphore 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[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; + + if (id >= DCE_MAX_NO_SS) { + dce_err(d, "Invalid Shared Semaphore ID"); + return; + } + + val = dce_ss_get_state(d, id); + + dce_bitmap_set(&val, bpos, 1); + + dce_writel(d, ss_set_regs[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..5ff9d8df --- /dev/null +++ b/drivers/platform/tegra/dce/dce-init-deinit.c @@ -0,0 +1,76 @@ +/* + * 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 + +/** + * 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; + + 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_worker_thread_init(d); + if (ret) { + dce_err(d, "dce worker thread init failed"); + goto err_worker_thread_init; + } + + return ret; + +err_worker_thread_init: + dce_admin_deinit(d); +err_admin_interface_init: + dce_boot_interface_deinit(d); +err_boot_interface_init: + 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_worker_thread_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..2f661afa --- /dev/null +++ b/drivers/platform/tegra/dce/dce-ipc-signal.c @@ -0,0 +1,158 @@ +/* + * 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 +#include + +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..1a83b0f1 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-ipc.c @@ -0,0 +1,832 @@ +/* + * 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 +#include +#include + +#define CREATE_TRACE_POINTS +#include + +struct dce_ipc_channel ivc_channels[DCE_IPC_CH_KMD_TYPE_MAX] = { + [DCE_IPC_CH_KMD_TYPE_ADMIN] = { + .flags = DCE_IPC_CHANNEL_VALID, + .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, + .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_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_info(d, "Tot_raw_q_size : [%lu]", tot_q_sz); + + tot_ivc_q_sz = tegra_ivc_total_queue_size(tot_q_sz); + + dce_info(d, "Tot_aligned_q_size : [%lu]", tot_ivc_q_sz); + + region->size = dce_get_nxt_pow_of_2(&tot_ivc_q_sz, 32); + + dce_info(d, "Region size as a power of 2 : [%lu]", region->size); + + 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 ivc. + * + * Do not take a channel lock here. + * + * Return : Void. + */ +static void dce_ipc_signal_target(struct ivc *ivc) +{ + struct dce_ipc_channel *ch; + + ch = container_of(ivc, struct dce_ipc_channel, d_ivc); + + ch->signal.notify(ch->d, &ch->signal.to_d); + /** + * TODO : Get rid of prints + */ + dce_info(ch->d, "Notify : notify called for [%d]", ch->ch_type); +} + +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; + } + + dce_mutex_lock(&ch->lock); + + 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, w_type, ch_type); + + dce_mutex_lock(&ch->lock); + + ch->w_type = DCE_IPC_WAIT_TYPE_INVALID; + +out: + dce_mutex_unlock(&ch->lock); + 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 (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); + dce_mutex_destroy(&ch->lock); + 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; + return ret; + } + + 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); + + ret = tegra_ivc_init_with_dma_handle(&ch->d_ivc, + (uintptr_t)r->base + r->s_offset, + r->iova + r->s_offset, + (uintptr_t)r->base + r->s_offset + q_sz, + r->iova + r->s_offset + q_sz, + q_info->nframes, msg_sz, dev, dce_ipc_signal_target); + 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; + + + dce_info(d, "Info for chn_type [%d]", ch_type); + dce_info(d, "======================"); + dce_info(d, "msg_sz [%u]", msg_sz); + dce_info(d, "frame_sz [%u]", q_info->frame_sz); + dce_info(d, "Max frames [%u]", q_info->nframes); + dce_info(d, "q_sz [%u]", q_sz); + dce_info(d, "r->s_offset [%u]", r->s_offset); + dce_info(d, "base address [0x%p]", r->base); + dce_info(d, "q_info->rx_iova [0x%llx]", q_info->rx_iova); + dce_info(d, "q_info->tx_iova [0x%llx]", q_info->tx_iova); + dce_info(d, "======================"); + + trace_ivc_channel_init_complete(d, ch); + + d->d_ipc.ch[ch_type] = ch; + r->s_offset += (2 * q_sz); + + dce_mutex_unlock(&ch->lock); + +out_lock_destroy: + 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 ret; + + struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type]; + + dce_mutex_lock(&ch->lock); + + ret = (tegra_ivc_channel_notified(&ch->d_ivc) ? false : true); + + 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_channel_reset(&ch->d_ivc); + + dce_info(d, "Channel [%d] sync triggered", ch_type); + trace_ivc_channel_reset_triggered(d, ch); + + ch->flags &= ~DCE_IPC_CHANNEL_SYNCED; + + dce_mutex_unlock(&ch->lock); + + dce_ipc_wait(ch->d, DCE_IPC_WAIT_TYPE_SYNC, ch_type); + + dce_mutex_lock(&ch->lock); + + ch->flags |= DCE_IPC_CHANNEL_SYNCED; + + trace_ivc_channel_reset_complete(d, ch); + + dce_info(d, "Channel [%d] : Wait type : [%u]", ch_type, ch->w_type); + + 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) +{ + 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; + 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 ((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); + + 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; + } + /** + * TODO : Get rid of prints + */ + dce_info(d, "Send Message : Retrieved Next Buff to write: [%d]", + ch_type); + + ret = _dce_ipc_write_channel(ch, data, size); + if (ret) { + dce_err(ch->d, "Error writing to channel"); + goto out; + } + + /** + * TODO : Get rid of prints + */ + dce_info(d, "Send Message : Advanced Channel w_pos: [%d]", ch_type); + + ch->signal.notify(d, &ch->signal.to_d); + /** + * TODO : Get rid of prints + */ + dce_info(d, "Send Message : Notified target: [%d]", ch_type); + + 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) +{ + 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; + 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 ((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); + + 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_err(ch->d, "Error getting next free buf to read"); + goto out; + } + /** + * TODO : Get rid of prints + */ + dce_info(d, "Recv Message : Retrieved Next Buff to read: [%d]", + ch_type); + + + ret = _dce_ipc_read_channel(ch, data, size); + if (ret) { + dce_err(ch->d, "Error reading from channel"); + goto out; + } + /** + * TODO : Get rid of prints + */ + dce_info(d, "Recv Message : Advanced r_pos: [%d]", ch_type); + + 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; + } + /** + * TODO : Get rid of prints + */ + dce_info(d, "%s : Send successful, Waiting for recv: [%d]", + __func__, ch_type); + + + ret = dce_ipc_wait(ch->d, DCE_IPC_WAIT_TYPE_RPC, ch_type); + if (ret) { + dce_err(ch->d, "Error in waiting for ack"); + goto done; + } + /** + * TODO : Get rid of prints + */ + dce_info(d, "%s : Wait complete... Reading Message: [%d]", + __func__, ch_type); + + 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; + } + /** + * TODO : Get rid of prints + */ + dce_info(d, "Send Message Sync: Read Sucessful: [%d]", ch_type); + + +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; + struct dce_ipc_channel *ch = d->d_ipc.ch[ch_type]; + + dce_mutex_lock(&ch->lock); + + if (tegra_ivc_can_read(&ch->d_ivc)) + ret = true; + + 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..97280402 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-mailbox.c @@ -0,0 +1,272 @@ +/* + * 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 +#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); + + dce_info(d, "Mailbox INTR Rcvd. IRQ SOURCES = [%x]", irq_sources); + + 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_mailbox_store_interface_status(d, value, i); + d_mb->notify(d, d_mb->notify_data); + dce_smb_set(d, 0U, d_mb->r_mb); + } + 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, "Warning : 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_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_interface *d_mb; + + dce_info(d, "write cmd received for interface : %d", interface); + + 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_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..d396762c --- /dev/null +++ b/drivers/platform/tegra/dce/dce-module.c @@ -0,0 +1,257 @@ +/* + * 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 +#include +#include +#include +#include +#include + +static const struct dce_platform_data t234_dce_platform_data = { + .dce_stream_id = 0x05, + .phys_stream_id = 0x7f, + .fw_carveout_id = 9, + .fw_vmindex = 0, + .fw_name = "dce.bin", + .fw_dce_addr = 0x40000000, + .fw_info_valid = true, + .use_physical_id = false, +}; + +static const struct of_device_id tegra_dce_of_match[] = { + { + .compatible = "nvidia,tegra234-dce", + .data = (struct dce_platform_data *)&t234_dce_platform_data + }, + { }, +}; + +/** + * dce_set_pdata_dce - inline function to set the tegra_dce pointer in + * pdata point to actual tegra_dce data structure. + * + * @pdev : Pointer to the platform device data structure. + * @d : Pointer pointing to actual tegra_dce data structure. + * + * Return : Void. + */ +static inline void dce_set_pdata_dce(struct platform_device *pdev, + struct tegra_dce *d) +{ + ((struct dce_platform_data *)dev_get_drvdata(&pdev->dev))->d = d; +} + +/** + * dce_get_pdata_dce - inline function to get the tegra_dce pointer + * from platform devicve. + * + * @pdev : Pointer to the platform device data structure. + * @d : Pointer pointing to actual tegra_dce data structure. + * + * Return : Void. + */ +static inline struct tegra_dce *dce_get_pdata_dce(struct platform_device *pdev) +{ + return (((struct dce_platform_data *)dev_get_drvdata(&pdev->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 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; + + dce_set_pdata_dce(pdev, &d_dev->d); + d_dev->dev = dev; + 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; + } + + + 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_platform_data *pdata = NULL; + + pdata = dev_get_drvdata(&pdev->dev); + d = pdata->d; + + for (i = 0; i < pdata->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_platform_data *pdata = NULL; + + pdata = dev_get_drvdata(&pdev->dev); + d = pdata->d; + no_ints = of_irq_count(pdev->dev.of_node); + if (no_ints == 0) { + dev_err(&pdev->dev, + "Invalid number of interrupts configured = %d", + no_ints); + return -EINVAL; + } + + pdata->max_cpu_irqs = no_ints; + + for (i = 0; i < no_ints; i++) { + ret = of_irq_get(pdev->dev.of_node, 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 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; + + match = of_match_device(tegra_dce_of_match, dev); + 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; + } + dev_set_drvdata(dev, pdata); + + err = dce_init_dev_data(pdev); + 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 = pdata->d; + + 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 + 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); + dce_set_irqs(pdev, false); + dce_driver_deinit(d); + return 0; +} + +static struct platform_driver tegra_dce_driver = { + .driver = { + .name = "tegra-dce", + .of_match_table = + of_match_ptr(tegra_dce_of_match), + }, + .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-reset.c b/drivers/platform/tegra/dce/dce-reset.c new file mode 100644 index 00000000..c4a2c112 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-reset.c @@ -0,0 +1,84 @@ +/* + * 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_info(d, "EVP_RESET_ADDR_R : 0x%x", + dce_readl(d, evp_reset_addr_r())); +} + +/** + * 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_info(d, "PM_R5_CTRL_R : 0x%x", dce_readl(d, pm_r5_ctrl_r())); +} + +/** + * 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..2380b68e --- /dev/null +++ b/drivers/platform/tegra/dce/dce-util-common.c @@ -0,0 +1,600 @@ +/* + * 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 +#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; + u32 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; + + request_firmware(&l_fw, fw_name, dev); + + if (!l_fw) + goto err; + + /* Make sure the address is aligned to 4K */ + fw->size = l_fw->size; + dce_info(d, "Size of l_fw is %lu\n", 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; + dce_info(d, "Size of fw after alignment is %lu\n", fw->size); + + fw->data = dma_alloc_coherent(dev, fw->size, + (dma_addr_t *)&fw->dma_handle, + GFP_KERNEL); + if (!fw->data) + goto err_release; + + dce_info(d, "Value of dma_address 0x%llx\n", fw->dma_handle); + + 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)->dce_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_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); +}; + +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; +} diff --git a/drivers/platform/tegra/dce/dce-worker.c b/drivers/platform/tegra/dce/dce-worker.c new file mode 100644 index 00000000..b37d68b5 --- /dev/null +++ b/drivers/platform/tegra/dce/dce-worker.c @@ -0,0 +1,318 @@ +/* + * 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 +#include +#include +#include + +/** + * dce_worker_wakeup_cond - Generic wake up condition for + * dce_worker thread. + * + * @d : Pointer to struct tegra_dce. + * + * Return : Boolean + */ +static bool dce_worker_wakeup_cond(struct tegra_dce *d) +{ + struct dce_worker_info *w = &d->wrk_info; + + return (w->state_changed == true || + dce_thread_should_stop(&w->wrk_thread)); +} + +/** + * dce_worker_thread_wait - Will wait in a given state. + * + * @d : Pointer to tegra_dce struct. + * @event : Event that will trigger the wait. + * + * Will change state and wait based on the event that + * triggers the wait. + * + * Return : Void + */ +void dce_worker_thread_wait(struct tegra_dce *d, + enum dce_worker_event_id_type event) +{ + int ret; + u32 timeout_val_ms = 0; + enum dce_worker_state new_state; + struct dce_worker_info *w = &d->wrk_info; + + if (w->state_changed == true) { + dce_warn(d, "Unexpected state_changed value"); + return; + } + + switch (event) { + case EVENT_ID_DCE_BOOT_COMPLETE_IRQ_REQ_SET: + if ((w->c_state != STATE_DCE_WORKER_IDLE) && + (w->c_state != STATE_DCE_WORKER_BOOT_WAIT)) { + dce_warn(d, "Unexpected wait event [%d] rcvd in state [%d]", + event, w->c_state); + return; + } + new_state = STATE_DCE_WORKER_BOOT_WAIT; + break; + case EVENT_ID_DCE_IPC_SYNC_TRIGGERED: + case EVENT_ID_DCE_IPC_MESSAGE_SENT: + new_state = STATE_DCE_WORKER_WFI; + break; + case EVENT_ID_DCE_BOOT_COMPLETE: + new_state = STATE_DCE_WORKER_IDLE; + break; + default: + dce_warn(d, "Invalid wait event [%d] rcvd in state [%d]", + event, w->c_state); + return; + } + + dce_mutex_lock(&w->lock); + w->c_state = new_state; + dce_mutex_unlock(&w->lock); + + if (new_state == STATE_DCE_WORKER_BOOT_WAIT) + timeout_val_ms = 1000; + + ret = DCE_COND_WAIT_INTERRUPTIBLE(&w->cond, + dce_worker_wakeup_cond(d), + timeout_val_ms); + if (ret) + dce_info(d, "Timeout occurred in event [%d] & state [%d]", + event, w->c_state); + + dce_mutex_lock(&w->lock); + + w->state_changed = false; + + if (ret) + w->c_state = STATE_DCE_WORKER_IDLE; + + dce_mutex_unlock(&w->lock); +} + +/** + * dce_worker_thread_wakeup - Wakeup the dce worker thread + * + * @d : Pointer to tegra_dce struct. + * @event : Event that will trigger the wakeup. + * + * Will change state and wakeup the worker thread based on + * the event that triggers it. + * + * Return : Void + */ +void dce_worker_thread_wakeup(struct tegra_dce *d, + enum dce_worker_event_id_type event) +{ + struct dce_worker_info *w = &d->wrk_info; + enum dce_worker_state new_state = w->c_state; + + if (w->state_changed == true) { + dce_warn(d, "Unexpected state_changed value"); + dce_mutex_unlock(&w->lock); + return; + } + + switch (event) { + case EVENT_ID_DCE_IPC_SIGNAL_RECEIVED: + if (w->c_state != STATE_DCE_WORKER_WFI) { + dce_warn(d, "Unexpected wakeup event rcvd: [%d]. Cur State: [%d]", + event, w->c_state); + return; + } + new_state = STATE_DCE_WORKER_IDLE; + break; + case EVENT_ID_DCE_BOOT_COMPLETE_IRQ_RECEIVED: + if (w->c_state != STATE_DCE_WORKER_BOOT_WAIT) { + dce_warn(d, "Unexpected wakeup event rcvd: [%d]. Cur State: [%d]", + event, w->c_state); + return; + } + new_state = STATE_DCE_WORKER_IDLE; + break; + case EVENT_ID_DCE_INTERFACE_ERROR_RECEIVED: + new_state = STATE_DCE_WORKER_HANDLE_DCE_ERROR; + dce_warn(d, "Error Event Rcvd: [%d]. Cur State: [%d]", + event, w->c_state); + break; + case EVENT_ID_DCE_THREAD_ABORT_REQ_RECEIVED: + if (dce_thread_should_stop(&w->wrk_thread)) + new_state = STATE_DCE_WORKER_ABORTED; + else + dce_warn(d, "Unexpected wakeup event rcvd: [%d]. Cur State: [%d]", + event, w->c_state); + break; + default: + dce_warn(d, "Unexpected wakeup event rcvd: [%d]. Cur State: [%d]", + event, w->c_state); + return; + } + + dce_mutex_lock(&w->lock); + w->c_state = new_state; + w->state_changed = true; + dce_mutex_unlock(&w->lock); + + dce_cond_signal_interruptible(&w->cond); +} + +static void dce_handle_dce_error(struct tegra_dce *d) +{ + /** + * TODO : Handle error messages from DCE + */ +} + + +/** + * dce_worker - dce worker main function to manage dce thread states. + * + * @arg : Void pointer to be typecast to tegra_dce struct before use. + * + * Return : 0 on success. + */ +static int dce_worker(void *arg) +{ + int ret = 0; + struct tegra_dce *d = (struct tegra_dce *)arg; + struct dce_worker_info *w = &d->wrk_info; + + dce_info(d, "Starting DCE Worker Thread..."); + ret = dce_wait_boot_complete(d); + if (ret) { + dce_warn(d, "DCE Boot didn't complete"); + goto worker_exit; + } + + dce_info(d, "DCE Ready to bootstrap ..."); + + ret = dce_start_bootstrap_flow(d); + if (ret) { + dce_warn(d, "DCE Bootstrap flow didn't complete"); + goto worker_exit; + } + + dce_info(d, "DCE Bootstrapping Complete..."); + + dce_admin_ivc_channel_reset(d); + + dce_info(d, "DCE Admin Channel Reset Complete..."); + + ret = dce_start_admin_seq(d); + if (ret) + dce_warn(d, "DCE Admin flow didn't complete"); + + dce_worker_thread_wait(d, EVENT_ID_DCE_BOOT_COMPLETE); + + while ((w->c_state != STATE_DCE_WORKER_ABORTED) || + (!dce_thread_should_stop(&w->wrk_thread))) { + if (w->c_state == STATE_DCE_WORKER_HANDLE_DCE_ERROR) + dce_handle_dce_error(d); + } + +worker_exit: + if (w->c_state == STATE_DCE_WORKER_ABORTED) + dce_info(d, "Exiting Dce Worker Thread"); + return 0; +} + +/** + * dce_worker_get_state - Gets the current state of dce_worker + * + * @d : Pointer to tegra_dce struct + * + * Return : Current state + */ +enum dce_worker_state dce_worker_get_state(struct tegra_dce *d) +{ + return d->wrk_info.c_state; +} + +/** + * dce_worker_thread_init - Initializes the worker thread and + * its corresponding resources. + * + * @d : Pointer to tegra_dce struct. + * + * Return : 0 if success. + */ +int dce_worker_thread_init(struct tegra_dce *d) +{ + int ret = 0; + struct dce_worker_info *w + = &d->wrk_info; + + ret = dce_cond_init(&w->cond); + if (ret) { + dce_err(d, "dce condition initialization failed for worker"); + goto err_cond_init; + } + + ret = dce_mutex_init(&w->lock); + if (ret) { + dce_err(d, "dce condition initialization failed for worker"); + goto err_lock_init; + } + + dce_mutex_lock(&w->lock); + + w->state_changed = false; + + w->c_state = STATE_DCE_WORKER_IDLE; + + ret = dce_thread_create(&w->wrk_thread, d, + dce_worker, "dce_worker_thread"); + if (ret) { + dce_err(d, "Dce Worker Thread creation failed"); + dce_mutex_unlock(&w->lock); + goto err_thread_create; + } + + dce_mutex_unlock(&w->lock); + + return ret; + +err_thread_create: + dce_mutex_destroy(&w->lock); +err_lock_init: + dce_cond_destroy(&w->cond); +err_cond_init: + return ret; +} + +/** + * dce_worker_thread_deinit - Kill the dce worker thread and + * its corresponding resources. + * + * @d : Pointer to tegra_dce struct. + * + * Return :Void + */ +void dce_worker_thread_deinit(struct tegra_dce *d) +{ + struct dce_worker_info *w = &d->wrk_info; + + if (dce_thread_is_running(&w->wrk_thread)) + dce_thread_stop(&w->wrk_thread); + + dce_thread_join(&w->wrk_thread); + + dce_mutex_destroy(&w->lock); + + dce_cond_destroy(&w->cond); +} 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..842b715c --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-client-ipc-internal.h @@ -0,0 +1,34 @@ +/* + * 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_CLIENT_IPC_INTERNAL_H +#define DCE_CLIENT_IPC_INTERNAL_H + +#include + +struct tegra_dce_client_ipc { + bool valid; + void *data; + uint32_t type; + uint32_t int_type; + struct tegra_dce *d; + struct dce_ivc_channel *ch; + struct completion recv_wait; + tegra_dce_client_ipc_callback_t callback_fn; +}; + +void dce_client_ipc_wakeup(struct tegra_dce *d, u32 ch_type); + +int dce_client_ipc_wait(struct tegra_dce *d, u32 w_type, u32 ch_type); + +#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..0f5886b3 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-cond.h @@ -0,0 +1,102 @@ +/* + * 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_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 + * @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(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 - 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(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-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..d39ab07b --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-ipc.h @@ -0,0 +1,188 @@ +/* + * 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_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 2U + +/** + * 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_ADMIN_CMD_MAX_FSIZE 1024U + +#define DCE_IPC_WAIT_TYPE_INVALID 0U +#define DCE_IPC_WAIT_TYPE_SYNC 1U +#define DCE_IPC_WAIT_TYPE_RPC 2U + +#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_MAX 3U +/** + * 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 __iomem *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; + void *ibuff; + void *obuff; + struct 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); + +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..edad0386 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-log.h @@ -0,0 +1,69 @@ +/* + * 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_LOG_H +#define DCE_LOG_H + +struct tegra_dce; + +enum dce_log_type { + DCE_ERROR, + DCE_WARNING, + DCE_INFO, +}; + +/* + * 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) + +#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..e4c05d2b --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-mailbox.h @@ -0,0 +1,66 @@ +/* + * 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_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_MAX_INTERFACES 3U + +/** + * 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_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-regs.h b/drivers/platform/tegra/dce/include/dce-regs.h new file mode 100644 index 00000000..0ade4950 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-regs.h @@ -0,0 +1,23 @@ +/* + * 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 + +#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..27d0d23f --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-util-common.h @@ -0,0 +1,63 @@ +/* + * 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_UTIL_COMMON_H +#define DCE_UTIL_COMMON_H + +#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..53f522e1 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce-worker.h @@ -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. + */ + +#ifndef DCE_WORKER_H +#define DCE_WORKER_H + +#include +#include +#include + +struct tegra_dce; + +/** + * enum dce_worker_event_id_type - IDs to be used to convey various + * events thoughout the life cycle of dce worker thread. + */ +enum dce_worker_event_id_type { + EVENT_ID_DCE_BOOT_COMPLETE_IRQ_REQ_SET = 0, + EVENT_ID_DCE_BOOT_COMPLETE_IRQ_RECEIVED = 1, + EVENT_ID_DCE_IPC_SYNC_TRIGGERED = 2, + EVENT_ID_DCE_IPC_MESSAGE_SENT = 3, + EVENT_ID_DCE_IPC_SIGNAL_RECEIVED = 4, + EVENT_ID_DCE_THREAD_ABORT_REQ_RECEIVED = 5, + EVENT_ID_DCE_INTERFACE_ERROR_RECEIVED = 6, + EVENT_ID_DCE_BOOT_COMPLETE = 7, +}; + +/** + * dce_worker_state - Different states of dce worker thread. + */ +enum dce_worker_state { + STATE_DCE_WORKER_IDLE = 0, + STATE_DCE_WORKER_BOOT_WAIT = 1, + STATE_DCE_WORKER_WFI = 2, + STATE_DCE_WORKER_ABORTED = 3, + STATE_DCE_WORKER_HANDLE_DCE_ERROR = 4, +}; + +/** + * struct dce_worker_info - Contains information to control and manage + * dce worker thread throughout its lifecycle. + * @wrk_thread : Event driven dce thread to manage initilaization and + * release workflow. + * @state_changed : Boolean to convey state changes. + * @c_state : Stores the current state of dce worker thread. State changes + * are triggered by vrious events. + * @lock : Used for exclusive state modifications from thread and + * interrupt context. + * @cond : dce_cond to manage various thread states. + */ +struct dce_worker_info { + struct dce_thread wrk_thread; + bool state_changed; + enum dce_worker_state c_state; + struct dce_mutex lock; + struct dce_cond cond; +}; + +void dce_worker_thread_wait(struct tegra_dce *d, + enum dce_worker_event_id_type event); + +void dce_worker_thread_wakeup(struct tegra_dce *d, + enum dce_worker_event_id_type event); + +enum dce_worker_state dce_worker_get_state(struct tegra_dce *d); + +int dce_worker_thread_init(struct tegra_dce *d); + +void dce_worker_thread_deinit(struct tegra_dce *d); + +#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..dec9fa21 --- /dev/null +++ b/drivers/platform/tegra/dce/include/dce.h @@ -0,0 +1,325 @@ +/* + * 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_H +#define TEGRA_DCE_H + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define DCE_MAX_CPU_IRQS 4 + +struct tegra_dce; + +/** + * struct dce_platform_data - Data Structure to hold platform specific DCE + * cluster data. + */ +struct dce_platform_data { + /** + * @d : Pointer to OS agnostic dce struct. Stores all runitme info + * for dce cluster elements. + */ + struct tegra_dce *d; + /** + * @max_cpu_irqs : stores maximum no. os irqs from DCE cluster to CPU + * for this platform. + */ + u8 max_cpu_irqs; + /** + * @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; + /** + * dce_stream_id : DCE stream ID to program the ASTs in debug mode + * only. + */ + u32 dce_stream_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]; + /** + * @wrk_info - Data Structure to manage dce worker thread states. + */ + struct dce_worker_info wrk_info; + /** + * @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]; + /** + * @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; + /** + * @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; +#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 dev_get_drvdata(dev_from_dce(d)); +} + +/** + * 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; +} + +/** + * 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_wait_boot_complete(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_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_ipc_wait(struct tegra_dce *d, u32 w_type); +void dce_admin_ipc_handle_signal(struct tegra_dce *d, u32 ch_type); + +/** + * 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); + +void dce_init_debug(struct tegra_dce *d); +#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..daa6b572 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-admin-cmds.h @@ -0,0 +1,156 @@ +/* + * Copyright (c) 2018-2020, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA Corporation and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA Corporation is strictly prohibited. + */ + +#ifndef DCE_ADMIN_CMDS_H +#define DCE_ADMIN_CMDS_H + +#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 2 + +#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 0x02U // echo data back to CCPLEX +#define DCE_ADMIN_CMD_MEM_MAP 0x03U // map a region of memory +#define DCE_ADMIN_CMD_MEM_INFO 0x04U // return info about a region +#define DCE_ADMIN_CMD_IPC_INFO 0x05U // return IPC chan info +#define DCE_ADMIN_CMD_IPC_CREATE 0x06U // create an IPC channel +#define DCE_ADMIN_CMD_PREPARE_SC7 0x07U // prepare to enter SC7 +#define DCE_ADMIN_CMD_ENTER_SC7 0x08U // enter SC7 +#define DCE_ADMIN_CMD_SET_LOGGING 0x09U // set logging level +#define DCE_ADMIN_CMD_GET_LOG_INFO 0x08U // get current log info +#define DCE_ADMIN_CMD_LOCK_CHANGES 0x0AU // lock creating new channels + // and changing memory areas +#define DCE_ADMIN_CMD_CODE_COVERAGE_START 0x0BU // start collecting code + // coverage data +#define DCE_ADMIN_CMD_CODE_COVERAGE_STOP 0x0CU // stop collecting code + // coverage data +#define DCE_ADMIN_CMD_PERF_START 0x0DU // start collecting perf data +#define DCE_ADMIN_CMD_PERF_STOP 0x0EU // stop collecting perf data +#define DCE_ADMIN_CMD_TEST_START 0x0FU // start tests +#define DCE_ADMIN_CMD_TEST_STOP 0x10U // stop tests and return status +#define DCE_ADMIN_CMD_DEBUG 0x11U // debug command + +#define DCE_ADMIN_CMD_RM_BOOTSTRAP 0x12U // tell RM to "bootstrap" + +#define DCE_ADMIN_CMD_NEXT 0x13U // 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; +}; + +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_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; + } 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; + } args; +}; + +#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..848bd68f --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-bitops.h @@ -0,0 +1,27 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA Corporation and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA Corporation is strictly prohibited. + */ + +#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..6f52e675 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-boot-cmds.h @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2018-2020, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA Corporation and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA Corporation is strictly prohibited. + */ + +#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-ipc-types.h b/drivers/platform/tegra/dce/include/interface/dce-core-interface-ipc-types.h new file mode 100644 index 00000000..d7bbb2ae --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-core-interface-ipc-types.h @@ -0,0 +1,34 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA Corporation and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA Corporation is strictly prohibited. + */ + +#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..a716fd24 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-driver-header-version.h @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA Corporation and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA Corporation is strictly prohibited. + */ + +#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..50a1512a --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-interface.h @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2016-2020, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA Corporation and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA Corporation is strictly prohibited. + */ + +#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 + +/* + * 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_BPMP (hsp_mbox_t)2U // signal from BPMP IPC +#define DCE_MBOX_TO_BPMP (hsp_mbox_t)3U // signal to BPMP 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..2d3d6201 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-ipc-header.h @@ -0,0 +1,18 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA Corporation and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA Corporation is strictly prohibited. + */ + +#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..16d98aaf --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-ipc-state.h @@ -0,0 +1,36 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA Corporation and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA Corporation is strictly prohibited. + */ + +#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..81e52ad7 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-memory-ids.h @@ -0,0 +1,31 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA Corporation and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA Corporation is strictly prohibited. + */ + +#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..f9859187 --- /dev/null +++ b/drivers/platform/tegra/dce/include/interface/dce-types.h @@ -0,0 +1,16 @@ +/* + * Copyright (c) 2020, NVIDIA CORPORATION. All rights reserved. + * + * NVIDIA Corporation and its licensors retain all intellectual property + * and proprietary rights in and to this software, related documentation + * and any modifications thereto. Any use, reproduction, disclosure or + * distribution of this software and related documentation without an express + * license agreement from NVIDIA Corporation is strictly prohibited. + */ + +#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..a2e147b8 --- /dev/null +++ b/include/linux/platform/tegra/dce/dce-client-ipc.h @@ -0,0 +1,83 @@ +/* + * 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_MAX 2U + +/** + * 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..198397ac --- /dev/null +++ b/include/trace/events/dce_events.h @@ -0,0 +1,98 @@ +/* + * include/trace/events/dce_events.h + * + * Display event logging to ftrace. + * + * Copyright (c) 2020-, 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.w_pos, __entry->ch->d_ivc.r_pos, + __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