blob: 5c2ec16683825cafdde238ac2cd1170d2333114f [file] [edit]
/* SPDX-License-Identifier: MIT */
#include <drm/display/drm_dsc_helper.h>
#include <drm/drm_vblank.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/thermal.h>
#include <video/mipi_display.h>
#include "trace/panel_trace.h"
#include "gs_panel/drm_panel_funcs_defaults.h"
#include "gs_panel/gs_panel.h"
#include "gs_panel/gs_panel_funcs_defaults.h"
/**
* struct km4_panel - panel specific info
*
* This struct maintains km4 panel specific info. The variables with the prefix hw_ keep
* track of the features that were actually committed to hardware, and should be modified
* after sending cmds to panel, i.e., updating hw state.
*/
struct km4_panel {
/** @base: base panel struct */
struct gs_panel base;
/** @force_changeable_te: force changeable TE (instead of fixed) during early exit */
bool force_changeable_te;
/** @force_changeable_te2: force changeable TE2 for monitoring refresh rate */
bool force_changeable_te2;
/** @force_za_off: force to turn off zonal attenuation */
bool force_za_off;
/**
* @is_pixel_off: pixel-off command is sent to panel. Only sending normal-on or resetting
* panel can recover to normal mode after entering pixel-off state.
*/
bool is_pixel_off;
/**
* @is_mrr_v1: indicates panel is running in mrr v1 mode
*/
bool is_mrr_v1;
/**
* @frame_rate: real time frame rate
*/
u16 frame_rate;
/**
* @dbi_frame_count: frame counter to alternatively set dbi ref if frame rate > 60
*/
u64 dbi_frame_count;
};
#define to_spanel(ctx) container_of(ctx, struct km4_panel, base)
/* DSCv1.2a 1344x2992 */
static const struct drm_dsc_config wqhd_pps_config = {
.line_buf_depth = 9,
.bits_per_component = 8,
.convert_rgb = true,
.slice_width = 672,
.slice_height = 34,
.slice_count = 2,
.simple_422 = false,
.pic_width = 1344,
.pic_height = 2992,
.rc_tgt_offset_high = 3,
.rc_tgt_offset_low = 3,
.bits_per_pixel = 128,
.rc_edge_factor = 6,
.rc_quant_incr_limit1 = 11,
.rc_quant_incr_limit0 = 11,
.initial_xmit_delay = 512,
.initial_dec_delay = 592,
.block_pred_enable = true,
.first_line_bpg_offset = 12,
.initial_offset = 6144,
.rc_buf_thresh = {
14, 28, 42, 56,
70, 84, 98, 105,
112, 119, 121, 123,
125, 126
},
.rc_range_params = {
{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 2},
{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 0},
{.range_min_qp = 1, .range_max_qp = 5, .range_bpg_offset = 0},
{.range_min_qp = 1, .range_max_qp = 6, .range_bpg_offset = 62},
{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 60},
{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 58},
{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 56},
{.range_min_qp = 3, .range_max_qp = 8, .range_bpg_offset = 56},
{.range_min_qp = 3, .range_max_qp = 9, .range_bpg_offset = 56},
{.range_min_qp = 3, .range_max_qp = 10, .range_bpg_offset = 54},
{.range_min_qp = 5, .range_max_qp = 11, .range_bpg_offset = 54},
{.range_min_qp = 5, .range_max_qp = 12, .range_bpg_offset = 52},
{.range_min_qp = 5, .range_max_qp = 13, .range_bpg_offset = 52},
{.range_min_qp = 7, .range_max_qp = 13, .range_bpg_offset = 52},
{.range_min_qp = 13, .range_max_qp = 15, .range_bpg_offset = 52}
},
.rc_model_size = 8192,
.flatness_min_qp = 3,
.flatness_max_qp = 12,
.initial_scale_value = 32,
.scale_decrement_interval = 9,
.scale_increment_interval = 932,
.nfl_bpg_offset = 745,
.slice_bpg_offset = 616,
.final_offset = 4336,
.vbr_enable = false,
.slice_chunk_size = 672,
.dsc_version_minor = 2,
.dsc_version_major = 1,
.native_422 = false,
.native_420 = false,
.second_line_bpg_offset = 0,
.nsl_bpg_offset = 0,
.second_line_offset_adj = 0,
};
/* DSCv1.2a 1008x2244 */
static const struct drm_dsc_config fhd_pps_config = {
.line_buf_depth = 9,
.bits_per_component = 8,
.convert_rgb = true,
.slice_width = 504,
.slice_height = 34,
.slice_count = 2,
.simple_422 = false,
.pic_width = 1008,
.pic_height = 2244,
.rc_tgt_offset_high = 3,
.rc_tgt_offset_low = 3,
.bits_per_pixel = 128,
.rc_edge_factor = 6,
.rc_quant_incr_limit1 = 11,
.rc_quant_incr_limit0 = 11,
.initial_xmit_delay = 512,
.initial_dec_delay = 508,
.block_pred_enable = true,
.first_line_bpg_offset = 12,
.initial_offset = 6144,
.rc_buf_thresh = {
14, 28, 42, 56,
70, 84, 98, 105,
112, 119, 121, 123,
125, 126
},
.rc_range_params = {
{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 2},
{.range_min_qp = 0, .range_max_qp = 4, .range_bpg_offset = 0},
{.range_min_qp = 1, .range_max_qp = 5, .range_bpg_offset = 0},
{.range_min_qp = 1, .range_max_qp = 6, .range_bpg_offset = 62},
{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 60},
{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 58},
{.range_min_qp = 3, .range_max_qp = 7, .range_bpg_offset = 56},
{.range_min_qp = 3, .range_max_qp = 8, .range_bpg_offset = 56},
{.range_min_qp = 3, .range_max_qp = 9, .range_bpg_offset = 56},
{.range_min_qp = 3, .range_max_qp = 10, .range_bpg_offset = 54},
{.range_min_qp = 5, .range_max_qp = 11, .range_bpg_offset = 54},
{.range_min_qp = 5, .range_max_qp = 12, .range_bpg_offset = 52},
{.range_min_qp = 5, .range_max_qp = 13, .range_bpg_offset = 52},
{.range_min_qp = 7, .range_max_qp = 13, .range_bpg_offset = 52},
{.range_min_qp = 13, .range_max_qp = 15, .range_bpg_offset = 52}
},
.rc_model_size = 8192,
.flatness_min_qp = 3,
.flatness_max_qp = 12,
.initial_scale_value = 32,
.scale_decrement_interval = 7,
.scale_increment_interval = 810,
.nfl_bpg_offset = 745,
.slice_bpg_offset = 821,
.final_offset = 4336,
.vbr_enable = false,
.slice_chunk_size = 504,
.dsc_version_minor = 2,
.dsc_version_major = 1,
.native_422 = false,
.native_420 = false,
.second_line_bpg_offset = 0,
.nsl_bpg_offset = 0,
.second_line_offset_adj = 0,
};
#define KM4_WRCTRLD_DIMMING_BIT 0x08
#define KM4_WRCTRLD_BCTRL_BIT 0x20
#define KM4_WRCTRLD_HBM_BIT 0xC0
#define KM4_TE2_CHANGEABLE 0x04
#define KM4_TE2_FIXED_120HZ 0x51
#define KM4_TE2_FIXED_240HZ 0x41
#define KM4_TE2_RISING_EDGE_OFFSET 0x20
#define KM4_TE2_FALLING_EDGE_OFFSET 0x57
#define KM4_TE_USEC_120HZ_HS 273
#define KM4_TE_USEC_60HZ_HS 8500
#define KM4_TE_USEC_60HZ_NS 1588
#define KM4_TE_USEC_VRR_HS 273
#define KM4_TE_USEC_VRR_NS 1588
#define KM4_DBI_REF_DEFAULT 60
#define WIDTH_MM 70
#define HEIGHT_MM 156
#define MIPI_DSI_FREQ_MBPS_DEFAULT 1368
#define MIPI_DSI_FREQ_MBPS_ALTERNATIVE 1288
#define COMP_TEMP_MIN 10
#define COMP_TEMP_MAX 49
#define COMP_TEMP_OFFSET -10
#define PROJECT "KM4"
static const u8 unlock_cmd_f0[] = { 0xF0, 0x5A, 0x5A };
static const u8 lock_cmd_f0[] = { 0xF0, 0xA5, 0xA5 };
static const u8 freq_update[] = { 0xF7, 0x0F };
static const u8 aod_on[] = { MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24 };
static const u8 aod_off[] = { MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20 };
static const u8 pixel_off[] = { 0x22 };
static const struct gs_dsi_cmd km4_lp_night_cmds[] = {
/* AOD Night Mode, 2nit */
GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x00, 0xB0),
};
static const struct gs_dsi_cmd km4_lp_low_cmds[] = {
/* AOD Low Mode, 10nit */
GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x01, 0x6D),
};
static const struct gs_dsi_cmd km4_lp_high_cmds[] = {
/* AOD High Mode, 50nit */
GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x02, 0xF6),
};
static const struct gs_binned_lp km4_binned_lp[] = {
/* night threshold 4 nits */
BINNED_LP_MODE_TIMING("night", 240, km4_lp_night_cmds, KM4_TE2_RISING_EDGE_OFFSET,
KM4_TE2_FALLING_EDGE_OFFSET),
/* low threshold 40 nits */
BINNED_LP_MODE_TIMING("low", 686, km4_lp_low_cmds, KM4_TE2_RISING_EDGE_OFFSET,
KM4_TE2_FALLING_EDGE_OFFSET),
BINNED_LP_MODE_TIMING("high", 3271, km4_lp_high_cmds, KM4_TE2_RISING_EDGE_OFFSET,
KM4_TE2_FALLING_EDGE_OFFSET),
};
static unsigned int km4_get_te_usec(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
{
struct km4_panel *spanel = to_spanel(ctx);
const int vrefresh = drm_mode_vrefresh(&pmode->mode);
if (vrefresh != 60 || gs_is_vrr_mode(pmode)) {
return pmode->gs_mode.te_usec;
} else {
if (spanel->is_mrr_v1) {
return(test_bit(FEAT_OP_NS, ctx->sw_status.feat) ? KM4_TE_USEC_60HZ_NS :
KM4_TE_USEC_60HZ_HS);
} else {
return(test_bit(FEAT_OP_NS, ctx->sw_status.feat) ? KM4_TE_USEC_VRR_NS :
KM4_TE_USEC_VRR_HS);
}
}
}
/* In HS 60Hz mode, TE period is 16.6ms but DDIC vsync period is 8.3ms. */
#define KM4_HS_VSYNC_PERIOD_US 8333
/**
* km4_check_command_timing_for_te2 - control timing between a command and DDIC vsync
* @ctx: gs_panel struct
*
* Control the timing of sending the command in the 2nd DDIC vsync period within two contiguous
* TE to avoid a 120Hz frame in HS 60Hz mode. This function should be called if the command could
* cause a 120Hz frame and mess up the timing, e.g. TE2. The below diagram illustrates the desired
* timing of sending the command, where vsync ~= TE rising (vblank) + TE width (te_usec).
*
* send the command
* /
* TE . v TE
* | . |
* ----------------------------------
* <------ 16.6ms ---- -->
*
* vsync vsync vsync
* | | |
* ----------------------------------
* <-- 8.3ms --> <-- 8.3ms -->
* 1st 2nd
*/
static void km4_check_command_timing_for_te2(struct gs_panel *ctx)
{
struct device *dev = ctx->dev;
struct drm_crtc *crtc = NULL;
const struct gs_panel_mode *pmode = ctx->current_mode;
ktime_t last_te, last_vsync, now;
s64 since_last_vsync_us, temp_us, delay_us;
if (!pmode) {
dev_dbg(dev, "%s: unable to get current mode\n", __func__);
return;
}
/* only HS 60Hz mode and changeable TE2 need the timing control */
if (drm_mode_vrefresh(&pmode->mode) == ctx->op_hz || ctx->te2.option == TEX_OPT_FIXED)
return;
if (ctx->gs_connector->base.state)
crtc = ctx->gs_connector->base.state->crtc;
if (!crtc) {
dev_dbg(dev, "%s: unable to get crtc\n", __func__);
return;
}
drm_crtc_vblank_count_and_time(crtc, &last_te);
if (!last_te) {
dev_dbg(dev, "%s: unable to get last vblank\n", __func__);
return;
}
last_vsync = last_te + km4_get_te_usec(ctx, pmode);
now = ktime_get();
since_last_vsync_us = ktime_us_delta(now, last_vsync);
temp_us = since_last_vsync_us;
/**
* While DPU enters/exits hibernation, we may not get the nearest vblank successfully.
* Divided by TE period (vsync period * 2) then we can get the remaining time (remainder).
*/
temp_us %= (KM4_HS_VSYNC_PERIOD_US * 2);
/**
* Do nothing if it's greater than a vsync time, i.e. sent in the 2nd vsync period.
* The additional 1ms is for the tolerance.
*/
if (temp_us > (KM4_HS_VSYNC_PERIOD_US + 1000))
return;
/* Adding 1ms tolerance to make sure the command will be sent in the 2nd vsync period. */
delay_us = KM4_HS_VSYNC_PERIOD_US - temp_us + 1000;
dev_dbg(dev, "%s: te %lld, vsync %lld, now %lld, since_vsync %lld, delay %lld\n", __func__,
last_te, last_vsync, now, since_last_vsync_us, delay_us);
PANEL_ATRACE_BEGIN(__func__);
usleep_range(delay_us, delay_us + 100);
PANEL_ATRACE_END(__func__);
}
/* Read temperature and apply appropriate gain into DDIC for burn-in compensation if needed */
static void km4_update_disp_therm(struct gs_panel *ctx)
{
/* temperature*1000 in celsius */
int temp, ret;
const int offset = (ctx->panel_rev > PANEL_REV_PROTO1_1) ? COMP_TEMP_OFFSET : 0;
struct device *dev = ctx->dev;
if (!ctx->thermal || IS_ERR_OR_NULL(ctx->thermal->tz))
return;
if (ctx->panel_state != GPANEL_STATE_NORMAL)
return;
ctx->thermal->pending_temp_update = false;
ret = thermal_zone_get_temp(ctx->thermal->tz, &temp);
if (ret) {
dev_err(dev, "%s: fail to read temperature ret:%d\n", __func__, ret);
return;
}
temp = DIV_ROUND_CLOSEST(temp, 1000);
dev_dbg(dev, "%s: temp=%d\n", __func__, temp);
temp = clamp(temp + offset, COMP_TEMP_MIN, COMP_TEMP_MAX);
if (temp == ctx->thermal->hw_temp)
return;
dev_dbg(dev, "%s: apply gain into ddic at %ddeg c (offset=%d)\n", __func__, temp, offset);
PANEL_ATRACE_BEGIN(__func__);
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x03, 0x67);
GS_DCS_BUF_ADD_CMD(dev, 0x67, temp);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
PANEL_ATRACE_END(__func__);
ctx->thermal->hw_temp = temp;
}
static void km4_update_te2_option(struct gs_panel *ctx, u8 val)
{
struct device *dev = ctx->dev;
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xB9);
GS_DCS_BUF_ADD_CMD(dev, 0xB9, val);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
notify_panel_te2_option_changed(ctx);
dev_dbg(dev, "te2 option is updated to %s\n",
(val == KM4_TE2_CHANGEABLE) ? "changeable" :
((val == KM4_TE2_FIXED_240HZ) ? "fixed:240" : "fixed:120"));
}
static void km4_update_te2(struct gs_panel *ctx)
{
struct km4_panel *spanel = to_spanel(ctx);
if (spanel->force_changeable_te2 && ctx->te2.option == TEX_OPT_FIXED) {
dev_dbg(ctx->dev, "force to changeable TE2\n");
ctx->te2.option = TEX_OPT_CHANGEABLE;
km4_update_te2_option(ctx, KM4_TE2_CHANGEABLE);
}
}
static void km4_te2_setting(struct gs_panel *ctx)
{
struct km4_panel *spanel = to_spanel(ctx);
struct device *dev = ctx->dev;
u32 rising = KM4_TE2_RISING_EDGE_OFFSET;
u32 falling = KM4_TE2_FALLING_EDGE_OFFSET;
u8 option;
if (ctx->te2.option == TEX_OPT_FIXED && !spanel->force_changeable_te2)
option = (ctx->te2.rate_hz == 240) ? KM4_TE2_FIXED_240HZ :
KM4_TE2_FIXED_120HZ;
else
option = KM4_TE2_CHANGEABLE;
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
/* TE2 on */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x42, 0xF2);
GS_DCS_BUF_ADD_CMD(dev, 0xF2, 0x0D);
/* changeable or 240/120Hz fixed TE2 */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xB9);
GS_DCS_BUF_ADD_CMD(dev, 0xB9, option);
/* changeable TE2 */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x1E, 0xB9);
GS_DCS_BUF_ADD_CMD(dev, 0xB9, (rising >> 8) & 0xF, rising & 0xFF,
(falling >> 8) & 0xF, falling & 0xFF);
/* 120Hz fixed TE2 */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x22, 0xB9);
GS_DCS_BUF_ADD_CMD(dev, 0xB9, (rising >> 8) & 0xF, rising & 0xFF,
(falling >> 8) & 0xF, falling & 0xFF,
(rising >> 8) & 0xF, rising & 0xFF,
(falling >> 8) & 0xF, falling & 0xFF);
/* 240Hz fixed TE2: set the same width as 120Hz */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x2E, 0xB9);
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x00, 0x21, 0x00, 0x37, 0x05, 0xB9);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
notify_panel_te2_rate_changed(ctx, 0);
notify_panel_te2_option_changed(ctx);
dev_dbg(dev, "TE2 setting: option %s, rising=0x%X falling=0x%X\n",
(option == TEX_OPT_CHANGEABLE) ? "changeable" :
((ctx->te2.rate_hz == 240) ? "fixed:240" : "fixed:120"),
rising, falling);
}
static bool km4_set_te2_rate(struct gs_panel *ctx, u32 rate_hz)
{
struct device *dev = ctx->dev;
if (ctx->te2.rate_hz == rate_hz)
return false;
if (ctx->te2.option == TEX_OPT_FIXED) {
bool lp_mode = ctx->current_mode->gs_mode.is_lp_mode;
if ((!lp_mode && rate_hz != 120 && rate_hz != 240) ||
(lp_mode && rate_hz != 30)) {
dev_warn(dev, "unsupported fixed TE2 rate (%u) in %s mode\n",
rate_hz, lp_mode ? "lp" : "normal");
return false;
}
ctx->te2.rate_hz = rate_hz;
/**
* Fixed TE2 rate will be limited at 30Hz automatically in AOD mode,
* so we don't need to send any commands.
*/
if (!lp_mode)
km4_update_te2_option(ctx, (rate_hz == 240) ? KM4_TE2_FIXED_240HZ :
KM4_TE2_FIXED_120HZ);
} else if (ctx->te2.option == TEX_OPT_CHANGEABLE) {
dev_dbg(dev, "set changeable TE2 rate %uhz\n", rate_hz);
ctx->te2.rate_hz = rate_hz;
} else {
dev_warn(dev, "TE2 option is unsupported (%u)\n", ctx->te2.option);
return false;
}
PANEL_ATRACE_INT("te2_rate", ctx->te2.rate_hz);
return true;
}
static u32 km4_get_te2_rate(struct gs_panel *ctx)
{
return ctx->te2.rate_hz;
}
static bool km4_set_te2_option(struct gs_panel *ctx, u32 option)
{
struct km4_panel *spanel = to_spanel(ctx);
struct device *dev = ctx->dev;
u8 val;
if (option == ctx->te2.option)
return false;
if (option == TEX_OPT_FIXED) {
if (spanel->force_changeable_te2) {
dev_dbg(dev, "force changeable TE2 is set\n");
return false;
}
val = (ctx->te2.rate_hz == 240) ? KM4_TE2_FIXED_240HZ : KM4_TE2_FIXED_120HZ;
} else if (option == TEX_OPT_CHANGEABLE) {
val = KM4_TE2_CHANGEABLE;
} else {
dev_warn(dev, "unsupported TE2 option (%u)\n", option);
return false;
}
km4_update_te2_option(ctx, val);
ctx->te2.option = option;
return true;
}
static enum gs_panel_tex_opt km4_get_te2_option(struct gs_panel *ctx)
{
return ctx->te2.option;
}
static inline bool is_auto_mode_allowed(struct gs_panel *ctx)
{
/* don't want to enable auto mode/early exit during dimming on */
if (ctx->dimming_on)
return false;
if (ctx->idle_data.idle_delay_ms) {
const unsigned int delta_ms = gs_panel_get_idle_time_delta(ctx);
if (delta_ms < ctx->idle_data.idle_delay_ms)
return false;
}
return ctx->idle_data.panel_idle_enabled;
}
static u32 km4_get_idle_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
{
struct km4_panel *spanel = to_spanel(ctx);
const int vrefresh = drm_mode_vrefresh(&pmode->mode);
if (spanel->is_mrr_v1)
return (vrefresh == 60) ? GIDLE_MODE_ON_SELF_REFRESH : GIDLE_MODE_ON_INACTIVITY;
return pmode->idle_mode;
}
static u32 km4_get_min_idle_vrefresh(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
{
const int vrefresh = drm_mode_vrefresh(&pmode->mode);
int min_idle_vrefresh = ctx->min_vrefresh;
if ((min_idle_vrefresh < 0) || !is_auto_mode_allowed(ctx))
return 0;
if (min_idle_vrefresh <= 1)
min_idle_vrefresh = 1;
else if (min_idle_vrefresh <= 10)
min_idle_vrefresh = 10;
else if (min_idle_vrefresh <= 30)
min_idle_vrefresh = 30;
else
return 0;
if (min_idle_vrefresh >= vrefresh) {
dev_dbg(ctx->dev, "min idle vrefresh (%d) higher than target (%d)\n",
min_idle_vrefresh, vrefresh);
return 0;
}
dev_dbg(ctx->dev, "%s: min_idle_vrefresh %d\n", __func__, min_idle_vrefresh);
return min_idle_vrefresh;
}
static void km4_set_panel_feat_manual_mode_fi(struct gs_panel *ctx, bool enabled)
{
struct device *dev = ctx->dev;
u8 val;
val = enabled ? 0x33 : 0x00;
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x10, 0xBD);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, val);
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x82, 0xBD);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, val, val);
if (!enabled) {
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x80, 0xBD);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x16);
}
dev_dbg(ctx->dev, "%s: manual mode fi %s\n", __func__, enabled ? "enabled" : "disabled");
}
static void km4_set_panel_feat_te(struct gs_panel *ctx, unsigned long *feat,
const struct gs_panel_mode *pmode)
{
struct km4_panel *spanel = to_spanel(ctx);
struct device *dev = ctx->dev;
bool is_vrr = gs_is_vrr_mode(pmode);
u32 te_freq = gs_drm_mode_te_freq(&pmode->mode);
__maybe_unused u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
if (test_bit(FEAT_EARLY_EXIT, feat) && !spanel->force_changeable_te) {
if (is_vrr && te_freq == 240) {
/* 240Hz multi TE */
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x61);
/* TE width */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x08, 0xB9);
if (test_bit(FEAT_OP_NS, feat))
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0xC8, 0x00, 0x1F, 0x02, 0xE0,
0x00, 0x1F);
else
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x9B, 0x00, 0x1F, 0x05, 0xAB,
0x00, 0x1F);
} else {
/* Fixed TE */
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x51);
/* TE width */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x08, 0xB9);
if (test_bit(FEAT_OP_NS, feat))
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0A, 0xDC, 0x00, 0x1F, 0x0A, 0xDC,
0x00, 0x1F);
else
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x9A, 0x00, 0x1F, 0x0B, 0x9A,
0x00, 0x1F);
#ifndef PANEL_FACTORY_BUILD
/* TE Freq */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x02, 0xB9);
if (!test_bit(FEAT_OP_NS, feat) && vrefresh == 60)
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x01);
else
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x00);
#endif
}
ctx->hw_status.te.option = TEX_OPT_FIXED;
} else {
/* Changeable TE */
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x04);
/* TE width */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x04, 0xB9);
if (test_bit(FEAT_OP_NS, feat))
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0A, 0xDC, 0x00);
else
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x0B, 0x9A, 0x00, 0x1F);
ctx->hw_status.te.option = TEX_OPT_CHANGEABLE;
}
}
static void km4_set_panel_feat_hbm_irc(struct gs_panel *ctx)
{
struct device *dev = ctx->dev;
struct gs_panel_status *sw_status = &ctx->sw_status;
/*
* "Flat mode" is used to replace IRC on for normal mode and HDR video,
* and "Flat Z mode" is used to replace IRC off for sunlight
* environment.
*/
if (ctx->panel_rev <= PANEL_REV_DVT1) {
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x01, 0x9B, 0x92);
if (unlikely(sw_status->irc_mode == IRC_OFF))
GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x07);
else /* IRC_FLAT_DEFAULT or IRC_FLAT_Z */
GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x27);
}
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x02, 0x00, 0x92);
if (ctx->panel_rev == PANEL_REV_EVT1_1) {
if (sw_status->irc_mode == IRC_FLAT_Z)
GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x78, 0x2D, 0xFF, 0xDC);
else /* IRC_FLAT_DEFAULT or IRC_OFF */
GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x00, 0x00, 0xFF, 0xD0);
} else if (ctx->panel_rev <= PANEL_REV_DVT1) {
if (sw_status->irc_mode == IRC_FLAT_Z)
GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x70, 0x26, 0xFF, 0xDC);
else /* IRC_FLAT_DEFAULT or IRC_OFF */
GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x00, 0x00, 0xFF, 0xD0);
} else {
if (sw_status->irc_mode == IRC_FLAT_Z)
GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x1E, 0xE5, 0xFF, 0xD4);
else /* IRC_FLAT_DEFAULT or IRC_OFF */
GS_DCS_BUF_ADD_CMD(dev, 0x92, 0x00, 0x00, 0xFF, 0xD0);
}
/* SP settings (burn-in compensation) */
if (ctx->panel_rev >= PANEL_REV_DVT1) {
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x02, 0xF3, 0x68);
if (ctx->panel_rev < PANEL_REV_PVT) {
if (sw_status->irc_mode == IRC_FLAT_Z)
GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x77, 0x77, 0x86, 0xE1, 0xE1, 0xF0);
else
GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x11, 0x1A, 0x13, 0x18, 0x21, 0x18);
} else {
if (sw_status->irc_mode == IRC_FLAT_Z)
GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x76, 0x78, 0x86, 0xE0, 0xE1, 0xF0);
else
GS_DCS_BUF_ADD_CMD(dev, 0x68, 0x12, 0x16, 0x0B, 0x22, 0x23, 0x19);
}
}
ctx->hw_status.irc_mode = sw_status->irc_mode;
dev_info(dev, "%s: irc_mode=%d\n", __func__, ctx->hw_status.irc_mode);
}
static void km4_set_panel_feat_early_exit(struct gs_panel *ctx, unsigned long *feat, u32 vrefresh)
{
struct device *dev = ctx->dev;
struct km4_panel *spanel = to_spanel(ctx);
u8 val = (test_bit(FEAT_EARLY_EXIT, feat) && vrefresh != 80) ? 0x01 : 0x81;
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xBD);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, val);
if (spanel->is_mrr_v1) {
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x10, 0xBD);
val = test_bit(FEAT_EARLY_EXIT, feat) ? 0x22 : 0x00;
GS_DCS_BUF_ADD_CMD(dev, 0xBD, val);
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x82, 0xBD);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, val, val, val, val);
}
}
static void km4_set_panel_feat_tsp_sync(struct gs_panel *ctx) {
struct device *dev = ctx->dev;
/* Fixed 240Hz TSP Vsync */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x3C, 0xB9);
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x19, 0x09); /* Sync On */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x05, 0xF2); /* Global para */
GS_DCS_BUF_ADD_CMD(dev, 0xF2, 0xD0); /* 240Hz setting */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x41, 0xB9); /* Global para */
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x02); /* TSP Sync setting */
}
static void km4_set_panel_feat_frequency(struct gs_panel *ctx, unsigned long *feat, u32 vrefresh,
u32 idle_vrefresh, u32 te_freq, bool is_vrr)
{
struct device *dev = ctx->dev;
u8 val;
const bool is_ns_mode = test_bit(FEAT_OP_NS, feat);
/*
* Description: this sequence possibly overrides some configs early-exit
* and operation set, depending on FI mode.
*/
if (test_bit(FEAT_FRAME_AUTO, feat)) {
if (is_ns_mode) {
/* threshold setting */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x0C, 0xBD);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00);
} else {
/* initial frequency */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x92, 0xBD);
if (vrefresh == 60) {
val = 0x02;
} else {
if (vrefresh != 120)
dev_warn(dev, "%s: unsupported init freq %d (hs)\n",
__func__, vrefresh);
/* 120Hz */
val = 0x00;
}
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, val);
}
/* target frequency */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x12, 0xBD);
if (is_ns_mode) {
if (idle_vrefresh == 30) {
val = 0x04;
} else if (idle_vrefresh == 10) {
val = 0x14;
} else {
if (idle_vrefresh != 1)
dev_warn(dev, "%s: unsupported target freq %d (ns)\n",
__func__, idle_vrefresh);
/* 1Hz */
val = 0xEC;
}
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, val);
} else {
if (idle_vrefresh == 30) {
val = 0x06;
} else if (idle_vrefresh == 10) {
val = 0x16;
} else {
if (idle_vrefresh != 1)
dev_warn(dev, "%s: unsupported target freq %d (hs)\n",
__func__, idle_vrefresh);
/* 1Hz */
val = 0xEE;
}
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, val);
}
/* step setting */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x9E, 0xBD);
if (is_ns_mode)
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00);
else
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x02, 0x00, 0x06, 0x00, 0x16);
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xAE, 0xBD);
if (is_ns_mode) {
if (idle_vrefresh == 30) {
/* 60Hz -> 30Hz idle */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, 0x00);
} else if (idle_vrefresh == 10) {
/* 60Hz -> 10Hz idle */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x00, 0x00);
} else {
if (idle_vrefresh != 1)
dev_warn(dev, "%s: unsupported freq step to %d (ns)\n",
__func__, idle_vrefresh);
/* 60Hz -> 1Hz idle */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x03, 0x00);
}
} else {
if (vrefresh == 60) {
if (idle_vrefresh == 30) {
/* 60Hz -> 30Hz idle */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x00, 0x00);
} else if (idle_vrefresh == 10) {
/* 60Hz -> 10Hz idle */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x01, 0x00);
} else {
if (idle_vrefresh != 1)
dev_warn(dev,
"%s: unsupported freq step to %d (hs)\n",
__func__, vrefresh);
/* 60Hz -> 1Hz idle */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x01, 0x01, 0x03);
}
} else {
if (vrefresh != 120)
dev_warn(dev, "%s: unsupported freq step from %d (hs)\n",
__func__, vrefresh);
if (idle_vrefresh == 30) {
/* 120Hz -> 30Hz idle */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x00, 0x00);
} else if (idle_vrefresh == 10) {
/* 120Hz -> 10Hz idle */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x03, 0x00);
} else {
if (idle_vrefresh != 1)
dev_warn(dev,
"%s: unsupported freq step to %d (hs)\n",
__func__, idle_vrefresh);
/* 120Hz -> 1Hz idle */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x01, 0x03);
}
}
}
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0xA3);
} else { /* manual */
if (is_vrr) {
/* set gating TE 240hz or 120hz */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21, (te_freq == 240) ? 0x41 : 0x01);
} else {
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21);
}
if (is_ns_mode) {
if (vrefresh == 1) {
val = 0x1F;
} else if (vrefresh == 10) {
val = 0x1B;
} else if (vrefresh == 30) {
val = 0x19;
} else {
if (vrefresh != 60)
dev_warn(dev, "%s: unsupported manual freq %d (ns)\n",
__func__, vrefresh);
/* 60Hz */
val = 0x18;
}
} else {
if (vrefresh == 1) {
val = 0x07;
} else if (vrefresh == 10) {
val = 0x03;
} else if (vrefresh == 30) {
val = 0x02;
} else if (vrefresh == 60) {
val = 0x01;
} else if (vrefresh == 80) {
val = 0x04;
} else {
if (vrefresh != 120)
dev_warn(dev, "%s: unsupported manual freq %d (hs)\n",
__func__, vrefresh);
/* 120Hz */
val = 0x00;
}
}
GS_DCS_BUF_ADD_CMD(dev, 0x60, val);
}
GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
}
static void km4_set_panel_feat_dbi(struct gs_panel *ctx, u8 value)
{
struct device *dev = ctx->dev;
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x67, 0x69);
GS_DCS_BUF_ADD_CMD(dev, 0x69, value);
dev_dbg(dev, "%s: DBI: setting %#02X\n", __func__, value);
}
static u8 km4_calc_dbi_ref(struct gs_panel *ctx)
{
struct km4_panel *spanel = to_spanel(ctx);
u16 frame_rate = spanel->frame_rate;
u8 value = 60 / frame_rate;
if (frame_rate <= 60)
return value;
if (spanel->dbi_frame_count++ % (frame_rate == 120 ? 2 : 4))
return 1;
else
return 0;
}
static void km4_set_dbi_ref(struct gs_panel *ctx, u8 value)
{
struct device *dev = ctx->dev;
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
km4_set_panel_feat_dbi(ctx, value);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
}
static void km4_set_frame_rate(struct gs_panel *ctx, u16 frame_rate)
{
struct device *dev = ctx->dev;
struct km4_panel *spanel = to_spanel(ctx);
if (frame_rate == spanel->frame_rate ||
test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat) ||
ctx->sw_status.idle_vrefresh == drm_mode_vrefresh(&ctx->current_mode->mode))
return;
dev_dbg(dev, "%s: DBI: updating for %u fps\n", __func__, frame_rate);
spanel->frame_rate = frame_rate;
km4_set_dbi_ref(ctx, km4_calc_dbi_ref(ctx));
}
/**
* km4_set_panel_feat - configure panel features
* @ctx: gs_panel struct
* @pmode: gs_panel_mode struct, target panel mode
* @idle_vrefresh: target vrefresh rate in auto mode, 0 if disabling auto mode
* @enforce: force to write all of registers even if no feature state changes
*
* Configure panel features based on the context.
*/
static void km4_set_panel_feat(struct gs_panel *ctx, const struct gs_panel_mode *pmode,
bool enforce)
{
struct device *dev = ctx->dev;
struct km4_panel *spanel = to_spanel(ctx);
struct gs_panel_status *sw_status = &ctx->sw_status;
struct gs_panel_status *hw_status = &ctx->hw_status;
unsigned long *feat = sw_status->feat;
u32 idle_vrefresh = sw_status->idle_vrefresh;
u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
u32 te_freq = gs_drm_mode_te_freq(&pmode->mode);
bool is_vrr = !spanel->is_mrr_v1 && gs_is_vrr_mode(pmode);
bool irc_mode_changed;
u8 val;
DECLARE_BITMAP(changed_feat, FEAT_MAX);
/* override settings if mrr v2 or vrr */
if (!spanel->is_mrr_v1) {
if (!test_bit(FEAT_FRAME_AUTO, feat)) {
vrefresh = idle_vrefresh ?: 1;
idle_vrefresh = 0;
}
set_bit(FEAT_EARLY_EXIT, feat);
if (is_vrr) {
if (pmode->mode.flags & DRM_MODE_FLAG_NS)
set_bit(FEAT_OP_NS, feat);
else
clear_bit(FEAT_OP_NS, feat);
}
}
/* Create bitmap of changed feature values to modify */
if (enforce) {
bitmap_fill(changed_feat, FEAT_MAX);
irc_mode_changed = true;
} else {
bitmap_xor(changed_feat, feat, hw_status->feat, FEAT_MAX);
irc_mode_changed = (sw_status->irc_mode != hw_status->irc_mode);
if (bitmap_empty(changed_feat, FEAT_MAX) && vrefresh == hw_status->vrefresh &&
idle_vrefresh == hw_status->idle_vrefresh &&
te_freq == hw_status->te.rate_hz &&
!irc_mode_changed) {
dev_dbg(dev, "%s: no changes, skip update\n", __func__);
return;
}
}
dev_dbg(dev, "hbm=%u irc=%u ns=%u vrr=%u fi=%u@a,%u@m ee=%u rr=%u-%u:%u\n",
test_bit(FEAT_HBM, feat), sw_status->irc_mode, test_bit(FEAT_OP_NS, feat),
is_vrr, test_bit(FEAT_FRAME_AUTO, feat), test_bit(FEAT_FRAME_MANUAL_FI, feat),
test_bit(FEAT_EARLY_EXIT, feat), idle_vrefresh ?: vrefresh,
drm_mode_vrefresh(&pmode->mode), te_freq);
PANEL_ATRACE_BEGIN(__func__);
/* Unlock */
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
/* TE setting */
sw_status->te.rate_hz = te_freq;
if (test_bit(FEAT_EARLY_EXIT, changed_feat) || test_bit(FEAT_OP_NS, changed_feat) ||
hw_status->te.rate_hz != te_freq)
km4_set_panel_feat_te(ctx, feat, pmode);
/*
* HBM IRC setting
*/
if (irc_mode_changed)
km4_set_panel_feat_hbm_irc(ctx);
/*
* Operating Mode: NS or HS
*
* Description: the configs could possibly be overrided by frequency setting,
* depending on FI mode.
*/
if (test_bit(FEAT_OP_NS, changed_feat)) {
/* mode set */
GS_DCS_BUF_ADD_CMD(dev, 0xF2, 0x01);
val = test_bit(FEAT_OP_NS, feat) ? 0x18 : 0x00;
GS_DCS_BUF_ADD_CMD(dev, 0x60, val);
}
/*
* Early-exit: enable or disable
*/
km4_set_panel_feat_early_exit(ctx, feat, vrefresh);
/*
* Manual FI: enable or disable manual mode FI
*/
if (test_bit(FEAT_FRAME_MANUAL_FI, changed_feat))
km4_set_panel_feat_manual_mode_fi(ctx, test_bit(FEAT_FRAME_MANUAL_FI, feat));
/* TSP Sync setting */
if (enforce)
km4_set_panel_feat_tsp_sync(ctx);
/* Reset DBI Reference frequency */
if (test_bit(FEAT_FRAME_AUTO, feat) || vrefresh == 120)
km4_set_panel_feat_dbi(ctx, KM4_DBI_REF_DEFAULT);
/*
* Frequency setting: FI, frequency, idle frequency
*/
km4_set_panel_feat_frequency(ctx, feat, vrefresh, idle_vrefresh, te_freq, is_vrr);
/* Lock */
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
PANEL_ATRACE_END(__func__);
hw_status->vrefresh = vrefresh;
hw_status->idle_vrefresh = idle_vrefresh;
hw_status->te.rate_hz = te_freq;
bitmap_copy(hw_status->feat, feat, FEAT_MAX);
}
/**
* km4_update_panel_feat - configure panel features with current refresh rate
* @ctx: gs_panel struct
* @enforce: force to write all of registers even if no feature state changes
*
* Configure panel features based on the context without changing current refresh rate
* and idle setting.
*/
static void km4_update_panel_feat(struct gs_panel *ctx, bool enforce)
{
const struct gs_panel_mode *pmode = ctx->current_mode;
km4_set_panel_feat(ctx, pmode, enforce);
}
static void km4_update_refresh_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode,
const u32 idle_vrefresh)
{
struct km4_panel *spanel = to_spanel(ctx);
struct gs_panel_status *sw_status = &ctx->sw_status;
/* TODO: b/308978878 - move refresh control logic to HWC */
/*
* Skip idle update if going through RRS without refresh rate change. If
* we're switching resolution and refresh rate in the same atomic commit
* (MODE_RES_AND_RR_IN_PROGRESS), we shouldn't skip the update to
* ensure the refresh rate will be set correctly to avoid problems.
*/
if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS) {
dev_dbg(ctx->dev, "%s: RRS in progress without RR change, skip\n", __func__);
notify_panel_mode_changed(ctx);
return;
}
dev_dbg(ctx->dev, "%s: mode: %s set idle_vrefresh: %u\n", __func__, pmode->mode.name,
idle_vrefresh);
if (spanel->is_mrr_v1) {
u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
if (idle_vrefresh)
set_bit(FEAT_FRAME_AUTO, sw_status->feat);
else
clear_bit(FEAT_FRAME_AUTO, sw_status->feat);
if (vrefresh == 120 || idle_vrefresh)
set_bit(FEAT_EARLY_EXIT, sw_status->feat);
else
clear_bit(FEAT_EARLY_EXIT, sw_status->feat);
}
sw_status->idle_vrefresh = idle_vrefresh;
/*
* Note: when mode is explicitly set, panel performs early exit to get out
* of idle at next vsync, and will not back to idle until not seeing new
* frame traffic for a while. If idle_vrefresh != 0, try best to guess what
* panel_idle_vrefresh will be soon, and km4_update_idle_state() in
* new frame commit will correct it if the guess is wrong.
*/
ctx->idle_data.panel_idle_vrefresh = idle_vrefresh;
km4_set_panel_feat(ctx, pmode, false);
notify_panel_mode_changed(ctx);
dev_dbg(ctx->dev, "%s: display state is notified\n", __func__);
}
static void km4_change_frequency(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
{
struct km4_panel *spanel = to_spanel(ctx);
u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
u32 idle_vrefresh = 0;
if (vrefresh > ctx->op_hz) {
/* resolution may have been changed without refresh rate change */
if (ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS)
notify_panel_mode_changed(ctx);
dev_err(ctx->dev, "invalid freq setting: op_hz=%u, vrefresh=%u\n", ctx->op_hz,
vrefresh);
return;
}
if (km4_get_idle_mode(ctx, pmode) == GIDLE_MODE_ON_INACTIVITY)
idle_vrefresh = km4_get_min_idle_vrefresh(ctx, pmode);
/**
* While TE2 is changeable, the idle_vrefresh should be set. We should use the
* idle_vrefresh instead of 0 for the proximity sensor.
*/
if ((!spanel->is_mrr_v1 && test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat)) ||
(ctx->te2.option == TEX_OPT_CHANGEABLE && !idle_vrefresh))
idle_vrefresh = ctx->sw_status.idle_vrefresh;
km4_update_refresh_mode(ctx, pmode, idle_vrefresh);
ctx->sw_status.te.rate_hz = gs_drm_mode_te_freq(&pmode->mode);
dev_dbg(ctx->dev, "change to %u hz\n", vrefresh);
}
static void km4_panel_idle_notification(struct gs_panel *ctx, u32 display_id, u32 vrefresh,
u32 idle_te_vrefresh)
{
char event_string[64];
char *envp[] = { event_string, NULL };
struct drm_device *dev = ctx->bridge.dev;
if (!dev) {
dev_warn(ctx->dev, "%s: drm_device is null\n", __func__);
} else {
scnprintf(event_string, sizeof(event_string), "PANEL_IDLE_ENTER=%u,%u,%u",
display_id, vrefresh, idle_te_vrefresh);
kobject_uevent_env(&dev->primary->kdev->kobj, KOBJ_CHANGE, envp);
}
}
static void km4_wait_one_vblank(struct gs_panel *ctx)
{
struct drm_crtc *crtc = NULL;
if (ctx->gs_connector->base.state)
crtc = ctx->gs_connector->base.state->crtc;
PANEL_ATRACE_BEGIN(__func__);
if (crtc) {
int ret = drm_crtc_vblank_get(crtc);
if (!ret) {
drm_crtc_wait_one_vblank(crtc);
drm_crtc_vblank_put(crtc);
} else {
usleep_range(8350, 8500);
}
} else {
usleep_range(8350, 8500);
}
PANEL_ATRACE_END(__func__);
}
static bool km4_set_self_refresh(struct gs_panel *ctx, bool enable)
{
const struct gs_panel_mode *pmode = ctx->current_mode;
struct km4_panel *spanel = to_spanel(ctx);
u32 idle_vrefresh;
if (ctx->thermal && ctx->thermal->pending_temp_update && enable)
km4_update_disp_therm(ctx);
PANEL_ATRACE_INT(__func__, enable);
if (!spanel->is_mrr_v1) {
u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
idle_vrefresh = ctx->sw_status.idle_vrefresh;
/* notify the changes of TE2 rate in case DPU enters/exits hibernation */
if (ctx->te2.option == TEX_OPT_CHANGEABLE && vrefresh != idle_vrefresh &&
test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat) &&
km4_set_te2_rate(ctx, enable ? idle_vrefresh : vrefresh))
notify_panel_te2_rate_changed(ctx, 0);
return false;
}
dev_dbg(ctx->dev, "%s: %d\n", __func__, enable);
if (unlikely(!pmode))
return false;
/* self refresh is not supported in lp mode since that always makes use of early exit */
if (pmode->gs_mode.is_lp_mode) {
/* set 1Hz while self refresh is active, otherwise clear it */
ctx->idle_data.panel_idle_vrefresh = enable ? 1 : 0;
notify_panel_mode_changed(ctx);
return false;
}
idle_vrefresh = km4_get_min_idle_vrefresh(ctx, pmode);
if (km4_get_idle_mode(ctx, pmode) != GIDLE_MODE_ON_SELF_REFRESH) {
/*
* if idle mode is on inactivity, may need to update the target fps for auto mode,
* or switch to manual mode if idle should be disabled (idle_vrefresh=0)
*/
if ((km4_get_idle_mode(ctx, pmode) == GIDLE_MODE_ON_INACTIVITY) &&
(ctx->sw_status.idle_vrefresh != idle_vrefresh)) {
km4_update_refresh_mode(ctx, pmode, idle_vrefresh);
return true;
}
return false;
}
if (!enable)
idle_vrefresh = 0;
/* if there's no change in idle state then skip cmds */
if (ctx->idle_data.panel_idle_vrefresh == idle_vrefresh)
return false;
PANEL_ATRACE_BEGIN(__func__);
km4_update_refresh_mode(ctx, pmode, idle_vrefresh);
if (idle_vrefresh) {
const int vrefresh = drm_mode_vrefresh(&pmode->mode);
km4_panel_idle_notification(ctx, 0, vrefresh, 120);
} else if (ctx->idle_data.panel_need_handle_idle_exit) {
/*
* after exit idle mode with fixed TE at non-120hz, TE may still keep at 120hz.
* If any layer that already be assigned to DPU that can't be handled at 120hz,
* panel_need_handle_idle_exit will be set then we need to wait one vblank to
* avoid underrun issue.
*/
dev_dbg(ctx->dev, "wait one vblank after exit idle\n");
km4_wait_one_vblank(ctx);
}
PANEL_ATRACE_END(__func__);
return true;
}
static void km4_set_panel_lp_feat(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
{
struct device *dev = ctx->dev;
struct km4_panel *spanel = to_spanel(ctx);
unsigned long *feat = ctx->sw_status.feat;
struct gs_panel_status *sw_status = &ctx->sw_status;
u32 idle_vrefresh = sw_status->idle_vrefresh;
bool is_auto = (test_bit(FEAT_FRAME_AUTO, feat) || spanel->is_mrr_v1) ? true : false;
if (!pmode->gs_mode.is_lp_mode)
return;
dev_dbg(dev, "%s: auto=%u rr=%u-%u\n",
__func__, is_auto, idle_vrefresh, drm_mode_vrefresh(&pmode->mode));
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
if (is_auto) {
/* Default is 1 Hz */
u8 val = 0x74;
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x18, 0xBD);
if (idle_vrefresh == 10)
val = 0x08;
else if (idle_vrefresh != 1)
dev_warn(dev, "%s: unsupported idle vrefresh %u\n",
__func__, idle_vrefresh);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x04, 0x00, val);
/* Step settings */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xB8, 0xBD);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00, 0x08);
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xC8, 0xBD);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x03);
/* Auto mode */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0xA7);
} else {
/* Manual mode */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21);
/* 30 Hz */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0x60);
GS_DCS_BUF_ADD_CMD(dev, 0x60, 0x00);
}
GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
}
#ifndef PANEL_FACTORY_BUILD
static void km4_update_refresh_ctrl_feat(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
{
const u32 ctrl = ctx->refresh_ctrl;
struct km4_panel *spanel = to_spanel(ctx);
unsigned long *feat = ctx->sw_status.feat;
u32 min_vrefresh = ctx->sw_status.idle_vrefresh;
bool mrr_changed = false;
bool idle_vrefresh_changed = false;
bool feat_frame_auto_changed = false;
bool prev_feat_frame_auto_enabled = test_bit(FEAT_FRAME_AUTO, feat);
u32 vrefresh;
bool lp_mode;
if (!pmode)
return;
dev_dbg(ctx->dev, "%s: ctrl=0x%X\n", __func__, ctrl);
vrefresh = drm_mode_vrefresh(&pmode->mode);
lp_mode = pmode->gs_mode.is_lp_mode;
if (ctrl & GS_PANEL_REFRESH_CTRL_MIN_REFRESH_RATE_MASK) {
min_vrefresh = (ctrl & GS_PANEL_REFRESH_CTRL_MIN_REFRESH_RATE_MASK) >>
GS_PANEL_REFRESH_CTRL_MIN_REFRESH_RATE_OFFSET;
if (min_vrefresh > vrefresh) {
dev_warn(ctx->dev, "%s: min RR %uHz requested, but valid range is 1-%uHz\n",
__func__, min_vrefresh, vrefresh);
min_vrefresh = vrefresh;
}
ctx->sw_status.idle_vrefresh = min_vrefresh;
idle_vrefresh_changed = true;
}
if (ctrl & GS_PANEL_REFRESH_CTRL_FI_AUTO) {
if (min_vrefresh == vrefresh) {
clear_bit(FEAT_FRAME_AUTO, feat);
clear_bit(FEAT_FRAME_MANUAL_FI, feat);
} else if ((min_vrefresh > 1) || lp_mode) {
set_bit(FEAT_FRAME_AUTO, feat);
clear_bit(FEAT_FRAME_MANUAL_FI, feat);
} else {
set_bit(FEAT_FRAME_MANUAL_FI, feat);
clear_bit(FEAT_FRAME_AUTO, feat);
}
} else {
clear_bit(FEAT_FRAME_AUTO, feat);
clear_bit(FEAT_FRAME_MANUAL_FI, feat);
}
if (lp_mode) {
km4_set_panel_lp_feat(ctx, pmode);
return;
}
/**
* TODO(b/344478264): avoid setting idle 60Hz in the composer.
*
* 60Hz idle_vrefresh is not supported in non-60Hz mode. Default to 1Hz to save power.
* Otherwise, set to 30Hz to respect proximity sensor's working frequency.
*/
if (test_bit(FEAT_FRAME_AUTO, feat) && ctx->sw_status.idle_vrefresh == 60 &&
vrefresh != 60) {
dev_warn(ctx->dev, "%s: idle_vrefresh 60Hz is not supported in %uHz mode\n",
__func__, vrefresh);
ctx->sw_status.idle_vrefresh = (ctx->te2.option == TEX_OPT_CHANGEABLE) ? 30 : 1;
}
if (prev_feat_frame_auto_enabled != test_bit(FEAT_FRAME_AUTO, feat))
feat_frame_auto_changed = true;
if (ctrl & GS_PANEL_REFRESH_CTRL_MRR_V1_OVER_V2) {
if (gs_is_vrr_mode(pmode)) {
dev_err(ctx->dev, "%s: using vrr display mode for mrr\n", __func__);
} else if (!spanel->is_mrr_v1) {
mrr_changed = true;
spanel->is_mrr_v1 = true;
ctx->gs_connector->ignore_op_rate = true;
}
} else if (spanel->is_mrr_v1) {
mrr_changed = true;
spanel->is_mrr_v1 = false;
ctx->gs_connector->ignore_op_rate = false;
}
PANEL_ATRACE_INT("idle_vrefresh", ctx->sw_status.idle_vrefresh);
PANEL_ATRACE_INT("FEAT_FRAME_AUTO", test_bit(FEAT_FRAME_AUTO, feat));
/**
* While DPU is not in hibernation, it may keep transferring frames and TE2 will remain
* at peak refresh rate, e.g. 60Hz or 120Hz. Set and notify peak refresh rate even though
* idle_vrefresh is at a lower rate (e.g. 30Hz) to avoid misalignment between the display
* and ALSP.
*/
if (ctx->te2.option == TEX_OPT_CHANGEABLE && vrefresh != ctx->sw_status.idle_vrefresh &&
test_bit(FEAT_FRAME_AUTO, feat) && !ctx->idle_data.self_refresh_active &&
km4_set_te2_rate(ctx, vrefresh))
notify_panel_te2_rate_changed(ctx, 0);
/**
* The changes of idle vrefresh and frame auto could trigger a 120Hz frame.
* Check whether we need to adjust the timing of sending the commands in these
* conditions.
*/
if (idle_vrefresh_changed && feat_frame_auto_changed &&
!test_bit(FEAT_FRAME_MANUAL_FI, feat))
km4_check_command_timing_for_te2(ctx);
if (mrr_changed)
km4_change_frequency(ctx, pmode);
else
km4_set_panel_feat(ctx, pmode, false);
}
static void km4_refresh_ctrl(struct gs_panel *ctx)
{
struct device *dev = ctx->dev;
const u32 ctrl = ctx->refresh_ctrl;
PANEL_ATRACE_BEGIN(__func__);
km4_update_refresh_ctrl_feat(ctx, ctx->current_mode);
if (ctrl & GS_PANEL_REFRESH_CTRL_FI_FRAME_COUNT_MASK){
/* TODO(b/323251635): parse frame count for inserting multiple frames */
dev_dbg(dev, "%s: manually inserting frame\n", __func__);
PANEL_ATRACE_BEGIN("insert_frame");
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
GS_DCS_BUF_ADD_CMD(dev, 0xF7, 0x02);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
PANEL_ATRACE_END("insert_frame");
}
PANEL_ATRACE_END(__func__);
}
#endif
static int km4_atomic_check(struct gs_panel *ctx, struct drm_atomic_state *state)
{
struct drm_connector *conn = &ctx->gs_connector->base;
struct drm_connector_state *new_conn_state =
drm_atomic_get_new_connector_state(state, conn);
struct drm_crtc_state *old_crtc_state, *new_crtc_state;
if (!ctx->current_mode || drm_mode_vrefresh(&ctx->current_mode->mode) == 120 ||
!new_conn_state || !new_conn_state->crtc)
return 0;
new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
old_crtc_state = drm_atomic_get_old_crtc_state(state, new_conn_state->crtc);
if (!old_crtc_state || !new_crtc_state || !new_crtc_state->active)
return 0;
if ((ctx->sw_status.idle_vrefresh && old_crtc_state->self_refresh_active) ||
!drm_atomic_crtc_effectively_active(old_crtc_state)) {
struct drm_display_mode *mode = &new_crtc_state->adjusted_mode;
/* set clock to max refresh rate on self refresh exit or resume due to early exit */
mode->clock = mode->htotal * mode->vtotal * 120 / 1000;
if (mode->clock != new_crtc_state->mode.clock) {
new_crtc_state->mode_changed = true;
dev_dbg(ctx->dev, "raise mode (%s) clock to 120hz on %s\n", mode->name,
old_crtc_state->self_refresh_active ? "self refresh exit" :
"resume");
}
} else if (old_crtc_state->active_changed &&
(old_crtc_state->adjusted_mode.clock != old_crtc_state->mode.clock)) {
/* clock hacked in last commit due to self refresh exit or resume, undo that */
new_crtc_state->mode_changed = true;
new_crtc_state->adjusted_mode.clock = new_crtc_state->mode.clock;
dev_dbg(ctx->dev, "restore mode (%s) clock after self refresh exit or resume\n",
new_crtc_state->mode.name);
}
return 0;
}
static void km4_write_display_mode(struct gs_panel *ctx, const struct drm_display_mode *mode)
{
struct device *dev = ctx->dev;
u8 val = KM4_WRCTRLD_BCTRL_BIT;
PANEL_ATRACE_BEGIN(__func__);
if (GS_IS_HBM_ON(ctx->hbm_mode))
val |= KM4_WRCTRLD_HBM_BIT;
if (ctx->dimming_on)
val |= KM4_WRCTRLD_DIMMING_BIT;
dev_dbg(dev, "%s(wrctrld:0x%x, hbm: %s, dimming: %s)\n", __func__, val,
GS_IS_HBM_ON(ctx->hbm_mode) ? "on" : "off", ctx->dimming_on ? "on" : "off");
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY, val);
PANEL_ATRACE_END(__func__);
}
#define KM4_OPR_VAL_LEN 2
#define KM4_MAX_OPR_VAL 0x3FF
/* Get OPR (on pixel ratio), the unit is percent */
static int km4_get_opr(struct gs_panel *ctx, u8 *opr)
{
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
struct device *dev = ctx->dev;
u8 buf[KM4_OPR_VAL_LEN] = { 0 };
u16 val;
int ret;
/*TODO(tknelms)DPU_ATRACE_BEGIN(__func__);*/
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xB0, 0x00, 0xE7, 0x91);
ret = mipi_dsi_dcs_read(dsi, 0x91, buf, KM4_OPR_VAL_LEN);
GS_DCS_WRITE_CMDLIST(dev, lock_cmd_f0);
/*TODO(tknelms)DPU_ATRACE_END(__func__);*/
if (ret != KM4_OPR_VAL_LEN) {
dev_warn(dev, "Failed to read OPR (%d)\n", ret);
return ret;
}
val = (buf[0] << 8) | buf[1];
*opr = DIV_ROUND_CLOSEST(val * 100, KM4_MAX_OPR_VAL);
return 0;
}
static void km4_disable_acl_mode(struct gs_panel *ctx)
{
struct device *dev = ctx->dev;
struct gs_panel_status *hw_status = &ctx->hw_status;
if (hw_status->acl_mode != ACL_OFF) {
GS_DCS_WRITE_CMD(dev, 0x55, 0x00);
hw_status->acl_mode = ACL_OFF;
dev_info(dev, "%s : set acl_mode off\n", __func__);
}
}
static u8 get_acl_mode_setting(enum gs_acl_mode acl_mode)
{
/*
* KM4 ACL mode and setting:
*
* DVT1 and Later
* NORMAL - 10% (0x01)
* ENHANCED - 15% (0x02)
*/
switch (acl_mode) {
case ACL_OFF:
return 0x00;
case ACL_NORMAL:
return 0x01;
case ACL_ENHANCED:
return 0x02;
}
}
#define KM4_ZA_THRESHOLD_OPR 85
#define KM4_ACL_ENHANCED_THRESHOLD_DBV 3726
/* Manage the ACL settings to DDIC that consider the dbv and opr value */
static void km4_acl_modes_manager(struct gs_panel *ctx)
{
struct device *dev = ctx->dev;
struct gs_panel_status *sw_status = &ctx->sw_status;
struct gs_panel_status *hw_status = &ctx->hw_status;
// Check if ACL can be enabled based on conditions
bool can_enable_acl = hw_status->dbv >= KM4_ACL_ENHANCED_THRESHOLD_DBV;
u8 opr;
u8 target_acl_state;
bool update_acl_settings;
if (!can_enable_acl) {
km4_disable_acl_mode(ctx);
return;
}
// Check if ACL settings can be wrote based on conditions
if (!km4_get_opr(ctx, &opr)) {
update_acl_settings = (opr > KM4_ZA_THRESHOLD_OPR);
} else {
dev_warn(ctx->dev, "Unable to update acl mode\n");
return;
}
if (update_acl_settings) {
if (sw_status->acl_mode == hw_status->acl_mode) {
dev_dbg(dev, "%s : skip to update acl_mode\n", __func__);
return;
}
target_acl_state = get_acl_mode_setting(sw_status->acl_mode);
GS_DCS_WRITE_CMD(dev, 0x55, target_acl_state);
hw_status->acl_mode = sw_status->acl_mode;
dev_info(dev, "%s: set acl : %d, opr : %hhu\n", __func__, target_acl_state, opr);
} else {
km4_disable_acl_mode(ctx);
}
}
/* updated za when acl mode changed */
static void km4_set_acl_mode(struct gs_panel *ctx, enum gs_acl_mode mode)
{
bool can_enable_acl = ctx->hw_status.dbv >= KM4_ACL_ENHANCED_THRESHOLD_DBV;
ctx->sw_status.acl_mode = mode;
if (can_enable_acl) {
if (ctx->sw_status.acl_mode != ctx->hw_status.acl_mode)
km4_acl_modes_manager(ctx);
} else
km4_disable_acl_mode(ctx);
}
static int km4_set_brightness(struct gs_panel *ctx, u16 br)
{
int ret;
u16 brightness;
struct km4_panel *spanel = to_spanel(ctx);
struct device *dev = ctx->dev;
if (ctx->current_mode->gs_mode.is_lp_mode) {
const struct gs_panel_funcs *funcs;
/* don't stay at pixel-off state in AOD, or black screen is possibly seen */
if (spanel->is_pixel_off) {
GS_DCS_WRITE_CMD(dev, MIPI_DCS_ENTER_NORMAL_MODE);
spanel->is_pixel_off = false;
}
funcs = ctx->desc->gs_panel_func;
if (funcs && funcs->set_binned_lp)
funcs->set_binned_lp(ctx, br);
return 0;
}
/* Use pixel off command instead of setting DBV 0 */
if (!br) {
if (!spanel->is_pixel_off) {
GS_DCS_WRITE_CMDLIST(dev, pixel_off);
spanel->is_pixel_off = true;
dev_dbg(dev, "%s: pixel off instead of dbv 0\n", __func__);
}
return 0;
} else if (br && spanel->is_pixel_off) {
GS_DCS_WRITE_CMD(dev, MIPI_DCS_ENTER_NORMAL_MODE);
spanel->is_pixel_off = false;
}
brightness = (br & 0xff) << 8 | br >> 8;
km4_check_command_timing_for_te2(ctx);
ret = gs_dcs_set_brightness(ctx, brightness);
if (!ret) {
ctx->hw_status.dbv = br;
km4_set_acl_mode(ctx, ctx->sw_status.acl_mode);
}
return ret;
}
static void km4_wait_for_vsync_done(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
{
PANEL_ATRACE_BEGIN(__func__);
gs_panel_wait_for_vsync_done(ctx, km4_get_te_usec(ctx, pmode),
GS_VREFRESH_TO_PERIOD_USEC(ctx->hw_status.vrefresh));
PANEL_ATRACE_END(__func__);
}
static void km4_enforce_manual_and_peak(struct gs_panel *ctx)
{
struct device *dev = ctx->dev;
if (!ctx->current_mode)
return;
dev_dbg(dev, "%s\n", __func__);
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
/* manual mode */
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x21);
/* peak refresh rate */
if (ctx->current_mode->gs_mode.is_lp_mode) {
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0x60);
GS_DCS_BUF_ADD_CMD(dev, 0x60, 0x00);
} else {
GS_DCS_BUF_ADD_CMD(dev, 0x60,
!test_bit(FEAT_OP_NS, ctx->sw_status.feat) ? 0x00 : 0x18);
}
GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
}
static void km4_set_lp_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
{
struct device *dev = ctx->dev;
const u16 brightness = gs_panel_get_brightness(ctx);
dev_dbg(dev, "%s\n", __func__);
PANEL_ATRACE_BEGIN(__func__);
/* enforce manual and peak to have a smooth transition */
km4_enforce_manual_and_peak(ctx);
km4_wait_for_vsync_done(ctx, ctx->current_mode);
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
GS_DCS_BUF_ADD_CMDLIST(dev, aod_on);
/* Fixed TE: sync on */
GS_DCS_BUF_ADD_CMD(dev, 0xB9, 0x51);
/* Enable early exit */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0xE8, 0xBD);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x10, 0xBD);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x22);
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x82, 0xBD);
GS_DCS_BUF_ADD_CMD(dev, 0xBD, 0x22, 0x22, 0x22, 0x22);
GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
#ifndef PANEL_FACTORY_BUILD
km4_update_refresh_ctrl_feat(ctx, pmode);
#else
km4_set_panel_lp_feat(ctx, pmode);
#endif
km4_set_dbi_ref(ctx, KM4_DBI_REF_DEFAULT);
gs_panel_set_binned_lp_helper(ctx, brightness);
ctx->hw_status.vrefresh = 30;
ctx->hw_status.te.rate_hz = 30;
ctx->sw_status.te.rate_hz = 30;
ctx->sw_status.te.option = TEX_OPT_FIXED;
PANEL_ATRACE_END(__func__);
dev_info(dev, "enter %dhz LP mode\n", drm_mode_vrefresh(&pmode->mode));
}
static void km4_set_nolp_mode(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
{
struct device *dev = ctx->dev;
dev_dbg(dev, "%s\n", __func__);
PANEL_ATRACE_BEGIN(__func__);
km4_wait_for_vsync_done(ctx, ctx->current_mode);
/* manual mode 30Hz */
km4_enforce_manual_and_peak(ctx);
/* b/346215549
* ensure AOD off command is sent a bit later than vsync done
*/
km4_wait_for_vsync_done(ctx, ctx->current_mode);
usleep_range(5000, 5000 + 100);
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
GS_DCS_BUF_ADD_CMDLIST(dev, aod_off);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
km4_wait_for_vsync_done(ctx, ctx->current_mode);
#ifndef PANEL_FACTORY_BUILD
km4_update_refresh_ctrl_feat(ctx, pmode);
#endif
km4_set_panel_feat(ctx, pmode, true);
/* backlight control and dimming */
km4_write_display_mode(ctx, &pmode->mode);
km4_change_frequency(ctx, pmode);
PANEL_ATRACE_END(__func__);
dev_info(dev, "exit LP mode\n");
}
static const struct gs_dsi_cmd km4_init_cmds[] = {
/* Enable TE*/
GS_DSI_CMD(MIPI_DCS_SET_TEAR_ON),
/* CASET: 1343 */
GS_DSI_CMD(MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x05, 0x3F),
/* PASET: 2991 */
GS_DSI_CMD(MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x0B, 0xAF),
GS_DSI_CMDLIST(unlock_cmd_f0),
/* FFC: off, 165MHz, MIPI Speed 1368 Mbps */
GS_DSI_CMD(0xB0, 0x00, 0x36, 0xC5),
GS_DSI_CMD(0xC5, 0x10, 0x10, 0x50, 0x05, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00,
0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40,
0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40, 0x00),
/* enable OPEC (auto still IMG detect off) */
GS_DSI_CMD(0xB0, 0x00, 0x1D, 0x63),
GS_DSI_CMD(0x63, 0x02, 0x18),
/* PMIC Fast Discharge off */
GS_DSI_CMD(0xB0, 0x00, 0x13, 0xB1),
GS_DSI_CMD(0xB1, 0x80),
GS_DSI_CMDLIST(freq_update),
GS_DSI_CMDLIST(lock_cmd_f0),
};
static DEFINE_GS_CMDSET(km4_init);
static int km4_enable(struct drm_panel *panel)
{
struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
struct device *dev = ctx->dev;
const struct gs_panel_mode *pmode = ctx->current_mode;
const struct drm_display_mode *mode;
const bool needs_reset = !gs_is_panel_enabled(ctx);
bool is_fhd;
if (!pmode) {
dev_err(dev, "no current mode set\n");
return -EINVAL;
}
mode = &pmode->mode;
is_fhd = mode->hdisplay == 1008;
dev_info(dev, "%s (%s)\n", __func__, is_fhd ? "fhd" : "wqhd");
PANEL_ATRACE_BEGIN(__func__);
if (needs_reset)
gs_panel_reset_helper(ctx);
/* wait TE falling for RRS since DSC and framestart must in the same VSYNC */
if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS ||
ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS)
km4_wait_for_vsync_done(ctx, pmode);
/* DSC related configuration */
GS_DCS_WRITE_CMD(dev, 0x9D, 0x01);
gs_dcs_write_dsc_config(dev, is_fhd ? &fhd_pps_config : &wqhd_pps_config);
if (needs_reset) {
struct km4_panel *spanel = to_spanel(ctx);
GS_DCS_WRITE_DELAY_CMD(dev, 120, MIPI_DCS_EXIT_SLEEP_MODE);
gs_panel_send_cmdset(ctx, &km4_init_cmdset);
km4_te2_setting(ctx);
spanel->is_pixel_off = false;
ctx->dsi_hs_clk_mbps = MIPI_DSI_FREQ_MBPS_DEFAULT;
if (test_bit(FEAT_OP_NS, ctx->sw_status.feat))
spanel->frame_rate = 60;
else
spanel->frame_rate = 120;
spanel->dbi_frame_count = 0;
}
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
GS_DCS_BUF_ADD_CMD(dev, 0xC3, is_fhd ? 0x0D : 0x0C);
/* 8/10bit config for QHD/FHD */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x01, 0xF2);
GS_DCS_BUF_ADD_CMD(dev, 0xF2, is_fhd ? 0x81 : 0x01);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
if (pmode->gs_mode.is_lp_mode) {
km4_set_lp_mode(ctx, pmode);
GS_DCS_WRITE_CMD(dev, MIPI_DCS_SET_DISPLAY_ON);
} else {
#ifndef PANEL_FACTORY_BUILD
km4_update_refresh_ctrl_feat(ctx, pmode);
#endif
km4_update_panel_feat(ctx, true);
km4_write_display_mode(ctx, mode); /* dimming and HBM */
km4_change_frequency(ctx, pmode);
if (needs_reset || (ctx->panel_state == GPANEL_STATE_BLANK)) {
GS_DCS_WRITE_CMD(dev, MIPI_DCS_SET_DISPLAY_ON);
}
}
PANEL_ATRACE_END(__func__);
return 0;
}
static int km4_disable(struct drm_panel *panel)
{
struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
struct device *dev = ctx->dev;
int ret;
dev_info(dev, "%s\n", __func__);
/* skip disable sequence if going through RRS */
if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS ||
ctx->mode_in_progress == MODE_RR_IN_PROGRESS ||
ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS) {
dev_dbg(dev, "%s: RRS in progress, skip\n", __func__);
return 0;
}
ret = gs_panel_disable(panel);
if (ret)
return ret;
/* panel register state gets reset after disabling hardware */
bitmap_clear(ctx->hw_status.feat, 0, FEAT_MAX);
ctx->hw_status.vrefresh = 60;
ctx->sw_status.te.rate_hz = 60;
ctx->hw_status.te.rate_hz = 60;
ctx->hw_status.idle_vrefresh = 0;
ctx->hw_status.acl_mode = 0;
ctx->hw_status.dbv = 0;
ctx->hw_status.irc_mode = IRC_FLAT_DEFAULT;
/* set manual and peak before turning off display */
km4_enforce_manual_and_peak(ctx);
GS_DCS_WRITE_DELAY_CMD(dev, 20, MIPI_DCS_SET_DISPLAY_OFF);
if (ctx->panel_state == GPANEL_STATE_OFF)
GS_DCS_WRITE_DELAY_CMD(dev, 100, MIPI_DCS_ENTER_SLEEP_MODE);
return 0;
}
/*
* 120hz auto mode takes at least 2 frames to start lowering refresh rate in addition to
* time to next vblank. Use just over 2 frames time to consider worst case scenario
*/
#define EARLY_EXIT_THRESHOLD_US 17000
/**
* km4_update_idle_state - update panel auto frame insertion state
* @ctx: panel struct
*
* - update timestamp of switching to manual mode in case its been a while since the
* last frame update and auto mode may have started to lower refresh rate.
* - trigger early exit by command if it's changeable TE and no switching delay, which
* could result in fast 120 Hz boost and seeing 120 Hz TE earlier, otherwise disable
* auto refresh mode to avoid lowering frequency too fast.
*/
static void km4_update_idle_state(struct gs_panel *ctx)
{
s64 delta_us;
struct km4_panel *spanel = to_spanel(ctx);
struct device *dev = ctx->dev;
if (!spanel->is_mrr_v1)
return;
ctx->idle_data.panel_idle_vrefresh = 0;
if (!test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat))
return;
delta_us = ktime_us_delta(ktime_get(), ctx->timestamps.last_commit_ts);
if (delta_us < EARLY_EXIT_THRESHOLD_US) {
dev_dbg(dev, "skip early exit. %lldus since last commit\n", delta_us);
return;
}
/* triggering early exit causes a switch to 120hz */
ctx->timestamps.last_mode_set_ts = ktime_get();
PANEL_ATRACE_BEGIN(__func__);
if (!ctx->idle_data.idle_delay_ms && spanel->force_changeable_te) {
dev_dbg(dev, "sending early exit out cmd\n");
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
GS_DCS_BUF_ADD_CMDLIST(dev, freq_update);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
} else {
/* turn off auto mode to prevent panel from lowering frequency too fast */
km4_update_refresh_mode(ctx, ctx->current_mode, 0);
}
PANEL_ATRACE_END(__func__);
}
static void km4_commit_done(struct gs_panel *ctx)
{
struct km4_panel *spanel = to_spanel(ctx);
if (ctx->current_mode->gs_mode.is_lp_mode)
return;
if (!test_bit(FEAT_FRAME_AUTO, ctx->sw_status.feat) &&
spanel->frame_rate > 60 &&
ctx->sw_status.idle_vrefresh < drm_mode_vrefresh(&ctx->current_mode->mode)) {
km4_set_dbi_ref(ctx, km4_calc_dbi_ref(ctx));
}
/* skip idle update if going through RRS */
if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS ||
ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS) {
dev_dbg(ctx->dev, "%s: RRS in progress, skip\n", __func__);
return;
}
km4_update_idle_state(ctx);
km4_acl_modes_manager(ctx);
if (ctx->thermal && ctx->thermal->pending_temp_update)
km4_update_disp_therm(ctx);
}
static void km4_set_hbm_mode(struct gs_panel *ctx, enum gs_hbm_mode mode)
{
const struct gs_panel_mode *pmode = ctx->current_mode;
struct gs_panel_status *sw_status = &ctx->sw_status;
if (mode == ctx->hbm_mode)
return;
if (unlikely(!pmode))
return;
ctx->hbm_mode = mode;
if (GS_IS_HBM_ON(mode)) {
set_bit(FEAT_HBM, sw_status->feat);
/* enforce IRC on for factory builds */
#ifndef PANEL_FACTORY_BUILD
if (mode == GS_HBM_ON_IRC_ON)
sw_status->irc_mode = IRC_FLAT_DEFAULT;
else
sw_status->irc_mode = IRC_FLAT_Z;
#endif
km4_update_panel_feat(ctx, false);
km4_write_display_mode(ctx, &pmode->mode);
} else {
clear_bit(FEAT_HBM, sw_status->feat);
sw_status->irc_mode = IRC_FLAT_DEFAULT;
km4_write_display_mode(ctx, &pmode->mode);
km4_update_panel_feat(ctx, false);
}
}
static void km4_set_dimming(struct gs_panel *ctx, bool dimming_on)
{
const struct gs_panel_mode *pmode = ctx->current_mode;
ctx->dimming_on = dimming_on;
if (pmode->gs_mode.is_lp_mode) {
dev_info(ctx->dev, "in lp mode, skip to update");
return;
}
km4_write_display_mode(ctx, &pmode->mode);
}
static void km4_mode_set(struct gs_panel *ctx, const struct gs_panel_mode *pmode)
{
km4_change_frequency(ctx, pmode);
/**
* TODO(b/344478250): update the refresh rate change in the composer
*
* Notify the changes of TE2 rate while switching the modes with different vrefresh in
* case we miss the refresh rate change report from the composer.
*/
if (ctx->current_mode && ctx->te2.option == TEX_OPT_CHANGEABLE) {
u32 current_vrefresh = drm_mode_vrefresh(&ctx->current_mode->mode);
u32 target_vrefresh = drm_mode_vrefresh(&pmode->mode);
if (current_vrefresh != target_vrefresh && !ctx->idle_data.self_refresh_active &&
km4_set_te2_rate(ctx, target_vrefresh))
notify_panel_te2_rate_changed(ctx, 0);
}
}
static bool km4_is_mode_seamless(const struct gs_panel *ctx, const struct gs_panel_mode *pmode)
{
const struct drm_display_mode *c = &ctx->current_mode->mode;
const struct drm_display_mode *n = &pmode->mode;
/* seamless mode set can happen if active region resolution is same */
return (c->vdisplay == n->vdisplay) && (c->hdisplay == n->hdisplay);
}
static int km4_set_op_hz(struct gs_panel *ctx, unsigned int hz)
{
u32 vrefresh = drm_mode_vrefresh(&ctx->current_mode->mode);
if (gs_is_vrr_mode(ctx->current_mode)) {
dev_warn(ctx->dev, "%s: should be set via mode switch\n", __func__);
return -EINVAL;
}
if (vrefresh > hz || (hz != 60 && hz != 120)) {
dev_err(ctx->dev, "invalid op_hz=%d for vrefresh=%d\n", hz, vrefresh);
return -EINVAL;
}
PANEL_ATRACE_BEGIN(__func__);
ctx->op_hz = hz;
if (hz == 60)
set_bit(FEAT_OP_NS, ctx->sw_status.feat);
else
clear_bit(FEAT_OP_NS, ctx->sw_status.feat);
if (gs_is_panel_active(ctx))
km4_update_panel_feat(ctx, false);
dev_info(ctx->dev, "%s op_hz at %d\n", gs_is_panel_active(ctx) ? "set" : "cache", hz);
PANEL_ATRACE_END(__func__);
return 0;
}
static int km4_read_id(struct gs_panel *ctx)
{
return gs_panel_read_slsi_ddic_id(ctx);
}
static void km4_get_panel_rev(struct gs_panel *ctx, u32 id)
{
/* extract command 0xDB */
u8 build_code = (id & 0xFF00) >> 8;
u8 rev = ((build_code & 0xE0) >> 3) | ((build_code & 0x0C) >> 2);
/* b/306527241 - Ensure EVT 1.0 panels use the correct revision */
if (id == 0x20A4040A)
rev = 8;
gs_panel_get_panel_rev(ctx, rev);
}
static void km4_normal_mode_work(struct gs_panel *ctx)
{
if (ctx->idle_data.self_refresh_active) {
km4_update_disp_therm(ctx);
} else {
if (ctx->thermal)
ctx->thermal->pending_temp_update = true;
}
}
static void km4_pre_update_ffc(struct gs_panel *ctx)
{
struct device *dev = ctx->dev;
dev_dbg(dev, "%s\n", __func__);
PANEL_ATRACE_BEGIN(__func__);
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
/* FFC off */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x36, 0xC5);
GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x10);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
PANEL_ATRACE_END(__func__);
}
static void km4_update_ffc(struct gs_panel *ctx, unsigned int hs_clk_mbps)
{
struct device *dev = ctx->dev;
dev_dbg(dev, "%s: hs_clk_mbps: current=%d, target=%d\n", __func__,
ctx->dsi_hs_clk_mbps, hs_clk_mbps);
PANEL_ATRACE_BEGIN(__func__);
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
if (hs_clk_mbps != MIPI_DSI_FREQ_MBPS_DEFAULT &&
hs_clk_mbps != MIPI_DSI_FREQ_MBPS_ALTERNATIVE) {
dev_warn(dev, "%s: invalid hs_clk_mbps=%d for FFC\n", __func__, hs_clk_mbps);
} else if (ctx->dsi_hs_clk_mbps != hs_clk_mbps) {
dev_info(dev, "%s: updating for hs_clk_mbps=%d\n", __func__, hs_clk_mbps);
ctx->dsi_hs_clk_mbps = hs_clk_mbps;
/* Update FFC */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x37, 0xC5);
if (hs_clk_mbps == MIPI_DSI_FREQ_MBPS_DEFAULT)
GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x10, 0x50, 0x05, 0x4D, 0x31, 0x40, 0x00,
0x40, 0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40,
0x00, 0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00,
0x40, 0x00, 0x4D, 0x31, 0x40, 0x00, 0x40, 0x00, 0x40,
0x00);
else /* MIPI_DSI_FREQ_MBPS_ALTERNATIVE */
GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x10, 0x50, 0x05, 0x51, 0xFD, 0x40, 0x00,
0x40, 0x00, 0x40, 0x00, 0x51, 0xFD, 0x40, 0x00, 0x40,
0x00, 0x40, 0x00, 0x51, 0xFD, 0x40, 0x00, 0x40, 0x00,
0x40, 0x00, 0x51, 0xFD, 0x40, 0x00, 0x40, 0x00, 0x40,
0x00);
}
/* FFC on */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x36, 0xC5);
GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x11);
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
PANEL_ATRACE_END(__func__);
}
static const struct gs_display_underrun_param underrun_param = {
.te_idle_us = 350,
.te_var = 1,
};
static const u32 km4_bl_range[] = {
94, 180, 270, 360, 3271
};
#define KM4_WQHD_DSC {\
.enabled = true,\
.dsc_count = 2,\
.cfg = &wqhd_pps_config,\
}
#define KM4_FHD_DSC {\
.enabled = true,\
.dsc_count = 2,\
.cfg = &fhd_pps_config,\
}
static const struct gs_panel_mode_array km4_modes = {
#ifdef PANEL_FACTORY_BUILD
.num_modes = 6,
#else
.num_modes = 8,
#endif
.modes = {
/* MRR modes */
#ifdef PANEL_FACTORY_BUILD
{
.mode = {
.name = "1344x2992@1:1",
DRM_MODE_TIMING(1, 1344, 80, 24, 52, 2992, 12, 4, 22),
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = KM4_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
{
.mode = {
.name = "1344x2992@10:10",
DRM_MODE_TIMING(10, 1344, 80, 24, 42, 2992, 12, 4, 22),
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = KM4_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
{
.mode = {
.name = "1344x2992@30:30",
DRM_MODE_TIMING(30, 1344, 80, 22, 44, 2992, 12, 4, 22),
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = KM4_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
{
.mode = {
.name = "1344x2992@80:80",
DRM_MODE_TIMING(80, 1344, 80, 24, 42, 2992, 12, 4, 22),
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = KM4_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
#endif
{
.mode = {
/* 60Hz supports HS/NS, see km4_get_te_usec for widths used */
.name = "1344x2992@60:60",
DRM_MODE_TIMING(60, 1344, 80, 24, 42, 2992, 12, 4, 22),
/* aligned to bootloader resolution */
.flags = DRM_MODE_FLAG_BTS_OP_RATE,
.type = DRM_MODE_TYPE_PREFERRED,
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = KM4_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
{
.mode = {
.name = "1344x2992@120:120",
DRM_MODE_TIMING(120, 1344, 80, 24, 42, 2992, 12, 4, 22),
.flags = DRM_MODE_FLAG_BTS_OP_RATE,
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = KM4_TE_USEC_120HZ_HS,
.bpc = 8,
.dsc = KM4_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
#ifndef PANEL_FACTORY_BUILD
{
.mode = {
/* 60Hz supports HS/NS, see km4_get_te_usec for widths used */
.name = "1008x2244@60:60",
DRM_MODE_TIMING(60, 1008, 80, 24, 38, 2244, 12, 4, 20),
.flags = DRM_MODE_FLAG_BTS_OP_RATE,
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = KM4_FHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
{
.mode = {
.name = "1008x2244@120:120",
DRM_MODE_TIMING(120, 1008, 80, 24, 38, 2244, 12, 4, 20),
.flags = DRM_MODE_FLAG_BTS_OP_RATE,
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = KM4_TE_USEC_120HZ_HS,
.bpc = 8,
.dsc = KM4_FHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
/* VRR modes */
{
.mode = {
.name = "1344x2992@120:240",
DRM_MODE_TIMING(120, 1344, 80, 24, 42, 2992, 12, 4, 22),
.flags = DRM_MODE_FLAG_TE_FREQ_X2,
/* aligned to bootloader resolution */
.type = DRM_MODE_TYPE_VRR | DRM_MODE_TYPE_PREFERRED,
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = KM4_TE_USEC_VRR_HS,
.bpc = 8,
.dsc = KM4_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
{
.mode = {
.name = "1008x2244@120:240",
DRM_MODE_TIMING(120, 1008, 80, 24, 38, 2244, 12, 4, 20),
.flags = DRM_MODE_FLAG_TE_FREQ_X2,
.type = DRM_MODE_TYPE_VRR,
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = KM4_TE_USEC_VRR_HS,
.bpc = 8,
.dsc = KM4_FHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
{
.mode = {
.name = "1344x2992@120:120",
DRM_MODE_TIMING(120, 1344, 80, 24, 42, 2992, 12, 4, 22),
.flags = DRM_MODE_FLAG_TE_FREQ_X1,
.type = DRM_MODE_TYPE_VRR,
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = KM4_TE_USEC_VRR_HS,
.bpc = 8,
.dsc = KM4_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
{
.mode = {
.name = "1008x2244@120:120",
DRM_MODE_TIMING(120, 1008, 80, 24, 38, 2244, 12, 4, 20),
.flags = DRM_MODE_FLAG_TE_FREQ_X1,
.type = DRM_MODE_TYPE_VRR,
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = KM4_TE_USEC_VRR_HS,
.bpc = 8,
.dsc = KM4_FHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = KM4_TE2_RISING_EDGE_OFFSET,
.falling_edge = KM4_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = GIDLE_MODE_UNSUPPORTED,
},
#endif
},/* modes */
};
static const struct gs_panel_mode_array km4_lp_modes = {
#ifdef PANEL_FACTORY_BUILD
.num_modes = 1,
#else
.num_modes = 2,
#endif
.modes = {
{
.mode = {
.name = "1344x2992@30:30",
/* hsa and hbp are different from normal 30 Hz */
DRM_MODE_TIMING(30, 1344, 80, 24, 42, 2992, 12, 4, 22),
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = 1096,
.bpc = 8,
.dsc = KM4_WQHD_DSC,
.underrun_param = &underrun_param,
.is_lp_mode = true,
},
},
#ifndef PANEL_FACTORY_BUILD
{
.mode = {
.name = "1008x2244@30:30",
DRM_MODE_TIMING(30, 1008, 80, 24, 38, 2244, 12, 4, 20),
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = 1096,
.bpc = 8,
.dsc = KM4_FHD_DSC,
.underrun_param = &underrun_param,
.is_lp_mode = true,
},
},
#endif
}, /* modes */
};
static struct gs_thermal_data km4_thermal_data = {
/* ddic default temp */
.hw_temp = 25,
.pending_temp_update = false,
};
static void km4_debugfs_init(struct drm_panel *panel, struct dentry *root)
{
#ifdef CONFIG_DEBUG_FS
struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
struct dentry *panel_root, *csroot;
struct km4_panel *spanel;
if (!ctx)
return;
panel_root = debugfs_lookup("panel", root);
if (!panel_root)
return;
csroot = debugfs_lookup("cmdsets", panel_root);
if (!csroot) {
goto panel_out;
}
spanel = to_spanel(ctx);
gs_panel_debugfs_create_cmdset(csroot, &km4_init_cmdset, "init");
debugfs_create_bool("force_changeable_te", 0644, panel_root, &spanel->force_changeable_te);
debugfs_create_bool("force_changeable_te2", 0644, panel_root,
&spanel->force_changeable_te2);
debugfs_create_bool("force_za_off", 0644, panel_root, &spanel->force_za_off);
debugfs_create_u32("hw_acl_mode", 0644, panel_root, &ctx->hw_status.acl_mode);
dput(csroot);
panel_out:
dput(panel_root);
#endif
}
static void km4_set_ssc_en(struct gs_panel *ctx, bool enabled)
{
struct device *dev = ctx->dev;
const bool ssc_mode_update = ctx->ssc_en != enabled;
if (!ssc_mode_update) {
dev_dbg(ctx->dev, "ssc_mode skip update\n");
return;
}
ctx->ssc_en = enabled;
GS_DCS_BUF_ADD_CMDLIST(dev, unlock_cmd_f0);
GS_DCS_BUF_ADD_CMD(dev, 0xFC, 0x5A, 0x5A); /* test_key_on */
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x00, 0x6E, 0xC5); /* global para */
if (enabled)
GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x07, 0x7F, 0x00, 0x00); /* ssc enable */
else
GS_DCS_BUF_ADD_CMD(dev, 0xC5, 0x04, 0x00); /* ssc disable */
GS_DCS_BUF_ADD_CMD(dev, 0xFC, 0xA5, 0xA5); /* test_key_off */
GS_DCS_BUF_ADD_CMDLIST_AND_FLUSH(dev, lock_cmd_f0);
dev_info(ctx->dev, "ssc_mode=%d \n", ctx->ssc_en);
}
/* this function is called at drm bridge atomic_enable() while presenting 1st frame */
static void km4_panel_init(struct gs_panel *ctx)
{
struct km4_panel *spanel = to_spanel(ctx);
const struct gs_panel_mode *pmode = ctx->current_mode;
#ifdef PANEL_FACTORY_BUILD
spanel->is_mrr_v1 = true;
ctx->idle_data.panel_idle_enabled = false;
set_bit(FEAT_FRAME_MANUAL_FI, ctx->sw_status.feat);
#else
spanel->is_mrr_v1 = false;
km4_update_refresh_ctrl_feat(ctx, pmode);
#endif
spanel->frame_rate = 120;
spanel->dbi_frame_count = 0;
ctx->hw_status.irc_mode = IRC_FLAT_DEFAULT;
/* default fixed TE2 120Hz */
ctx->te2.option = TEX_OPT_FIXED;
ctx->te2.rate_hz = 120;
if (!ctx->thermal) {
dev_err(ctx->dev, "%s: error retrieving thermal data\n", __func__);
return;
}
ctx->thermal->tz = thermal_zone_get_zone_by_name("disp_therm");
if (IS_ERR_OR_NULL(ctx->thermal->tz))
dev_err(ctx->dev, "%s: failed to get thermal zone disp_therm\n", __func__);
/* re-init panel to decouple bootloader settings */
if (pmode) {
dev_info(ctx->dev, "%s: set mode: %s\n", __func__, pmode->mode.name);
ctx->sw_status.idle_vrefresh = 0;
km4_set_panel_feat(ctx, pmode, true);
km4_change_frequency(ctx, pmode);
km4_te2_setting(ctx);
}
}
static int km4_panel_probe(struct mipi_dsi_device *dsi)
{
struct km4_panel *spanel;
struct gs_panel *ctx;
spanel = devm_kzalloc(&dsi->dev, sizeof(*spanel), GFP_KERNEL);
if (!spanel)
return -ENOMEM;
ctx = &spanel->base;
ctx->op_hz = 120;
ctx->hw_status.vrefresh = 60;
ctx->hw_status.te.rate_hz = 60;
ctx->hw_status.acl_mode = ACL_OFF;
ctx->hw_status.dbv = 0;
ctx->thermal = &km4_thermal_data;
clear_bit(FEAT_ZA, ctx->hw_status.feat);
spanel->is_pixel_off = false;
return gs_dsi_panel_common_init(dsi, ctx);
}
static int km4_panel_config(struct gs_panel *ctx);
static const struct drm_panel_funcs km4_drm_funcs = {
.disable = km4_disable,
.unprepare = gs_panel_unprepare,
.prepare = gs_panel_prepare,
.enable = km4_enable,
.get_modes = gs_panel_get_modes,
.debugfs_init = km4_debugfs_init,
};
static const struct gs_panel_funcs km4_gs_funcs = {
.set_brightness = km4_set_brightness,
.set_lp_mode = km4_set_lp_mode,
.set_nolp_mode = km4_set_nolp_mode,
.set_binned_lp = gs_panel_set_binned_lp_helper,
.set_hbm_mode = km4_set_hbm_mode,
.set_dimming = km4_set_dimming,
.is_mode_seamless = km4_is_mode_seamless,
.mode_set = km4_mode_set,
.panel_init = km4_panel_init,
.panel_config = km4_panel_config,
.get_panel_rev = km4_get_panel_rev,
.get_te2_edges = gs_panel_get_te2_edges_helper,
.set_te2_edges = gs_panel_set_te2_edges_helper,
.update_te2 = km4_update_te2,
.commit_done = km4_commit_done,
.atomic_check = km4_atomic_check,
.set_self_refresh = km4_set_self_refresh,
#ifndef PANEL_FACTORY_BUILD
.refresh_ctrl = km4_refresh_ctrl,
#endif
.set_frame_rate = km4_set_frame_rate,
.set_op_hz = km4_set_op_hz,
.read_id = km4_read_id,
.get_te_usec = km4_get_te_usec,
.set_acl_mode = km4_set_acl_mode,
.run_normal_mode_work = km4_normal_mode_work,
.pre_update_ffc = km4_pre_update_ffc,
.update_ffc = km4_update_ffc,
.set_ssc_en = km4_set_ssc_en,
.set_te2_rate = km4_set_te2_rate,
.get_te2_rate = km4_get_te2_rate,
.set_te2_option = km4_set_te2_option,
.get_te2_option = km4_get_te2_option,
};
static const struct gs_brightness_configuration km4_btr_configs[] = {
{
.panel_rev = PANEL_REV_EVT1 | PANEL_REV_LATEST,
.default_brightness = 1209,
.brt_capability = {
.normal = {
.nits = {
.min = 2,
.max = 1250,
},
.level = {
.min = 176,
.max = 3271,
},
.percentage = {
.min = 0,
.max = 61,
},
},
.hbm = {
.nits = {
.min = 1250,
.max = 2050,
},
.level = {
.min = 3272,
.max = 4095,
},
.percentage = {
.min = 61,
.max = 100,
},
},
},
},
{
.panel_rev = PANEL_REV_PROTO1_1,
.default_brightness = 1319,
.brt_capability = {
.normal = {
.nits = {
.min = 2,
.max = 1250,
},
.level = {
.min = 184,
.max = 3427,
},
.percentage = {
.min = 0,
.max = 68,
},
},
.hbm = {
.nits = {
.min = 1250,
.max = 1850,
},
.level = {
.min = 3428,
.max = 4095,
},
.percentage = {
.min = 68,
.max = 100,
},
},
},
},
{
.panel_rev = PANEL_REV_PROTO1,
.default_brightness = 1313,
.brt_capability = {
.normal = {
.nits = {
.min = 2,
.max = 1200,
},
.level = {
.min = 186,
.max = 3406,
},
.percentage = {
.min = 0,
.max = 67,
},
},
.hbm = {
.nits = {
.min = 1200,
.max = 1800,
},
.level = {
.min = 3407,
.max = 4095,
},
.percentage = {
.min = 67,
.max = 100,
},
},
},
},
};
static struct gs_panel_brightness_desc km4_brightness_desc = {
.max_luminance = 10000000,
.max_avg_luminance = 10000000,
.min_luminance = 5,
};
static struct gs_panel_reg_ctrl_desc km4_reg_ctrl_desc = {
.reg_ctrl_enable = {
{PANEL_REG_ID_VDDI, 1},
{PANEL_REG_ID_VCI, 10},
},
.reg_ctrl_post_enable = {
{PANEL_REG_ID_VDDD, 2},
},
.reg_ctrl_pre_disable = {
{PANEL_REG_ID_VDDD, 5},
},
.reg_ctrl_disable = {
{PANEL_REG_ID_VCI, 1},
{PANEL_REG_ID_VDDI, 1},
},
};
static struct gs_panel_desc gs_km4 = {
.data_lane_cnt = 4,
.dbv_extra_frame = true,
.brightness_desc = &km4_brightness_desc,
.reg_ctrl_desc = &km4_reg_ctrl_desc,
/* supported HDR format bitmask : 1(DOLBY_VISION), 2(HDR10), 3(HLG) */
.hdr_formats = BIT(2) | BIT(3),
.bl_range = km4_bl_range,
.bl_num_ranges = ARRAY_SIZE(km4_bl_range),
.modes = &km4_modes,
.lp_modes = &km4_lp_modes,
.binned_lp = km4_binned_lp,
.num_binned_lp = ARRAY_SIZE(km4_binned_lp),
.rr_switch_duration = 1,
.has_off_binned_lp_entry = false,
.is_idle_supported = true,
.panel_func = &km4_drm_funcs,
.gs_panel_func = &km4_gs_funcs,
.default_dsi_hs_clk_mbps = MIPI_DSI_FREQ_MBPS_DEFAULT,
.reset_timing_ms = { 1, 1, 5 },
.normal_mode_work_delay_ms = 30000,
/* TODO(b/335574896): delay time in NS mode */
/**
* While the proximity is active, we will set the min vrefresh to 30Hz with auto
* frame insertion. Thus when the display is idle, we will have the refresh rate
* change from 120Hz to 30Hz. According to the measurement, the pattern is: 3x120Hz
* frame > 1x60Hz frame > 30Hz. With additional tolerance due to scheduler in the
* kernel, the delay of notification is estimated to be ~50ms.
*/
.notify_te2_rate_changed_work_delay_ms = 50,
};
static int km4_panel_config(struct gs_panel *ctx)
{
gs_panel_model_init(ctx, PROJECT, 0);
return gs_panel_update_brightness_desc(&km4_brightness_desc, km4_btr_configs,
ARRAY_SIZE(km4_btr_configs), ctx->panel_rev);
}
static const struct of_device_id gs_panel_of_match[] = {
{ .compatible = "google,gs-km4", .data = &gs_km4 },
{ }
};
MODULE_DEVICE_TABLE(of, gs_panel_of_match);
static struct mipi_dsi_driver gs_panel_driver = {
.probe = km4_panel_probe,
.remove = gs_dsi_panel_common_remove,
.driver = {
.name = "panel-gs-km4",
.of_match_table = gs_panel_of_match,
},
};
module_mipi_dsi_driver(gs_panel_driver);
MODULE_AUTHOR("Taylor Nelms <tknelms@google.com>");
MODULE_DESCRIPTION("MIPI-DSI based Google KM4 panel driver");
MODULE_LICENSE("Dual MIT/GPL");