blob: f7a4ca1590e31b17a03429b082413f83ed97417a [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (C) 2020 Samsung Electronics Co.Ltd
*
* 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.
*
*/
#define pr_fmt(fmt) "[PARTIAL]: %s: " fmt, __func__
#include <linux/device.h>
#include <linux/of.h>
#include <video/mipi_display.h>
#include <drm/drm_fourcc.h>
#include "exynos_drm_decon.h"
#include "exynos_drm_format.h"
#include "exynos_drm_dsim.h"
#include "exynos_drm_connector.h"
#include "cal_common/dsim_cal.h"
#define pr_region(str, r) \
pr_debug("%s["DRM_RECT_FMT"]\n", (str), DRM_RECT_ARG(r))
static int exynos_partial_init(struct exynos_partial *partial,
const struct exynos_display_partial *partial_mode,
const struct drm_display_mode *mode)
{
partial->min_w = partial_mode->min_width;
partial->min_h = partial_mode->min_height;
if ((partial->min_w < MIN_WIN_BLOCK_WIDTH) ||
(partial->min_h < MIN_WIN_BLOCK_HEIGHT)) {
pr_err("invalid min size(%dx%d) of partial update\n",
partial->min_w, partial->min_h);
return -EINVAL;
}
if ((mode->hdisplay % partial->min_w) ||
(mode->vdisplay % partial->min_h)) {
pr_err("cannot support partial update(%dx%d, %dx%d)\n",
mode->hdisplay, mode->vdisplay,
partial->min_w, partial->min_h);
return -EINVAL;
}
return 0;
}
void exynos_partial_set_full(const struct drm_display_mode *mode,
struct drm_rect *partial_r)
{
partial_r->x1 = 0;
partial_r->y1 = 0;
partial_r->x2 = mode->hdisplay;
partial_r->y2 = mode->vdisplay;
}
static int exynos_partial_adjust_region(struct exynos_partial *partial,
const struct drm_display_mode *mode,
const struct drm_rect *req, struct drm_rect *r)
{
pr_region("requested update region", req);
if (!req->x1 && !req->y1 && !req->x2 && !req->y2) {
pr_region("invalid partial update region", req);
return -EINVAL;
}
if ((req->x2 > mode->hdisplay) || (req->y2 > mode->vdisplay)) {
pr_debug("changed full: requested region is bigger\n");
return -EINVAL;
}
/* adjusted update region */
r->y1 = rounddown(req->y1, partial->min_h);
r->y2 = roundup(req->y2, partial->min_h);
/*
* TODO: Currently, partial width is fixed by LCD width. This will be
* changed to be configurable in the future.
*/
r->x1 = 0;
r->x2 = mode->hdisplay;
pr_region("adjusted update region", r);
return 0;
}
static void exynos_plane_print_info(const struct drm_plane_state *state)
{
const struct drm_plane *plane = state->plane;
const struct drm_rect src = drm_plane_state_src(state);
const struct drm_rect dst = drm_plane_state_dest(state);
const struct drm_rect *clipped_src = &state->src;
const struct drm_rect *clipped_dst = &state->dst;
pr_debug("plane%d/win%d src["DRM_RECT_FP_FMT"] dst["DRM_RECT_FMT"]\n",
drm_plane_index(plane), state->normalized_zpos,
DRM_RECT_FP_ARG(&src), DRM_RECT_ARG(&dst));
pr_debug("\t\tclipped src["DRM_RECT_FP_FMT"] dst["DRM_RECT_FMT"]\n",
DRM_RECT_FP_ARG(clipped_src), DRM_RECT_ARG(clipped_dst));
}
static inline bool
exynos_plane_state_rotation(const struct drm_plane_state *state)
{
unsigned int simplified_rot;
simplified_rot = drm_rotation_simplify(state->rotation,
DRM_MODE_ROTATE_0 | DRM_MODE_ROTATE_90 |
DRM_MODE_REFLECT_X | DRM_MODE_REFLECT_Y);
return (simplified_rot & DRM_MODE_ROTATE_90) != 0;
}
static inline bool
exynos_plane_state_scaling(const struct drm_plane_state *state)
{
return (state->src_w >> 16 != state->crtc_w) ||
(state->src_h >> 16 != state->crtc_h);
}
static bool is_partial_supported(const struct drm_plane_state *state,
const struct drm_rect *crtc_r, const struct drm_rect *partial_r,
const struct dpp_restriction *res)
{
const struct dpu_fmt *fmt_info;
unsigned int adj_src_x = 0, adj_src_y = 0;
u32 format;
int sz_align = 1;
if (exynos_plane_state_rotation(state)) {
pr_debug("rotation is detected. partial->full\n");
goto not_supported;
}
if (exynos_plane_state_scaling(state)) {
pr_debug("scaling is detected. partial->full\n");
goto not_supported;
}
format = state->fb->format->format;
fmt_info = dpu_find_fmt_info(format);
if (IS_YUV(fmt_info)) {
adj_src_x = state->src_x >> 16;
adj_src_y = state->src_y >> 16;
sz_align = 2;
if (partial_r->x1 > state->crtc_x)
adj_src_x += partial_r->x1 - state->crtc_x;
if (partial_r->y1 > state->crtc_y)
adj_src_y += partial_r->y1 - state->crtc_y;
/* YUV format must be aligned to 2 */
if (!IS_ALIGNED(adj_src_x, sz_align) ||
!IS_ALIGNED(adj_src_y, sz_align)) {
pr_debug("align limitation. src_x/y[%d/%d] align[%d]\n",
adj_src_x, adj_src_y, sz_align);
goto not_supported;
}
}
if ((drm_rect_width(crtc_r) < res->src_f_w.min * sz_align) ||
(drm_rect_height(crtc_r) < res->src_f_h.min * sz_align)) {
pr_debug("min size limitation. width[%d] height[%d]\n",
drm_rect_width(crtc_r), drm_rect_height(crtc_r));
goto not_supported;
}
return true;
not_supported:
exynos_plane_print_info(state);
return false;
}
#define to_dpp_device(x) container_of(x, struct dpp_device, plane)
static bool exynos_partial_check(struct exynos_partial *partial,
struct exynos_drm_crtc_state *exynos_crtc_state)
{
struct drm_crtc_state *crtc_state = &exynos_crtc_state->base;
struct drm_plane *plane;
const struct drm_plane_state *plane_state;
const struct drm_rect *partial_r = &exynos_crtc_state->partial_region;
struct drm_rect r;
const struct dpp_device *dpp;
const struct dpp_restriction *res;
drm_for_each_plane_mask(plane, crtc_state->state->dev, crtc_state->plane_mask) {
plane_state = drm_atomic_get_plane_state(crtc_state->state, plane);
if (IS_ERR(plane_state))
return false;
r = drm_plane_state_dest(plane_state);
if (!drm_rect_intersect(&r, partial_r))
continue;
dpp = to_dpp_device(to_exynos_plane(plane));
res = &dpp->restriction;
pr_debug("checking plane%d ...\n", drm_plane_index(plane));
if (!is_partial_supported(plane_state, &r, partial_r, res))
return false;
}
return true;
}
static int exynos_partial_send_command(struct exynos_partial *partial,
const struct drm_rect *partial_r)
{
struct decon_device *decon = partial->decon;
struct dsim_device *dsim;
int ret;
pr_debug("partial command: [%d %d %d %d]\n",
partial_r->x1, partial_r->y1,
drm_rect_width(partial_r), drm_rect_height(partial_r));
if (!decon)
return -ENODEV;
dsim = decon_get_dsim(decon);
if (!dsim)
return -ENODEV;
ret = mipi_dsi_dcs_set_column_address(dsim->dsi_device, partial_r->x1,
partial_r->x2 - 1);
if (ret)
return ret;
ret = mipi_dsi_dcs_set_page_address(dsim->dsi_device, partial_r->y1,
partial_r->y2 - 1);
if (ret)
return ret;
return ret;
}
static void exynos_partial_find_included_slice(struct exynos_dsc *dsc,
const struct drm_rect *rect, bool in_slice[])
{
int slice_left, slice_right;
int i;
for (i = 0; i < dsc->slice_count; ++i) {
slice_left = dsc->slice_width * i;
slice_right = slice_left + dsc->slice_width;
in_slice[i] = (slice_left >= rect->x1) && (slice_right <= rect->x2);
pr_debug("slice left(%d) right(%d)\n", slice_left, slice_right);
pr_debug("slice[%d] is %s\n", i, in_slice[i] ? "in" : "out");
}
}
#define MAX_DSC_SLICE_CNT 4
static void exynos_partial_set_size(struct exynos_partial *partial,
const struct drm_rect *partial_r)
{
struct decon_device *decon = partial->decon;
struct dsim_device *dsim;
struct dsim_reg_config dsim_config;
bool in_slice[MAX_DSC_SLICE_CNT];
bool dsc_en;
u32 partial_w, partial_h;
if (!decon)
return;
dsim = decon_get_dsim(decon);
if (!dsim)
return;
dsc_en = dsim->config.dsc.enabled;
partial_w = drm_rect_width(partial_r);
partial_h = drm_rect_height(partial_r);
memcpy(&dsim_config, &dsim->config, sizeof(struct dsim_reg_config));
dsim_config.p_timing.hactive = partial_w;
dsim_config.p_timing.vactive = partial_h;
dsim_config.p_timing.hfp +=
((dsim->config.p_timing.hactive - partial_w) / (dsc_en ? 3 : 1));
dsim_config.p_timing.vfp += (dsim->config.p_timing.vactive - partial_h);
dsim_reg_set_partial_update(dsim->id, &dsim_config);
exynos_partial_find_included_slice(&decon->config.dsc, partial_r,
in_slice);
decon_reg_set_partial_update(decon->id, &decon->config, in_slice,
partial_w, partial_h);
if (decon->dqe) {
dqe_reg_set_size(decon->id, partial_w, partial_h);
decon_reg_update_req_dqe(decon->id);
}
pr_debug("partial[%dx%d] vporch[%d %d %d] hporch[%d %d %d]\n",
partial_w, partial_h,
dsim_config.p_timing.vbp, dsim_config.p_timing.vfp,
dsim_config.p_timing.vsa, dsim_config.p_timing.hbp,
dsim_config.p_timing.hfp, dsim_config.p_timing.hsa);
}
static const struct exynos_partial_funcs partial_funcs = {
.init = exynos_partial_init,
.check = exynos_partial_check,
.adjust_partial_region = exynos_partial_adjust_region,
.send_partial_command = exynos_partial_send_command,
.set_partial_size = exynos_partial_set_size,
};
struct exynos_partial *exynos_partial_initialize(struct decon_device *decon,
const struct exynos_display_partial *partial_mode,
const struct drm_display_mode *mode)
{
int ret;
struct exynos_partial *partial = NULL;
struct device *dev = decon->dev;
if (!partial_mode->enabled) {
pr_debug("This panel doesn't support partial update feature\n");
return NULL;
}
if (!decon->partial) {
partial = devm_kzalloc(dev, sizeof(struct exynos_partial),
GFP_KERNEL);
if (!partial)
return NULL;
partial->decon = decon;
partial->funcs = &partial_funcs;
} else {
partial = decon->partial;
}
ret = partial->funcs->init(partial, partial_mode, mode);
if (ret) {
pr_err("failed to initialize partial update\n");
kfree(partial);
return NULL;
}
pr_debug("partial update is initialized: min rect(%dx%d)\n",
partial->min_w, partial->min_h);
DPU_EVENT_LOG(DPU_EVT_PARTIAL_INIT, partial->decon->id, partial);
return partial;
}
static void
exynos_partial_save_log(struct dpu_log_partial *plog, const struct drm_rect *prev,
struct drm_rect *req, struct drm_rect *adj,
bool reconfigure)
{
memcpy(&plog->prev, prev, sizeof(struct drm_rect));
memcpy(&plog->req, req, sizeof(struct drm_rect));
memcpy(&plog->adj, adj, sizeof(struct drm_rect));
plog->reconfigure = reconfigure;
}
static bool
exynos_partial_is_full(const struct drm_display_mode *mode, const struct drm_rect *rect)
{
struct drm_rect full;
exynos_partial_set_full(mode, &full);
return drm_rect_equals(&full, rect);
}
void exynos_partial_prepare(struct exynos_partial *partial,
struct exynos_drm_crtc_state *old_exynos_crtc_state,
struct exynos_drm_crtc_state *new_exynos_crtc_state)
{
struct drm_crtc_state *crtc_state = &new_exynos_crtc_state->base;
struct drm_rect *partial_r = &new_exynos_crtc_state->partial_region;
const struct drm_rect *old_partial_r = &old_exynos_crtc_state->partial_region;
struct decon_device *decon = partial->decon;
struct dpu_log_partial plog;
struct drm_clip_rect *req_region;
struct drm_rect req;
int ret = -ENOENT;
bool region_changed = false;
pr_debug("plane mask[0x%x]\n", crtc_state->plane_mask);
new_exynos_crtc_state->needs_reconfigure = false;
if (drm_atomic_crtc_needs_modeset(crtc_state)) {
exynos_partial_set_full(&crtc_state->mode, partial_r);
return;
}
if (!crtc_state->plane_mask)
return;
if (old_exynos_crtc_state->partial != new_exynos_crtc_state->partial) {
if (new_exynos_crtc_state->partial) {
req_region = new_exynos_crtc_state->partial->data;
req.x1 = req_region->x1;
req.y1 = req_region->y1;
req.x2 = req_region->x2;
req.y2 = req_region->y2;
/* find adjusted update region on LCD */
ret = partial->funcs->adjust_partial_region(partial,
&crtc_state->mode, &req, partial_r);
}
if (ret)
exynos_partial_set_full(&crtc_state->mode, partial_r);
region_changed = !drm_rect_equals(partial_r, old_partial_r);
}
if (!region_changed) {
if (!crtc_state->planes_changed) {
new_exynos_crtc_state->needs_reconfigure =
!exynos_partial_is_full(&crtc_state->mode, partial_r);
return;
} else if (exynos_partial_is_full(&crtc_state->mode, partial_r)) {
return;
}
} else {
/* if region changed, DQE needs to be updated */
crtc_state->color_mgmt_changed = true;
}
/* check DPP hw limit if violated, update region is changed to full */
if (!partial->funcs->check(partial, new_exynos_crtc_state))
exynos_partial_set_full(&crtc_state->mode,
&new_exynos_crtc_state->partial_region);
pr_region("final update region", partial_r);
/*
* If partial update region is requested, source and destination
* coordinates are needed to change if overlapped with update region.
*/
new_exynos_crtc_state->needs_reconfigure =
!exynos_partial_is_full(&crtc_state->mode, partial_r);
pr_debug("reconfigure(%d)\n", new_exynos_crtc_state->needs_reconfigure);
exynos_partial_save_log(&plog, old_partial_r, &req, partial_r,
new_exynos_crtc_state->needs_reconfigure);
DPU_EVENT_LOG(DPU_EVT_PARTIAL_PREPARE, decon->id, &plog);
}
void exynos_partial_reconfig_coords(struct exynos_partial *partial,
struct drm_plane_state *plane_state,
const struct drm_rect *partial_r)
{
plane_state->visible = drm_rect_clip_scaled(&plane_state->src,
&plane_state->dst, partial_r);
if (!plane_state->visible)
return;
drm_rect_translate(&plane_state->dst, -(partial_r->x1), -(partial_r->y1));
pr_debug("reconfigured coordinates:\n");
exynos_plane_print_info(plane_state);
}
void exynos_partial_update(struct exynos_partial *partial,
const struct drm_rect *old_partial_region,
struct drm_rect *new_partial_region)
{
struct decon_device *decon = partial->decon;
if (!decon)
return;
if (drm_rect_equals(old_partial_region, new_partial_region))
return;
partial->funcs->send_partial_command(partial, new_partial_region);
partial->funcs->set_partial_size(partial, new_partial_region);
DPU_EVENT_LOG(DPU_EVT_PARTIAL_UPDATE, decon->id, new_partial_region);
pr_region("applied partial region", new_partial_region);
}
void exynos_partial_restore(struct exynos_partial *partial)
{
struct decon_device *decon = partial->decon;
struct drm_crtc_state *crtc_state;
struct exynos_drm_crtc_state *exynos_crtc_state;
struct drm_rect *old_partial_region;
if (!decon)
return;
crtc_state = decon->crtc->base.state;
if (!crtc_state)
return;
exynos_crtc_state = to_exynos_crtc_state(crtc_state);
old_partial_region = &exynos_crtc_state->partial_region;
if (exynos_partial_is_full(&crtc_state->mode, old_partial_region))
return;
partial->funcs->set_partial_size(partial, old_partial_region);
DPU_EVENT_LOG(DPU_EVT_PARTIAL_RESTORE, decon->id, old_partial_region);
pr_region("restored partial region", old_partial_region);
}