blob: 4ff06f26346e3b9154c965d54b780de96269328e [file] [log] [blame]
/*
* drivers/amlogic/drm/meson_vpu_pipeline_traverse.c
*
* Copyright (C) 2017 Amlogic, Inc. All rights reserved.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
*/
#include <dt-bindings/display/meson-drm-ids.h>
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include "meson_vpu_pipeline.h"
#include "meson_drv.h"
static void stack_init(struct meson_vpu_stack *mvs)
{
mvs->top = 0;
memset(mvs->stack, 0, sizeof(struct meson_vpu_block *) *
MESON_MAX_BLOCKS);
}
static void stack_push(struct meson_vpu_stack *mvs, struct meson_vpu_block *mvb)
{
mvs->stack[mvs->top++] = mvb;
}
static struct meson_vpu_block *stack_pop(struct meson_vpu_stack *mvs)
{
struct meson_vpu_block *mvb;
mvb = mvs->stack[--mvs->top];
mvs->stack[mvs->top] = NULL;
return mvb;
}
static struct meson_vpu_block *neighbour(struct meson_vpu_block_state *mvbs,
int *index,
struct drm_atomic_state *state)
{
int i;
struct meson_vpu_block_link *mvbl;
struct meson_vpu_block_state *next_state;
for (i = 0; i < MESON_BLOCK_MAX_OUTPUTS; i++) {
mvbl = &mvbs->outputs[i];
if (!mvbl->link)
continue;
next_state = meson_vpu_block_get_state(mvbl->link, state);
if (next_state->in_stack) {
DRM_DEBUG("%s already in stack.\n", mvbl->link->name);
continue;
}
if (!next_state->active) {
DRM_DEBUG("%s is not active.\n", mvbl->link->name);
continue;
}
if (!mvbl->edges_active) {
DRM_DEBUG("edges is not active.\n");
continue;
}
if (mvbl->edges_visited) {
DRM_DEBUG("edges is already visited.\n");
continue;
}
*index = i;
return mvbl->link;
}
return NULL;
}
static void pipeline_visit_clean(struct meson_vpu_block_state *curr_state)
{
int index;
for (index = 0; index < MESON_BLOCK_MAX_OUTPUTS; index++)
curr_state->outputs[index].edges_visited = 0;
}
/**
* pipeline_dfs - dfs algorithm to search path
* @osd_index: osd layer index
* @start: the start block of the dfs
* @end: the end block of the dfs
*
* use the non-recursive dfs algorithm to search all paths from the start block
* to the end block, the result will be saved to the meson_vpu_traverse struct.
*
*/
static void pipeline_dfs(int osd_index, struct meson_vpu_pipeline_state *mvps,
struct meson_vpu_block *start,
struct meson_vpu_block *end,
struct drm_atomic_state *state)
{
struct meson_vpu_block *curr, *next, *prev;
struct meson_vpu_block_state *curr_state, *next_state, *prev_state;
int i, j, index;
struct meson_vpu_stack *mvs = &mvps->osd_stack[osd_index];
struct meson_vpu_traverse *mvt = &mvps->osd_traverse[osd_index];
stack_init(mvs);
stack_push(mvs, start);
mvt->num_path = 0;
j = 0;
DRM_DEBUG("start->id=%d,name=%s\n", start->id, start->name);
DRM_DEBUG("end->id=%d,name=%s\n", end->id, end->name);
while (mvs->top) {
if (mvs->stack[mvs->top - 1] == end) {
for (i = 0; i < mvs->top; i++) {
mvt->path[j][i] = mvs->stack[i];
DRM_DEBUG("%s->\n", mvs->stack[i]->name);
}
j++;
mvt->num_path++;
DRM_DEBUG("\n");
prev = stack_pop(mvs);
prev_state = meson_vpu_block_get_state(prev, state);
prev_state->in_stack = 0;
} else {
curr = mvs->stack[mvs->top - 1];
curr_state = meson_vpu_block_get_state(curr, state);
next = neighbour(curr_state, &index, state);
if (next) {
DRM_DEBUG("next->id=%d,name=%s\n",
next->id, next->name);
curr_state->outputs[index].edges_visited = 1;
next_state =
meson_vpu_block_get_state(next, state);
stack_push(mvs, next);
next_state->in_stack = 1;
} else {
DRM_DEBUG("next is NULL!!\n");
stack_pop(mvs);
curr_state->in_stack = 0;
pipeline_visit_clean(curr_state);
}
}
}
}
static u8 find_out_port(struct meson_vpu_block *in,
struct meson_vpu_block *out)
{
int i;
struct meson_vpu_block_link *mvbl;
for (i = 0; i < out->avail_inputs; i++) {
mvbl = &out->inputs[i];
if (mvbl->link == in)
return mvbl->port;
}
return 0;
}
void vpu_pipeline_scaler_scope_size_calc(u8 index, u8 osd_index,
struct meson_vpu_pipeline_state *mvps)
{
u8 m, i;
u32 ratio_x[MESON_MAX_SCALERS], ratio_y[MESON_MAX_SCALERS];
struct meson_vpu_scaler_param *scaler_param, *scaler_param_1;
i = index;
if (mvps->scaler_cnt[i] == 0) {
/*scope size calc*/
mvps->osd_scope_pre[osd_index].h_start =
mvps->plane_info[osd_index].src_x;
mvps->osd_scope_pre[osd_index].v_start =
mvps->plane_info[osd_index].src_y;
mvps->osd_scope_pre[osd_index].h_end =
mvps->osd_scope_pre[osd_index].h_start
+ mvps->plane_info[osd_index].src_w - 1;
mvps->osd_scope_pre[osd_index].v_end =
mvps->osd_scope_pre[osd_index].v_start
+ mvps->plane_info[osd_index].src_h - 1;
} else if (mvps->scaler_cnt[i] == 1) {
m = mvps->scale_blk[i][0]->index;
scaler_param = &mvps->scaler_param[m];
scaler_param->ratio_x =
(mvps->plane_info[osd_index].src_w *
RATIO_BASE) /
mvps->plane_info[osd_index].dst_w;
scaler_param->ratio_y =
(mvps->plane_info[osd_index].src_h *
RATIO_BASE) /
mvps->plane_info[osd_index].dst_h;
scaler_param->calc_done_mask |=
SCALER_RATIO_X_CALC_DONE |
SCALER_RATIO_Y_CALC_DONE;
if (scaler_param->before_osdblend) {
/*scale size calc firstly*/
scaler_param->input_width =
mvps->plane_info[osd_index].src_w;
scaler_param->input_height =
mvps->plane_info[osd_index].src_h;
scaler_param->output_width =
mvps->plane_info[osd_index].dst_w;
scaler_param->output_height =
mvps->plane_info[osd_index].dst_h;
scaler_param->calc_done_mask |=
SCALER_IN_W_CALC_DONE |
SCALER_IN_H_CALC_DONE |
SCALER_OUT_W_CALC_DONE |
SCALER_OUT_H_CALC_DONE;
/*scope size calc*/
mvps->osd_scope_pre[osd_index].h_start =
mvps->plane_info[osd_index].dst_x;
mvps->osd_scope_pre[osd_index].v_start =
mvps->plane_info[osd_index].dst_y;
mvps->osd_scope_pre[osd_index].h_end =
mvps->osd_scope_pre[osd_index].h_start
+ scaler_param->output_width - 1;
mvps->osd_scope_pre[osd_index].v_end =
mvps->osd_scope_pre[osd_index].v_start
+ scaler_param->output_height - 1;
} else {/*scaler position is after osdlend*/
/*scope size calc firstly*/
mvps->osd_scope_pre[osd_index].h_start =
mvps->plane_info[osd_index].src_x;
mvps->osd_scope_pre[osd_index].v_start =
mvps->plane_info[osd_index].src_y;
mvps->osd_scope_pre[osd_index].h_end =
mvps->osd_scope_pre[osd_index].h_start
+ mvps->plane_info[osd_index].src_w - 1;
mvps->osd_scope_pre[osd_index].v_end =
mvps->osd_scope_pre[osd_index].v_start
+ mvps->plane_info[osd_index].src_h - 1;
/*scaler size calc*/
scaler_param->input_width =
mvps->osd_scope_pre[osd_index].h_end + 1;
scaler_param->input_height =
mvps->osd_scope_pre[osd_index].v_end + 1;
scaler_param->output_width =
mvps->plane_info[osd_index].dst_x +
mvps->plane_info[osd_index].dst_w;
scaler_param->output_height =
mvps->plane_info[osd_index].dst_y +
mvps->plane_info[osd_index].dst_h;
scaler_param->calc_done_mask |=
SCALER_IN_W_CALC_DONE |
SCALER_IN_H_CALC_DONE |
SCALER_OUT_W_CALC_DONE |
SCALER_OUT_H_CALC_DONE;
}
} else if (mvps->scaler_cnt[i] == 2) {
m = mvps->scale_blk[i][0]->index;
scaler_param = &mvps->scaler_param[m];
m = mvps->scale_blk[i][1]->index;
scaler_param_1 = &mvps->scaler_param[m];
if (scaler_param_1->calc_done_mask &
SCALER_RATIO_X_CALC_DONE) {/*TODO*/
ratio_x[1] = scaler_param_1->ratio_x;
ratio_y[1] = scaler_param_1->ratio_y;
/*recheck scaler size*/
} else {/*TODO*/
ratio_x[1] = RATIO_BASE;
ratio_y[1] = RATIO_BASE;
scaler_param_1->calc_done_mask |=
SCALER_RATIO_X_CALC_DONE |
SCALER_RATIO_Y_CALC_DONE;
}
/*calculate scaler input/output size and scope*/
if (scaler_param->before_osdblend) {
scaler_param->input_width =
mvps->plane_info[osd_index].src_w;
scaler_param->input_height =
mvps->plane_info[osd_index].src_h;
scaler_param->output_width =
mvps->plane_info[osd_index].dst_w *
ratio_x[1] / RATIO_BASE;
scaler_param->output_height =
mvps->plane_info[osd_index].dst_h *
ratio_y[1] / RATIO_BASE;
scaler_param->calc_done_mask |=
SCALER_IN_W_CALC_DONE |
SCALER_IN_H_CALC_DONE |
SCALER_OUT_W_CALC_DONE |
SCALER_OUT_H_CALC_DONE;
/*scope size calc*/
mvps->osd_scope_pre[osd_index].h_start =
mvps->plane_info[osd_index].dst_x *
ratio_x[1] / RATIO_BASE;
mvps->osd_scope_pre[osd_index].v_start =
mvps->plane_info[osd_index].dst_y *
ratio_y[1] / RATIO_BASE;
mvps->osd_scope_pre[osd_index].h_end =
mvps->osd_scope_pre[osd_index].h_start
+ scaler_param->output_width - 1;
mvps->osd_scope_pre[osd_index].v_end =
mvps->osd_scope_pre[osd_index].v_start
+ scaler_param->output_height - 1;
} else {
/*TODO*/
}
/*reclac second scaler size*/
/*scaler_param_1->before_osdblend == 0*/
if (scaler_param_1->input_width <
mvps->osd_scope_pre[osd_index].h_end + 1) {
scaler_param_1->input_width =
mvps->osd_scope_pre[osd_index].h_end + 1;
scaler_param_1->output_width =
mvps->plane_info[osd_index].dst_x +
mvps->plane_info[osd_index].dst_w;
}
if (scaler_param_1->input_height <
mvps->osd_scope_pre[osd_index].v_end + 1) {
scaler_param_1->input_height =
mvps->osd_scope_pre[osd_index].v_end + 1;
scaler_param_1->output_height =
mvps->plane_info[osd_index].dst_y +
mvps->plane_info[osd_index].dst_h;
}
}
}
static void vpu_osd_shift_recalc(struct meson_vpu_pipeline_state *state)
{
u8 i;
for (i = 0; i < MESON_MAX_OSDS; i++) {
state->osd_scope_pre[i].v_start += 1;
state->osd_scope_pre[i].v_end += 1;
}
state->scaler_param[0].input_height += 1;
}
int vpu_pipeline_scaler_check(int *combination, int num_planes,
struct meson_vpu_pipeline_state *mvps)
{
int i, j, osd_index, ret, m;
struct meson_vpu_traverse *mvt;
struct meson_vpu_block **mvb;
struct meson_vpu_block *block;
struct meson_vpu_scaler_param *scaler_param, *scaler_param_1;
u32 ratio_x[MESON_MAX_SCALERS], ratio_y[MESON_MAX_SCALERS];
bool have_blend;
ret = 0;
/*clean up scaler and scope size before check & calc*/
memset(mvps->scaler_param, 0,
MESON_MAX_SCALERS * sizeof(struct meson_vpu_scaler_param));
memset(mvps->osd_scope_pre, 0,
MESON_MAX_OSDS * sizeof(struct osd_scope_s));
for (i = 0; i < MESON_MAX_OSDS && !ret; i++) {
if (!mvps->plane_info[i].enable)
continue;
osd_index = mvps->plane_index[i];
mvt = &mvps->osd_traverse[osd_index];
mvb = mvt->path[combination[i]];
mvps->scaler_cnt[i] = 0;
have_blend = 0;
for (j = 0; j < MESON_MAX_BLOCKS; j++) {
block = mvb[j];
if (!block)
continue;
if (block->type == MESON_BLK_OSDBLEND)
have_blend = 1;
if (block->type == MESON_BLK_SCALER) {
m = mvps->scaler_cnt[i];
mvps->scale_blk[i][m] = block;
mvps->scaler_cnt[i]++;
m = block->index;
mvps->scaler_param[m].plane_mask |=
BIT(osd_index);
mvps->scaler_param[m].before_osdblend =
have_blend ? 0:1;
}
}
if (mvps->scaler_cnt[i] == 0) {
ratio_x[0] = (mvps->plane_info[osd_index].src_w *
RATIO_BASE) /
mvps->plane_info[osd_index].dst_w;
ratio_y[0] = (mvps->plane_info[osd_index].src_h *
RATIO_BASE) /
mvps->plane_info[osd_index].dst_h;
if (ratio_x[0] != RATIO_BASE ||
ratio_y[0] != RATIO_BASE) {
ret = -1;
break;
}
vpu_pipeline_scaler_scope_size_calc(i,
osd_index, mvps);
} else if (mvps->scaler_cnt[i] == 1) {
vpu_pipeline_scaler_scope_size_calc(i,
osd_index, mvps);
} else if (mvps->scaler_cnt[i] == 2) {
/*
*check second scaler firstly,
*if second scaler have not check,
*disable the second scaler.only use first scale.
*/
m = mvps->scale_blk[i][1]->index;
scaler_param_1 = &mvps->scaler_param[m];
if (scaler_param_1->calc_done_mask &
SCALER_RATIO_X_CALC_DONE) {/*TODO*/
ratio_x[1] = scaler_param_1->ratio_x;
ratio_y[1] = scaler_param_1->ratio_y;
/*recheck scaler size*/
} else {/*TODO*/
ratio_x[1] = RATIO_BASE;
ratio_y[1] = RATIO_BASE;
scaler_param_1->ratio_x = RATIO_BASE;
scaler_param_1->ratio_y = RATIO_BASE;
scaler_param_1->calc_done_mask |=
SCALER_RATIO_X_CALC_DONE |
SCALER_RATIO_Y_CALC_DONE;
}
ratio_x[0] = (mvps->plane_info[osd_index].src_w *
RATIO_BASE) /
mvps->plane_info[osd_index].dst_w;
ratio_y[0] = (mvps->plane_info[osd_index].src_h *
RATIO_BASE) /
mvps->plane_info[osd_index].dst_h;
m = mvps->scale_blk[i][0]->index;
scaler_param = &mvps->scaler_param[m];
scaler_param->ratio_x =
(ratio_x[0] * RATIO_BASE) / ratio_x[1];
scaler_param->ratio_y =
(ratio_y[0] * RATIO_BASE) / ratio_y[1];
scaler_param->calc_done_mask |=
SCALER_RATIO_X_CALC_DONE |
SCALER_RATIO_Y_CALC_DONE;
ratio_x[0] = scaler_param->ratio_x;
ratio_y[0] = scaler_param->ratio_y;
if ((ratio_x[0] > RATIO_BASE &&
ratio_x[1] < RATIO_BASE) ||
(ratio_y[0] > RATIO_BASE &&
ratio_y[1] < RATIO_BASE)) {
ret = -1;
break;
}
vpu_pipeline_scaler_scope_size_calc(i,
osd_index, mvps);
}
}
if (ret == 0 && mvps->num_plane > 0 &&
mvps->pipeline->osd_version <= OSD_V2)
vpu_osd_shift_recalc(mvps);
return ret;
}
/**
* vpu_pipeline_check_block: check pipeline block
* @combination: index array of every layer path
* @num_planes: the number of layer
*
* For some blocks that have multiple output port,
* call the ops->check interface to determain a valid path.
*
* RETURNS:
* 0 for the valid path or -1 for the invalid path
*/
int vpu_pipeline_check_block(int *combination, int num_planes,
struct meson_vpu_pipeline_state *mvps,
struct drm_atomic_state *state)
{
int i, j, osd_index, ret;
struct meson_vpu_traverse *mvt;
struct meson_vpu_block **mvb;
struct meson_vpu_block *block;
struct meson_vpu_block *osdblend;
struct meson_vpu_block_state *mvbs;
osdblend = &mvps->pipeline->osdblend->base;
ret = vpu_pipeline_scaler_check(combination, num_planes, mvps);
if (ret)
return -1;
for (i = 0; i < MESON_MAX_OSDS; i++) {
if (!mvps->plane_info[i].enable)
continue;
osd_index = mvps->plane_index[i];
mvt = &mvps->osd_traverse[osd_index];
mvb = mvt->path[combination[i]];
mvps->scaler_cnt[i] = 0;
for (j = 0; j < MESON_MAX_BLOCKS; j++) {
block = mvb[j];
if (!block)
break;
if (block == osdblend) {
mvps->dout_index[i] =
find_out_port(block, mvb[j+1]);
DRM_DEBUG("osd-%d blend out port: %d.\n",
i, mvps->dout_index[i]);
break;
}
}
}
for (i = 0; i < MESON_MAX_OSDS; i++) {
if (!mvps->plane_info[i].enable)
continue;
osd_index = mvps->plane_index[i];
mvt = &mvps->osd_traverse[osd_index];
mvb = mvt->path[combination[i]];
for (j = 0; j < MESON_MAX_BLOCKS; j++) {
block = mvb[j];
if (!block)
break;
if (block->ops && block->ops->check_state) {
mvbs = meson_vpu_block_get_state(block, state);
ret = block->ops->check_state(block,
mvbs, mvps);
if (ret) {
DRM_ERROR("%s block check error.\n",
block->name);
return ret;
}
}
}
}
return ret;
}
void vpu_pipeline_enable_block(int *combination, int num_planes,
struct meson_vpu_pipeline_state *mvps)
{
int i, j, osd_index;
struct meson_vpu_traverse *mvt;
struct meson_vpu_block **mvb;
struct meson_vpu_block *block;
mvps->enable_blocks = 0;
for (i = 0; i < MESON_MAX_OSDS; i++) {
if (!mvps->plane_info[i].enable)
continue;
osd_index = mvps->plane_index[i];
mvt = &mvps->osd_traverse[osd_index];
mvb = mvt->path[combination[i]];
for (j = 0; j < MESON_MAX_BLOCKS; j++) {
block = mvb[j];
if (!block)
break;
mvps->enable_blocks |= BIT(block->id);
}
}
}
/**
* combinate_layer_path - combinate every found layer path
* @path_num_array: the number of every layer's found path
* @num_planes: the number of layer
*
* use combination algorithm to check whether the path is valid
*
* RETURNS:
* 0 for the valid path or -1 for the invalid path
*/
int combinate_layer_path(int *path_num_array, int num_planes,
struct meson_vpu_pipeline_state *mvps,
struct drm_atomic_state *state)
{
int i, j, ret;
bool is_continue = false;
int combination[MESON_MAX_OSDS] = {0};
i = 0;
ret = -1;
do {
// sum the combination result to check osd blend block
ret = vpu_pipeline_check_block(combination,
num_planes, mvps, state);
if (!ret)
break;
i++;
combination[num_planes-1] = i;
for (j = num_planes - 1; j >= 0; j--) {
if (combination[j] >= path_num_array[j]) {
combination[j] = 0;
i = 0;
if ((j - 1) >= 0)
combination[j - 1] =
combination[j - 1] + 1;
}
}
is_continue = false;
for (j = 0; j < num_planes; j++) {
if (combination[j] != 0)
is_continue = true;
}
} while (is_continue);
if (!ret)
vpu_pipeline_enable_block(combination, num_planes, mvps);
return ret;
}
/**
* find every layer's path(from start block to the end block) through
* pipeline_dfs, combinate every found path of every layer
* and check whether the combination is a valid path
* that can meet the requirement of hardware limites.
*
* RETURNS:
* 0 for the valid path or -1 for the invalid path
*/
int vpu_pipeline_traverse(struct meson_vpu_pipeline_state *mvps,
struct drm_atomic_state *state)
{
int i, osd_index, ret;
int num_planes;
struct meson_vpu_block *start, *end;
int path[MESON_MAX_OSDS] = {0};
struct meson_vpu_pipeline *mvp = mvps->pipeline;
end = &mvp->postblend->base;
num_planes = mvps->num_plane;
if (!num_planes)
return 0;
DRM_DEBUG("traverse num: %d %p.\n", num_planes, mvps);
for (i = 0; i < MESON_MAX_OSDS; i++) {
if (!mvps->plane_info[i].enable)
continue;
osd_index = mvps->plane_index[i];
start = &mvp->osds[osd_index]->base;
DRM_DEBUG("do pipeline_dfs: OSD%d.\n", (osd_index + 1));
pipeline_dfs(osd_index, mvps, start, end, state);
}
// start to combination every layer case
for (i = 0; i < MESON_MAX_OSDS; i++) {
if (!mvps->plane_info[i].enable)
continue;
osd_index = mvps->plane_index[i];
path[i] = mvps->osd_traverse[osd_index].num_path;
DRM_DEBUG("osd%d traverse path num: %d\n",
(osd_index + 1), path[i]);
}
ret = combinate_layer_path(path, num_planes, mvps, state);
if (ret)
DRM_ERROR("can't find a valid path.\n");
return ret;
}