lineage_kernel_xcoverpro/drivers/video/fbdev/exynos/dpu20/win_update.c

662 lines
19 KiB
C
Raw Normal View History

2023-06-18 22:53:49 +00:00
/*
* Copyright (c) 2016 Samsung Electronics Co., Ltd.
* http://www.samsung.com
*
* Window update file for Samsung EXYNOS DPU driver
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <video/mipi_display.h>
#include "decon.h"
#include "dpp.h"
#include "dsim.h"
static void win_update_adjust_region(struct decon_device *decon,
struct decon_win_config *win_config,
struct decon_reg_data *regs)
{
int i;
int div_w, div_h;
struct decon_rect r1, r2;
struct decon_win_config *update_config = &win_config[DECON_WIN_UPDATE_IDX];
struct decon_win_config *config;
struct decon_frame adj_region;
struct v4l2_subdev *sd;
struct dpp_restriction res;
u32 min_src_w, min_src_h;
int sz_align = 1;
regs->need_update = false;
DPU_FULL_RECT(&regs->up_region, decon->lcd_info);
if (!decon->win_up.enabled)
return;
if (update_config->state != DECON_WIN_STATE_UPDATE)
return;
if ((update_config->dst.x < 0) || (update_config->dst.y < 0)) {
update_config->state = DECON_WIN_STATE_DISABLED;
return;
}
r1.left = update_config->dst.x;
r1.top = update_config->dst.y;
r1.right = r1.left + update_config->dst.w - 1;
r1.bottom = r1.top + update_config->dst.h - 1;
for (i = 0; i < decon->dt.max_win; i++) {
config = &win_config[i];
if (config->state != DECON_WIN_STATE_DISABLED) {
if (config->dpp_parm.rot || is_scaling(config)) {
update_config->state = DECON_WIN_STATE_DISABLED;
return;
}
}
}
DPU_DEBUG_WIN("original update region[%d %d %d %d]\n",
update_config->dst.x, update_config->dst.y,
update_config->dst.w, update_config->dst.h);
r2.left = (r1.left / decon->win_up.rect_w) * decon->win_up.rect_w;
r2.top = (r1.top / decon->win_up.rect_h) * decon->win_up.rect_h;
div_w = (r1.right + 1) / decon->win_up.rect_w;
div_w = (div_w * decon->win_up.rect_w == r1.right + 1) ? div_w : div_w + 1;
r2.right = div_w * decon->win_up.rect_w - 1;
div_h = (r1.bottom + 1) / decon->win_up.rect_h;
div_h = (div_h * decon->win_up.rect_h == r1.bottom + 1) ? div_h : div_h + 1;
r2.bottom = div_h * decon->win_up.rect_h - 1;
/* TODO: Now, 4 slices must be used. This will be modified */
if (decon->lcd_info->dsc_enabled) {
r2.left = 0;
r2.right = decon->lcd_info->xres - 1;
} else {
sd = decon->dpp_sd[0];
v4l2_subdev_call(sd, core, ioctl, DPP_GET_RESTRICTION, &res);
min_src_w = max((res.src_f_w.min * sz_align), decon->lcd_info->update_min_w);
min_src_h = max((res.src_f_h.min * sz_align), decon->lcd_info->update_min_h);
if (decon->lcd_info->xres - r2.left < min_src_w)
r2.left = ((r1.left - min_src_w) / decon->win_up.rect_w) *
decon->win_up.rect_w;
if (decon->lcd_info->yres - r2.top < min_src_h)
r2.top = ((r1.top - min_src_h) / decon->win_up.rect_h) *
decon->win_up.rect_h;
if (decon->lcd_info->xres < r2.right)
r2.right = decon->lcd_info->xres - 1;
if (decon->lcd_info->yres < r2.bottom)
r2.bottom = decon->lcd_info->yres - 1;
}
memcpy(&regs->up_region, &r2, sizeof(struct decon_rect));
memset(&adj_region, 0, sizeof(struct decon_frame));
adj_region.x = regs->up_region.left;
adj_region.y = regs->up_region.top;
adj_region.w = regs->up_region.right - regs->up_region.left + 1;
adj_region.h = regs->up_region.bottom - regs->up_region.top + 1;
DPU_EVENT_LOG_UPDATE_REGION(&decon->sd, &update_config->dst, &adj_region);
DPU_DEBUG_WIN("adjusted update region[%d %d %d %d]\n",
adj_region.x, adj_region.y, adj_region.w, adj_region.h);
}
static void win_update_check_limitation(struct decon_device *decon,
struct decon_win_config *win_config,
struct decon_reg_data *regs)
{
struct decon_win_config *config;
struct decon_win_rect update;
struct decon_rect r;
struct v4l2_subdev *sd;
struct dpp_restriction res;
int i;
int sz_align = 1;
int adj_src_x = 0, adj_src_y = 0;
for (i = 0; i < decon->dt.max_win; i++) {
config = &win_config[i];
if (config->state == DECON_WIN_STATE_DISABLED)
continue;
r.left = config->dst.x;
r.top = config->dst.y;
r.right = config->dst.w + config->dst.x - 1;
r.bottom = config->dst.h + config->dst.y - 1;
if (!decon_intersect(&regs->up_region, &r))
continue;
decon_intersection(&regs->up_region, &r, &r);
if (is_yuv(config)) {
/* check alignment for NV12/NV21 format */
update.x = regs->up_region.left;
update.y = regs->up_region.top;
sz_align = 2;
if (update.y > config->dst.y)
adj_src_y = config->src.y + (update.y - config->dst.y);
if (update.x > config->dst.x)
adj_src_x = config->src.x + (update.x - config->dst.x);
if (adj_src_x & 0x1 || adj_src_y & 0x1)
goto change_full;
}
sd = decon->dpp_sd[0];
v4l2_subdev_call(sd, core, ioctl, DPP_GET_RESTRICTION, &res);
if (((r.right - r.left) < (res.src_f_w.min * sz_align)) ||
((r.bottom - r.top) < (res.src_f_h.min * sz_align))) {
goto change_full;
}
/* cursor async */
if (((r.right - r.left) > decon->lcd_info->xres) ||
((r.bottom - r.top) > decon->lcd_info->yres)) {
goto change_full;
}
}
return;
change_full:
DPU_DEBUG_WIN("changed full: win(%d) idma(%d) [%d %d %d %d]\n",
i, config->idma_type,
config->dst.x, config->dst.y,
config->dst.w, config->dst.h);
DPU_FULL_RECT(&regs->up_region, decon->lcd_info);
}
static void win_update_reconfig_coordinates(struct decon_device *decon,
struct decon_win_config *win_config,
struct decon_reg_data *regs)
{
struct decon_win_config *config;
struct decon_win_rect update;
struct decon_frame origin_dst, origin_src;
struct decon_rect r;
int i;
/* Assume that, window update doesn't support in case of scaling */
for (i = 0; i < decon->dt.max_win; i++) {
config = &win_config[i];
if (config->state == DECON_WIN_STATE_DISABLED)
continue;
r.left = config->dst.x;
r.top = config->dst.y;
r.right = r.left + config->dst.w - 1;
r.bottom = r.top + config->dst.h - 1;
if (!decon_intersect(&regs->up_region, &r)) {
config->state = DECON_WIN_STATE_DISABLED;
continue;
}
update.x = regs->up_region.left;
update.y = regs->up_region.top;
update.w = regs->up_region.right - regs->up_region.left + 1;
update.h = regs->up_region.bottom - regs->up_region.top + 1;
memcpy(&origin_dst, &config->dst, sizeof(struct decon_frame));
memcpy(&origin_src, &config->src, sizeof(struct decon_frame));
/* reconfigure destination coordinates */
if (update.x > config->dst.x)
config->dst.w = min(update.w,
config->dst.x + config->dst.w - update.x);
else if (update.x + update.w < config->dst.x + config->dst.w)
config->dst.w = min(config->dst.w,
update.w + update.x - config->dst.x);
if (update.y > config->dst.y)
config->dst.h = min(update.h,
config->dst.y + config->dst.h - update.y);
else if (update.y + update.h < config->dst.y + config->dst.h)
config->dst.h = min(config->dst.h,
update.h + update.y - config->dst.y);
config->dst.x = max(config->dst.x - update.x, 0);
config->dst.y = max(config->dst.y - update.y, 0);
/* reconfigure source coordinates */
if (update.y > origin_dst.y)
config->src.y += (update.y - origin_dst.y);
if (update.x > origin_dst.x)
config->src.x += (update.x - origin_dst.x);
config->src.w = config->dst.w;
config->src.h = config->dst.h;
DPU_DEBUG_WIN("win(%d), idma(%d)\n", i, config->idma_type);
DPU_DEBUG_WIN("src: origin[%d %d %d %d] -> change[%d %d %d %d]\n",
origin_src.x, origin_src.y,
origin_src.w, origin_src.h,
config->src.x, config->src.y,
config->src.w, config->src.h);
DPU_DEBUG_WIN("dst: origin[%d %d %d %d] -> change[%d %d %d %d]\n",
origin_dst.x, origin_dst.y,
origin_dst.w, origin_dst.h,
config->dst.x, config->dst.y,
config->dst.w, config->dst.h);
}
}
static bool dpu_need_mres_config(struct decon_device *decon,
struct decon_win_config *win_config,
struct decon_reg_data *regs)
{
struct decon_win_config *mres_config = &win_config[DECON_WIN_UPDATE_IDX];
struct lcd_res_info *supported_res;
int i;
regs->mres_update = false;
if (!decon->mres_enabled) {
DPU_DEBUG_MRES("multi-resolution feature is disabled\n");
goto end;
}
if (decon->dt.out_type != DECON_OUT_DSI) {
DPU_DEBUG_MRES("multi resolution only support DSI path\n");
goto end;
}
if (!decon->lcd_info->dt_lcd_mres.mres_en) {
DPU_DEBUG_MRES("panel doesn't support multi-resolution\n");
goto end;
}
if (!(mres_config->state & DECON_WIN_STATE_MRESOL))
goto end;
/* requested LCD resolution */
regs->lcd_width = mres_config->dst.f_w;
regs->lcd_height = mres_config->dst.f_h;
/* compare previous and requested LCD resolution */
if ((decon->lcd_info->xres == regs->lcd_width) &&
(decon->lcd_info->yres == regs->lcd_height)) {
DPU_DEBUG_MRES("prev & req LCD resolution is same(%d %d)\n",
regs->lcd_width, regs->lcd_height);
goto end;
}
/* match supported and requested LCD resolution */
for (i = 0; i < decon->lcd_info->dt_lcd_mres.mres_number; i++) {
supported_res = &decon->lcd_info->dt_lcd_mres.res_info[i];
if ((supported_res->width == regs->lcd_width) &&
(supported_res->height == regs->lcd_height)) {
regs->mres_update = true;
regs->mres_idx = i;
break;
}
}
DPU_DEBUG_MRES("update(%d), mode(%d), resolution(%d %d -> %d %d)\n",
regs->mres_update, regs->mres_idx,
decon->lcd_info->xres, decon->lcd_info->yres,
regs->lcd_width, regs->lcd_height);
end:
return regs->mres_update;
}
void dpu_prepare_win_update_config(struct decon_device *decon,
struct decon_win_config_data *win_data,
struct decon_reg_data *regs)
{
struct decon_win_config *win_config = win_data->config;
bool reconfigure = false;
struct decon_rect r;
struct decon_win_config *update_config = &win_config[DECON_WIN_UPDATE_IDX];
#if defined(CONFIG_EXYNOS_DOZE)
if (decon->dt.out_type == DECON_OUT_DSI && decon->state == DECON_STATE_DOZE)
memset(update_config, 0, sizeof(struct decon_win_config));
#endif
if (decon->partial_force_disable)
memset(update_config, 0, sizeof(struct decon_win_config));
if (!decon->win_up.enabled)
return;
if (decon->dt.out_type != DECON_OUT_DSI)
return;
/* If LCD resolution is changed, window update is ignored */
if (dpu_need_mres_config(decon, win_config, regs)) {
regs->up_region.left = 0;
regs->up_region.top = 0;
regs->up_region.right = regs->lcd_width - 1;
regs->up_region.bottom = regs->lcd_height - 1;
return;
}
/* find adjusted update region on LCD */
win_update_adjust_region(decon, win_config, regs);
/* check DPP hw limitation if violated, update region is changed to full */
win_update_check_limitation(decon, win_config, regs);
/*
* If update region is changed, need_update flag is set.
* That means hw configuration is needed
*/
if (is_decon_rect_differ(&decon->win_up.prev_up_region, &regs->up_region))
regs->need_update = true;
else
regs->need_update = false;
/*
* If partial update region is requested, source and destination
* coordinates are needed to change if overlapped with update region.
*/
DPU_FULL_RECT(&r, decon->lcd_info);
if (is_decon_rect_differ(&regs->up_region, &r))
reconfigure = true;
if (regs->need_update || reconfigure) {
DPU_DEBUG_WIN("need_update(%d), reconfigure(%d)\n",
regs->need_update, reconfigure);
DPU_EVENT_LOG_WINUP_FLAGS(&decon->sd, regs->need_update, reconfigure);
}
/* Reconfigure source and destination coordinates, if needed. */
if (reconfigure)
win_update_reconfig_coordinates(decon, win_config, regs);
}
void dpu_set_mres_config(struct decon_device *decon, struct decon_reg_data *regs)
{
struct dsim_device *dsim = get_dsim_drvdata(0);
struct lcd_mres_info *mres_info = &dsim->lcd_info.dt_lcd_mres;
struct decon_param p;
int idx;
if (!decon->mres_enabled) {
DPU_DEBUG_MRES("multi-resolution feature is disabled\n");
return;
}
if (decon->dt.out_type != DECON_OUT_DSI) {
DPU_DEBUG_MRES("multi resolution only support DSI path\n");
return;
}
if (!decon->lcd_info->dt_lcd_mres.mres_en) {
DPU_DEBUG_MRES("panel doesn't support multi-resolution\n");
return;
}
if (!regs->mres_update)
return;
if (IS_ERR_OR_NULL(dsim)) {
DPU_ERR_MRES("%s: dsim device ptr is invalid\n", __func__);
return;
}
/*
* Before LCD resolution is changed, previous frame data must be
* finished to transfer.
*/
decon_reg_wait_idle_status_timeout(decon->id, IDLE_WAIT_TIMEOUT);
/* backup current LCD resolution information to previous one */
dsim->lcd_info.xres = regs->lcd_width;
dsim->lcd_info.yres = regs->lcd_height;
dsim->lcd_info.mres_mode = regs->mres_idx;
idx = regs->mres_idx;
dsim->lcd_info.dsc_enabled = mres_info->res_info[idx].dsc_en;
dsim->lcd_info.dsc_slice_h = mres_info->res_info[idx].dsc_height;
/* transfer LCD resolution change commands to panel */
dsim->panel_ops->mres(dsim, regs->mres_idx);
/* DECON and DSIM are reconfigured by changed LCD resolution */
dsim_reg_set_mres(dsim->id, &dsim->lcd_info);
decon_to_init_param(decon, &p);
decon_reg_set_mres(decon->id, &p);
/* If LCD resolution is changed, initial partial size is also changed */
dpu_init_win_update(decon);
DPU_DEBUG_MRES("changed LCD resolution(%d %d)\n",
decon->lcd_info->xres, decon->lcd_info->yres);
}
static int win_update_send_partial_command(struct dsim_device *dsim,
struct decon_rect *rect)
{
char column[5];
char page[5];
int retry;
DPU_DEBUG_WIN("SET: [%d %d %d %d]\n", rect->left, rect->top,
rect->right - rect->left + 1, rect->bottom - rect->top + 1);
column[0] = MIPI_DCS_SET_COLUMN_ADDRESS;
column[1] = (rect->left >> 8) & 0xff;
column[2] = rect->left & 0xff;
column[3] = (rect->right >> 8) & 0xff;
column[4] = rect->right & 0xff;
page[0] = MIPI_DCS_SET_PAGE_ADDRESS;
page[1] = (rect->top >> 8) & 0xff;
page[2] = rect->top & 0xff;
page[3] = (rect->bottom >> 8) & 0xff;
page[4] = rect->bottom & 0xff;
retry = 2;
while (dsim_write_data(dsim, MIPI_DSI_DCS_LONG_WRITE,
(unsigned long)column, ARRAY_SIZE(column)) != 0) {
dsim_err("failed to write COLUMN_ADDRESS\n");
dsim_reg_function_reset(dsim->id);
if (--retry <= 0) {
dsim_err("COLUMN_ADDRESS is failed: exceed retry count\n");
return -EINVAL;
}
}
retry = 2;
while (dsim_write_data(dsim, MIPI_DSI_DCS_LONG_WRITE,
(unsigned long)page, ARRAY_SIZE(page)) != 0) {
dsim_err("failed to write PAGE_ADDRESS\n");
dsim_reg_function_reset(dsim->id);
if (--retry <= 0) {
dsim_err("PAGE_ADDRESS is failed: exceed retry count\n");
return -EINVAL;
}
}
return 0;
}
static void win_update_find_included_slice(struct decon_lcd *lcd,
struct decon_rect *rect, bool in_slice[])
{
int slice_left, slice_right, slice_width;
int i;
slice_left = 0;
slice_right = 0;
slice_width = lcd->xres / lcd->dsc_slice_num;
for (i = 0; i < lcd->dsc_slice_num; ++i) {
slice_left = slice_width * i;
slice_right = slice_left + slice_width - 1;
in_slice[i] = false;
if ((slice_left >= rect->left) && (slice_right <= rect->right))
in_slice[i] = true;
DPU_DEBUG_WIN("slice_left(%d), right(%d)\n", slice_left, slice_right);
DPU_DEBUG_WIN("slice[%d] is %s\n", i,
in_slice[i] ? "included" : "not included");
}
}
static void win_update_set_partial_size(struct decon_device *decon,
struct decon_rect *rect)
{
struct decon_lcd lcd_info;
struct dsim_device *dsim = get_dsim_drvdata(0);
bool in_slice[MAX_DSC_SLICE_CNT];
memcpy(&lcd_info, decon->lcd_info, sizeof(struct decon_lcd));
lcd_info.xres = rect->right - rect->left + 1;
lcd_info.yres = rect->bottom - rect->top + 1;
lcd_info.hfp = decon->lcd_info->hfp +
((decon->lcd_info->xres - lcd_info.xres) >> 1);
lcd_info.vfp = decon->lcd_info->vfp + decon->lcd_info->yres - lcd_info.yres;
dsim_reg_set_partial_update(dsim->id, &lcd_info);
win_update_find_included_slice(decon->lcd_info, rect, in_slice);
decon_reg_set_partial_update(decon->id, decon->dt.dsi_mode,
decon->lcd_info, in_slice,
lcd_info.xres, lcd_info.yres);
DPU_DEBUG_WIN("SET: vfp %d vbp %d vsa %d hfp %d hbp %d hsa %d w %d h %d\n",
lcd_info.vfp, lcd_info.vbp, lcd_info.vsa,
lcd_info.hfp, lcd_info.hbp, lcd_info.hsa,
lcd_info.xres, lcd_info.yres);
}
void dpu_set_win_update_config(struct decon_device *decon,
struct decon_reg_data *regs)
{
struct dsim_device *dsim = get_dsim_drvdata(0);
bool in_slice[MAX_DSC_SLICE_CNT];
bool full_partial_update = false;
if (!decon->win_up.enabled)
return;
if (decon->dt.out_type != DECON_OUT_DSI)
return;
if (regs == NULL) {
regs = kzalloc(sizeof(struct decon_reg_data), GFP_KERNEL);
if (!regs) {
decon_err("%s: reg_data allocation fail\n", __func__);
return;
}
DPU_FULL_RECT(&regs->up_region, decon->lcd_info);
regs->need_update = true;
full_partial_update = true;
}
if (regs->need_update) {
win_update_find_included_slice(decon->lcd_info,
&regs->up_region, in_slice);
/* TODO: Is waiting framedone irq needed in KC ? */
/*
* hw configuration related to partial update must be set
* without DMA operation
*/
decon_reg_wait_idle_status_timeout(decon->id, IDLE_WAIT_TIMEOUT);
win_update_send_partial_command(dsim, &regs->up_region);
win_update_set_partial_size(decon, &regs->up_region);
DPU_EVENT_LOG_APPLY_REGION(&decon->sd, &regs->up_region);
}
if (full_partial_update)
kfree(regs);
}
void dpu_set_win_update_partial_size(struct decon_device *decon,
struct decon_rect *up_region)
{
if (!decon->win_up.enabled)
return;
win_update_set_partial_size(decon, up_region);
}
void dpu_init_win_update(struct decon_device *decon)
{
struct decon_lcd *lcd = decon->lcd_info;
struct v4l2_subdev *sd;
struct dpp_restriction res;
int sz_align = 1;
decon->win_up.enabled = false;
decon->cursor.xpos = lcd->xres / 2;
decon->cursor.ypos = lcd->yres / 2;
if (!IS_ENABLED(CONFIG_EXYNOS_WINDOW_UPDATE)) {
decon_info("window update feature is disabled\n");
return;
}
if (decon->dt.out_type != DECON_OUT_DSI) {
decon_info("out_type(%d) doesn't support window update\n",
decon->dt.out_type);
return;
}
sd = decon->dpp_sd[0];
v4l2_subdev_call(sd, core, ioctl, DPP_GET_RESTRICTION, &res);
if (lcd->dsc_enabled) {
decon->win_up.rect_w = lcd->xres / lcd->dsc_slice_num;
decon->win_up.rect_h = lcd->dsc_slice_h;
} else {
decon->win_up.rect_w = max((res.src_f_w.min * sz_align), decon->lcd_info->update_min_w);
decon->win_up.rect_h = max((res.src_f_h.min * sz_align), decon->lcd_info->update_min_h);
}
DPU_FULL_RECT(&decon->win_up.prev_up_region, lcd);
decon->win_up.hori_cnt = decon->lcd_info->xres / decon->win_up.rect_w;
if (lcd->dsc_enabled) {
if (decon->lcd_info->xres - decon->win_up.hori_cnt * decon->win_up.rect_w) {
decon_warn("%s: parameters is wrong. lcd w(%d), win rect w(%d)\n",
__func__, decon->lcd_info->xres,
decon->win_up.rect_w);
return;
}
}
decon->win_up.verti_cnt = decon->lcd_info->yres / decon->win_up.rect_h;
if (lcd->dsc_enabled) {
if (decon->lcd_info->yres - decon->win_up.verti_cnt * decon->win_up.rect_h) {
decon_warn("%s: parameters is wrong. lcd h(%d), win rect h(%d)\n",
__func__, decon->lcd_info->yres,
decon->win_up.rect_h);
return;
}
}
decon_info("window update is enabled: win rectangle w(%d), h(%d)\n",
decon->win_up.rect_w, decon->win_up.rect_h);
decon_info("horizontal count(%d), vertical count(%d)\n",
decon->win_up.hori_cnt, decon->win_up.verti_cnt);
decon->win_up.enabled = true;
decon->mres_enabled = false;
if (!IS_ENABLED(CONFIG_EXYNOS_MULTIRESOLUTION)) {
decon_info("multi-resolution feature is disabled\n");
return;
}
/* TODO: will be printed supported resolution list */
decon_info("multi-resolution feature is enabled\n");
decon->mres_enabled = true;
}