kernel_samsung_a53x/drivers/gpu/drm/samsung/panel/panel_vrr.c
2024-06-15 16:02:09 -03:00

493 lines
12 KiB
C
Executable file

// SPDX-License-Identifier: GPL-2.0
/*
* Copyright (c) Samsung Electronics Co., Ltd.
* Gwanghui Lee <gwanghui.lee@samsung.com>
*
* 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 "panel_kunit.h"
#include "panel.h"
#include "panel_drv.h"
#include "panel_vrr.h"
#include "panel_debug.h"
const char *vrr_lfd_client_name[MAX_VRR_LFD_CLIENT] = {
[VRR_LFD_CLIENT_FAC] = "fac",
[VRR_LFD_CLIENT_DISP] = "disp",
[VRR_LFD_CLIENT_INPUT] = "input",
[VRR_LFD_CLIENT_AOD] = "aod",
[VRR_LFD_CLIENT_VID] = "vid",
};
const char *vrr_lfd_scope_name[MAX_VRR_LFD_SCOPE] = {
[VRR_LFD_SCOPE_NORMAL] = "normal",
[VRR_LFD_SCOPE_HMD] = "hmd",
[VRR_LFD_SCOPE_LPM] = "lpm",
};
const char *get_vrr_lfd_client_name(int index)
{
if (index < 0 || index >= MAX_VRR_LFD_CLIENT)
return NULL;
return vrr_lfd_client_name[index];
}
int find_vrr_lfd_client_name(const char *name)
{
int i;
if (name == NULL)
return -EINVAL;
for (i = 0; i < MAX_VRR_LFD_CLIENT; i++)
if (!strcmp(vrr_lfd_client_name[i], name))
break;
if (i == MAX_VRR_LFD_CLIENT)
return -EINVAL;
return i;
}
const char *get_vrr_lfd_scope_name(int index)
{
if (index < 0 || index >= MAX_VRR_LFD_SCOPE)
return NULL;
return vrr_lfd_scope_name[index];
}
int find_vrr_lfd_scope_name(const char *name)
{
int i;
if (name == NULL)
return -EINVAL;
for (i = 0; i < MAX_VRR_LFD_SCOPE; i++)
if (!strcmp(vrr_lfd_scope_name[i], name))
break;
if (i == MAX_VRR_LFD_SCOPE)
return -EINVAL;
return i;
}
int update_vrr_lfd(struct vrr_lfd_info *vrr_lfd_info)
{
int i, scope;
u32 lfd_fix, lfd_min, lfd_max, lfd_scalability;
static struct vrr_lfd_info old_vrr_lfd_info;
int updated = VRR_LFD_NOT_UPDATED;
if (vrr_lfd_info == NULL)
return -EINVAL;
/* fix */
for (scope = 0; scope < MAX_VRR_LFD_SCOPE; scope++) {
lfd_fix = VRR_LFD_FREQ_NONE;
for (i = 0; i < MAX_VRR_LFD_CLIENT; i++) {
if (vrr_lfd_info->req[i][scope].fix != VRR_LFD_FREQ_NONE) {
lfd_fix = vrr_lfd_info->req[i][scope].fix;
panel_info("client:%s scope:%s fix:%d\n",
get_vrr_lfd_client_name(i),
get_vrr_lfd_scope_name(scope),
vrr_lfd_info->req[i][scope].fix);
break;
}
}
vrr_lfd_info->cur[scope].fix = lfd_fix;
if (old_vrr_lfd_info.cur[scope].fix != vrr_lfd_info->cur[scope].fix) {
panel_info("scope:%s fix:%d->%d\n",
get_vrr_lfd_scope_name(scope),
old_vrr_lfd_info.cur[scope].fix,
vrr_lfd_info->cur[scope].fix);
updated = VRR_LFD_UPDATED;
}
}
/* scalability */
for (scope = 0; scope < MAX_VRR_LFD_SCOPE; scope++) {
lfd_scalability = VRR_LFD_SCALABILITY_MAX;
for (i = 0; i < MAX_VRR_LFD_CLIENT; i++) {
/* scalability changed */
if (vrr_lfd_info->req[i][scope].scalability != VRR_LFD_SCALABILITY_NONE) {
lfd_scalability = min(lfd_scalability,
vrr_lfd_info->req[i][scope].scalability);
panel_info("client:%s scope:%s scalability:%d\n",
get_vrr_lfd_client_name(i),
get_vrr_lfd_scope_name(scope),
vrr_lfd_info->req[i][scope].scalability);
}
}
vrr_lfd_info->cur[scope].scalability =
(lfd_scalability == VRR_LFD_SCALABILITY_MAX) ?
VRR_LFD_SCALABILITY_NONE : lfd_scalability;
if (old_vrr_lfd_info.cur[scope].scalability !=
vrr_lfd_info->cur[scope].scalability) {
panel_info("scope:%s scalability:%d->%d\n",
get_vrr_lfd_scope_name(scope),
old_vrr_lfd_info.cur[scope].scalability,
vrr_lfd_info->cur[scope].scalability);
updated = VRR_LFD_UPDATED;
}
}
/* min */
for (scope = 0; scope < MAX_VRR_LFD_SCOPE; scope++) {
lfd_min = 0;
for (i = 0; i < MAX_VRR_LFD_CLIENT; i++) {
if (vrr_lfd_info->req[i][scope].min != 0)
panel_info("client:%s scope:%s min:%d\n",
get_vrr_lfd_client_name(i),
get_vrr_lfd_scope_name(scope),
vrr_lfd_info->req[i][scope].min);
lfd_min = max(lfd_min, vrr_lfd_info->req[i][scope].min);
}
vrr_lfd_info->cur[scope].min = lfd_min;
if (old_vrr_lfd_info.cur[scope].min !=
vrr_lfd_info->cur[scope].min) {
panel_info("scope:%s min:%d->%d\n",
get_vrr_lfd_scope_name(scope),
old_vrr_lfd_info.cur[scope].min,
vrr_lfd_info->cur[scope].min);
updated = VRR_LFD_UPDATED;
}
}
/* max */
for (scope = 0; scope < MAX_VRR_LFD_SCOPE; scope++) {
lfd_max = 0;
for (i = 0; i < MAX_VRR_LFD_CLIENT; i++) {
if (vrr_lfd_info->req[i][scope].max != 0)
panel_info("client:%s scope:%s max:%d\n",
get_vrr_lfd_client_name(i),
get_vrr_lfd_scope_name(scope),
vrr_lfd_info->req[i][scope].max);
lfd_max = max(lfd_max, vrr_lfd_info->req[i][scope].max);
}
vrr_lfd_info->cur[scope].max = lfd_max;
if (old_vrr_lfd_info.cur[scope].max !=
vrr_lfd_info->cur[scope].max) {
panel_info("scope:%s max:%d->%d\n",
get_vrr_lfd_scope_name(scope),
old_vrr_lfd_info.cur[scope].max,
vrr_lfd_info->cur[scope].max);
updated = VRR_LFD_UPDATED;
}
}
memcpy(&old_vrr_lfd_info,
vrr_lfd_info, sizeof(struct vrr_lfd_info));
return updated;
}
bool panel_vrr_is_supported(struct panel_device *panel)
{
struct panel_info *data;
if (!panel)
return false;
data = &panel->panel_data;
panel_dbg("support_vrr %s vrrtbl %s nr_vrrtbl %d\n",
data->ddi_props.support_vrr ? "true" : "false",
data->vrrtbl == NULL ? "is null" : "is not null",
data->nr_vrrtbl);
if (!data->ddi_props.support_vrr)
return false;
if (data->vrrtbl == NULL || data->nr_vrrtbl < 1)
return false;
return true;
}
bool panel_vrr_lfd_is_supported(struct panel_device *panel)
{
struct panel_info *data;
if (!panel)
return false;
data = &panel->panel_data;
return (panel_vrr_is_supported(panel) && data->ddi_props.support_vrr_lfd);
}
bool panel_vrr_is_valid(struct panel_device *panel)
{
struct panel_info *data = &panel->panel_data;
struct panel_properties *props = &data->props;
if (panel_vrr_is_supported(panel) == false)
return false;
if (data->nr_vrrtbl <= props->vrr_idx) {
panel_warn("vrr_idx(%d) exceed number of vrrtbl(%d)\n",
props->vrr_idx, data->nr_vrrtbl);
return false;
}
return true;
}
__mockable struct panel_vrr *get_panel_vrr(struct panel_device *panel)
{
struct panel_properties *props = &panel->panel_data.props;
if (panel_vrr_is_valid(panel) == false)
return NULL;
return panel->panel_data.vrrtbl[props->vrr_idx];
}
EXPORT_SYMBOL(get_panel_vrr);
__mockable int get_panel_refresh_rate(struct panel_device *panel)
{
struct panel_vrr *vrr;
vrr = get_panel_vrr(panel);
if (vrr == NULL)
return -EINVAL;
return vrr->fps;
}
EXPORT_SYMBOL(get_panel_refresh_rate);
__mockable int get_panel_refresh_mode(struct panel_device *panel)
{
struct panel_vrr *vrr;
vrr = get_panel_vrr(panel);
if (vrr == NULL)
return -EINVAL;
return vrr->mode;
}
EXPORT_SYMBOL(get_panel_refresh_mode);
#ifdef CONFIG_PANEL_VRR_BRIDGE
bool panel_vrr_bridge_is_supported(struct panel_device *panel)
{
struct common_panel_display_modes *common_panel_modes =
panel->panel_data.common_panel_modes;
if (!panel_display_mode_is_supported(panel)) {
panel_warn("panel_display_mode not supported\n");
return false;
}
if (!panel_vrr_is_supported(panel))
return false;
if (!common_panel_modes->bridges)
return false;
return true;
}
bool panel_vrr_bridge_changeable(struct panel_device *panel)
{
struct common_panel_display_modes *common_panel_modes =
panel->panel_data.common_panel_modes;
struct common_panel_display_mode_bridge_ops *bridge_ops;
struct panel_properties *props =
&panel->panel_data.props;
if (!panel_vrr_bridge_is_supported(panel)) {
panel_warn("panel_vrr_bridge not supported\n");
return false;
}
if (!common_panel_modes ||
!props->vrr_bridge_enable ||
panel->state.cur_state != PANEL_STATE_NORMAL)
return false;
bridge_ops = common_panel_modes->bridge_ops;
if (!bridge_ops || !bridge_ops->check_changeable)
return false;
return bridge_ops->check_changeable(panel);
}
bool panel_vrr_bridge_is_reached_target_nolock(struct panel_device *panel)
{
struct panel_properties *props =
&panel->panel_data.props;
return (props->target_panel_mode == props->panel_mode);
}
bool panel_vrr_bridge_is_reached_target(struct panel_device *panel)
{
bool reached;
mutex_lock(&panel->op_lock);
reached = panel_vrr_bridge_is_reached_target_nolock(panel);
mutex_unlock(&panel->op_lock);
return reached;
}
static struct common_panel_display_mode_bridge *
panel_display_mode_get_bridge(struct panel_device *panel, int from, int to)
{
struct common_panel_display_modes *common_panel_modes =
panel->panel_data.common_panel_modes;
struct common_panel_display_mode_bridge *bridge;
if (!panel_display_mode_is_supported(panel)) {
panel_err("panel_display_mode not supported\n");
return NULL;
}
if (!common_panel_modes->bridges)
return NULL;
if (from >= common_panel_modes->num_modes ||
to >= common_panel_modes->num_modes)
return NULL;
bridge = common_panel_modes->bridges +
(from * (int)common_panel_modes->num_modes) + to;
if (bridge->mode == NULL)
return NULL;
return bridge;
}
int panel_vrr_bridge_set_display_mode(struct panel_device *panel)
{
struct panel_properties *props =
&panel->panel_data.props;
int i, ret = 0, panel_mode, nframe_duration = 1;
struct common_panel_display_mode_bridge *bridge;
/*
* change display_mode at once caese
* normal case:
* a. brightness changed.
*
* error case:
* a. could not find next bridge cpdm.
* b. brightness too low to change vrr seamlessly.
*/
mutex_lock(&panel->op_lock);
panel_mode = props->target_panel_mode;
if (panel_vrr_bridge_is_reached_target_nolock(panel)) {
panel_info("display_mode(%d) reached to target\n",
panel_mode);
mutex_unlock(&panel->op_lock);
return 0;
}
if (!panel_vrr_bridge_changeable(panel)) {
panel_info("apply directly\n");
goto apply_refresh_rate;
}
/*
* vrr-bridge action: 1. find next panel_mode
* a. detemine bridge-refresh-rate.
* find bridge-refresh-rate.
* if true, determine next bridge-refresh-rate.
* if false, goto target-refresh-rate directly.
* b. determine duration(n-vsync with bridge-refresh-rate)
*/
bridge = panel_display_mode_get_bridge(panel,
props->panel_mode, props->target_panel_mode);
if (bridge && bridge->mode) {
panel_dbg("found bridge %s\n", bridge->mode->name);
panel_mode =
find_panel_mode_by_common_panel_display_mode(panel, bridge->mode);
nframe_duration = bridge->nframe_duration;
if (panel_mode < 0) {
panel_err("cpdm bridge not found\n");
panel_mode = props->target_panel_mode;
nframe_duration = 1;
}
}
/*
* vrr-bridge action: 2. apply next panel_mode
* a. apply bridge-refresh-rate
* b. wait for n-vsync under bridge-refresh-rate
*/
apply_refresh_rate:
panel_info("update panel_mode %d->%d\n",
props->panel_mode, panel_mode);
props->panel_mode = panel_mode;
mutex_unlock(&panel->op_lock);
ret = panel_update_display_mode(panel);
if (ret < 0) {
panel_err("failed to panel_update_display_mode\n");
return ret;
}
for (i = 0; i < nframe_duration; i++) {
ret = panel_dsi_wait_for_vsync(panel, 500);
if (ret < 0) {
panel_err("failed to panel_dsi_wait_for_vsync\n");
return ret;
}
}
return 0;
}
int panel_vrr_bridge_thread(void *data)
{
struct panel_device *panel = data;
int ret;
bool should_stop = false;
if (unlikely(!panel)) {
panel_warn("panel is null\n");
return 0;
}
if (panel_bypass_is_on(panel)) {
panel_warn("panel no use\n");
return -ENODEV;
}
while (!kthread_should_stop()) {
ret = wait_event_interruptible(
panel->thread[PANEL_THREAD_VRR_BRIDGE].wait,
(should_stop =
panel->thread[PANEL_THREAD_VRR_BRIDGE].should_stop ||
kthread_should_stop()) ||
!panel_vrr_bridge_is_reached_target(panel));
if (should_stop)
break;
panel_wake_lock(panel, WAKE_TIMEOUT_MSEC);
ret = panel_vrr_bridge_set_display_mode(panel);
if (ret < 0)
panel_err("failed to panel_vrr_bridge_set_display_mode\n");
panel_wake_unlock(panel);
}
return 0;
}
#endif