// SPDX-License-Identifier: GPL-2.0 /* * Copyright (c) Samsung Electronics Co., Ltd. * Gwanghui Lee * * 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