blob: 48efca59d755cdb3939b7f42fbd862e1fbcf59a9 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2018-2020, The Linux Foundation. All rights reserved.
*/
#include "msm_cvp_internal.h"
#define MSM_VIDC_NOMINAL_CYCLES (444 * 1000 * 1000)
#define MSM_VIDC_UHD60E_VPSS_CYCLES (111 * 1000 * 1000)
#define MSM_VIDC_UHD60E_ISE_CYCLES (175 * 1000 * 1000)
#define MAX_CVP_VPSS_CYCLES (MSM_VIDC_NOMINAL_CYCLES - \
MSM_VIDC_UHD60E_VPSS_CYCLES)
#define MAX_CVP_ISE_CYCLES (MSM_VIDC_NOMINAL_CYCLES - \
MSM_VIDC_UHD60E_ISE_CYCLES)
static void print_client_buffer(u32 tag, const char *str,
struct msm_vidc_inst *inst, struct msm_cvp_buffer *cbuf)
{
if (!(tag & msm_vidc_debug) || !inst || !cbuf)
return;
dprintk(tag, inst->sid,
"%s: idx %2d fd %d off %d size %d type %d flags 0x%x\n",
str, cbuf->index, cbuf->fd,
cbuf->offset, cbuf->size, cbuf->type, cbuf->flags);
}
static void print_cvp_buffer(u32 tag, const char *str,
struct msm_vidc_inst *inst, struct msm_vidc_cvp_buffer *cbuf)
{
if (!(tag & msm_vidc_debug) || !inst || !cbuf)
return;
dprintk(tag, inst->sid,
"%s: idx %2d fd %d off %d daddr %x size %d type %d flags 0x%x\n",
str, cbuf->buf.index, cbuf->buf.fd,
cbuf->buf.offset, cbuf->smem.device_addr, cbuf->buf.size,
cbuf->buf.type, cbuf->buf.flags);
}
static enum hal_buffer get_hal_buftype(const char *str,
unsigned int type, u32 sid)
{
enum hal_buffer buftype = HAL_BUFFER_NONE;
if (type == MSM_CVP_BUFTYPE_INPUT)
buftype = HAL_BUFFER_INPUT;
else if (type == MSM_CVP_BUFTYPE_OUTPUT)
buftype = HAL_BUFFER_OUTPUT;
else if (type == MSM_CVP_BUFTYPE_INTERNAL_1)
buftype = HAL_BUFFER_INTERNAL_SCRATCH_1;
else if (type == MSM_CVP_BUFTYPE_INTERNAL_2)
buftype = HAL_BUFFER_INTERNAL_SCRATCH_1;
else
s_vpr_e(sid, "%s: unknown buffer type %#x\n",
str, type);
return buftype;
}
void handle_session_register_buffer_done(enum hal_command_response cmd,
void *resp)
{
struct msm_vidc_cb_cmd_done *response = resp;
struct msm_vidc_inst *inst;
struct msm_vidc_cvp_buffer *cbuf;
struct v4l2_event event = {0};
u32 *data;
bool found;
if (!response) {
d_vpr_e("%s: invalid response\n", __func__);
return;
}
inst = get_inst(get_vidc_core(response->device_id),
response->inst_id);
if (!inst) {
d_vpr_e("%s: invalid session %pK\n", __func__,
response->inst_id);
return;
}
mutex_lock(&inst->cvpbufs.lock);
found = false;
list_for_each_entry(cbuf, &inst->cvpbufs.list, list) {
if (response->data.regbuf.client_data ==
cbuf->smem.device_addr) {
found = true;
break;
}
}
mutex_unlock(&inst->cvpbufs.lock);
if (!found) {
s_vpr_e(inst->sid, "%s: client_data %x not found\n",
__func__, response->data.regbuf.client_data);
goto exit;
}
print_cvp_buffer(VIDC_HIGH, "register_done", inst, cbuf);
event.type = V4L2_EVENT_MSM_VIDC_REGISTER_BUFFER_DONE;
data = (u32 *)event.u.data;
data[0] = cbuf->buf.index;
data[1] = cbuf->buf.type;
data[2] = cbuf->buf.fd;
data[3] = cbuf->buf.offset;
v4l2_event_queue_fh(&inst->event_handler, &event);
exit:
s_vpr_l(inst->sid, "handled: SESSION_REGISTER_BUFFER_DONE\n");
put_inst(inst);
}
void handle_session_unregister_buffer_done(enum hal_command_response cmd,
void *resp)
{
int rc;
struct msm_vidc_cb_cmd_done *response = resp;
struct msm_vidc_inst *inst;
struct msm_vidc_cvp_buffer *cbuf, *dummy;
struct v4l2_event event = {0};
u32 *data;
bool found;
if (!response) {
d_vpr_e("%s: invalid response\n", __func__);
return;
}
inst = get_inst(get_vidc_core(response->device_id),
response->inst_id);
if (!inst) {
d_vpr_e("%s: invalid session %pK\n", __func__,
response->inst_id);
return;
}
mutex_lock(&inst->cvpbufs.lock);
found = false;
list_for_each_entry_safe(cbuf, dummy, &inst->cvpbufs.list, list) {
if (response->data.unregbuf.client_data ==
cbuf->smem.device_addr) {
found = true;
break;
}
}
mutex_unlock(&inst->cvpbufs.lock);
if (!found) {
s_vpr_e(inst->sid, "%s: client_data %x not found\n",
__func__, response->data.unregbuf.client_data);
goto exit;
}
print_cvp_buffer(VIDC_HIGH, "unregister_done", inst, cbuf);
rc = inst->smem_ops->smem_unmap_dma_buf(inst, &cbuf->smem);
if (rc) {
print_cvp_buffer(VIDC_ERR, "unmap fail", inst, cbuf);
goto exit;
}
event.type = V4L2_EVENT_MSM_VIDC_UNREGISTER_BUFFER_DONE;
data = (u32 *)event.u.data;
data[0] = cbuf->buf.index;
data[1] = cbuf->buf.type;
data[2] = cbuf->buf.fd;
data[3] = cbuf->buf.offset;
v4l2_event_queue_fh(&inst->event_handler, &event);
mutex_lock(&inst->cvpbufs.lock);
list_del(&cbuf->list);
mutex_unlock(&inst->cvpbufs.lock);
kfree(cbuf);
cbuf = NULL;
exit:
s_vpr_l(inst->sid, "handled: SESSION_UNREGISTER_BUFFER_DONE\n");
put_inst(inst);
}
static void print_cvp_cycles(struct msm_vidc_inst *inst)
{
struct msm_vidc_core *core;
struct msm_vidc_inst *temp;
if (!inst || !inst->core)
return;
core = inst->core;
mutex_lock(&core->lock);
list_for_each_entry(temp, &core->instances, list) {
if (temp->session_type == MSM_VIDC_CVP) {
s_vpr_e(temp->sid, "vpss %d ise %d\n",
temp->clk_data.vpss_cycles,
temp->clk_data.ise_cycles);
}
}
mutex_unlock(&core->lock);
}
static bool msm_cvp_check_session_supported(struct msm_vidc_inst *inst,
u32 vpss_cycles, u32 ise_cycles)
{
struct msm_vidc_core *core;
struct msm_vidc_inst *temp;
u32 total_vpss_cycles = 0;
u32 total_ise_cycles = 0;
if (!inst || !inst->core) {
d_vpr_e("%s: invalid params %pK\n", __func__, inst);
return false;
}
core = inst->core;
mutex_lock(&core->lock);
list_for_each_entry(temp, &core->instances, list) {
if (temp->session_type == MSM_VIDC_CVP) {
total_vpss_cycles += inst->clk_data.vpss_cycles;
total_ise_cycles += inst->clk_data.ise_cycles;
}
}
mutex_unlock(&core->lock);
if ((total_vpss_cycles > MAX_CVP_VPSS_CYCLES) ||
(total_ise_cycles > MAX_CVP_ISE_CYCLES))
return false;
return true;
}
static int msm_cvp_scale_clocks_and_bus(struct msm_vidc_inst *inst)
{
int rc = 0;
if (!inst || !inst->core) {
d_vpr_e("%s: invalid params %pK\n", __func__, inst);
return -EINVAL;
}
rc = msm_vidc_set_clocks(inst->core, inst->sid);
if (rc) {
s_vpr_e(inst->sid, "%s: failed set_clocks for inst %pK\n",
__func__, inst);
goto exit;
}
rc = msm_comm_vote_bus(inst);
if (rc) {
s_vpr_e(inst->sid,
"%s: failed vote_bus for inst %pK\n", __func__, inst);
goto exit;
}
exit:
return rc;
}
static int msm_cvp_get_session_info(struct msm_vidc_inst *inst,
struct msm_cvp_session_info *session)
{
int rc = 0;
if (!inst || !inst->core || !session) {
d_vpr_e("%s: invalid params %pK %pK\n",
__func__, inst, session);
return -EINVAL;
}
session->session_id = inst->sid;
s_vpr_h(inst->sid, "%s: id 0x%x\n", __func__, session->session_id);
return rc;
}
static int msm_cvp_request_power(struct msm_vidc_inst *inst,
struct msm_cvp_request_power *power)
{
int rc = 0;
if (!inst || !power) {
d_vpr_e("%s: invalid params %pK %pK\n",
__func__, inst, power);
return -EINVAL;
}
s_vpr_h(inst->sid,
"%s: clock_cycles_a %d, clock_cycles_b %d, ddr_bw %d sys_cache_bw %d\n",
__func__, power->clock_cycles_a, power->clock_cycles_b,
power->ddr_bw, power->sys_cache_bw);
rc = msm_cvp_check_session_supported(inst, power->clock_cycles_a,
power->clock_cycles_b);
if (!rc) {
s_vpr_e(inst->sid, "%s: rejected, cycles: vpss %d, ise %d\n",
__func__, power->clock_cycles_a, power->clock_cycles_b);
print_cvp_cycles(inst);
msm_comm_kill_session(inst);
return -EOVERFLOW;
}
inst->clk_data.min_freq = max(power->clock_cycles_a,
power->clock_cycles_b);
/* convert client provided bps into kbps as expected by driver */
inst->clk_data.ddr_bw = power->ddr_bw / 1000;
inst->clk_data.sys_cache_bw = power->sys_cache_bw / 1000;
rc = msm_cvp_scale_clocks_and_bus(inst);
if (rc) {
s_vpr_e(inst->sid,
"%s: failed to scale clocks and bus for inst %pK\n",
__func__, inst);
goto exit;
}
if (!inst->clk_data.min_freq && !inst->clk_data.ddr_bw &&
!inst->clk_data.sys_cache_bw) {
rc = msm_cvp_inst_pause(inst);
if (rc) {
s_vpr_e(inst->sid, "%s: failed to pause\n", __func__);
goto exit;
}
} else {
rc = msm_cvp_inst_resume(inst);
if (rc) {
s_vpr_e(inst->sid, "%s: failed to resume\n", __func__);
goto exit;
}
}
exit:
return rc;
}
static int msm_cvp_register_buffer(struct msm_vidc_inst *inst,
struct msm_cvp_buffer *buf)
{
int rc = 0;
bool found;
struct hfi_device *hdev;
struct msm_vidc_cvp_buffer *cbuf;
struct vidc_register_buffer vbuf;
if (!inst || !inst->core || !buf) {
d_vpr_e("%s: invalid params %pK %pK\n",
__func__, inst, buf);
return -EINVAL;
}
hdev = inst->core->device;
print_client_buffer(VIDC_HIGH, "register", inst, buf);
mutex_lock(&inst->cvpbufs.lock);
found = false;
list_for_each_entry(cbuf, &inst->cvpbufs.list, list) {
if (cbuf->buf.index == buf->index &&
cbuf->buf.fd == buf->fd &&
cbuf->buf.offset == buf->offset) {
found = true;
break;
}
}
mutex_unlock(&inst->cvpbufs.lock);
if (found) {
print_client_buffer(VIDC_ERR, "duplicate", inst, buf);
return -EINVAL;
}
cbuf = kzalloc(sizeof(struct msm_vidc_cvp_buffer), GFP_KERNEL);
if (!cbuf) {
s_vpr_e(inst->sid, "%s: cbuf alloc failed\n", __func__);
return -ENOMEM;
}
memcpy(&cbuf->buf, buf, sizeof(struct msm_cvp_buffer));
cbuf->smem.buffer_type = get_hal_buftype(__func__, buf->type,
inst->sid);
cbuf->smem.fd = buf->fd;
cbuf->smem.offset = buf->offset;
cbuf->smem.size = buf->size;
rc = inst->smem_ops->smem_map_dma_buf(inst, &cbuf->smem);
if (rc) {
print_client_buffer(VIDC_ERR, "map failed", inst, buf);
goto exit;
}
memset(&vbuf, 0, sizeof(struct vidc_register_buffer));
vbuf.index = buf->index;
vbuf.type = get_hal_buftype(__func__, buf->type, inst->sid);
vbuf.size = buf->size;
vbuf.device_addr = cbuf->smem.device_addr;
vbuf.client_data = cbuf->smem.device_addr;
vbuf.response_required = true;
rc = call_hfi_op(hdev, session_register_buffer,
(void *)inst->session, &vbuf);
if (rc) {
print_cvp_buffer(VIDC_ERR, "register failed", inst, cbuf);
goto exit;
}
mutex_lock(&inst->cvpbufs.lock);
list_add_tail(&cbuf->list, &inst->cvpbufs.list);
mutex_unlock(&inst->cvpbufs.lock);
return rc;
exit:
if (cbuf->smem.device_addr)
inst->smem_ops->smem_unmap_dma_buf(inst, &cbuf->smem);
kfree(cbuf);
cbuf = NULL;
return rc;
}
static int msm_cvp_unregister_buffer(struct msm_vidc_inst *inst,
struct msm_cvp_buffer *buf)
{
int rc = 0;
bool found;
struct hfi_device *hdev;
struct msm_vidc_cvp_buffer *cbuf;
struct vidc_unregister_buffer vbuf;
if (!inst || !inst->core || !buf) {
d_vpr_e("%s: invalid params %pK %pK\n",
__func__, inst, buf);
return -EINVAL;
}
hdev = inst->core->device;
print_client_buffer(VIDC_HIGH, "unregister", inst, buf);
mutex_lock(&inst->cvpbufs.lock);
found = false;
list_for_each_entry(cbuf, &inst->cvpbufs.list, list) {
if (cbuf->buf.index == buf->index &&
cbuf->buf.fd == buf->fd &&
cbuf->buf.offset == buf->offset) {
found = true;
break;
}
}
mutex_unlock(&inst->cvpbufs.lock);
if (!found) {
print_client_buffer(VIDC_ERR, "invalid", inst, buf);
return -EINVAL;
}
memset(&vbuf, 0, sizeof(struct vidc_unregister_buffer));
vbuf.index = cbuf->buf.index;
vbuf.type = get_hal_buftype(__func__, cbuf->buf.type, inst->sid);
vbuf.size = cbuf->buf.size;
vbuf.device_addr = cbuf->smem.device_addr;
vbuf.client_data = cbuf->smem.device_addr;
vbuf.response_required = true;
rc = call_hfi_op(hdev, session_unregister_buffer,
(void *)inst->session, &vbuf);
if (rc)
print_cvp_buffer(VIDC_ERR, "unregister failed", inst, cbuf);
return rc;
}
int msm_vidc_cvp(struct msm_vidc_inst *inst, struct msm_vidc_arg *arg)
{
int rc = 0;
if (!inst || !arg) {
d_vpr_e("%s: invalid args %pK %pK\n",
__func__, inst, arg);
return -EINVAL;
}
switch (arg->type) {
case MSM_CVP_GET_SESSION_INFO:
{
struct msm_cvp_session_info *session =
(struct msm_cvp_session_info *)&arg->data.session;
rc = msm_cvp_get_session_info(inst, session);
break;
}
case MSM_CVP_REQUEST_POWER:
{
struct msm_cvp_request_power *power =
(struct msm_cvp_request_power *)&arg->data.req_power;
rc = msm_cvp_request_power(inst, power);
break;
}
case MSM_CVP_REGISTER_BUFFER:
{
struct msm_cvp_buffer *buf =
(struct msm_cvp_buffer *)&arg->data.regbuf;
rc = msm_cvp_register_buffer(inst, buf);
break;
}
case MSM_CVP_UNREGISTER_BUFFER:
{
struct msm_cvp_buffer *buf =
(struct msm_cvp_buffer *)&arg->data.unregbuf;
rc = msm_cvp_unregister_buffer(inst, buf);
break;
}
default:
s_vpr_e(inst->sid, "%s: unknown arg type 0x%x\n",
__func__, arg->type);
rc = -ENOTSUPP;
break;
}
return rc;
}
static struct msm_vidc_ctrl msm_cvp_ctrls[] = {
{
.id = V4L2_CID_MPEG_VIDEO_UNKNOWN,
.name = "Invalid control",
.type = V4L2_CTRL_TYPE_INTEGER,
.minimum = 0,
.maximum = 0,
.default_value = 0,
.step = 1,
.menu_skip_mask = 0,
.qmenu = NULL,
},
};
int msm_cvp_ctrl_init(struct msm_vidc_inst *inst,
const struct v4l2_ctrl_ops *ctrl_ops)
{
return msm_comm_ctrl_init(inst, msm_cvp_ctrls,
ARRAY_SIZE(msm_cvp_ctrls), ctrl_ops);
}
int msm_cvp_inst_pause(struct msm_vidc_inst *inst)
{
int rc;
struct hfi_device *hdev;
if (!inst || !inst->core || !inst->core->device) {
d_vpr_e("%s: invalid params %pK\n", __func__, inst);
return -EINVAL;
}
hdev = inst->core->device;
rc = call_hfi_op(hdev, session_pause, (void *)inst->session);
if (rc)
s_vpr_e(inst->sid, "%s: failed to pause\n", __func__);
return rc;
}
int msm_cvp_inst_resume(struct msm_vidc_inst *inst)
{
int rc;
struct hfi_device *hdev;
if (!inst || !inst->core || !inst->core->device) {
d_vpr_e("%s: invalid params %pK\n", __func__, inst);
return -EINVAL;
}
hdev = inst->core->device;
rc = call_hfi_op(hdev, session_resume, (void *)inst->session);
if (rc)
s_vpr_e(inst->sid, "%s: failed to resume\n", __func__);
return rc;
}
int msm_cvp_inst_deinit(struct msm_vidc_inst *inst)
{
int rc = 0;
struct msm_vidc_cvp_buffer *cbuf, *temp;
if (!inst || !inst->core) {
d_vpr_e("%s: invalid params %pK\n", __func__, inst);
return -EINVAL;
}
s_vpr_h(inst->sid, "%s: inst %pK\n", __func__, inst);
rc = msm_comm_try_state(inst, MSM_VIDC_CLOSE_DONE);
if (rc)
s_vpr_e(inst->sid, "%s: close failed\n", __func__);
mutex_lock(&inst->cvpbufs.lock);
list_for_each_entry_safe(cbuf, temp, &inst->cvpbufs.list, list) {
print_cvp_buffer(VIDC_ERR, "unregistered", inst, cbuf);
rc = inst->smem_ops->smem_unmap_dma_buf(inst, &cbuf->smem);
if (rc)
s_vpr_e(inst->sid, "%s: unmap failed\n", __func__);
list_del(&cbuf->list);
kfree(cbuf);
}
mutex_unlock(&inst->cvpbufs.lock);
inst->clk_data.min_freq = 0;
inst->clk_data.ddr_bw = 0;
inst->clk_data.sys_cache_bw = 0;
rc = msm_cvp_scale_clocks_and_bus(inst);
if (rc)
s_vpr_e(inst->sid, "%s: failed to scale_clocks_and_bus\n",
__func__);
return rc;
}
int msm_cvp_inst_init(struct msm_vidc_inst *inst)
{
int rc = 0;
if (!inst) {
d_vpr_e("%s: invalid params\n", __func__);
return -EINVAL;
}
s_vpr_h(inst->sid, "%s: inst %pK\n", __func__, inst);
/* set default frequency */
inst->clk_data.core_id = VIDC_CORE_ID_2;
inst->clk_data.min_freq = 1000;
inst->clk_data.ddr_bw = 1000;
inst->clk_data.sys_cache_bw = 1000;
return rc;
}