blob: ac338e1d394435e6052ab084c0f892613d5460e4 [file] [log] [blame]
/* Copyright (c) 2014-2016, 2018 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 "msm_vidc_common.h"
#include "vidc_hfi_api.h"
#include "msm_vidc_debug.h"
#include "msm_vidc_dcvs.h"
#define IS_VALID_DCVS_SESSION(__cur_mbpf, __min_mbpf) \
((__cur_mbpf) >= (__min_mbpf))
static bool msm_dcvs_check_supported(struct msm_vidc_inst *inst);
static bool msm_dcvs_enc_check(struct msm_vidc_inst *inst);
static int msm_dcvs_enc_scale_clocks(struct msm_vidc_inst *inst);
static int msm_dcvs_dec_scale_clocks(struct msm_vidc_inst *inst, bool fbd);
static inline int msm_dcvs_get_mbs_per_frame(struct msm_vidc_inst *inst)
{
int height, width;
if (!inst->in_reconfig) {
height = max(inst->prop.height[CAPTURE_PORT],
inst->prop.height[OUTPUT_PORT]);
width = max(inst->prop.width[CAPTURE_PORT],
inst->prop.width[OUTPUT_PORT]);
} else {
height = inst->reconfig_height;
width = inst->reconfig_width;
}
return NUM_MBS_PER_FRAME(height, width);
}
static inline int msm_dcvs_count_active_instances(struct msm_vidc_core *core)
{
int active_instances = 0;
struct msm_vidc_inst *inst = NULL;
if (!core) {
dprintk(VIDC_ERR, "%s: Invalid args: %pK\n", __func__, core);
return -EINVAL;
}
mutex_lock(&core->lock);
list_for_each_entry(inst, &core->instances, list) {
if (inst->state >= MSM_VIDC_OPEN_DONE &&
inst->state < MSM_VIDC_STOP_DONE)
active_instances++;
}
mutex_unlock(&core->lock);
return active_instances;
}
static bool msm_dcvs_check_codec_supported(int fourcc,
unsigned long codecs_supported, enum session_type type)
{
int codec_bit, session_type_bit;
bool codec_type, session_type;
unsigned long session;
session = VIDC_VOTE_DATA_SESSION_VAL(get_hal_codec(fourcc),
get_hal_domain(type));
if (!codecs_supported || !session)
return false;
/* ffs returns a 1 indexed, test_bit takes a 0 indexed...index */
codec_bit = ffs(session) - 1;
session_type_bit = codec_bit + 1;
codec_type =
test_bit(codec_bit, &codecs_supported) ==
test_bit(codec_bit, &session);
session_type =
test_bit(session_type_bit, &codecs_supported) ==
test_bit(session_type_bit, &session);
return codec_type && session_type;
}
static void msm_dcvs_update_dcvs_params(int idx, struct msm_vidc_inst *inst)
{
struct dcvs_stats *dcvs = NULL;
struct msm_vidc_platform_resources *res = NULL;
struct dcvs_table *table = NULL;
if (!inst || !inst->core) {
dprintk(VIDC_ERR, "%s Invalid args: %pK\n", __func__, inst);
return;
}
dcvs = &inst->dcvs;
res = &inst->core->resources;
table = res->dcvs_tbl;
dcvs->load_low = table[idx].load_low;
dcvs->load_high = table[idx].load_high;
dcvs->supported_codecs = table[idx].supported_codecs;
}
static void msm_dcvs_enc_check_and_scale_clocks(struct msm_vidc_inst *inst)
{
int rc = 0;
if (inst->session_type == MSM_VIDC_ENCODER && msm_vidc_enc_dcvs_mode) {
inst->dcvs_mode = msm_dcvs_check_supported(inst);
dprintk(VIDC_DBG, "%s: session DCVS %s supported\n",
__func__, inst->dcvs_mode ? "" : "not");
if (inst->dcvs_mode) {
rc = msm_dcvs_enc_scale_clocks(inst);
if (rc) {
dprintk(VIDC_DBG,
"ENC_DCVS: error while scaling clocks\n");
}
}
}
}
static void msm_dcvs_dec_check_and_scale_clocks(struct msm_vidc_inst *inst)
{
int rc = 0;
if (inst->session_type != MSM_VIDC_DECODER || !msm_vidc_dec_dcvs_mode)
return;
if (msm_dcvs_check_supported(inst)) {
inst->dcvs_mode = true;
dprintk(VIDC_DBG,
"%s: session DCVS supported, decode_dcvs_mode = %d\n",
__func__, inst->dcvs_mode);
} else {
inst->dcvs_mode = false;
dprintk(VIDC_DBG,
"%s: session DCVS not supported, decode_dcvs_mode = %d\n",
__func__, inst->dcvs_mode);
}
if (msm_vidc_dec_dcvs_mode && inst->dcvs_mode) {
msm_dcvs_monitor_buffer(inst);
rc = msm_dcvs_dec_scale_clocks(inst, false);
if (rc) {
dprintk(VIDC_ERR,
"%s: Failed to scale clocks in DCVS: %d\n",
__func__, rc);
}
}
}
void msm_dcvs_check_and_scale_clocks(struct msm_vidc_inst *inst, bool is_etb)
{
if (!inst) {
dprintk(VIDC_ERR, "%s Invalid args: %pK\n", __func__, inst);
return;
}
if (is_etb)
msm_dcvs_enc_check_and_scale_clocks(inst);
else
msm_dcvs_dec_check_and_scale_clocks(inst);
}
static inline int get_pending_bufs_fw(struct msm_vidc_inst *inst)
{
int fw_out_qsize = 0, buffers_in_driver = 0;
if (!inst) {
dprintk(VIDC_ERR, "%s Invalid args\n", __func__);
return -EINVAL;
}
if (inst->state >= MSM_VIDC_OPEN_DONE &&
inst->state < MSM_VIDC_STOP_DONE) {
fw_out_qsize = inst->count.ftb - inst->count.fbd;
buffers_in_driver = inst->buffers_held_in_driver;
}
return fw_out_qsize + buffers_in_driver;
}
static inline void msm_dcvs_print_dcvs_stats(struct dcvs_stats *dcvs)
{
dprintk(VIDC_DBG,
"DCVS: Load_Low %d, Load High %d\n",
dcvs->load_low,
dcvs->load_high);
dprintk(VIDC_DBG,
"DCVS: ThrDispBufLow %d, ThrDispBufHigh %d\n",
dcvs->threshold_disp_buf_low,
dcvs->threshold_disp_buf_high);
dprintk(VIDC_DBG,
"DCVS: min_threshold %d, max_threshold %d\n",
dcvs->min_threshold, dcvs->max_threshold);
}
void msm_dcvs_init_load(struct msm_vidc_inst *inst)
{
struct msm_vidc_core *core;
struct hal_buffer_requirements *output_buf_req;
struct dcvs_stats *dcvs;
struct dcvs_table *table;
struct msm_vidc_platform_resources *res = NULL;
int i, num_rows, fourcc;
dprintk(VIDC_DBG, "Init DCVS Load\n");
if (!inst || !inst->core) {
dprintk(VIDC_ERR, "%s Invalid args: %pK\n", __func__, inst);
return;
}
core = inst->core;
dcvs = &inst->dcvs;
res = &core->resources;
dcvs->load = msm_comm_get_inst_load(inst, LOAD_CALC_NO_QUIRKS);
num_rows = res->dcvs_tbl_size;
table = res->dcvs_tbl;
if (!num_rows || !table) {
dprintk(VIDC_ERR,
"%s: Dcvs table entry not found.\n", __func__);
return;
}
fourcc = inst->session_type == MSM_VIDC_DECODER ?
inst->fmts[OUTPUT_PORT].fourcc :
inst->fmts[CAPTURE_PORT].fourcc;
for (i = 0; i < num_rows; i++) {
bool matches = msm_dcvs_check_codec_supported(
fourcc,
table[i].supported_codecs,
inst->session_type);
if (!matches)
continue;
if (dcvs->load > table[i].load) {
msm_dcvs_update_dcvs_params(i, inst);
break;
}
}
if (inst->session_type == MSM_VIDC_ENCODER)
goto print_stats;
output_buf_req = get_buff_req_buffer(inst,
msm_comm_get_hal_output_buffer(inst));
if (!output_buf_req) {
dprintk(VIDC_ERR,
"%s: No buffer requirement for buffer type %x\n",
__func__, HAL_BUFFER_OUTPUT);
return;
}
dcvs->transition_turbo = false;
/* calculating the min and max threshold */
if (output_buf_req->buffer_count_actual) {
dcvs->min_threshold = output_buf_req->buffer_count_actual -
output_buf_req->buffer_count_min -
msm_dcvs_get_extra_buff_count(inst) + 1;
dcvs->max_threshold = output_buf_req->buffer_count_actual;
if (dcvs->max_threshold <= dcvs->min_threshold)
dcvs->max_threshold =
dcvs->min_threshold + DCVS_BUFFER_SAFEGUARD;
dcvs->threshold_disp_buf_low = dcvs->min_threshold;
dcvs->threshold_disp_buf_high = dcvs->max_threshold;
}
print_stats:
msm_dcvs_print_dcvs_stats(dcvs);
}
void msm_dcvs_init(struct msm_vidc_inst *inst)
{
dprintk(VIDC_DBG, "Init DCVS Struct\n");
if (!inst) {
dprintk(VIDC_ERR, "%s Invalid args: %pK\n", __func__, inst);
return;
}
inst->dcvs = (struct dcvs_stats){ {0} };
inst->dcvs.threshold_disp_buf_high = DCVS_NOMINAL_THRESHOLD;
inst->dcvs.threshold_disp_buf_low = DCVS_TURBO_THRESHOLD;
}
void msm_dcvs_monitor_buffer(struct msm_vidc_inst *inst)
{
int new_ftb, i, prev_buf_count;
int fw_pending_bufs, total_output_buf, buffers_outside_fw;
struct dcvs_stats *dcvs;
struct hal_buffer_requirements *output_buf_req;
if (!inst) {
dprintk(VIDC_ERR, "%s Invalid args: %pK\n", __func__, inst);
return;
}
dcvs = &inst->dcvs;
mutex_lock(&inst->lock);
output_buf_req = get_buff_req_buffer(inst,
msm_comm_get_hal_output_buffer(inst));
if (!output_buf_req) {
dprintk(VIDC_ERR, "%s : Get output buffer req failed %pK\n",
__func__, inst);
mutex_unlock(&inst->lock);
return;
}
total_output_buf = output_buf_req->buffer_count_actual;
fw_pending_bufs = get_pending_bufs_fw(inst);
mutex_unlock(&inst->lock);
buffers_outside_fw = total_output_buf - fw_pending_bufs;
dcvs->num_ftb[dcvs->ftb_index] = buffers_outside_fw;
dcvs->ftb_index = (dcvs->ftb_index + 1) % DCVS_FTB_WINDOW;
if (dcvs->ftb_counter < DCVS_FTB_WINDOW)
dcvs->ftb_counter++;
dprintk(VIDC_PROF,
"DCVS: ftb_counter %d\n", dcvs->ftb_counter);
if (dcvs->ftb_counter == DCVS_FTB_WINDOW) {
new_ftb = 0;
for (i = 0; i < dcvs->ftb_counter; i++) {
if (dcvs->num_ftb[i] > new_ftb)
new_ftb = dcvs->num_ftb[i];
}
dcvs->threshold_disp_buf_high = new_ftb;
if (dcvs->threshold_disp_buf_high <=
dcvs->threshold_disp_buf_low +
DCVS_BUFFER_SAFEGUARD) {
dcvs->threshold_disp_buf_high =
dcvs->threshold_disp_buf_low +
DCVS_BUFFER_SAFEGUARD
+ (DCVS_BUFFER_SAFEGUARD == 0);
}
dcvs->threshold_disp_buf_high =
clamp(dcvs->threshold_disp_buf_high,
dcvs->min_threshold,
dcvs->max_threshold);
}
if (dcvs->ftb_counter == DCVS_FTB_WINDOW &&
dcvs->load == dcvs->load_low) {
prev_buf_count =
dcvs->num_ftb[((dcvs->ftb_index - 2 +
DCVS_FTB_WINDOW) % DCVS_FTB_WINDOW)];
if (prev_buf_count == dcvs->threshold_disp_buf_low &&
buffers_outside_fw <= dcvs->threshold_disp_buf_low) {
dcvs->transition_turbo = true;
} else if (buffers_outside_fw > dcvs->threshold_disp_buf_low &&
(buffers_outside_fw -
(prev_buf_count - buffers_outside_fw))
< dcvs->threshold_disp_buf_low){
dcvs->transition_turbo = true;
}
}
dprintk(VIDC_PROF,
"DCVS: total_output_buf %d buffers_outside_fw %d load %d transition_turbo %d\n",
total_output_buf, buffers_outside_fw, dcvs->load_low,
dcvs->transition_turbo);
}
static int msm_dcvs_enc_scale_clocks(struct msm_vidc_inst *inst)
{
int rc = 0, fw_pending_bufs = 0, total_input_buf = 0;
struct msm_vidc_core *core;
struct dcvs_stats *dcvs;
if (!inst || !inst->core || !inst->core->device) {
dprintk(VIDC_ERR, "%s Invalid params\n", __func__);
return -EINVAL;
}
core = inst->core;
dcvs = &inst->dcvs;
mutex_lock(&inst->lock);
total_input_buf = inst->buff_req.buffer[0].buffer_count_actual;
fw_pending_bufs = (inst->count.etb - inst->count.ebd);
mutex_unlock(&inst->lock);
dprintk(VIDC_PROF,
"DCVS: total_input_buf %d, fw_pending_bufs %d\n",
total_input_buf, fw_pending_bufs);
if (dcvs->etb_counter < total_input_buf) {
dcvs->etb_counter++;
if (dcvs->etb_counter != total_input_buf)
return rc;
}
dprintk(VIDC_PROF,
"DCVS: total_input_buf %d, fw_pending_bufs %d etb_counter %d dcvs->load %d\n",
total_input_buf, fw_pending_bufs,
dcvs->etb_counter, dcvs->load);
if (fw_pending_bufs <= DCVS_ENC_LOW_THR &&
dcvs->load > dcvs->load_low) {
dcvs->load = dcvs->load_low;
dcvs->prev_freq_lowered = true;
} else {
dcvs->prev_freq_lowered = false;
}
if (fw_pending_bufs >= DCVS_ENC_HIGH_THR &&
dcvs->load <= dcvs->load_low) {
dcvs->load = dcvs->load_high;
dcvs->prev_freq_increased = true;
} else {
dcvs->prev_freq_increased = false;
}
if (dcvs->prev_freq_lowered || dcvs->prev_freq_increased) {
dprintk(VIDC_PROF,
"DCVS: (Scaling Clock %s) etb clock set = %d total_input_buf = %d fw_pending_bufs %d\n",
dcvs->prev_freq_lowered ? "Lower" : "Higher",
dcvs->load, total_input_buf, fw_pending_bufs);
rc = msm_comm_scale_clocks_load(core, dcvs->load,
LOAD_CALC_NO_QUIRKS);
if (rc) {
dprintk(VIDC_PROF,
"Failed to set clock rate in FBD: %d\n", rc);
}
} else {
dprintk(VIDC_PROF,
"DCVS: etb clock load_old = %d total_input_buf = %d fw_pending_bufs %d\n",
dcvs->load, total_input_buf, fw_pending_bufs);
}
return rc;
}
/*
* In DCVS scale_clocks will be done both in qbuf and FBD
* 1 indicates call made from fbd that lowers clock
* 0 indicates call made from qbuf that increases clock
* based on DCVS algorithm
*/
static int msm_dcvs_dec_scale_clocks(struct msm_vidc_inst *inst, bool fbd)
{
int rc = 0;
int fw_pending_bufs = 0;
int total_output_buf = 0;
int buffers_outside_fw = 0;
struct msm_vidc_core *core;
struct hal_buffer_requirements *output_buf_req;
struct dcvs_stats *dcvs;
if (!inst || !inst->core || !inst->core->device) {
dprintk(VIDC_ERR, "%s Invalid params\n", __func__);
return -EINVAL;
}
core = inst->core;
dcvs = &inst->dcvs;
mutex_lock(&inst->lock);
fw_pending_bufs = get_pending_bufs_fw(inst);
output_buf_req = get_buff_req_buffer(inst,
msm_comm_get_hal_output_buffer(inst));
mutex_unlock(&inst->lock);
if (!output_buf_req) {
dprintk(VIDC_ERR,
"%s: No buffer requirement for buffer type %x\n",
__func__, HAL_BUFFER_OUTPUT);
return -EINVAL;
}
/* Total number of output buffers */
total_output_buf = output_buf_req->buffer_count_actual;
/* Buffers outside FW are with display */
buffers_outside_fw = total_output_buf - fw_pending_bufs;
if (buffers_outside_fw >= dcvs->threshold_disp_buf_high &&
!dcvs->prev_freq_increased &&
dcvs->load > dcvs->load_low) {
dcvs->load = dcvs->load_low;
dcvs->prev_freq_lowered = true;
dcvs->prev_freq_increased = false;
} else if (dcvs->transition_turbo && dcvs->load == dcvs->load_low) {
dcvs->load = dcvs->load_high;
dcvs->prev_freq_increased = true;
dcvs->prev_freq_lowered = false;
dcvs->transition_turbo = false;
} else {
dcvs->prev_freq_increased = false;
dcvs->prev_freq_lowered = false;
}
if (dcvs->prev_freq_lowered || dcvs->prev_freq_increased) {
dprintk(VIDC_PROF,
"DCVS: clock set = %d tot_output_buf = %d buffers_outside_fw %d threshold_high %d transition_turbo %d\n",
dcvs->load, total_output_buf, buffers_outside_fw,
dcvs->threshold_disp_buf_high, dcvs->transition_turbo);
rc = msm_comm_scale_clocks_load(core, dcvs->load,
LOAD_CALC_NO_QUIRKS);
if (rc) {
dprintk(VIDC_ERR,
"Failed to set clock rate in FBD: %d\n", rc);
}
} else {
dprintk(VIDC_PROF,
"DCVS: clock old = %d tot_output_buf = %d buffers_outside_fw %d threshold_high %d transition_turbo %d\n",
dcvs->load, total_output_buf, buffers_outside_fw,
dcvs->threshold_disp_buf_high, dcvs->transition_turbo);
}
return rc;
}
static bool msm_dcvs_enc_check(struct msm_vidc_inst *inst)
{
int num_mbs_per_frame = 0;
long int instance_load = 0;
long int dcvs_limit = 0;
bool dcvs_check_passed = false, is_codec_supported = false;
struct msm_vidc_platform_resources *res = NULL;
if (!inst || !inst->core) {
dprintk(VIDC_ERR, "%s Invalid params\n", __func__);
return dcvs_check_passed;
}
res = &inst->core->resources;
if (!res->dcvs_limit) {
dprintk(VIDC_ERR,
"%s Dcvs limit table uninitialized\n", __func__);
return false;
}
is_codec_supported =
msm_dcvs_check_codec_supported(
inst->fmts[CAPTURE_PORT].fourcc,
inst->dcvs.supported_codecs,
inst->session_type);
num_mbs_per_frame = msm_dcvs_get_mbs_per_frame(inst);
instance_load = msm_comm_get_inst_load(inst, LOAD_CALC_NO_QUIRKS);
dcvs_limit =
(long int)res->dcvs_limit[inst->session_type].min_mbpf *
res->dcvs_limit[inst->session_type].fps;
if (msm_vidc_enc_dcvs_mode && is_codec_supported &&
inst->dcvs.is_power_save_mode &&
IS_VALID_DCVS_SESSION(num_mbs_per_frame,
res->dcvs_limit[inst->session_type].min_mbpf) &&
IS_VALID_DCVS_SESSION(instance_load, dcvs_limit)) {
dcvs_check_passed = true;
}
return dcvs_check_passed;
}
static bool msm_dcvs_check_supported(struct msm_vidc_inst *inst)
{
int num_mbs_per_frame = 0, instance_count = 0;
long int instance_load = 0;
long int dcvs_limit = 0;
struct msm_vidc_inst *temp = NULL;
struct msm_vidc_core *core;
struct hal_buffer_requirements *output_buf_req;
struct dcvs_stats *dcvs;
bool is_codec_supported = false;
struct msm_vidc_platform_resources *res = NULL;
if (!inst || !inst->core || !inst->core->device) {
dprintk(VIDC_WARN, "%s: Invalid parameter\n", __func__);
return -EINVAL;
}
core = inst->core;
dcvs = &inst->dcvs;
res = &core->resources;
if (!res->dcvs_limit) {
dprintk(VIDC_WARN,
"%s: dcvs limit table not found\n", __func__);
return false;
}
instance_count = msm_dcvs_count_active_instances(core);
if (instance_count == 1 && inst->session_type == MSM_VIDC_DECODER &&
!msm_comm_turbo_session(inst)) {
num_mbs_per_frame = msm_dcvs_get_mbs_per_frame(inst);
instance_load = msm_comm_get_inst_load(inst,
LOAD_CALC_NO_QUIRKS);
output_buf_req = get_buff_req_buffer(inst,
msm_comm_get_hal_output_buffer(inst));
dcvs_limit =
(long int)res->dcvs_limit[inst->session_type].min_mbpf *
res->dcvs_limit[inst->session_type].fps;
is_codec_supported =
msm_dcvs_check_codec_supported(
inst->fmts[OUTPUT_PORT].fourcc,
inst->dcvs.supported_codecs,
inst->session_type);
if (!is_codec_supported ||
!IS_VALID_DCVS_SESSION(num_mbs_per_frame,
res->dcvs_limit[inst->session_type].min_mbpf) ||
!IS_VALID_DCVS_SESSION(instance_load, dcvs_limit) ||
inst->seqchanged_count > 1)
return false;
if (!output_buf_req) {
dprintk(VIDC_ERR,
"%s: No buffer requirement for buffer type %x\n",
__func__, HAL_BUFFER_OUTPUT);
return false;
}
} else if (instance_count == 1 &&
inst->session_type == MSM_VIDC_ENCODER &&
!msm_comm_turbo_session(inst)) {
if (!msm_dcvs_enc_check(inst))
return false;
} else {
/*
* For multiple instance use case with 4K, clocks will be scaled
* as per load in streamon, but the clocks may be scaled
* down as DCVS is running for first playback instance
* Rescaling the core clock for multiple instance use case
*/
if (!dcvs->is_clock_scaled) {
if (!msm_comm_scale_clocks(core)) {
dcvs->is_clock_scaled = true;
dprintk(VIDC_DBG,
"%s: Scaled clocks = %d\n",
__func__, dcvs->is_clock_scaled);
} else {
dprintk(VIDC_DBG,
"%s: Failed to Scale clocks. Perf might be impacted\n",
__func__);
}
}
/*
* For multiple instance use case turn OFF DCVS algorithm
* immediately
*/
if (instance_count > 1) {
mutex_lock(&core->lock);
list_for_each_entry(temp, &core->instances, list)
temp->dcvs_mode = false;
mutex_unlock(&core->lock);
}
return false;
}
return true;
}
int msm_dcvs_get_extra_buff_count(struct msm_vidc_inst *inst)
{
int extra_buffer = 0;
if (!inst) {
dprintk(VIDC_ERR, "%s Invalid args\n", __func__);
return 0;
}
if (inst->session_type == MSM_VIDC_ENCODER) {
if (msm_dcvs_enc_check(inst))
extra_buffer = DCVS_ENC_EXTRA_OUTPUT_BUFFERS;
} else if (inst->session_type == MSM_VIDC_DECODER) {
if (msm_dcvs_check_supported(inst))
extra_buffer = DCVS_DEC_EXTRA_OUTPUT_BUFFERS;
}
return extra_buffer;
}
void msm_dcvs_enc_set_power_save_mode(struct msm_vidc_inst *inst,
bool is_power_save_mode)
{
if (!inst) {
dprintk(VIDC_ERR, "%s Invalid args\n", __func__);
return;
}
inst->dcvs.is_power_save_mode = is_power_save_mode;
}