/* Copyright (c) 2012-2013, The Linux Foundation. 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 version 2 and * only version 2 as published by the Free Software Foundation. * * 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. * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include "hfi_packetization.h" #include "venus_hfi.h" #include "vidc_hfi_io.h" #include "msm_vidc_debug.h" #define FIRMWARE_SIZE 0X00A00000 #define REG_ADDR_OFFSET_BITMASK 0x000FFFFF /*Workaround for simulator */ #define HFI_SIM_FW_BIAS 0x0 #define SHARED_QSIZE 0x1000000 static struct hal_device_data hal_ctxt; static const u32 venus_qdss_entries[][2] = { {0xFC307000, 0x1000}, {0xFC322000, 0x1000}, {0xFC319000, 0x1000}, {0xFC31A000, 0x1000}, {0xFC31B000, 0x1000}, {0xFC321000, 0x1000}, {0xFA180000, 0x1000}, {0xFA181000, 0x1000}, }; #define TZBSP_MEM_PROTECT_VIDEO_VAR 0x8 struct tzbsp_memprot { u32 cp_start; u32 cp_size; u32 cp_nonpixel_start; u32 cp_nonpixel_size; }; struct tzbsp_resp { int ret; }; static void venus_hfi_dump_packet(u8 *packet) { u32 c = 0, packet_size = *(u32 *)packet; const int row_size = 32; /* row must contain enough for 0xdeadbaad * 8 to be converted into * "de ad ba ab " * 8 + '\0' */ char row[3 * row_size]; for (c = 0; c * row_size < packet_size; ++c) { int bytes_to_read = ((c + 1) * row_size > packet_size) ? packet_size % row_size : row_size; hex_dump_to_buffer(packet + c * row_size, bytes_to_read, row_size, 4, row, sizeof(row), false); dprintk(VIDC_PKT, "%s\n", row); } } static void venus_hfi_sim_modify_cmd_packet(u8 *packet) { struct hfi_cmd_sys_session_init_packet *sys_init; struct hal_session *sess; u8 i; if (!packet) { dprintk(VIDC_ERR, "Invalid Param"); return; } sys_init = (struct hfi_cmd_sys_session_init_packet *)packet; sess = (struct hal_session *) sys_init->session_id; switch (sys_init->packet_type) { case HFI_CMD_SESSION_EMPTY_BUFFER: if (sess->is_decoder) { struct hfi_cmd_session_empty_buffer_compressed_packet *pkt = (struct hfi_cmd_session_empty_buffer_compressed_packet *) packet; pkt->packet_buffer -= HFI_SIM_FW_BIAS; } else { struct hfi_cmd_session_empty_buffer_uncompressed_plane0_packet *pkt = (struct hfi_cmd_session_empty_buffer_uncompressed_plane0_packet *) packet; pkt->packet_buffer -= HFI_SIM_FW_BIAS; } break; case HFI_CMD_SESSION_FILL_BUFFER: { struct hfi_cmd_session_fill_buffer_packet *pkt = (struct hfi_cmd_session_fill_buffer_packet *)packet; pkt->packet_buffer -= HFI_SIM_FW_BIAS; break; } case HFI_CMD_SESSION_SET_BUFFERS: { struct hfi_cmd_session_set_buffers_packet *pkt = (struct hfi_cmd_session_set_buffers_packet *)packet; if ((pkt->buffer_type == HFI_BUFFER_OUTPUT) || (pkt->buffer_type == HFI_BUFFER_OUTPUT2)) { struct hfi_buffer_info *buff; buff = (struct hfi_buffer_info *) pkt->rg_buffer_info; buff->buffer_addr -= HFI_SIM_FW_BIAS; buff->extra_data_addr -= HFI_SIM_FW_BIAS; } else { for (i = 0; i < pkt->num_buffers; i++) pkt->rg_buffer_info[i] -= HFI_SIM_FW_BIAS; } break; } case HFI_CMD_SESSION_RELEASE_BUFFERS: { struct hfi_cmd_session_release_buffer_packet *pkt = (struct hfi_cmd_session_release_buffer_packet *)packet; if ((pkt->buffer_type == HFI_BUFFER_OUTPUT) || (pkt->buffer_type == HFI_BUFFER_OUTPUT2)) { struct hfi_buffer_info *buff; buff = (struct hfi_buffer_info *) pkt->rg_buffer_info; buff->buffer_addr -= HFI_SIM_FW_BIAS; buff->extra_data_addr -= HFI_SIM_FW_BIAS; } else { for (i = 0; i < pkt->num_buffers; i++) pkt->rg_buffer_info[i] -= HFI_SIM_FW_BIAS; } break; } case HFI_CMD_SESSION_PARSE_SEQUENCE_HEADER: { struct hfi_cmd_session_parse_sequence_header_packet *pkt = (struct hfi_cmd_session_parse_sequence_header_packet *) packet; pkt->packet_buffer -= HFI_SIM_FW_BIAS; break; } case HFI_CMD_SESSION_GET_SEQUENCE_HEADER: { struct hfi_cmd_session_get_sequence_header_packet *pkt = (struct hfi_cmd_session_get_sequence_header_packet *) packet; pkt->packet_buffer -= HFI_SIM_FW_BIAS; break; } default: break; } } static int venus_hfi_write_queue(void *info, u8 *packet, u32 *rx_req_is_set) { struct hfi_queue_header *queue; u32 packet_size_in_words, new_write_idx; struct vidc_iface_q_info *qinfo; u32 empty_space, read_idx; u32 *write_ptr; if (!info || !packet || !rx_req_is_set) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } qinfo = (struct vidc_iface_q_info *) info; if (!qinfo || !qinfo->q_array.align_virtual_addr) { dprintk(VIDC_WARN, "Queues have already been freed\n"); return -EINVAL; } queue = (struct hfi_queue_header *) qinfo->q_hdr; if (!queue) { dprintk(VIDC_ERR, "queue not present"); return -ENOENT; } venus_hfi_sim_modify_cmd_packet(packet); if (msm_vidc_debug & VIDC_PKT) { dprintk(VIDC_PKT, "%s: %p\n", __func__, qinfo); venus_hfi_dump_packet(packet); } packet_size_in_words = (*(u32 *)packet) >> 2; dprintk(VIDC_DBG, "Packet_size in words: %d", packet_size_in_words); if (packet_size_in_words == 0) { dprintk(VIDC_ERR, "Zero packet size"); return -ENODATA; } read_idx = queue->qhdr_read_idx; empty_space = (queue->qhdr_write_idx >= read_idx) ? (queue->qhdr_q_size - (queue->qhdr_write_idx - read_idx)) : (read_idx - queue->qhdr_write_idx); dprintk(VIDC_DBG, "Empty_space: %d", empty_space); if (empty_space <= packet_size_in_words) { queue->qhdr_tx_req = 1; dprintk(VIDC_ERR, "Insufficient size (%d) to write (%d)", empty_space, packet_size_in_words); return -ENOTEMPTY; } queue->qhdr_tx_req = 0; new_write_idx = (queue->qhdr_write_idx + packet_size_in_words); write_ptr = (u32 *)((qinfo->q_array.align_virtual_addr) + (queue->qhdr_write_idx << 2)); dprintk(VIDC_DBG, "Write Ptr: %d", (u32) write_ptr); if (new_write_idx < queue->qhdr_q_size) { memcpy(write_ptr, packet, packet_size_in_words << 2); } else { new_write_idx -= queue->qhdr_q_size; memcpy(write_ptr, packet, (packet_size_in_words - new_write_idx) << 2); memcpy((void *)qinfo->q_array.align_virtual_addr, packet + ((packet_size_in_words - new_write_idx) << 2), new_write_idx << 2); } /* Memory barrier to make sure packet is written before updating the * write index */ mb(); queue->qhdr_write_idx = new_write_idx; *rx_req_is_set = (1 == queue->qhdr_rx_req) ? 1 : 0; /*Memory barrier to make sure write index is updated before an * interupt is raised on venus.*/ mb(); dprintk(VIDC_DBG, "Out : "); return 0; } static void venus_hfi_hal_sim_modify_msg_packet(u8 *packet) { struct hfi_msg_sys_session_init_done_packet *sys_idle; struct hal_session *sess; if (!packet) { dprintk(VIDC_ERR, "Invalid Param: "); return; } sys_idle = (struct hfi_msg_sys_session_init_done_packet *)packet; sess = (struct hal_session *) sys_idle->session_id; switch (sys_idle->packet_type) { case HFI_MSG_SESSION_FILL_BUFFER_DONE: if (sess->is_decoder) { struct hfi_msg_session_fbd_uncompressed_plane0_packet *pkt_uc = (struct hfi_msg_session_fbd_uncompressed_plane0_packet *) packet; pkt_uc->packet_buffer += HFI_SIM_FW_BIAS; } else { struct hfi_msg_session_fill_buffer_done_compressed_packet *pkt = (struct hfi_msg_session_fill_buffer_done_compressed_packet *) packet; pkt->packet_buffer += HFI_SIM_FW_BIAS; } break; case HFI_MSG_SESSION_EMPTY_BUFFER_DONE: { struct hfi_msg_session_empty_buffer_done_packet *pkt = (struct hfi_msg_session_empty_buffer_done_packet *)packet; pkt->packet_buffer += HFI_SIM_FW_BIAS; break; } case HFI_MSG_SESSION_GET_SEQUENCE_HEADER_DONE: { struct hfi_msg_session_get_sequence_header_done_packet *pkt = (struct hfi_msg_session_get_sequence_header_done_packet *) packet; pkt->sequence_header += HFI_SIM_FW_BIAS; break; } default: break; } } static int venus_hfi_read_queue(void *info, u8 *packet, u32 *pb_tx_req_is_set) { struct hfi_queue_header *queue; u32 packet_size_in_words, new_read_idx; u32 *read_ptr; struct vidc_iface_q_info *qinfo; int rc = 0; if (!info || !packet || !pb_tx_req_is_set) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } qinfo = (struct vidc_iface_q_info *) info; if (!qinfo || !qinfo->q_array.align_virtual_addr) { dprintk(VIDC_WARN, "Queues have already been freed\n"); return -EINVAL; } /*Memory barrier to make sure data is valid before *reading it*/ mb(); queue = (struct hfi_queue_header *) qinfo->q_hdr; if (!queue) { dprintk(VIDC_ERR, "Queue memory is not allocated\n"); return -ENOMEM; } if (queue->qhdr_read_idx == queue->qhdr_write_idx) { queue->qhdr_rx_req = 1; *pb_tx_req_is_set = 0; return -EPERM; } read_ptr = (u32 *)((qinfo->q_array.align_virtual_addr) + (queue->qhdr_read_idx << 2)); packet_size_in_words = (*read_ptr) >> 2; dprintk(VIDC_DBG, "packet_size_in_words: %d", packet_size_in_words); if (packet_size_in_words == 0) { dprintk(VIDC_ERR, "Zero packet size"); return -ENODATA; } new_read_idx = queue->qhdr_read_idx + packet_size_in_words; dprintk(VIDC_DBG, "Read Ptr: %d", (u32) new_read_idx); if (((packet_size_in_words << 2) <= VIDC_IFACEQ_MED_PKT_SIZE) && queue->qhdr_read_idx <= queue->qhdr_q_size) { if (new_read_idx < queue->qhdr_q_size) { memcpy(packet, read_ptr, packet_size_in_words << 2); } else { new_read_idx -= queue->qhdr_q_size; memcpy(packet, read_ptr, (packet_size_in_words - new_read_idx) << 2); memcpy(packet + ((packet_size_in_words - new_read_idx) << 2), (u8 *)qinfo->q_array.align_virtual_addr, new_read_idx << 2); } } else { dprintk(VIDC_WARN, "BAD packet received, read_idx: 0x%x, pkt_size: %d\n", queue->qhdr_read_idx, packet_size_in_words << 2); dprintk(VIDC_WARN, "Dropping this packet\n"); new_read_idx = queue->qhdr_write_idx; rc = -ENODATA; } queue->qhdr_read_idx = new_read_idx; if (queue->qhdr_read_idx != queue->qhdr_write_idx) queue->qhdr_rx_req = 0; else queue->qhdr_rx_req = 1; *pb_tx_req_is_set = (1 == queue->qhdr_tx_req) ? 1 : 0; venus_hfi_hal_sim_modify_msg_packet(packet); if (msm_vidc_debug & VIDC_PKT) { dprintk(VIDC_PKT, "%s: %p\n", __func__, qinfo); venus_hfi_dump_packet(packet); } dprintk(VIDC_DBG, "Out : "); return rc; } static int venus_hfi_alloc(void *mem, void *clnt, u32 size, u32 align, u32 flags, u32 usage) { struct vidc_mem_addr *vmem; struct msm_smem *alloc; int rc = 0; if (!mem || !clnt || !size) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } vmem = (struct vidc_mem_addr *)mem; dprintk(VIDC_INFO, "start to alloc: size:%d, Flags: %d", size, flags); alloc = msm_smem_alloc(clnt, size, align, flags, usage, 1); dprintk(VIDC_DBG, "Alloc done"); if (!alloc) { dprintk(VIDC_ERR, "Alloc failed\n"); rc = -ENOMEM; goto fail_smem_alloc; } dprintk(VIDC_DBG, "venus_hfi_alloc:ptr=%p,size=%d", alloc->kvaddr, size); rc = msm_smem_cache_operations(clnt, alloc, SMEM_CACHE_CLEAN); if (rc) { dprintk(VIDC_WARN, "Failed to clean cache\n"); dprintk(VIDC_WARN, "This may result in undefined behavior\n"); } vmem->mem_size = alloc->size; vmem->mem_data = alloc; vmem->align_virtual_addr = (u8 *) alloc->kvaddr; vmem->align_device_addr = (u8 *)alloc->device_addr; return rc; fail_smem_alloc: return rc; } static void venus_hfi_free(struct smem_client *clnt, struct msm_smem *mem) { msm_smem_free(clnt, mem); } static void venus_hfi_write_register(struct venus_hfi_device *device, u32 reg, u32 value, u8 *vaddr) { u32 hwiosymaddr = reg; u8 *base_addr; if (!device) { dprintk(VIDC_ERR, "Invalid params: %p\n", device); return; } base_addr = device->hal_data->register_base_addr; if (!device->clocks_enabled) { dprintk(VIDC_WARN, "HFI Write register failed : Clocks are OFF\n"); return; } reg &= REG_ADDR_OFFSET_BITMASK; if (reg == (u32)VIDC_CPU_CS_SCIACMDARG2) { /* workaround to offset of FW bias */ struct hfi_queue_header *qhdr; struct hfi_queue_table_header *qtbl_hdr = (struct hfi_queue_table_header *)vaddr; qhdr = VIDC_IFACEQ_GET_QHDR_START_ADDR(qtbl_hdr, 0); qhdr->qhdr_start_addr -= HFI_SIM_FW_BIAS; qhdr = VIDC_IFACEQ_GET_QHDR_START_ADDR(qtbl_hdr, 1); qhdr->qhdr_start_addr -= HFI_SIM_FW_BIAS; qhdr = VIDC_IFACEQ_GET_QHDR_START_ADDR(qtbl_hdr, 2); qhdr->qhdr_start_addr -= HFI_SIM_FW_BIAS; value -= HFI_SIM_FW_BIAS; } hwiosymaddr = ((u32)base_addr + (hwiosymaddr)); dprintk(VIDC_DBG, "Base addr: 0x%x, written to: 0x%x, Value: 0x%x...", (u32)base_addr, hwiosymaddr, value); writel_relaxed(value, hwiosymaddr); wmb(); } static int venus_hfi_read_register(struct venus_hfi_device *device, u32 reg) { int rc ; u8 *base_addr; if (!device) { dprintk(VIDC_ERR, "Invalid params: %p\n", device); return -EINVAL; } base_addr = device->hal_data->register_base_addr; if (!device->clocks_enabled) { dprintk(VIDC_WARN, "HFI Read register failed : Clocks are OFF\n"); return -EINVAL; } rc = readl_relaxed((u32)base_addr + reg); rmb(); return rc; } static inline void venus_hfi_clk_gating_on(struct venus_hfi_device *device) { int i; struct venus_core_clock *cl; if (!device) { dprintk(VIDC_ERR, "Invalid params: %p\n", device); return; } if (!device->clocks_enabled) { dprintk(VIDC_DBG, "Clocks are already disabled"); goto already_disabled; } for (i = 0; i <= device->clk_gating_level; i++) { cl = &device->resources.clock[i]; clk_disable(cl->clk); } already_disabled: device->clocks_enabled = 0; } static inline int venus_hfi_clk_gating_off(struct venus_hfi_device *device) { int i; struct venus_core_clock *cl; int rc = 0; if (!device) { dprintk(VIDC_ERR, "Invalid params: %p\n", device); return -EINVAL; } if (device->clocks_enabled) { dprintk(VIDC_DBG, "Clocks are already enabled"); goto already_enabled; } for (i = 0; i <= device->clk_gating_level; i++) { cl = &device->resources.clock[i]; rc = clk_enable(cl->clk); if (rc) { dprintk(VIDC_ERR, "Failed to enable clocks\n"); goto fail_clk_enable; } else { dprintk(VIDC_DBG, "Clock: %s enabled\n", cl->name); } } already_enabled: device->clocks_enabled = 1; return rc; fail_clk_enable: for (; i >= 0; i--) { cl = &device->resources.clock[i]; clk_disable(cl->clk); } return rc; } static unsigned long venus_hfi_get_clock_rate(struct venus_core_clock *clock, int num_mbs_per_sec) { int num_rows = clock->count; struct load_freq_table *table = clock->load_freq_tbl; unsigned long ret = table[0].freq; int i; for (i = 0; i < num_rows; i++) { if (num_mbs_per_sec > table[i].load) break; ret = table[i].freq; } dprintk(VIDC_PROF, "Required clock rate = %lu\n", ret); return ret; } static int venus_hfi_scale_clocks(void *dev, int load) { int rc = 0; struct venus_hfi_device *device = dev; if (!device) { dprintk(VIDC_ERR, "Invalid args: %p\n", device); return -EINVAL; } device->load = load; rc = clk_set_rate(device->resources.clock[VCODEC_CLK].clk, venus_hfi_get_clock_rate(&device->resources.clock[VCODEC_CLK], load)); if (rc) dprintk(VIDC_ERR, "Failed to set clock rate: %d\n", rc); return rc; } static int venus_hfi_iface_cmdq_write(struct venus_hfi_device *device, void *pkt) { u32 rx_req_is_set = 0; struct vidc_iface_q_info *q_info; int result = -EPERM; if (!device || !pkt) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } mutex_lock(&device->write_lock); q_info = &device->iface_queues[VIDC_IFACEQ_CMDQ_IDX]; if (!q_info) { dprintk(VIDC_ERR, "cannot write to shared Q's"); goto err_q_null; } mutex_lock(&device->clock_lock); result = venus_hfi_clk_gating_off(device); if (result) { dprintk(VIDC_ERR, "%s : Clock enable failed\n", __func__); goto err_q_write; } result = venus_hfi_scale_clocks(device, device->load); if (result) { dprintk(VIDC_ERR, "Clock scaling failed\n"); goto err_q_write; } if (!venus_hfi_write_queue(q_info, (u8 *)pkt, &rx_req_is_set)) { if (rx_req_is_set) venus_hfi_write_register( device, VIDC_CPU_IC_SOFTINT, 1 << VIDC_CPU_IC_SOFTINT_H2A_SHFT, 0); result = 0; } else { dprintk(VIDC_ERR, "venus_hfi_iface_cmdq_write:queue_full"); } err_q_write: mutex_unlock(&device->clock_lock); err_q_null: mutex_unlock(&device->write_lock); return result; } static int venus_hfi_iface_msgq_read(struct venus_hfi_device *device, void *pkt) { u32 tx_req_is_set = 0; int rc = 0; struct vidc_iface_q_info *q_info; if (!pkt) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } mutex_lock(&device->read_lock); if (device->iface_queues[VIDC_IFACEQ_MSGQ_IDX]. q_array.align_virtual_addr == 0) { dprintk(VIDC_ERR, "cannot read from shared MSG Q's"); rc = -ENODATA; goto read_error_null; } q_info = &device->iface_queues[VIDC_IFACEQ_MSGQ_IDX]; mutex_lock(&device->clock_lock); rc = venus_hfi_clk_gating_off(device); if (rc) { dprintk(VIDC_ERR, "%s : Clock enable failed\n", __func__); goto read_error; } if (!venus_hfi_read_queue(q_info, (u8 *)pkt, &tx_req_is_set)) { if (tx_req_is_set) venus_hfi_write_register( device, VIDC_CPU_IC_SOFTINT, 1 << VIDC_CPU_IC_SOFTINT_H2A_SHFT, 0); rc = 0; } else { dprintk(VIDC_INFO, "venus_hfi_iface_msgq_read:queue_empty"); rc = -ENODATA; } read_error: mutex_unlock(&device->clock_lock); read_error_null: mutex_unlock(&device->read_lock); return rc; } static int venus_hfi_iface_dbgq_read(struct venus_hfi_device *device, void *pkt) { u32 tx_req_is_set = 0; int rc = 0; struct vidc_iface_q_info *q_info; if (!pkt) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } mutex_lock(&device->read_lock); if (device->iface_queues[VIDC_IFACEQ_DBGQ_IDX]. q_array.align_virtual_addr == 0) { dprintk(VIDC_ERR, "cannot read from shared DBG Q's"); rc = -ENODATA; goto dbg_error_null; } mutex_lock(&device->clock_lock); rc = venus_hfi_clk_gating_off(device); if (rc) { dprintk(VIDC_ERR, "%s : Clock enable failed\n", __func__); goto dbg_error; } q_info = &device->iface_queues[VIDC_IFACEQ_DBGQ_IDX]; if (!venus_hfi_read_queue(q_info, (u8 *)pkt, &tx_req_is_set)) { if (tx_req_is_set) venus_hfi_write_register( device, VIDC_CPU_IC_SOFTINT, 1 << VIDC_CPU_IC_SOFTINT_H2A_SHFT, 0); rc = 0; } else { dprintk(VIDC_INFO, "venus_hfi_iface_dbgq_read:queue_empty"); rc = -ENODATA; } dbg_error: mutex_unlock(&device->clock_lock); dbg_error_null: mutex_unlock(&device->read_lock); return rc; } static void venus_hfi_set_queue_hdr_defaults(struct hfi_queue_header *q_hdr) { q_hdr->qhdr_status = 0x1; q_hdr->qhdr_type = VIDC_IFACEQ_DFLT_QHDR; q_hdr->qhdr_q_size = VIDC_IFACEQ_QUEUE_SIZE / 4; q_hdr->qhdr_pkt_size = 0; q_hdr->qhdr_rx_wm = 0x1; q_hdr->qhdr_tx_wm = 0x1; q_hdr->qhdr_rx_req = 0x1; q_hdr->qhdr_tx_req = 0x0; q_hdr->qhdr_rx_irq_status = 0x0; q_hdr->qhdr_tx_irq_status = 0x0; q_hdr->qhdr_read_idx = 0x0; q_hdr->qhdr_write_idx = 0x0; } static void venus_hfi_interface_queues_release(struct venus_hfi_device *device) { int i; struct hfi_mem_map_table *qdss; struct hfi_mem_map *mem_map; int num_entries = sizeof(venus_qdss_entries)/(2 * sizeof(u32)); int domain, partition; mutex_lock(&device->write_lock); mutex_lock(&device->read_lock); if (device->qdss.mem_data) { qdss = (struct hfi_mem_map_table *) device->qdss.align_virtual_addr; qdss->mem_map_num_entries = num_entries; qdss->mem_map_table_base_addr = (u32 *)((u32)device->qdss.align_device_addr + sizeof(struct hfi_mem_map_table)); mem_map = (struct hfi_mem_map *)(qdss + 1); msm_smem_get_domain_partition(device->hal_client, 0, HAL_BUFFER_INTERNAL_CMD_QUEUE, &domain, &partition); for (i = 0; i < num_entries; i++) { msm_iommu_unmap_contig_buffer( (unsigned long)(mem_map[i].virtual_addr), domain, partition, SZ_4K); } venus_hfi_free(device->hal_client, device->qdss.mem_data); } venus_hfi_free(device->hal_client, device->iface_q_table.mem_data); venus_hfi_free(device->hal_client, device->sfr.mem_data); for (i = 0; i < VIDC_IFACEQ_NUMQ; i++) { device->iface_queues[i].q_hdr = NULL; device->iface_queues[i].q_array.mem_data = NULL; device->iface_queues[i].q_array.align_virtual_addr = NULL; device->iface_queues[i].q_array.align_device_addr = NULL; } device->iface_q_table.align_virtual_addr = NULL; device->iface_q_table.align_device_addr = NULL; device->qdss.align_virtual_addr = NULL; device->qdss.align_device_addr = NULL; device->sfr.align_virtual_addr = NULL; device->sfr.align_device_addr = NULL; device->mem_addr.align_virtual_addr = NULL; device->mem_addr.align_device_addr = NULL; msm_smem_delete_client(device->hal_client); device->hal_client = NULL; mutex_unlock(&device->read_lock); mutex_unlock(&device->write_lock); } static int venus_hfi_get_qdss_iommu_virtual_addr(struct hfi_mem_map *mem_map, int domain, int partition) { int i; int rc = 0; unsigned long iova = 0; int num_entries = sizeof(venus_qdss_entries)/(2 * sizeof(u32)); for (i = 0; i < num_entries; i++) { rc = msm_iommu_map_contig_buffer(venus_qdss_entries[i][0], domain, 1 , venus_qdss_entries[i][1], SZ_4K, 0, &iova); if (rc) { dprintk(VIDC_ERR, "IOMMU QDSS mapping failed for addr 0x%x", venus_qdss_entries[i][0]); rc = -ENOMEM; break; } mem_map[i].virtual_addr = (u32) iova; mem_map[i].physical_addr = venus_qdss_entries[i][0]; mem_map[i].size = venus_qdss_entries[i][1]; mem_map[i].attr = 0x0; } if (i < num_entries) { dprintk(VIDC_ERR, "IOMMU QDSS mapping failed, Freeing entries %d", i); for (--i; i >= 0; i--) { msm_iommu_unmap_contig_buffer( (unsigned long)(mem_map[i].virtual_addr), domain, partition, SZ_4K); } } return rc; } static int venus_hfi_interface_queues_init(struct venus_hfi_device *dev) { struct hfi_queue_table_header *q_tbl_hdr; struct hfi_queue_header *q_hdr; u8 i; int rc = 0; struct hfi_mem_map_table *qdss; struct hfi_mem_map *mem_map; struct vidc_iface_q_info *iface_q; struct hfi_sfr_struct *vsfr; struct vidc_mem_addr *mem_addr; int offset = 0; int num_entries = sizeof(venus_qdss_entries)/(2 * sizeof(u32)); int domain, partition; mem_addr = &dev->mem_addr; rc = venus_hfi_alloc((void *) mem_addr, dev->hal_client, QUEUE_SIZE, 1, 0, HAL_BUFFER_INTERNAL_CMD_QUEUE); if (rc) { dprintk(VIDC_ERR, "iface_q_table_alloc_fail"); goto fail_alloc_queue; } dev->iface_q_table.align_virtual_addr = mem_addr->align_virtual_addr; dev->iface_q_table.align_device_addr = mem_addr->align_device_addr; dev->iface_q_table.mem_size = VIDC_IFACEQ_TABLE_SIZE; dev->iface_q_table.mem_data = mem_addr->mem_data; offset += dev->iface_q_table.mem_size; for (i = 0; i < VIDC_IFACEQ_NUMQ; i++) { iface_q = &dev->iface_queues[i]; iface_q->q_array.align_device_addr = mem_addr->align_device_addr + offset; iface_q->q_array.align_virtual_addr = mem_addr->align_virtual_addr + offset; iface_q->q_array.mem_size = VIDC_IFACEQ_QUEUE_SIZE; iface_q->q_array.mem_data = NULL; offset += iface_q->q_array.mem_size; iface_q->q_hdr = VIDC_IFACEQ_GET_QHDR_START_ADDR( dev->iface_q_table.align_virtual_addr, i); venus_hfi_set_queue_hdr_defaults(iface_q->q_hdr); } rc = venus_hfi_alloc((void *) mem_addr, dev->hal_client, QDSS_SIZE, 1, 0, HAL_BUFFER_INTERNAL_CMD_QUEUE); if (rc) { dprintk(VIDC_WARN, "qdss_alloc_fail: QDSS messages logging will not work"); dev->qdss.align_device_addr = NULL; } else { dev->qdss.align_device_addr = mem_addr->align_device_addr; dev->qdss.align_virtual_addr = mem_addr->align_virtual_addr; dev->qdss.mem_size = QDSS_SIZE; dev->qdss.mem_data = mem_addr->mem_data; } rc = venus_hfi_alloc((void *) mem_addr, dev->hal_client, SFR_SIZE, 1, 0, HAL_BUFFER_INTERNAL_CMD_QUEUE); if (rc) { dprintk(VIDC_WARN, "sfr_alloc_fail: SFR not will work"); dev->sfr.align_device_addr = NULL; } else { dev->sfr.align_device_addr = mem_addr->align_device_addr; dev->sfr.align_virtual_addr = mem_addr->align_virtual_addr; dev->sfr.mem_size = SFR_SIZE; dev->sfr.mem_data = mem_addr->mem_data; } q_tbl_hdr = (struct hfi_queue_table_header *) dev->iface_q_table.align_virtual_addr; q_tbl_hdr->qtbl_version = 0; q_tbl_hdr->qtbl_size = VIDC_IFACEQ_TABLE_SIZE; q_tbl_hdr->qtbl_qhdr0_offset = sizeof( struct hfi_queue_table_header); q_tbl_hdr->qtbl_qhdr_size = sizeof( struct hfi_queue_header); q_tbl_hdr->qtbl_num_q = VIDC_IFACEQ_NUMQ; q_tbl_hdr->qtbl_num_active_q = VIDC_IFACEQ_NUMQ; iface_q = &dev->iface_queues[VIDC_IFACEQ_CMDQ_IDX]; q_hdr = iface_q->q_hdr; q_hdr->qhdr_start_addr = (u32) iface_q->q_array.align_device_addr; q_hdr->qhdr_type |= HFI_Q_ID_HOST_TO_CTRL_CMD_Q; iface_q = &dev->iface_queues[VIDC_IFACEQ_MSGQ_IDX]; q_hdr = iface_q->q_hdr; q_hdr->qhdr_start_addr = (u32) iface_q->q_array.align_device_addr; q_hdr->qhdr_type |= HFI_Q_ID_CTRL_TO_HOST_MSG_Q; iface_q = &dev->iface_queues[VIDC_IFACEQ_DBGQ_IDX]; q_hdr = iface_q->q_hdr; q_hdr->qhdr_start_addr = (u32) iface_q->q_array.align_device_addr; q_hdr->qhdr_type |= HFI_Q_ID_CTRL_TO_HOST_DEBUG_Q; venus_hfi_write_register(dev, VIDC_UC_REGION_ADDR, (u32) dev->iface_q_table.align_device_addr, 0); venus_hfi_write_register(dev, VIDC_UC_REGION_SIZE, SHARED_QSIZE, 0); venus_hfi_write_register(dev, VIDC_CPU_CS_SCIACMDARG2, (u32) dev->iface_q_table.align_device_addr, dev->iface_q_table.align_virtual_addr); venus_hfi_write_register(dev, VIDC_CPU_CS_SCIACMDARG1, 0x01, dev->iface_q_table.align_virtual_addr); qdss = (struct hfi_mem_map_table *) dev->qdss.align_virtual_addr; qdss->mem_map_num_entries = num_entries; qdss->mem_map_table_base_addr = (u32 *) ((u32)dev->qdss.align_device_addr + sizeof(struct hfi_mem_map_table)); mem_map = (struct hfi_mem_map *)(qdss + 1); msm_smem_get_domain_partition(dev->hal_client, 0, HAL_BUFFER_INTERNAL_CMD_QUEUE, &domain, &partition); rc = venus_hfi_get_qdss_iommu_virtual_addr(mem_map, domain, partition); if (rc) { dprintk(VIDC_ERR, "IOMMU mapping failed, Freeing qdss memdata"); venus_hfi_free(dev->hal_client, dev->qdss.mem_data); dev->qdss.mem_data = NULL; } if (!IS_ERR_OR_NULL(dev->qdss.align_device_addr)) venus_hfi_write_register(dev, VIDC_MMAP_ADDR, (u32) dev->qdss.align_device_addr, 0); vsfr = (struct hfi_sfr_struct *) dev->sfr.align_virtual_addr; vsfr->bufSize = SFR_SIZE; if (!IS_ERR_OR_NULL(dev->sfr.align_device_addr)) venus_hfi_write_register(dev, VIDC_SFR_ADDR, (u32)dev->sfr.align_device_addr , 0); return 0; fail_alloc_queue: return -ENOMEM; } static int venus_hfi_core_start_cpu(struct venus_hfi_device *device) { u32 ctrl_status = 0, count = 0, rc = 0; int max_tries = 100; venus_hfi_write_register(device, VIDC_WRAPPER_INTR_MASK, 0x8, 0); venus_hfi_write_register(device, VIDC_CPU_CS_SCIACMDARG3, 1, 0); while (!ctrl_status && count < max_tries) { ctrl_status = venus_hfi_read_register( device, VIDC_CPU_CS_SCIACMDARG0); if ((ctrl_status & 0xFE) == 0x4) { dprintk(VIDC_ERR, "invalid setting for UC_REGION\n"); break; } usleep_range(500, 1000); count++; } if (count >= max_tries) rc = -ETIME; return rc; } static void venus_hfi_set_registers(struct venus_hfi_device *device) { struct reg_set *reg_set; int i; if (!device->res) { dprintk(VIDC_ERR, "device resources null, cannot set registers\n"); return; } reg_set = &device->res->reg_set; for (i = 0; i < reg_set->count; i++) { venus_hfi_write_register(device, reg_set->reg_tbl[i].reg, reg_set->reg_tbl[i].value, 0); } } static int venus_hfi_sys_set_debug(struct venus_hfi_device *device, int debug) { u8 packet[VIDC_IFACEQ_VAR_SMALL_PKT_SIZE]; int rc = 0; struct hfi_cmd_sys_set_property_packet *pkt = (struct hfi_cmd_sys_set_property_packet *) &packet; rc = create_pkt_cmd_sys_debug_config(pkt, debug); if (rc) { dprintk(VIDC_WARN, "Debug mode setting to FW failed\n"); return -ENOTEMPTY; } if (venus_hfi_iface_cmdq_write(device, pkt)) return -ENOTEMPTY; return 0; } static int venus_hfi_sys_set_idle_message(struct venus_hfi_device *device, int enable) { u8 packet[VIDC_IFACEQ_VAR_SMALL_PKT_SIZE]; struct hfi_cmd_sys_set_property_packet *pkt = (struct hfi_cmd_sys_set_property_packet *) &packet; create_pkt_cmd_sys_idle_indicator(pkt, enable); if (venus_hfi_iface_cmdq_write(device, pkt)) return -ENOTEMPTY; return 0; } static int venus_hfi_core_init(void *device) { struct hfi_cmd_sys_init_packet pkt; int rc = 0; struct venus_hfi_device *dev; if (device) { dev = device; } else { dprintk(VIDC_ERR, "Invalid device"); return -ENODEV; } dev->intr_status = 0; INIT_LIST_HEAD(&dev->sess_head); mutex_init(&dev->read_lock); mutex_init(&dev->write_lock); venus_hfi_set_registers(dev); if (!dev->hal_client) { dev->hal_client = msm_smem_new_client(SMEM_ION, dev->res); if (dev->hal_client == NULL) { dprintk(VIDC_ERR, "Failed to alloc ION_Client"); rc = -ENODEV; goto err_core_init; } dprintk(VIDC_DBG, "Dev_Virt: 0x%x, Reg_Virt: 0x%x", dev->hal_data->device_base_addr, (u32) dev->hal_data->register_base_addr); rc = venus_hfi_interface_queues_init(dev); if (rc) { dprintk(VIDC_ERR, "failed to init queues"); rc = -ENOMEM; goto err_core_init; } } else { dprintk(VIDC_ERR, "hal_client exists"); rc = -EEXIST; goto err_core_init; } enable_irq(dev->hal_data->irq); venus_hfi_write_register(dev, VIDC_CTRL_INIT, 0x1, 0); rc = venus_hfi_core_start_cpu(dev); if (rc) { dprintk(VIDC_ERR, "Failed to start core"); rc = -ENODEV; goto err_core_init; } rc = create_pkt_cmd_sys_init(&pkt, HFI_VIDEO_ARCH_OX); if (rc) { dprintk(VIDC_ERR, "Failed to create sys init pkt"); goto err_core_init; } if (venus_hfi_iface_cmdq_write(dev, &pkt)) { rc = -ENOTEMPTY; goto err_core_init; } return rc; err_core_init: disable_irq_nosync(dev->hal_data->irq); return rc; } static int venus_hfi_core_release(void *device) { struct venus_hfi_device *dev; int rc = 0; if (device) { dev = device; } else { dprintk(VIDC_ERR, "invalid device"); return -ENODEV; } if (dev->hal_client) { mutex_lock(&dev->clock_lock); rc = venus_hfi_clk_gating_off(device); if (rc) { dprintk(VIDC_ERR, "%s : Clock enable failed\n", __func__); mutex_unlock(&dev->clock_lock); return -EIO; } venus_hfi_write_register(dev, VIDC_CPU_CS_SCIACMDARG3, 0, 0); if (!(dev->intr_status & VIDC_WRAPPER_INTR_STATUS_A2HWD_BMSK)) disable_irq_nosync(dev->hal_data->irq); dev->intr_status = 0; mutex_unlock(&dev->clock_lock); } dprintk(VIDC_INFO, "HAL exited\n"); return 0; } int venus_hfi_core_pc_prep(void *device) { struct hfi_cmd_sys_pc_prep_packet pkt; int rc = 0; struct venus_hfi_device *dev; if (device) { dev = device; } else { dprintk(VIDC_ERR, "invalid device"); return -ENODEV; } rc = create_pkt_cmd_sys_pc_prep(&pkt); if (rc) { dprintk(VIDC_ERR, "Failed to create sys pc prep pkt"); goto err_create_pkt; } if (venus_hfi_iface_cmdq_write(dev, &pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static void venus_hfi_core_clear_interrupt(struct venus_hfi_device *device) { u32 intr_status = 0; int rc = 0; if (!device->callback) return; mutex_lock(&device->clock_lock); rc = venus_hfi_clk_gating_off(device); if (rc) { dprintk(VIDC_ERR, "%s : Clock enable failed\n", __func__); mutex_unlock(&device->clock_lock); return; } intr_status = venus_hfi_read_register( device, VIDC_WRAPPER_INTR_STATUS); if ((intr_status & VIDC_WRAPPER_INTR_STATUS_A2H_BMSK) || (intr_status & VIDC_WRAPPER_INTR_STATUS_A2HWD_BMSK) || (intr_status & VIDC_CPU_CS_SCIACMDARG0_HFI_CTRL_INIT_IDLE_MSG_BMSK)) { device->intr_status |= intr_status; dprintk(VIDC_DBG, "INTERRUPT for device: 0x%x: " "times: %d interrupt_status: %d", (u32) device, ++device->reg_count, intr_status); } else { dprintk(VIDC_INFO, "SPURIOUS_INTR for device: 0x%x: " "times: %d interrupt_status: %d", (u32) device, ++device->spur_count, intr_status); } venus_hfi_write_register(device, VIDC_CPU_CS_A2HSOFTINTCLR, 1, 0); venus_hfi_write_register(device, VIDC_WRAPPER_INTR_CLEAR, intr_status, 0); mutex_unlock(&device->clock_lock); dprintk(VIDC_DBG, "Cleared WRAPPER/A2H interrupt"); } static int venus_hfi_core_set_resource(void *device, struct vidc_resource_hdr *resource_hdr, void *resource_value) { struct hfi_cmd_sys_set_resource_packet *pkt; u8 packet[VIDC_IFACEQ_VAR_SMALL_PKT_SIZE]; int rc = 0; struct venus_hfi_device *dev; if (!device || !resource_hdr || !resource_value) { dprintk(VIDC_ERR, "set_res: Invalid Params"); return -EINVAL; } else { dev = device; } pkt = (struct hfi_cmd_sys_set_resource_packet *) packet; rc = create_pkt_set_cmd_sys_resource(pkt, resource_hdr, resource_value); if (rc) { dprintk(VIDC_ERR, "set_res: failed to create packet"); goto err_create_pkt; } if (venus_hfi_iface_cmdq_write(dev, pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_core_release_resource(void *device, struct vidc_resource_hdr *resource_hdr) { struct hfi_cmd_sys_release_resource_packet pkt; int rc = 0; struct venus_hfi_device *dev; if (!device || !resource_hdr) { dprintk(VIDC_ERR, "Inv-Params in rel_res"); return -EINVAL; } else { dev = device; } rc = create_pkt_cmd_sys_release_resource(&pkt, resource_hdr); if (rc) { dprintk(VIDC_ERR, "release_res: failed to create packet"); goto err_create_pkt; } if (venus_hfi_iface_cmdq_write(dev, &pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_core_ping(void *device) { struct hfi_cmd_sys_ping_packet pkt; int rc = 0; struct venus_hfi_device *dev; if (device) { dev = device; } else { dprintk(VIDC_ERR, "invalid device"); return -ENODEV; } rc = create_pkt_cmd_sys_ping(&pkt); if (rc) { dprintk(VIDC_ERR, "core_ping: failed to create packet"); goto err_create_pkt; } if (venus_hfi_iface_cmdq_write(dev, &pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_core_trigger_ssr(void *device, enum hal_ssr_trigger_type type) { struct hfi_cmd_sys_test_ssr_packet pkt; int rc = 0; struct venus_hfi_device *dev; if (device) { dev = device; } else { dprintk(VIDC_ERR, "invalid device"); return -ENODEV; } rc = create_pkt_ssr_cmd(type, &pkt); if (rc) { dprintk(VIDC_ERR, "core_ping: failed to create packet"); goto err_create_pkt; } if (venus_hfi_iface_cmdq_write(dev, &pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_session_set_property(void *sess, enum hal_property ptype, void *pdata) { u8 packet[VIDC_IFACEQ_VAR_LARGE_PKT_SIZE]; struct hfi_cmd_session_set_property_packet *pkt = (struct hfi_cmd_session_set_property_packet *) &packet; struct hal_session *session; int rc = 0; if (!sess || !pdata) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } else { session = sess; } dprintk(VIDC_INFO, "in set_prop,with prop id: 0x%x", ptype); if (create_pkt_cmd_session_set_property(pkt, (u32)session, ptype, pdata)) { dprintk(VIDC_ERR, "set property: failed to create packet"); return -EINVAL; } if (venus_hfi_iface_cmdq_write(session->device, pkt)) return -ENOTEMPTY; return rc; } static int venus_hfi_session_get_property(void *sess, enum hal_property ptype, void *pdata) { struct hal_session *session; if (!sess || !pdata) { dprintk(VIDC_ERR, "Invalid Params in "); return -EINVAL; } else { session = sess; } dprintk(VIDC_INFO, "IN func: , with property id: %d", ptype); switch (ptype) { case HAL_CONFIG_FRAME_RATE: break; case HAL_PARAM_UNCOMPRESSED_FORMAT_SELECT: break; case HAL_PARAM_UNCOMPRESSED_PLANE_ACTUAL_CONSTRAINTS_INFO: break; case HAL_PARAM_UNCOMPRESSED_PLANE_ACTUAL_INFO: break; case HAL_PARAM_EXTRA_DATA_HEADER_CONFIG: break; case HAL_PARAM_FRAME_SIZE: break; case HAL_CONFIG_REALTIME: break; case HAL_PARAM_BUFFER_COUNT_ACTUAL: break; case HAL_PARAM_NAL_STREAM_FORMAT_SELECT: break; case HAL_PARAM_VDEC_OUTPUT_ORDER: break; case HAL_PARAM_VDEC_PICTURE_TYPE_DECODE: break; case HAL_PARAM_VDEC_OUTPUT2_KEEP_ASPECT_RATIO: break; case HAL_CONFIG_VDEC_POST_LOOP_DEBLOCKER: break; case HAL_PARAM_VDEC_MULTI_STREAM: break; case HAL_PARAM_VDEC_DISPLAY_PICTURE_BUFFER_COUNT: break; case HAL_PARAM_DIVX_FORMAT: break; case HAL_CONFIG_VDEC_MB_ERROR_MAP_REPORTING: break; case HAL_PARAM_VDEC_CONTINUE_DATA_TRANSFER: break; case HAL_CONFIG_VDEC_MB_ERROR_MAP: break; case HAL_CONFIG_VENC_REQUEST_IFRAME: break; case HAL_PARAM_VENC_MPEG4_SHORT_HEADER: break; case HAL_PARAM_VENC_MPEG4_AC_PREDICTION: break; case HAL_CONFIG_VENC_TARGET_BITRATE: break; case HAL_PARAM_PROFILE_LEVEL_CURRENT: break; case HAL_PARAM_VENC_H264_ENTROPY_CONTROL: break; case HAL_PARAM_VENC_RATE_CONTROL: break; case HAL_PARAM_VENC_MPEG4_TIME_RESOLUTION: break; case HAL_PARAM_VENC_MPEG4_HEADER_EXTENSION: break; case HAL_PARAM_VENC_H264_DEBLOCK_CONTROL: break; case HAL_PARAM_VENC_SESSION_QP: break; case HAL_CONFIG_VENC_INTRA_PERIOD: break; case HAL_CONFIG_VENC_IDR_PERIOD: break; case HAL_CONFIG_VPE_OPERATIONS: break; case HAL_PARAM_VENC_INTRA_REFRESH: break; case HAL_PARAM_VENC_MULTI_SLICE_CONTROL: break; case HAL_CONFIG_VPE_DEINTERLACE: break; case HAL_SYS_DEBUG_CONFIG: break; /*FOLLOWING PROPERTIES ARE NOT IMPLEMENTED IN CORE YET*/ case HAL_CONFIG_BUFFER_REQUIREMENTS: case HAL_CONFIG_PRIORITY: case HAL_CONFIG_BATCH_INFO: case HAL_PARAM_METADATA_PASS_THROUGH: case HAL_SYS_IDLE_INDICATOR: case HAL_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED: case HAL_PARAM_INTERLACE_FORMAT_SUPPORTED: case HAL_PARAM_CHROMA_SITE: case HAL_PARAM_PROPERTIES_SUPPORTED: case HAL_PARAM_PROFILE_LEVEL_SUPPORTED: case HAL_PARAM_CAPABILITY_SUPPORTED: case HAL_PARAM_NAL_STREAM_FORMAT_SUPPORTED: case HAL_PARAM_MULTI_VIEW_FORMAT: case HAL_PARAM_MAX_SEQUENCE_HEADER_SIZE: case HAL_PARAM_CODEC_SUPPORTED: case HAL_PARAM_VDEC_MULTI_VIEW_SELECT: case HAL_PARAM_VDEC_MB_QUANTIZATION: case HAL_PARAM_VDEC_NUM_CONCEALED_MB: case HAL_PARAM_VDEC_H264_ENTROPY_SWITCHING: case HAL_PARAM_VENC_SLICE_DELIVERY_MODE: case HAL_PARAM_VENC_MPEG4_DATA_PARTITIONING: case HAL_CONFIG_BUFFER_COUNT_ACTUAL: case HAL_CONFIG_VDEC_MULTI_STREAM: case HAL_PARAM_VENC_MULTI_SLICE_INFO: case HAL_CONFIG_VENC_TIMESTAMP_SCALE: case HAL_PARAM_VENC_LOW_LATENCY: default: dprintk(VIDC_INFO, "DEFAULT: Calling 0x%x", ptype); break; } return 0; } static void *venus_hfi_session_init(void *device, u32 session_id, enum hal_domain session_type, enum hal_video_codec codec_type) { struct hfi_cmd_sys_session_init_packet pkt; struct hal_session *new_session; struct venus_hfi_device *dev; if (device) { dev = device; } else { dprintk(VIDC_ERR, "invalid device"); return NULL; } new_session = (struct hal_session *) kzalloc(sizeof(struct hal_session), GFP_KERNEL); new_session->session_id = (u32) session_id; if (session_type == 1) new_session->is_decoder = 0; else if (session_type == 2) new_session->is_decoder = 1; new_session->device = dev; list_add_tail(&new_session->list, &dev->sess_head); if (create_pkt_cmd_sys_session_init(&pkt, (u32)new_session, session_type, codec_type)) { dprintk(VIDC_ERR, "session_init: failed to create packet"); goto err_session_init_fail; } if (venus_hfi_sys_set_debug(dev, msm_fw_debug)) dprintk(VIDC_ERR, "Setting fw_debug msg ON failed"); if (venus_hfi_sys_set_idle_message(dev, msm_fw_low_power_mode)) dprintk(VIDC_ERR, "Setting idle response ON failed"); if (venus_hfi_iface_cmdq_write(dev, &pkt)) goto err_session_init_fail; return (void *) new_session; err_session_init_fail: kfree(new_session); return NULL; } static int venus_hfi_send_session_cmd(void *session_id, int pkt_type) { struct vidc_hal_session_cmd_pkt pkt; int rc = 0; struct hal_session *session; if (session_id) { session = session_id; } else { dprintk(VIDC_ERR, "invalid session"); return -ENODEV; } rc = create_pkt_cmd_session_cmd(&pkt, pkt_type, (u32)session); if (rc) { dprintk(VIDC_ERR, "send session cmd: create pkt failed"); goto err_create_pkt; } if (venus_hfi_iface_cmdq_write(session->device, &pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_session_end(void *session) { return venus_hfi_send_session_cmd(session, HFI_CMD_SYS_SESSION_END); } static int venus_hfi_session_abort(void *session) { return venus_hfi_send_session_cmd(session, HFI_CMD_SYS_SESSION_ABORT); } static int venus_hfi_session_clean(void *session) { struct hal_session *sess_close; if (!session) { dprintk(VIDC_ERR, "Invalid Params %s", __func__); return -EINVAL; } sess_close = session; dprintk(VIDC_DBG, "deleted the session: 0x%p", sess_close); list_del(&sess_close->list); kfree(sess_close); return 0; } static int venus_hfi_session_set_buffers(void *sess, struct vidc_buffer_addr_info *buffer_info) { struct hfi_cmd_session_set_buffers_packet *pkt; u8 packet[VIDC_IFACEQ_VAR_LARGE_PKT_SIZE]; int rc = 0; struct hal_session *session; if (!sess || !buffer_info) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } else { session = sess; } if (buffer_info->buffer_type == HAL_BUFFER_INPUT) return 0; pkt = (struct hfi_cmd_session_set_buffers_packet *)packet; rc = create_pkt_cmd_session_set_buffers(pkt, (u32)session, buffer_info); if (rc) { dprintk(VIDC_ERR, "set buffers: failed to create packet"); goto err_create_pkt; } dprintk(VIDC_INFO, "set buffers: 0x%x", buffer_info->buffer_type); if (venus_hfi_iface_cmdq_write(session->device, pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_session_release_buffers(void *sess, struct vidc_buffer_addr_info *buffer_info) { struct hfi_cmd_session_release_buffer_packet *pkt; u8 packet[VIDC_IFACEQ_VAR_LARGE_PKT_SIZE]; int rc = 0; struct hal_session *session; if (!sess || !buffer_info) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } else { session = sess; } if (buffer_info->buffer_type == HAL_BUFFER_INPUT) return 0; pkt = (struct hfi_cmd_session_release_buffer_packet *) packet; rc = create_pkt_cmd_session_release_buffers(pkt, (u32)session, buffer_info); if (rc) { dprintk(VIDC_ERR, "release buffers: failed to create packet"); goto err_create_pkt; } dprintk(VIDC_INFO, "Release buffers: 0x%x", buffer_info->buffer_type); if (venus_hfi_iface_cmdq_write(session->device, pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_session_load_res(void *sess) { return venus_hfi_send_session_cmd(sess, HFI_CMD_SESSION_LOAD_RESOURCES); } static int venus_hfi_session_release_res(void *sess) { return venus_hfi_send_session_cmd(sess, HFI_CMD_SESSION_RELEASE_RESOURCES); } static int venus_hfi_session_start(void *sess) { return venus_hfi_send_session_cmd(sess, HFI_CMD_SESSION_START); } static int venus_hfi_session_stop(void *sess) { return venus_hfi_send_session_cmd(sess, HFI_CMD_SESSION_STOP); } static int venus_hfi_session_suspend(void *sess) { return venus_hfi_send_session_cmd(sess, HFI_CMD_SESSION_SUSPEND); } static int venus_hfi_session_resume(void *sess) { return venus_hfi_send_session_cmd(sess, HFI_CMD_SESSION_RESUME); } static int venus_hfi_session_etb(void *sess, struct vidc_frame_data *input_frame) { int rc = 0; struct hal_session *session; if (!sess || !input_frame) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } else { session = sess; } if (session->is_decoder) { struct hfi_cmd_session_empty_buffer_compressed_packet pkt; rc = create_pkt_cmd_session_etb_decoder(&pkt, (u32)session, input_frame); if (rc) { dprintk(VIDC_ERR, "Session etb decoder: failed to create pkt"); goto err_create_pkt; } dprintk(VIDC_DBG, "Q DECODER INPUT BUFFER"); if (venus_hfi_iface_cmdq_write(session->device, &pkt)) rc = -ENOTEMPTY; } else { struct hfi_cmd_session_empty_buffer_uncompressed_plane0_packet pkt; rc = create_pkt_cmd_session_etb_encoder(&pkt, (u32)session, input_frame); if (rc) { dprintk(VIDC_ERR, "Session etb encoder: failed to create pkt"); goto err_create_pkt; } dprintk(VIDC_DBG, "Q ENCODER INPUT BUFFER"); if (venus_hfi_iface_cmdq_write(session->device, &pkt)) rc = -ENOTEMPTY; } err_create_pkt: return rc; } static int venus_hfi_session_ftb(void *sess, struct vidc_frame_data *output_frame) { struct hfi_cmd_session_fill_buffer_packet pkt; int rc = 0; struct hal_session *session; if (!sess || !output_frame) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } else { session = sess; } rc = create_pkt_cmd_session_ftb(&pkt, (u32)session, output_frame); if (rc) { dprintk(VIDC_ERR, "Session ftb: failed to create pkt"); goto err_create_pkt; } if (venus_hfi_iface_cmdq_write(session->device, &pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_session_parse_seq_hdr(void *sess, struct vidc_seq_hdr *seq_hdr) { struct hfi_cmd_session_parse_sequence_header_packet *pkt; int rc = 0; u8 packet[VIDC_IFACEQ_VAR_SMALL_PKT_SIZE]; struct hal_session *session; if (!sess || !seq_hdr) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } else { session = sess; } pkt = (struct hfi_cmd_session_parse_sequence_header_packet *) packet; rc = create_pkt_cmd_session_parse_seq_header(pkt, (u32)session, seq_hdr); if (rc) { dprintk(VIDC_ERR, "Session parse seq hdr: failed to create pkt"); goto err_create_pkt; } if (venus_hfi_iface_cmdq_write(session->device, pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_session_get_seq_hdr(void *sess, struct vidc_seq_hdr *seq_hdr) { struct hfi_cmd_session_get_sequence_header_packet *pkt; int rc = 0; u8 packet[VIDC_IFACEQ_VAR_SMALL_PKT_SIZE]; struct hal_session *session; if (!sess || !seq_hdr) { dprintk(VIDC_ERR, "Invalid Params"); return -EINVAL; } else { session = sess; } pkt = (struct hfi_cmd_session_get_sequence_header_packet *) packet; rc = create_pkt_cmd_session_get_seq_hdr(pkt, (u32)session, seq_hdr); if (rc) { dprintk(VIDC_ERR, "Session get seq hdr: failed to create pkt"); goto err_create_pkt; } if (venus_hfi_iface_cmdq_write(session->device, pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_session_get_buf_req(void *sess) { struct hfi_cmd_session_get_property_packet pkt; int rc = 0; struct hal_session *session; if (sess) { session = sess; } else { dprintk(VIDC_ERR, "invalid session"); return -ENODEV; } rc = create_pkt_cmd_session_get_buf_req(&pkt, (u32)session); if (rc) { dprintk(VIDC_ERR, "Session get buf req: failed to create pkt"); goto err_create_pkt; } if (venus_hfi_iface_cmdq_write(session->device, &pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_session_flush(void *sess, enum hal_flush flush_mode) { struct hfi_cmd_session_flush_packet pkt; int rc = 0; struct hal_session *session; if (sess) { session = sess; } else { dprintk(VIDC_ERR, "invalid session"); return -ENODEV; } rc = create_pkt_cmd_session_flush(&pkt, (u32)session, flush_mode); if (rc) { dprintk(VIDC_ERR, "Session flush: failed to create pkt"); goto err_create_pkt; } if (venus_hfi_iface_cmdq_write(session->device, &pkt)) rc = -ENOTEMPTY; err_create_pkt: return rc; } static int venus_hfi_check_core_registered( struct hal_device_data core, u32 fw_addr, u32 reg_addr, u32 reg_size, u32 irq) { struct venus_hfi_device *device; struct list_head *curr, *next; if (core.dev_count) { list_for_each_safe(curr, next, &core.dev_head) { device = list_entry(curr, struct venus_hfi_device, list); if (device && device->hal_data->irq == irq && (CONTAINS(device->hal_data-> device_base_addr, FIRMWARE_SIZE, fw_addr) || CONTAINS(fw_addr, FIRMWARE_SIZE, device->hal_data-> device_base_addr) || CONTAINS((u32)device->hal_data-> register_base_addr, reg_size, reg_addr) || CONTAINS(reg_addr, reg_size, (u32)device->hal_data-> register_base_addr) || OVERLAPS((u32)device->hal_data-> register_base_addr, reg_size, reg_addr, reg_size) || OVERLAPS(reg_addr, reg_size, (u32)device->hal_data-> register_base_addr, reg_size) || OVERLAPS(device->hal_data-> device_base_addr, FIRMWARE_SIZE, fw_addr, FIRMWARE_SIZE) || OVERLAPS(fw_addr, FIRMWARE_SIZE, device->hal_data-> device_base_addr, FIRMWARE_SIZE))) { return 0; } else { dprintk(VIDC_INFO, "Device not registered"); return -EINVAL; } } } else { dprintk(VIDC_INFO, "no device Registered"); } return -EINVAL; } static void venus_hfi_process_sys_watchdog_timeout( struct venus_hfi_device *device) { struct msm_vidc_cb_cmd_done cmd_done; memset(&cmd_done, 0, sizeof(struct msm_vidc_cb_cmd_done)); cmd_done.device_id = device->device_id; device->callback(SYS_WATCHDOG_TIMEOUT, &cmd_done); } static int venus_hfi_is_cmd_pending(struct venus_hfi_device *dev) { struct hfi_queue_header *queue; struct vidc_iface_q_info *q_info; u32 write_ptr, read_ptr; u32 rc = 0; q_info = &dev->iface_queues[VIDC_IFACEQ_CMDQ_IDX]; if (!q_info) dprintk(VIDC_ERR, "cannot read shared Q's"); queue = (struct hfi_queue_header *) q_info->q_hdr; if (!queue) { dprintk(VIDC_ERR, "queue not present"); return -ENOENT; } write_ptr = (u32)queue->qhdr_write_idx; read_ptr = (u32)queue->qhdr_read_idx; rc = read_ptr - write_ptr; return rc; } static int venus_hfi_try_clk_gating(struct venus_hfi_device *device) { int rc = 0; u32 ctrl_status = 0; if (!device) { dprintk(VIDC_ERR, "invalid device"); return -ENODEV; } mutex_lock(&device->write_lock); mutex_lock(&device->clock_lock); rc = venus_hfi_is_cmd_pending(device); ctrl_status = venus_hfi_read_register( device, VIDC_CPU_CS_SCIACMDARG0); if (((ctrl_status & VIDC_CPU_CS_SCIACMDARG0_HFI_CTRL_INIT_IDLE_MSG_BMSK) != 0) && !rc) venus_hfi_clk_gating_on(device); mutex_unlock(&device->clock_lock); mutex_unlock(&device->write_lock); return rc; } static void venus_hfi_process_msg_event_notify( struct venus_hfi_device *device, void *packet) { struct hfi_sfr_struct *vsfr = NULL; struct hfi_msg_event_notify_packet *event_pkt; struct vidc_hal_msg_pkt_hdr *msg_hdr; msg_hdr = (struct vidc_hal_msg_pkt_hdr *)packet; event_pkt = (struct hfi_msg_event_notify_packet *)msg_hdr; if (event_pkt && event_pkt->event_id == HFI_EVENT_SYS_ERROR) { vsfr = (struct hfi_sfr_struct *) device->sfr.align_virtual_addr; if (vsfr) dprintk(VIDC_ERR, "SFR Message from FW : %s", vsfr->rg_data); } } static void venus_hfi_response_handler(struct venus_hfi_device *device) { u8 packet[VIDC_IFACEQ_MED_PKT_SIZE]; u32 rc = 0; struct hfi_sfr_struct *vsfr = NULL; dprintk(VIDC_INFO, "#####venus_hfi_response_handler#####\n"); if (device) { if ((device->intr_status & VIDC_WRAPPER_INTR_CLEAR_A2HWD_BMSK)) { dprintk(VIDC_ERR, "Received: Watchdog timeout %s", __func__); vsfr = (struct hfi_sfr_struct *) device->sfr.align_virtual_addr; if (vsfr) dprintk(VIDC_ERR, "SFR Message from FW : %s", vsfr->rg_data); venus_hfi_process_sys_watchdog_timeout(device); } while (!venus_hfi_iface_msgq_read(device, packet)) { rc = hfi_process_msg_packet(device->callback, device->device_id, (struct vidc_hal_msg_pkt_hdr *) packet); if (rc == HFI_MSG_EVENT_NOTIFY) venus_hfi_process_msg_event_notify( device, (void *)packet); } while (!venus_hfi_iface_dbgq_read(device, packet)) { struct hfi_msg_sys_debug_packet *pkt = (struct hfi_msg_sys_debug_packet *) packet; dprintk(VIDC_FW, "FW-SAYS: %s", pkt->rg_msg_data); } if (rc == HFI_MSG_SYS_IDLE) rc = venus_hfi_try_clk_gating(device); } else { dprintk(VIDC_ERR, "SPURIOUS_INTERRUPT"); } } static void venus_hfi_core_work_handler(struct work_struct *work) { struct venus_hfi_device *device = list_first_entry( &hal_ctxt.dev_head, struct venus_hfi_device, list); dprintk(VIDC_INFO, " GOT INTERRUPT () "); if (!device->callback) { dprintk(VIDC_ERR, "No interrupt callback function: %p\n", device); return; } venus_hfi_core_clear_interrupt(device); venus_hfi_response_handler(device); if (!(device->intr_status & VIDC_WRAPPER_INTR_STATUS_A2HWD_BMSK)) enable_irq(device->hal_data->irq); } static DECLARE_WORK(venus_hfi_work, venus_hfi_core_work_handler); static irqreturn_t venus_hfi_isr(int irq, void *dev) { struct venus_hfi_device *device = dev; dprintk(VIDC_INFO, "vidc_hal_isr() %d ", irq); disable_irq_nosync(irq); queue_work(device->vidc_workq, &venus_hfi_work); dprintk(VIDC_INFO, "vidc_hal_isr() %d ", irq); return IRQ_HANDLED; } static int venus_hfi_init_regs_and_interrupts( struct venus_hfi_device *device, struct msm_vidc_platform_resources *res) { struct hal_data *hal = NULL; int rc = 0; device->base_addr = res->fw_base_addr; device->register_base = res->register_base; device->register_size = res->register_size; device->irq = res->irq; rc = venus_hfi_check_core_registered(hal_ctxt, device->base_addr, device->register_base, device->register_size, device->irq); if (!rc) { dprintk(VIDC_ERR, "Core present/Already added"); rc = -EEXIST; goto err_core_init; } dprintk(VIDC_DBG, "HAL_DATA will be assigned now"); hal = (struct hal_data *) kzalloc(sizeof(struct hal_data), GFP_KERNEL); if (!hal) { dprintk(VIDC_ERR, "Failed to alloc"); rc = -ENOMEM; goto err_core_init; } hal->irq = device->irq; hal->device_base_addr = device->base_addr; hal->register_base_addr = ioremap_nocache(device->register_base, device->register_size); if (!hal->register_base_addr) { dprintk(VIDC_ERR, "could not map reg addr %d of size %d", device->register_base, device->register_size); goto error_irq_fail; } device->hal_data = hal; rc = request_irq(device->irq, venus_hfi_isr, IRQF_TRIGGER_HIGH, "msm_vidc", device); if (unlikely(rc)) { dprintk(VIDC_ERR, "() :request_irq failed\n"); goto error_irq_fail; } disable_irq_nosync(device->irq); return rc; error_irq_fail: kfree(hal); err_core_init: return rc; } static inline int venus_hfi_init_clocks(struct msm_vidc_platform_resources *res, struct venus_hfi_device *device) { struct venus_core_clock *cl; int i; int rc = 0; struct venus_core_clock *clock; if (!res || !device) { dprintk(VIDC_ERR, "Invalid params: %p\n", device); return -EINVAL; } clock = device->resources.clock; strlcpy(clock[VCODEC_CLK].name, "core_clk", sizeof(clock[VCODEC_CLK].name)); strlcpy(clock[VCODEC_AHB_CLK].name, "iface_clk", sizeof(clock[VCODEC_AHB_CLK].name)); strlcpy(clock[VCODEC_AXI_CLK].name, "bus_clk", sizeof(clock[VCODEC_AXI_CLK].name)); if (res->has_ocmem) { strlcpy(clock[VCODEC_OCMEM_CLK].name, "mem_clk", sizeof(clock[VCODEC_OCMEM_CLK].name)); } clock[VCODEC_CLK].count = res->load_freq_tbl_size; memcpy((void *)clock[VCODEC_CLK].load_freq_tbl, res->load_freq_tbl, clock[VCODEC_CLK].count * sizeof(*res->load_freq_tbl)); dprintk(VIDC_DBG, "count = %d\n", clock[VCODEC_CLK].count); if (!clock[VCODEC_CLK].count) { dprintk(VIDC_ERR, "Failed to read clock frequency\n"); goto fail_init_clocks; } for (i = 0; i < clock[VCODEC_CLK].count; i++) { dprintk(VIDC_DBG, "load = %d, freq = %d\n", clock[VCODEC_CLK].load_freq_tbl[i].load, clock[VCODEC_CLK].load_freq_tbl[i].freq ); } for (i = 0; i < VCODEC_MAX_CLKS; i++) { if (i == VCODEC_OCMEM_CLK && !res->has_ocmem) continue; cl = &device->resources.clock[i]; if (!cl->clk) { cl->clk = devm_clk_get(&res->pdev->dev, cl->name); if (IS_ERR_OR_NULL(cl->clk)) { dprintk(VIDC_ERR, "Failed to get clock: %s\n", cl->name); rc = PTR_ERR(cl->clk); break; } } } if (i < VCODEC_MAX_CLKS) { for (--i; i >= 0; i--) { if (i == VCODEC_OCMEM_CLK && !res->has_ocmem) continue; cl = &device->resources.clock[i]; clk_put(cl->clk); } } fail_init_clocks: return rc; } static inline void venus_hfi_deinit_clocks(struct venus_hfi_device *device) { int i; if (!device) { dprintk(VIDC_ERR, "Invalid args\n"); return; } for (i = 0; i < VCODEC_MAX_CLKS; i++) { if (i == VCODEC_OCMEM_CLK && !device->res->has_ocmem) continue; clk_put(device->resources.clock[i].clk); } } static inline void venus_hfi_disable_clks(struct venus_hfi_device *device) { int i; struct venus_core_clock *cl; if (!device) { dprintk(VIDC_ERR, "Invalid params: %p\n", device); return; } if (device->clocks_enabled) { for (i = VCODEC_CLK; i < VCODEC_MAX_CLKS; i++) { cl = &device->resources.clock[i]; clk_disable(cl->clk); } } else { for (i = device->clk_gating_level + 1; i < VCODEC_MAX_CLKS; i++) { cl = &device->resources.clock[i]; clk_disable(cl->clk); } } for (i = VCODEC_CLK; i < VCODEC_MAX_CLKS; i++) { if (i == VCODEC_OCMEM_CLK && !device->res->has_ocmem) continue; cl = &device->resources.clock[i]; clk_unprepare(cl->clk); } device->clocks_enabled = 0; } static inline int venus_hfi_enable_clks(struct venus_hfi_device *device) { int i = 0; struct venus_core_clock *cl; int rc = 0; if (!device) { dprintk(VIDC_ERR, "Invalid params: %p\n", device); return -EINVAL; } for (i = VCODEC_CLK; i < VCODEC_MAX_CLKS; i++) { if (i == VCODEC_OCMEM_CLK && !device->res->has_ocmem) continue; cl = &device->resources.clock[i]; rc = clk_prepare_enable(cl->clk); if (rc) { dprintk(VIDC_ERR, "Failed to enable clocks\n"); goto fail_clk_enable; } else { dprintk(VIDC_DBG, "Clock: %s enabled\n", cl->name); } } device->clocks_enabled = 1; return rc; fail_clk_enable: for (; i >= 0; i--) { cl = &device->resources.clock[i]; clk_disable_unprepare(cl->clk); } return rc; } static int venus_hfi_register_iommu_domains(struct venus_hfi_device *device, struct msm_vidc_platform_resources *res) { struct iommu_domain *domain; int rc = 0, i = 0; struct iommu_set *iommu_group_set; struct iommu_info *iommu_map; if (!device || !res) return -EINVAL; iommu_group_set = &device->res->iommu_group_set; for (i = 0; i < iommu_group_set->count; i++) { iommu_map = &iommu_group_set->iommu_maps[i]; iommu_map->group = iommu_group_find(iommu_map->name); if (!iommu_map->group) { dprintk(VIDC_ERR, "Failed to find group :%s\n", iommu_map->name); goto fail_group; } domain = iommu_group_get_iommudata(iommu_map->group); if (!domain) { dprintk(VIDC_ERR, "Failed to get domain data for group %p", iommu_map->group); goto fail_group; } iommu_map->domain = msm_find_domain_no(domain); if (iommu_map->domain < 0) { dprintk(VIDC_ERR, "Failed to get domain index for domain %p", domain); goto fail_group; } } return rc; fail_group: for (--i; i >= 0; i--) { iommu_map = &iommu_group_set->iommu_maps[i]; if (iommu_map->group) iommu_group_put(iommu_map->group); iommu_map->group = NULL; iommu_map->domain = -1; } return -EINVAL; } static void venus_hfi_deregister_iommu_domains(struct venus_hfi_device *device) { struct iommu_set *iommu_group_set; struct iommu_info *iommu_map; int i = 0; if (!device) return; iommu_group_set = &device->res->iommu_group_set; for (i = 0; i < iommu_group_set->count; i++) { iommu_map = &iommu_group_set->iommu_maps[i]; if (iommu_map->group) iommu_group_put(iommu_map->group); iommu_map->group = NULL; iommu_map->domain = -1; } } static void venus_hfi_deinit_bus(struct venus_hfi_device *device) { struct venus_bus_info *bus_info; int i = 0; if (!device) return; bus_info = &device->resources.bus_info; for (i = 0; i < MSM_VIDC_MAX_DEVICES; i++) { if (bus_info->ddr_handle[i]) { msm_bus_scale_unregister_client( bus_info->ddr_handle[i]); bus_info->ddr_handle[i] = 0; } if (bus_info->ocmem_handle[i]) { msm_bus_scale_unregister_client( bus_info->ocmem_handle[i]); bus_info->ocmem_handle[i] = 0; } } } static int venus_hfi_init_bus(struct venus_hfi_device *device) { struct venus_bus_info *bus_info; int rc = 0; if ((!device) || (!device->res->bus_pdata)) return -EINVAL; bus_info = &device->resources.bus_info; bus_info->ddr_handle[MSM_VIDC_ENCODER] = msm_bus_scale_register_client(&device->res->bus_pdata[2]); if (!bus_info->ddr_handle[MSM_VIDC_ENCODER]) { dprintk(VIDC_ERR, "Failed to register bus scale client\n"); goto err_init_bus; } bus_info->ddr_handle[MSM_VIDC_DECODER] = msm_bus_scale_register_client(&device->res->bus_pdata[3]); if (!bus_info->ddr_handle[MSM_VIDC_DECODER]) { dprintk(VIDC_ERR, "Failed to register bus scale client\n"); goto err_init_bus; } if (device->res->has_ocmem) { bus_info->ocmem_handle[MSM_VIDC_ENCODER] = msm_bus_scale_register_client( &device->res->bus_pdata[0]); if (!bus_info->ocmem_handle[MSM_VIDC_ENCODER]) { dprintk(VIDC_ERR, "Failed to register bus scale client\n"); goto err_init_bus; } bus_info->ocmem_handle[MSM_VIDC_DECODER] = msm_bus_scale_register_client( &device->res->bus_pdata[1]); if (!bus_info->ocmem_handle[MSM_VIDC_DECODER]) { dprintk(VIDC_ERR, "Failed to register bus scale client\n"); goto err_init_bus; } } return rc; err_init_bus: venus_hfi_deinit_bus(device); return -EINVAL; } static const u32 venus_hfi_bus_table[] = { 36000, 110400, 244800, 489000, 783360, 979200, }; static int venus_hfi_get_bus_vector(int load) { int num_rows = sizeof(venus_hfi_bus_table)/(sizeof(u32)); int i, j; for (i = 0; i < num_rows; i++) { if (load <= venus_hfi_bus_table[i]) break; } j = clamp(i, 0, num_rows-1) + 1; dprintk(VIDC_DBG, "Required bus = %d\n", j); return j; } static int venus_hfi_scale_bus(void *dev, int load, enum session_type type, enum mem_type mtype) { int rc = 0; u32 handle = 0; struct venus_hfi_device *device = dev; if (!device) { dprintk(VIDC_ERR, "%s invalid device handle %p", __func__, device); return -EINVAL; } if (mtype & DDR_MEM) handle = device->resources.bus_info.ddr_handle[type]; if (mtype & OCMEM_MEM) handle = device->resources.bus_info.ocmem_handle[type]; if (handle) { rc = msm_bus_scale_client_update_request( handle, venus_hfi_get_bus_vector(load)); if (rc) dprintk(VIDC_ERR, "Failed to scale bus: %d\n", rc); } else { dprintk(VIDC_ERR, "Failed to scale bus, mtype: %d\n", mtype); rc = -EINVAL; } return rc; } static int venus_hfi_unvote_bus(void *dev, enum session_type type, enum mem_type mtype) { int rc = 0; u32 handle = 0; struct venus_hfi_device *device = dev; if (!device) { dprintk(VIDC_ERR, "%s invalid device handle %p", __func__, device); return -EINVAL; } if (mtype & DDR_MEM) handle = device->resources.bus_info.ddr_handle[type]; if (mtype & OCMEM_MEM) handle = device->resources.bus_info.ocmem_handle[type]; if (handle) { rc = msm_bus_scale_client_update_request( handle, 0); if (rc) dprintk(VIDC_ERR, "Failed to unvote bus: %d\n", rc); } else { dprintk(VIDC_ERR, "Failed to unvote bus, mtype: %d\n", mtype); rc = -EINVAL; } return rc; } static int venus_hfi_set_ocmem(void *dev, struct ocmem_buf *ocmem) { struct vidc_resource_hdr rhdr; struct venus_hfi_device *device = dev; int rc = 0; if (!device || !ocmem) { dprintk(VIDC_ERR, "Invalid params, core:%p, ocmem: %p\n", device, ocmem); return -EINVAL; } rhdr.resource_id = VIDC_RESOURCE_OCMEM; rhdr.resource_handle = (u32) &device->resources.ocmem; rhdr.size = ocmem->len; rc = venus_hfi_core_set_resource(device, &rhdr, ocmem); if (rc) { dprintk(VIDC_ERR, "Failed to set OCMEM on driver\n"); goto ocmem_set_failed; } dprintk(VIDC_DBG, "OCMEM set, addr = %lx, size: %ld\n", ocmem->addr, ocmem->len); ocmem_set_failed: return rc; } static int venus_hfi_unset_ocmem(void *dev) { struct vidc_resource_hdr rhdr; struct venus_hfi_device *device = dev; int rc = 0; if (!device) { dprintk(VIDC_ERR, "%s Invalid params, device:%p\n", __func__, device); rc = -EINVAL; goto ocmem_unset_failed; } if (!device->resources.ocmem.buf) { dprintk(VIDC_INFO, "%s Trying to free OCMEM which is not set", __func__); rc = -EINVAL; goto ocmem_unset_failed; } rhdr.resource_id = VIDC_RESOURCE_OCMEM; rhdr.resource_handle = (u32) &device->resources.ocmem; rc = venus_hfi_core_release_resource(device, &rhdr); if (rc) dprintk(VIDC_ERR, "Failed to unset OCMEM on driver\n"); ocmem_unset_failed: return rc; } static int venus_hfi_ocmem_notify_handler(struct notifier_block *this, unsigned long event, void *data) { struct ocmem_buf *buff = data; struct venus_hfi_device *device; struct venus_resources *resources; struct on_chip_mem *ocmem; int rc = NOTIFY_DONE; if (event == OCMEM_ALLOC_GROW) { ocmem = container_of(this, struct on_chip_mem, vidc_ocmem_nb); if (!ocmem) { dprintk(VIDC_ERR, "Wrong handler passed\n"); rc = NOTIFY_BAD; goto err_ocmem_notify; } resources = container_of(ocmem, struct venus_resources, ocmem); device = container_of(resources, struct venus_hfi_device, resources); if (venus_hfi_set_ocmem(device, buff)) { dprintk(VIDC_ERR, "Failed to set ocmem: %d\n", rc); goto err_ocmem_notify; } rc = NOTIFY_OK; } err_ocmem_notify: return rc; } static void venus_hfi_ocmem_init(struct venus_hfi_device *device) { struct on_chip_mem *ocmem; ocmem = &device->resources.ocmem; ocmem->vidc_ocmem_nb.notifier_call = venus_hfi_ocmem_notify_handler; ocmem->handle = ocmem_notifier_register(OCMEM_VIDEO, &ocmem->vidc_ocmem_nb); if (!ocmem->handle) { dprintk(VIDC_WARN, "Failed to register OCMEM notifier."); dprintk(VIDC_INFO, " Performance will be impacted\n"); } } static int venus_hfi_alloc_ocmem(void *dev, unsigned long size) { int rc = 0; struct ocmem_buf *ocmem_buffer; struct venus_hfi_device *device = dev; if (!device || !size) { dprintk(VIDC_ERR, "%s Invalid param, core: %p, size: %lu\n", __func__, device, size); return -EINVAL; } ocmem_buffer = device->resources.ocmem.buf; if (!ocmem_buffer || ocmem_buffer->len < size) { ocmem_buffer = ocmem_allocate(OCMEM_VIDEO, size); if (IS_ERR_OR_NULL(ocmem_buffer)) { dprintk(VIDC_ERR, "ocmem_allocate_nb failed: %d\n", (u32) ocmem_buffer); rc = -ENOMEM; } device->resources.ocmem.buf = ocmem_buffer; rc = venus_hfi_set_ocmem(device, ocmem_buffer); if (rc) { dprintk(VIDC_ERR, "Failed to set ocmem: %d\n", rc); goto ocmem_set_failed; } } else dprintk(VIDC_DBG, "OCMEM is enough. reqd: %lu, available: %lu\n", size, ocmem_buffer->len); ocmem_set_failed: return rc; } static int venus_hfi_free_ocmem(void *dev) { struct venus_hfi_device *device = dev; int rc = 0; if (!device) { dprintk(VIDC_ERR, "%s invalid device handle %p", __func__, device); return -EINVAL; } if (device->resources.ocmem.buf) { rc = ocmem_free(OCMEM_VIDEO, device->resources.ocmem.buf); if (rc) dprintk(VIDC_ERR, "Failed to free ocmem\n"); device->resources.ocmem.buf = NULL; } return rc; } static void venus_hfi_deinit_ocmem(struct venus_hfi_device *device) { if (device->resources.ocmem.handle) ocmem_notifier_unregister(device->resources.ocmem.handle, &device->resources.ocmem.vidc_ocmem_nb); } static int venus_hfi_init_resources(struct venus_hfi_device *device, struct msm_vidc_platform_resources *res) { int rc = 0; device->res = res; rc = venus_hfi_init_clocks(res, device); if (rc) { dprintk(VIDC_ERR, "Failed to init clocks\n"); rc = -ENODEV; goto err_init_clocks; } rc = venus_hfi_init_bus(device); if (rc) { dprintk(VIDC_ERR, "Failed to init bus: %d\n", rc); goto err_init_bus; } rc = venus_hfi_register_iommu_domains(device, res); if (rc) { dprintk(VIDC_ERR, "Failed to register iommu domains: %d\n", rc); goto err_register_iommu_domain; } if (res->has_ocmem) venus_hfi_ocmem_init(device); return rc; err_register_iommu_domain: venus_hfi_deinit_bus(device); err_init_bus: venus_hfi_deinit_clocks(device); err_init_clocks: return rc; } static void venus_hfi_deinit_resources(struct venus_hfi_device *device) { if (device->res->has_ocmem) venus_hfi_deinit_ocmem(device); venus_hfi_deregister_iommu_domains(device); venus_hfi_deinit_bus(device); venus_hfi_deinit_clocks(device); } static int venus_hfi_iommu_attach(struct venus_hfi_device *device) { int rc = 0; struct iommu_domain *domain; int i; struct iommu_set *iommu_group_set; struct iommu_group *group; struct iommu_info *iommu_map; if (!device || !device->res) return -EINVAL; iommu_group_set = &device->res->iommu_group_set; for (i = 0; i < iommu_group_set->count; i++) { iommu_map = &iommu_group_set->iommu_maps[i]; group = iommu_map->group; domain = msm_get_iommu_domain(iommu_map->domain); if (IS_ERR_OR_NULL(domain)) { dprintk(VIDC_ERR, "Failed to get domain: %s\n", iommu_map->name); rc = PTR_ERR(domain); break; } rc = iommu_attach_group(domain, group); if (rc) { dprintk(VIDC_ERR, "IOMMU attach failed: %s\n", iommu_map->name); break; } } if (i < iommu_group_set->count) { i--; for (; i >= 0; i--) { iommu_map = &iommu_group_set->iommu_maps[i]; group = iommu_map->group; domain = msm_get_iommu_domain(iommu_map->domain); if (group && domain) iommu_detach_group(domain, group); } } return rc; } static void venus_hfi_iommu_detach(struct venus_hfi_device *device) { struct iommu_group *group; struct iommu_domain *domain; struct iommu_set *iommu_group_set; struct iommu_info *iommu_map; int i; if (!device || !device->res) { dprintk(VIDC_ERR, "Invalid paramter: %p\n", device); return; } iommu_group_set = &device->res->iommu_group_set; for (i = 0; i < iommu_group_set->count; i++) { iommu_map = &iommu_group_set->iommu_maps[i]; group = iommu_map->group; domain = msm_get_iommu_domain(iommu_map->domain); if (group && domain) iommu_detach_group(domain, group); } } static int venus_hfi_iommu_get_domain_partition(void *dev, u32 flags, u32 buffer_type, int *domain, int *partition) { struct venus_hfi_device *device = dev; if (!device) { dprintk(VIDC_ERR, "%s: Invalid param device: %p\n", __func__, device); return -EINVAL; } msm_smem_get_domain_partition(device->hal_client, flags, buffer_type, domain, partition); return 0; } static int protect_cp_mem(struct venus_hfi_device *device) { struct tzbsp_memprot memprot; unsigned int resp = 0; int rc = 0; struct iommu_set *iommu_group_set; struct iommu_info *iommu_map; int i; if (!device) return -EINVAL; iommu_group_set = &device->res->iommu_group_set; if (!iommu_group_set) { dprintk(VIDC_ERR, "invalid params: %p\n", iommu_group_set); return -EINVAL; } memprot.cp_start = 0x0; memprot.cp_size = 0x0; memprot.cp_nonpixel_start = 0x0; memprot.cp_nonpixel_size = 0x0; for (i = 0; i < iommu_group_set->count; i++) { iommu_map = &iommu_group_set->iommu_maps[i]; if (strcmp(iommu_map->name, "venus_ns") == 0) memprot.cp_size = iommu_map->addr_range[0].start; if (strcmp(iommu_map->name, "venus_sec_non_pixel") == 0) { memprot.cp_nonpixel_start = iommu_map->addr_range[0].start; memprot.cp_nonpixel_size = iommu_map->addr_range[0].size; } } rc = scm_call(SCM_SVC_MP, TZBSP_MEM_PROTECT_VIDEO_VAR, &memprot, sizeof(memprot), &resp, sizeof(resp)); if (rc) dprintk(VIDC_ERR, "Failed to protect memory , rc is :%d, response : %d\n", rc, resp); return rc; } static int venus_hfi_load_fw(void *dev) { int rc = 0; struct venus_hfi_device *device = dev; if (!device) { dprintk(VIDC_ERR, "%s Invalid paramter: %p\n", __func__, device); return -EINVAL; } mutex_init(&device->clock_lock); device->clk_gating_level = VCODEC_CLK; rc = venus_hfi_iommu_attach(device); if (rc) { dprintk(VIDC_ERR, "Failed to attach iommu"); goto fail_iommu_attach; } if (!device->resources.fw.cookie) device->resources.fw.cookie = subsystem_get("venus"); if (IS_ERR_OR_NULL(device->resources.fw.cookie)) { dprintk(VIDC_ERR, "Failed to download firmware\n"); rc = -ENOMEM; goto fail_load_fw; } /*Clocks can be enabled only after pil_get since * gdsc is turned-on in pil_get*/ rc = venus_hfi_enable_clks(device); if (rc) { dprintk(VIDC_ERR, "Failed to enable clocks: %d\n", rc); goto fail_enable_clks; } rc = protect_cp_mem(device); if (rc) { dprintk(VIDC_ERR, "Failed to protect memory\n"); goto fail_protect_mem; } return rc; fail_protect_mem: venus_hfi_disable_clks(device); fail_enable_clks: subsystem_put(device->resources.fw.cookie); device->resources.fw.cookie = NULL; fail_load_fw: venus_hfi_iommu_detach(device); fail_iommu_attach: return rc; } static void venus_hfi_unload_fw(void *dev) { struct venus_hfi_device *device = dev; if (!device) { dprintk(VIDC_ERR, "%s Invalid paramter: %p\n", __func__, device); return; } if (device->resources.fw.cookie) { flush_workqueue(device->vidc_workq); venus_hfi_disable_clks(device); subsystem_put(device->resources.fw.cookie); venus_hfi_interface_queues_release(dev); venus_hfi_iommu_detach(device); device->resources.fw.cookie = NULL; } } static int venus_hfi_get_fw_info(void *dev, enum fw_info info) { int rc = 0; struct venus_hfi_device *device = dev; if (!device) { dprintk(VIDC_ERR, "%s Invalid paramter: %p\n", __func__, device); return -EINVAL; } switch (info) { case FW_BASE_ADDRESS: rc = device->base_addr; break; case FW_REGISTER_BASE: rc = device->register_base; break; case FW_REGISTER_SIZE: rc = device->register_size; break; case FW_IRQ: rc = device->irq; break; default: dprintk(VIDC_ERR, "Invalid fw info requested"); } return rc; } int venus_hfi_get_stride_scanline(int color_fmt, int width, int height, int *stride, int *scanlines) { *stride = VENUS_Y_STRIDE(color_fmt, width); *scanlines = VENUS_Y_SCANLINES(color_fmt, height); return 0; } int venus_hfi_capability_check(u32 fourcc, u32 width, u32 *max_width, u32 *max_height) { int rc = 0; if (!max_width || !max_height) { dprintk(VIDC_ERR, "%s - invalid parameter\n", __func__); return -EINVAL; } if (msm_vp8_low_tier && fourcc == V4L2_PIX_FMT_VP8) { *max_width = DEFAULT_WIDTH; *max_height = DEFAULT_HEIGHT; } if (width > *max_width) { dprintk(VIDC_ERR, "Unsupported width = %u supported max width = %u", width, *max_width); rc = -ENOTSUPP; } return rc; } static void *venus_hfi_add_device(u32 device_id, struct msm_vidc_platform_resources *res, hfi_cmd_response_callback callback) { struct venus_hfi_device *hdevice = NULL; int rc = 0; if (device_id || !res || !callback) { dprintk(VIDC_ERR, "Invalid Paramters"); return NULL; } dprintk(VIDC_INFO, "entered , device_id: %d", device_id); hdevice = (struct venus_hfi_device *) kzalloc(sizeof(struct venus_hfi_device), GFP_KERNEL); if (!hdevice) { dprintk(VIDC_ERR, "failed to allocate new device"); goto err_alloc; } rc = venus_hfi_init_regs_and_interrupts(hdevice, res); if (rc) goto err_init_regs; hdevice->device_id = device_id; hdevice->callback = callback; hdevice->vidc_workq = create_singlethread_workqueue( "msm_vidc_workerq_venus"); if (!hdevice->vidc_workq) { dprintk(VIDC_ERR, ": create workq failed\n"); goto error_createq; } if (hal_ctxt.dev_count == 0) INIT_LIST_HEAD(&hal_ctxt.dev_head); INIT_LIST_HEAD(&hdevice->list); list_add_tail(&hdevice->list, &hal_ctxt.dev_head); hal_ctxt.dev_count++; return (void *) hdevice; error_createq: err_init_regs: kfree(hdevice); err_alloc: return NULL; } static void *venus_hfi_get_device(u32 device_id, struct msm_vidc_platform_resources *res, hfi_cmd_response_callback callback) { struct venus_hfi_device *device; int rc = 0; if (!res || !callback) { dprintk(VIDC_ERR, "Invalid params: %p %p\n", res, callback); return NULL; } device = venus_hfi_add_device(device_id, res, &handle_cmd_response); if (!device) { dprintk(VIDC_ERR, "Failed to create HFI device\n"); return NULL; } rc = venus_hfi_init_resources(device, res); if (rc) { dprintk(VIDC_ERR, "Failed to init resources: %d\n", rc); goto err_fail_init_res; } return device; err_fail_init_res: venus_hfi_delete_device(device); return NULL; } void venus_hfi_delete_device(void *device) { struct venus_hfi_device *close, *tmp, *dev; if (device) { venus_hfi_deinit_resources(device); dev = (struct venus_hfi_device *) device; list_for_each_entry_safe(close, tmp, &hal_ctxt.dev_head, list) { if (close->hal_data->irq == dev->hal_data->irq) { hal_ctxt.dev_count--; free_irq(dev->hal_data->irq, close); list_del(&close->list); destroy_workqueue(close->vidc_workq); kfree(close->hal_data); kfree(close); break; } } } } static void venus_init_hfi_callbacks(struct hfi_device *hdev) { hdev->core_init = venus_hfi_core_init; hdev->core_release = venus_hfi_core_release; hdev->core_pc_prep = venus_hfi_core_pc_prep; hdev->core_ping = venus_hfi_core_ping; hdev->core_trigger_ssr = venus_hfi_core_trigger_ssr; hdev->session_init = venus_hfi_session_init; hdev->session_end = venus_hfi_session_end; hdev->session_abort = venus_hfi_session_abort; hdev->session_clean = venus_hfi_session_clean; hdev->session_set_buffers = venus_hfi_session_set_buffers; hdev->session_release_buffers = venus_hfi_session_release_buffers; hdev->session_load_res = venus_hfi_session_load_res; hdev->session_release_res = venus_hfi_session_release_res; hdev->session_start = venus_hfi_session_start; hdev->session_stop = venus_hfi_session_stop; hdev->session_suspend = venus_hfi_session_suspend; hdev->session_resume = venus_hfi_session_resume; hdev->session_etb = venus_hfi_session_etb; hdev->session_ftb = venus_hfi_session_ftb; hdev->session_parse_seq_hdr = venus_hfi_session_parse_seq_hdr; hdev->session_get_seq_hdr = venus_hfi_session_get_seq_hdr; hdev->session_get_buf_req = venus_hfi_session_get_buf_req; hdev->session_flush = venus_hfi_session_flush; hdev->session_set_property = venus_hfi_session_set_property; hdev->session_get_property = venus_hfi_session_get_property; hdev->scale_clocks = venus_hfi_scale_clocks; hdev->scale_bus = venus_hfi_scale_bus; hdev->unvote_bus = venus_hfi_unvote_bus; hdev->unset_ocmem = venus_hfi_unset_ocmem; hdev->alloc_ocmem = venus_hfi_alloc_ocmem; hdev->free_ocmem = venus_hfi_free_ocmem; hdev->iommu_get_domain_partition = venus_hfi_iommu_get_domain_partition; hdev->load_fw = venus_hfi_load_fw; hdev->unload_fw = venus_hfi_unload_fw; hdev->get_fw_info = venus_hfi_get_fw_info; hdev->get_stride_scanline = venus_hfi_get_stride_scanline; hdev->capability_check = venus_hfi_capability_check; } int venus_hfi_initialize(struct hfi_device *hdev, u32 device_id, struct msm_vidc_platform_resources *res, hfi_cmd_response_callback callback) { int rc = 0; if (!hdev || !res || !callback) { dprintk(VIDC_ERR, "Invalid params: %p %p %p\n", hdev, res, callback); rc = -EINVAL; goto err_venus_hfi_init; } hdev->hfi_device_data = venus_hfi_get_device(device_id, res, callback); venus_init_hfi_callbacks(hdev); err_venus_hfi_init: return rc; }