blob: a6d53a60cfbac2f0dd7d6a46bd78bb7b90b89f9a [file] [log] [blame] [edit]
// SPDX-License-Identifier: GPL-2.0-only
/*
* MIPI-DSI based HK3 AMOLED LCD panel driver.
*
* Copyright (c) 2022 Google LLC
*
* 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 <drm/drm_vblank.h>
#include <linux/debugfs.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/thermal.h>
#include <video/mipi_display.h>
#include "include/trace/dpu_trace.h"
#include "include/trace/panel_trace.h"
#include "panel/panel-samsung-drv.h"
/**
* enum hk3_panel_feature - features supported by this panel
* @FEAT_HBM: high brightness mode
* @FEAT_IRC_OFF: IR compensation off state
* @FEAT_IRC_Z_MODE: IR compensation on state and use Flat Z mode
* @FEAT_EARLY_EXIT: early exit from a long frame
* @FEAT_OP_NS: normal speed (not high speed)
* @FEAT_FRAME_AUTO: automatic (not manual) frame control
* @FEAT_MAX: placeholder, counter for number of features
*
* The following features are correlated, if one or more of them change, the others need
* to be updated unconditionally.
*/
enum hk3_panel_feature {
FEAT_HBM = 0,
FEAT_IRC_OFF,
FEAT_IRC_Z_MODE,
FEAT_EARLY_EXIT,
FEAT_OP_NS,
FEAT_FRAME_AUTO,
FEAT_MAX,
};
/**
* enum hk3_lhbm_brt - local hbm brightness
* @LHBM_R_COARSE: red coarse
* @LHBM_GB_COARSE: green and blue coarse
* @LHBM_R_FINE: red fine
* @LHBM_G_FINE: green fine
* @LHBM_B_FINE: blue fine
* @LHBM_BRT_LEN: local hbm brightness array length
*/
enum hk3_lhbm_brt {
LHBM_R_COARSE = 0,
LHBM_GB_COARSE,
LHBM_R_FINE,
LHBM_G_FINE,
LHBM_B_FINE,
LHBM_BRT_LEN
};
#define LHBM_BRT_CMD_LEN (LHBM_BRT_LEN + 1)
/**
* enum hk3_lhbm_brt_overdrive_group - lhbm brightness overdrive group number
* @LHBM_OVERDRIVE_GRP_0_NIT: group number for 0 nit
* @LHBM_OVERDRIVE_GRP_6_NIT: group number for 0-6 nits
* @LHBM_OVERDRIVE_GRP_50_NIT: group number for 6-50 nits
* @LHBM_OVERDRIVE_GRP_300_NIT: group number for 50-300 nits
* @LHBM_OVERDRIVE_GRP_MAX: maximum group number
*/
enum hk3_lhbm_brt_overdrive_group {
LHBM_OVERDRIVE_GRP_0_NIT = 0,
LHBM_OVERDRIVE_GRP_6_NIT,
LHBM_OVERDRIVE_GRP_50_NIT,
LHBM_OVERDRIVE_GRP_300_NIT,
LHBM_OVERDRIVE_GRP_MAX
};
/**
* enum hk3_material - different materials in HW
* @MATERIAL_E6: EVT1 material E6
* @MATERIAL_E7_DOE: EVT1 material E7
* @MATERIAL_E7: EVT1.1 maetrial E7
* @MATERIAL_LPC5: EVT1.1 material LPC5
*/
enum hk3_material {
MATERIAL_E6 = 0,
MATERIAL_E7_DOE,
MATERIAL_E7,
MATERIAL_LPC5
};
struct hk3_lhbm_ctl {
/** @brt_normal: normal LHBM brightness parameters */
u8 brt_normal[LHBM_BRT_LEN];
/** @brt_overdrive: overdrive LHBM brightness parameters */
u8 brt_overdrive[LHBM_OVERDRIVE_GRP_MAX][LHBM_BRT_LEN];
/** @overdrived: whether LHBM is overdrived */
bool overdrived;
/** @hist_roi_configured: whether LHBM histogram configuration is done */
bool hist_roi_configured;
};
/**
* struct hk3_panel - panel specific info
*
* This struct maintains hk3 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 hk3_panel {
/** @base: base panel struct */
struct exynos_panel base;
/** @feat: software or working correlated features, not guaranteed to be effective in panel */
DECLARE_BITMAP(feat, FEAT_MAX);
/** @hw_feat: correlated states effective in panel */
DECLARE_BITMAP(hw_feat, FEAT_MAX);
/** @hw_vrefresh: vrefresh rate effective in panel */
u32 hw_vrefresh;
/** @hw_idle_vrefresh: idle vrefresh rate effective in panel */
u32 hw_idle_vrefresh;
/**
* @auto_mode_vrefresh: indicates current minimum refresh rate while in auto mode,
* if 0 it means that auto mode is not enabled
*/
u32 auto_mode_vrefresh;
/** @force_changeable_te: force changeable TE (instead of fixed) during early exit */
bool force_changeable_te;
/** @force_changeable_te2: force changeable TE (instead of fixed) for monitoring refresh rate */
bool force_changeable_te2;
/** @hw_acl_setting: automatic current limiting setting */
u8 hw_acl_setting;
/** @hw_dbv: indicate the current dbv, will be zero after sleep in/out */
u16 hw_dbv;
/** @hw_za_enabled: whether zonal attenuation is enabled */
bool hw_za_enabled;
/** @force_za_off: force to turn off zonal attenuation */
bool force_za_off;
/** @lhbm_ctl: lhbm brightness control */
struct hk3_lhbm_ctl lhbm_ctl;
/** @material: the material version used in panel */
enum hk3_material material;
/** @tz: thermal zone device for reading temperature */
struct thermal_zone_device *tz;
/** @hw_temp: the temperature applied into panel */
u32 hw_temp;
/** @pending_temp_update: whether there is pending temperature update. It will be
* handled in the commit_done function.
*/
bool pending_temp_update;
/**
* @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;
};
#define to_spanel(ctx) container_of(ctx, struct hk3_panel, base)
/* 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 = 187,
.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 = 5177,
.nfl_bpg_offset = 133,
.slice_bpg_offset = 112,
.final_offset = 4336,
.vbr_enable = false,
.slice_chunk_size = 672,
.dsc_version_minor = 1,
.dsc_version_major = 1,
.native_422 = false,
.native_420 = false,
.second_line_bpg_offset = 0,
.nsl_bpg_offset = 0,
.second_line_offset_adj = 0,
};
/* 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 = 187,
.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 = 4482,
.nfl_bpg_offset = 133,
.slice_bpg_offset = 150,
.final_offset = 4336,
.vbr_enable = false,
.slice_chunk_size = 504,
.dsc_version_minor = 1,
.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 HK3_WRCTRLD_DIMMING_BIT 0x08
#define HK3_WRCTRLD_BCTRL_BIT 0x20
#define HK3_WRCTRLD_HBM_BIT 0xC0
#define HK3_WRCTRLD_LOCAL_HBM_BIT 0x10
#define HK3_TE2_CHANGEABLE 0x04
#define HK3_TE2_FIXED 0x51
#define HK3_TE2_RISING_EDGE_OFFSET 0x10
#define HK3_TE2_FALLING_EDGE_OFFSET 0x30
#define HK3_TE2_FALLING_EDGE_OFFSET_NS 0x25
#define HK3_TE_USEC_AOD 693
#define HK3_TE_USEC_120HZ 273
#define HK3_TE_USEC_60HZ_HS 8500
#define HK3_TE_USEC_60HZ_NS 546
#define HK3_TE_PERIOD_DELTA_TOLERANCE_USEC 2000
#define MIPI_DSI_FREQ_DEFAULT 1368
#define MIPI_DSI_FREQ_ALTERNATIVE 1346
#define PROJECT "HK3"
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 lhbm_brightness_index[] = { 0xB0, 0x03, 0x21, 0x95 };
static const u8 lhbm_brightness_reg = 0x95;
static const u8 pixel_off[] = { 0x22 };
static const u8 sync_begin[] = { 0xE4, 0x00, 0x2C, 0x2C, 0xA2, 0x00, 0x00 };
static const u8 sync_end[] = { 0xE4, 0x00, 0x2C, 0x2C, 0x82, 0x00, 0x00 };
static const u8 aod_on[] = { MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x24 };
static const u8 aod_off[] = { MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20 };
/* 50 nits */
static const u8 aod_dbv[] = { MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x03, 0x55 };
static const struct exynos_dsi_cmd hk3_lp_low_cmds[] = {
EXYNOS_DSI_CMD0(unlock_cmd_f0),
/* AOD Low Mode, 10nit */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x52, 0x94),
EXYNOS_DSI_CMD_SEQ(0x94, 0x01, 0x07, 0x6A, 0x02),
EXYNOS_DSI_CMD0(lock_cmd_f0),
};
static const struct exynos_dsi_cmd hk3_lp_high_cmds[] = {
EXYNOS_DSI_CMD0(unlock_cmd_f0),
/* AOD High Mode, 50nit */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x52, 0x94),
EXYNOS_DSI_CMD_SEQ(0x94, 0x00, 0x07, 0x6A, 0x02),
EXYNOS_DSI_CMD0(lock_cmd_f0),
};
static const struct exynos_binned_lp hk3_binned_lp[] = {
/* low threshold 40 nits */
BINNED_LP_MODE_TIMING("low", 766, hk3_lp_low_cmds,
HK3_TE2_RISING_EDGE_OFFSET, HK3_TE2_FALLING_EDGE_OFFSET),
BINNED_LP_MODE_TIMING("high", 3307, hk3_lp_high_cmds,
HK3_TE2_RISING_EDGE_OFFSET, HK3_TE2_FALLING_EDGE_OFFSET)
};
static inline bool is_in_comp_range(int temp)
{
return (temp >= 10 && temp <= 49);
}
/* Read temperature and apply appropriate gain into DDIC for burn-in compensation if needed */
static void hk3_update_disp_therm(struct exynos_panel *ctx)
{
/* temperature*1000 in celsius */
int temp, ret;
struct hk3_panel *spanel = to_spanel(ctx);
if (IS_ERR_OR_NULL(spanel->tz))
return;
if (ctx->panel_rev < PANEL_REV_EVT1_1 || ctx->panel_state != PANEL_STATE_NORMAL)
return;
spanel->pending_temp_update = false;
ret = thermal_zone_get_temp(spanel->tz, &temp);
if (ret) {
dev_err(ctx->dev, "%s: fail to read temperature ret:%d\n", __func__, ret);
return;
}
temp = DIV_ROUND_CLOSEST(temp, 1000);
dev_dbg(ctx->dev, "%s: temp=%d\n", __func__, temp);
if (temp == spanel->hw_temp || !is_in_comp_range(temp))
return;
dev_dbg(ctx->dev, "%s: apply gain into ddic at %ddeg c\n", __func__, temp);
DPU_ATRACE_BEGIN(__func__);
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x03, 0x67);
EXYNOS_DCS_BUF_ADD(ctx, 0x67, temp);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
DPU_ATRACE_END(__func__);
spanel->hw_temp = temp;
}
static u8 hk3_get_te2_option(struct exynos_panel *ctx)
{
struct hk3_panel *spanel = to_spanel(ctx);
if (!ctx || !ctx->current_mode || spanel->force_changeable_te2)
return HK3_TE2_CHANGEABLE;
if (ctx->current_mode->exynos_mode.is_lp_mode ||
(test_bit(FEAT_EARLY_EXIT, spanel->feat) &&
spanel->auto_mode_vrefresh < 30))
return HK3_TE2_FIXED;
return HK3_TE2_CHANGEABLE;
}
static void hk3_update_te2_internal(struct exynos_panel *ctx, bool lock)
{
struct exynos_panel_te2_timing timing = {
.rising_edge = HK3_TE2_RISING_EDGE_OFFSET,
.falling_edge = HK3_TE2_FALLING_EDGE_OFFSET,
};
u32 rising, falling;
struct hk3_panel *spanel = to_spanel(ctx);
u8 option = hk3_get_te2_option(ctx);
u8 idx;
if (!ctx)
return;
/* skip TE2 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;
}
if (test_bit(FEAT_OP_NS, spanel->feat)) {
rising = HK3_TE2_RISING_EDGE_OFFSET;
falling = HK3_TE2_FALLING_EDGE_OFFSET_NS;
} else {
if (exynos_panel_get_current_mode_te2(ctx, &timing)) {
dev_dbg(ctx->dev, "failed to get TE2 timng\n");
return;
}
rising = timing.rising_edge;
falling = timing.falling_edge;
}
ctx->te2.option = (option == HK3_TE2_FIXED) ? TE2_OPT_FIXED : TE2_OPT_CHANGEABLE;
dev_dbg(ctx->dev,
"TE2 updated: %s mode, option %s, idle %s, rising=0x%X falling=0x%X\n",
test_bit(FEAT_OP_NS, spanel->feat) ? "NS" : "HS",
(option == HK3_TE2_CHANGEABLE) ? "changeable" : "fixed",
ctx->panel_idle_vrefresh ? "active" : "inactive",
rising, falling);
if (lock)
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x42, 0xF2);
EXYNOS_DCS_BUF_ADD(ctx, 0xF2, 0x0D);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x01, 0xB9);
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, option);
idx = option == HK3_TE2_FIXED ? 0x22 : 0x1E;
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, idx, 0xB9);
if (option == HK3_TE2_FIXED) {
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, (rising >> 8) & 0xF, rising & 0xFF,
(falling >> 8) & 0xF, falling & 0xFF,
(rising >> 8) & 0xF, rising & 0xFF,
(falling >> 8) & 0xF, falling & 0xFF);
} else {
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, (rising >> 8) & 0xF, rising & 0xFF,
(falling >> 8) & 0xF, falling & 0xFF);
}
if (lock)
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
}
static void hk3_update_te2(struct exynos_panel *ctx)
{
hk3_update_te2_internal(ctx, true);
}
static inline bool is_auto_mode_allowed(struct exynos_panel *ctx)
{
/* don't want to enable auto mode/early exit during dimming on */
if (ctx->dimming_on)
return false;
if (ctx->idle_delay_ms) {
const unsigned int delta_ms = panel_get_idle_time_delta(ctx);
if (delta_ms < ctx->idle_delay_ms)
return false;
}
return ctx->panel_idle_enabled;
}
static u32 hk3_get_min_idle_vrefresh(struct exynos_panel *ctx,
const struct exynos_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 hk3_set_panel_feat(struct exynos_panel *ctx,
const u32 vrefresh, const u32 idle_vrefresh, const unsigned long *feat, bool enforce)
{
struct hk3_panel *spanel = to_spanel(ctx);
u8 val;
DECLARE_BITMAP(changed_feat, FEAT_MAX);
if (enforce) {
bitmap_fill(changed_feat, FEAT_MAX);
} else {
bitmap_xor(changed_feat, feat, spanel->hw_feat, FEAT_MAX);
if (bitmap_empty(changed_feat, FEAT_MAX) &&
vrefresh == spanel->hw_vrefresh &&
idle_vrefresh == spanel->hw_idle_vrefresh) {
dev_dbg(ctx->dev, "%s: no changes, skip update\n", __func__);
return;
}
}
spanel->hw_vrefresh = vrefresh;
spanel->hw_idle_vrefresh = idle_vrefresh;
bitmap_copy(spanel->hw_feat, feat, FEAT_MAX);
dev_dbg(ctx->dev,
"op=%s ee=%s hbm=%s irc=%s fi=%s fps=%u idle_fps=%u\n",
test_bit(FEAT_OP_NS, feat) ? "ns" : "hs",
test_bit(FEAT_EARLY_EXIT, feat) ? "on" : "off",
test_bit(FEAT_HBM, feat) ? "on" : "off",
ctx->panel_rev >= PANEL_REV_EVT1 ?
(test_bit(FEAT_IRC_Z_MODE, feat) ? "flat_z" : "flat") :
(test_bit(FEAT_IRC_OFF, feat) ? "off" : "on"),
test_bit(FEAT_FRAME_AUTO, feat) ? "auto" : "manual",
vrefresh,
idle_vrefresh);
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
/* TE setting */
if (test_bit(FEAT_EARLY_EXIT, changed_feat) ||
test_bit(FEAT_OP_NS, changed_feat)) {
if (test_bit(FEAT_EARLY_EXIT, feat) && !spanel->force_changeable_te) {
/* Fixed TE */
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, 0x51);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x02, 0xB9);
val = test_bit(FEAT_OP_NS, feat) ? 0x01 : 0x00;
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, val);
} else {
/* Changeable TE */
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, 0x04);
/* Changeable TE width setting and frequency */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x04, 0xB9);
/* width 273us in normal mode */
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, 0x0B, 0xBB, 0x00, 0x2F);
}
}
/* TE2 setting */
if (test_bit(FEAT_OP_NS, changed_feat))
hk3_update_te2_internal(ctx, false);
/*
* HBM IRC setting
*
* Description: after EVT1, IRC will be always on. "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_EVT1) {
if (test_bit(FEAT_IRC_Z_MODE, changed_feat)) {
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x02, 0x00, 0x92);
if (test_bit(FEAT_IRC_Z_MODE, feat)) {
if (spanel->material == MATERIAL_E6) {
EXYNOS_DCS_BUF_ADD(ctx, 0x92, 0xBE, 0x98);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x02, 0xF3, 0x68);
EXYNOS_DCS_BUF_ADD(ctx, 0x68, 0x97, 0x87, 0x87, 0xFB, 0xFD, 0xF1);
} else {
EXYNOS_DCS_BUF_ADD(ctx, 0x92, 0xF1, 0xC1);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x02, 0xF3, 0x68);
EXYNOS_DCS_BUF_ADD(ctx, 0x68, 0x82, 0x70, 0x23, 0x91, 0x88, 0x3C);
}
} else {
EXYNOS_DCS_BUF_ADD(ctx, 0x92, 0x00, 0x00);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x02, 0xF3, 0x68);
if (spanel->material == MATERIAL_E6)
EXYNOS_DCS_BUF_ADD(ctx, 0x68, 0x71, 0x81, 0x59, 0x90, 0xA2, 0x80);
else
EXYNOS_DCS_BUF_ADD(ctx, 0x68, 0x77, 0x81, 0x23, 0x8C, 0x99, 0x3C);
}
}
} else {
if (test_bit(FEAT_IRC_OFF, changed_feat)) {
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x01, 0x9B, 0x92);
val = test_bit(FEAT_IRC_OFF, feat) ? 0x07 : 0x27;
EXYNOS_DCS_BUF_ADD(ctx, 0x92, val);
}
}
/*
* 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 */
EXYNOS_DCS_BUF_ADD(ctx, 0xF2, 0x01);
val = test_bit(FEAT_OP_NS, feat) ? 0x18 : 0x00;
EXYNOS_DCS_BUF_ADD(ctx, 0x60, val);
}
/*
* Note: the following command sequence should be sent as a whole if one of panel
* state defined by enum panel_state changes or at turning on panel, or unexpected
* behaviors will be seen, e.g. black screen, flicker.
*/
/*
* Early-exit: enable or disable
*
* Description: early-exit sequence overrides some configs HBM set.
*/
if (test_bit(FEAT_EARLY_EXIT, feat)) {
if (test_bit(FEAT_HBM, feat))
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x21, 0x00, 0x83, 0x03, 0x01);
else
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x21, 0x01, 0x83, 0x03, 0x03);
} else {
if (test_bit(FEAT_HBM, feat))
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x21, 0x80, 0x83, 0x03, 0x01);
else
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x21, 0x81, 0x83, 0x03, 0x03);
}
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x10, 0xBD);
val = test_bit(FEAT_EARLY_EXIT, feat) ? 0x22 : 0x00;
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, val);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x82, 0xBD);
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, val, val, val, val);
val = test_bit(FEAT_OP_NS, feat) ? 0x4E : 0x1E;
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, val, 0xBD);
if (test_bit(FEAT_HBM, feat)) {
if (test_bit(FEAT_OP_NS, feat))
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x00, 0x00, 0x02,
0x00, 0x04, 0x00, 0x0A, 0x00, 0x16, 0x00, 0x76);
else
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x00, 0x00, 0x01,
0x00, 0x03, 0x00, 0x0B, 0x00, 0x17, 0x00, 0x77);
} else {
if (test_bit(FEAT_OP_NS, feat))
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x00, 0x00, 0x04,
0x00, 0x08, 0x00, 0x14, 0x00, 0x2C, 0x00, 0xEC);
else
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x00, 0x00, 0x02,
0x00, 0x06, 0x00, 0x16, 0x00, 0x2E, 0x00, 0xEE);
}
/*
* Frequency setting: FI, frequency, idle frequency
*
* Description: this sequence possibly overrides some configs early-exit
* and operation set, depending on FI mode.
*/
if (test_bit(FEAT_FRAME_AUTO, feat)) {
if (test_bit(FEAT_OP_NS, feat)) {
/* threshold setting */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x0C, 0xBD);
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x00);
} else {
/* initial frequency */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x92, 0xBD);
if (vrefresh == 60) {
val = test_bit(FEAT_HBM, feat) ? 0x01 : 0x02;
} else {
if (vrefresh != 120)
dev_warn(ctx->dev, "%s: unsupported init freq %d (hs)\n",
__func__, vrefresh);
/* 120Hz */
val = 0x00;
}
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, val);
}
/* target frequency */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x12, 0xBD);
if (test_bit(FEAT_OP_NS, feat)) {
if (idle_vrefresh == 30) {
val = test_bit(FEAT_HBM, feat) ? 0x02 : 0x04;
} else if (idle_vrefresh == 10) {
val = test_bit(FEAT_HBM, feat) ? 0x0A : 0x14;
} else {
if (idle_vrefresh != 1)
dev_warn(ctx->dev, "%s: unsupported target freq %d (ns)\n",
__func__, idle_vrefresh);
/* 1Hz */
val = test_bit(FEAT_HBM, feat) ? 0x76 : 0xEC;
}
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x00, val);
} else {
if (idle_vrefresh == 30) {
val = test_bit(FEAT_HBM, feat) ? 0x03 : 0x06;
} else if (idle_vrefresh == 10) {
val = test_bit(FEAT_HBM, feat) ? 0x0B : 0x16;
} else {
if (idle_vrefresh != 1)
dev_warn(ctx->dev, "%s: unsupported target freq %d (hs)\n",
__func__, idle_vrefresh);
/* 1Hz */
val = test_bit(FEAT_HBM, feat) ? 0x77 : 0xEE;
}
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x00, val);
}
/* step setting */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x9E, 0xBD);
if (test_bit(FEAT_OP_NS, feat)) {
if (test_bit(FEAT_HBM, feat))
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x02, 0x00, 0x0A, 0x00, 0x00);
else
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x04, 0x00, 0x14, 0x00, 0x00);
} else {
if (test_bit(FEAT_HBM, feat))
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x01, 0x00, 0x03, 0x00, 0x0B);
else
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x02, 0x00, 0x06, 0x00, 0x16);
}
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0xAE, 0xBD);
if (test_bit(FEAT_OP_NS, feat)) {
if (idle_vrefresh == 30) {
/* 60Hz -> 30Hz idle */
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x00, 0x00);
} else if (idle_vrefresh == 10) {
/* 60Hz -> 10Hz idle */
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x01, 0x00, 0x00);
} else {
if (idle_vrefresh != 1)
dev_warn(ctx->dev, "%s: unsupported freq step to %d (ns)\n",
__func__, idle_vrefresh);
/* 60Hz -> 1Hz idle */
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x01, 0x03, 0x00);
}
} else {
if (vrefresh == 60) {
if (idle_vrefresh == 30) {
/* 60Hz -> 30Hz idle */
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x01, 0x00, 0x00);
} else if (idle_vrefresh == 10) {
/* 60Hz -> 10Hz idle */
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x01, 0x01, 0x00);
} else {
if (idle_vrefresh != 1)
dev_warn(ctx->dev, "%s: unsupported freq step to %d (hs)\n",
__func__, vrefresh);
/* 60Hz -> 1Hz idle */
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x01, 0x01, 0x03);
}
} else {
if (vrefresh != 120)
dev_warn(ctx->dev, "%s: unsupported freq step from %d (hs)\n",
__func__, vrefresh);
if (idle_vrefresh == 30) {
/* 120Hz -> 30Hz idle */
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x00, 0x00);
} else if (idle_vrefresh == 10) {
/* 120Hz -> 10Hz idle */
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x03, 0x00);
} else {
if (idle_vrefresh != 1)
dev_warn(ctx->dev, "%s: unsupported freq step to %d (hs)\n",
__func__, idle_vrefresh);
/* 120Hz -> 1Hz idle */
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x01, 0x03);
}
}
}
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0xA3);
} else { /* manual */
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x21);
if (test_bit(FEAT_OP_NS, feat)) {
if (vrefresh == 1) {
val = 0x1F;
} else if (vrefresh == 5) {
val = 0x1E;
} else if (vrefresh == 10) {
val = 0x1B;
} else if (vrefresh == 30) {
val = 0x19;
} else {
if (vrefresh != 60)
dev_warn(ctx->dev,
"%s: unsupported manual freq %d (ns)\n",
__func__, vrefresh);
/* 60Hz */
val = 0x18;
}
} else {
if (vrefresh == 1) {
val = 0x07;
} else if (vrefresh == 5) {
val = 0x06;
} else if (vrefresh == 10) {
val = 0x03;
} else if (vrefresh == 30) {
val = 0x02;
} else if (vrefresh == 60) {
val = 0x01;
} else {
if (vrefresh != 120)
dev_warn(ctx->dev,
"%s: unsupported manual freq %d (hs)\n",
__func__, vrefresh);
/* 120Hz */
val = 0x00;
}
}
EXYNOS_DCS_BUF_ADD(ctx, 0x60, val);
}
EXYNOS_DCS_BUF_ADD_SET(ctx, freq_update);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);;
}
/**
* hk3_disable_panel_feat - set the panel at the state of powering up except refresh rate
* @ctx: exynos_panel struct
* @vrefresh: refresh rate
* This function disables HBM, switches to HS, sets manual mode and changeable TE.
*/
static void hk3_disable_panel_feat(struct exynos_panel *ctx, u32 vrefresh)
{
DECLARE_BITMAP(feat, FEAT_MAX);
bitmap_zero(feat, FEAT_MAX);
hk3_set_panel_feat(ctx, vrefresh, 0, feat, true);
}
static void hk3_update_panel_feat(struct exynos_panel *ctx, u32 vrefresh, bool enforce)
{
struct hk3_panel *spanel = to_spanel(ctx);
hk3_set_panel_feat(ctx, vrefresh, spanel->auto_mode_vrefresh, spanel->feat, enforce);
}
static void hk3_update_refresh_mode(struct exynos_panel *ctx,
const struct exynos_panel_mode *pmode,
const u32 idle_vrefresh)
{
struct hk3_panel *spanel = to_spanel(ctx);
u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
/*
* 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__);
return;
}
dev_dbg(ctx->dev, "%s: mode: %s set idle_vrefresh: %u\n", __func__,
pmode->mode.name, idle_vrefresh);
if (idle_vrefresh)
set_bit(FEAT_FRAME_AUTO, spanel->feat);
else
clear_bit(FEAT_FRAME_AUTO, spanel->feat);
if (vrefresh == 120 || idle_vrefresh)
set_bit(FEAT_EARLY_EXIT, spanel->feat);
else
clear_bit(FEAT_EARLY_EXIT, spanel->feat);
spanel->auto_mode_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 hk3_update_idle_state() in
* new frame commit will correct it if the guess is wrong.
*/
ctx->panel_idle_vrefresh = idle_vrefresh;
hk3_update_panel_feat(ctx, vrefresh, false);
/* TODO: (b/303738012) perform notifications asyncly for P24*/
/* Prevent sysfs_notify from resolution switch */
if (ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS)
schedule_work(&ctx->state_notify);
else
backlight_state_changed(ctx->bl);
dev_dbg(ctx->dev, "%s: display state is notified\n", __func__);
}
static void hk3_change_frequency(struct exynos_panel *ctx,
const struct exynos_panel_mode *pmode)
{
u32 vrefresh = drm_mode_vrefresh(&pmode->mode);
u32 idle_vrefresh = 0;
if (vrefresh > ctx->op_hz) {
dev_err(ctx->dev,
"invalid freq setting: op_hz=%u, vrefresh=%u\n",
ctx->op_hz, vrefresh);
return;
}
if (pmode->idle_mode == IDLE_MODE_ON_INACTIVITY)
idle_vrefresh = hk3_get_min_idle_vrefresh(ctx, pmode);
hk3_update_refresh_mode(ctx, pmode, idle_vrefresh);
dev_dbg(ctx->dev, "change to %u hz\n", vrefresh);
}
static void hk3_panel_idle_notification(struct exynos_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 {
snprintf(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 hk3_wait_one_vblank(struct exynos_panel *ctx)
{
struct drm_crtc *crtc = NULL;
if (ctx->exynos_connector.base.state)
crtc = ctx->exynos_connector.base.state->crtc;
DPU_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);
}
DPU_ATRACE_END(__func__);
}
static bool hk3_set_self_refresh(struct exynos_panel *ctx, bool enable)
{
const struct exynos_panel_mode *pmode = ctx->current_mode;
struct hk3_panel *spanel = to_spanel(ctx);
u32 idle_vrefresh;
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->exynos_mode.is_lp_mode) {
/* set 1Hz while self refresh is active, otherwise clear it */
ctx->panel_idle_vrefresh = enable ? 1 : 0;
backlight_state_changed(ctx->bl);
return false;
}
if (spanel->pending_temp_update && enable)
hk3_update_disp_therm(ctx);
idle_vrefresh = hk3_get_min_idle_vrefresh(ctx, pmode);
if (pmode->idle_mode != IDLE_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 ((pmode->idle_mode == IDLE_MODE_ON_INACTIVITY) &&
(spanel->auto_mode_vrefresh != idle_vrefresh)) {
hk3_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->panel_idle_vrefresh == idle_vrefresh)
return false;
DPU_ATRACE_BEGIN(__func__);
hk3_update_refresh_mode(ctx, pmode, idle_vrefresh);
if (idle_vrefresh) {
const int vrefresh = drm_mode_vrefresh(&pmode->mode);
hk3_panel_idle_notification(ctx, 0, vrefresh, 120);
} else if (ctx->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");
hk3_wait_one_vblank(ctx);
}
DPU_ATRACE_END(__func__);
return true;
}
static void hk3_update_lhbm_hist_config(struct exynos_panel *ctx)
{
struct hk3_panel *spanel = to_spanel(ctx);
struct hk3_lhbm_ctl *ctl = &spanel->lhbm_ctl;
const struct exynos_panel_mode *pmode = ctx->current_mode;
const struct drm_display_mode *mode;
int d = 766, r = 115;
if (ctl->hist_roi_configured)
return;
if (!pmode) {
dev_err(ctx->dev, "no current mode set\n");
return;
}
mode = &pmode->mode;
if (mode->hdisplay == 1008) {
d = 575;
r = 87;
}
if (!exynos_drm_connector_set_lhbm_hist(&ctx->exynos_connector,
mode->hdisplay, mode->vdisplay, d, r)) {
ctl->hist_roi_configured = true;
dev_info(ctx->dev, "configure lhbm hist: %d %d %d %d\n",
mode->hdisplay, mode->vdisplay, d, r);
}
}
static int hk3_atomic_check(struct exynos_panel *ctx, struct drm_atomic_state *state)
{
struct drm_connector *conn = &ctx->exynos_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;
struct hk3_panel *spanel = to_spanel(ctx);
hk3_update_lhbm_hist_config(ctx);
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 ((spanel->auto_mode_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 hk3_write_display_mode(struct exynos_panel *ctx,
const struct drm_display_mode *mode)
{
u8 val = HK3_WRCTRLD_BCTRL_BIT;
if (IS_HBM_ON(ctx->hbm_mode))
val |= HK3_WRCTRLD_HBM_BIT;
if (ctx->hbm.local_hbm.enabled)
val |= HK3_WRCTRLD_LOCAL_HBM_BIT;
if (ctx->dimming_on)
val |= HK3_WRCTRLD_DIMMING_BIT;
dev_dbg(ctx->dev,
"%s(wrctrld:0x%x, hbm: %s, dimming: %s local_hbm: %s)\n",
__func__, val, IS_HBM_ON(ctx->hbm_mode) ? "on" : "off",
ctx->dimming_on ? "on" : "off",
ctx->hbm.local_hbm.enabled ? "on" : "off");
EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, val);
}
#define HK3_OPR_VAL_LEN 2
#define HK3_MAX_OPR_VAL 0x3FF
/* Get OPR (on pixel ratio), the unit is percent */
static int hk3_get_opr(struct exynos_panel *ctx, u8 *opr)
{
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
u8 buf[HK3_OPR_VAL_LEN] = {0};
u16 val;
int ret;
DPU_ATRACE_BEGIN(__func__);
EXYNOS_DCS_WRITE_TABLE(ctx, unlock_cmd_f0);
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x00, 0xE7, 0x91);
ret = mipi_dsi_dcs_read(dsi, 0x91, buf, HK3_OPR_VAL_LEN);
EXYNOS_DCS_WRITE_TABLE(ctx, lock_cmd_f0);
DPU_ATRACE_END(__func__);
if (ret != HK3_OPR_VAL_LEN) {
dev_warn(ctx->dev, "Failed to read OPR (%d)\n", ret);
return ret;
}
val = (buf[0] << 8) | buf[1];
*opr = DIV_ROUND_CLOSEST(val * 100, HK3_MAX_OPR_VAL);
dev_dbg(ctx->dev, "%s: %u (0x%X)\n", __func__, *opr, val);
return 0;
}
#define HK3_ZA_THRESHOLD_OPR 80
static void hk3_update_za(struct exynos_panel *ctx)
{
struct hk3_panel *spanel = to_spanel(ctx);
bool enable_za = false;
u8 opr;
if ((spanel->hw_acl_setting > 0) && !spanel->force_za_off) {
if (ctx->panel_rev != PANEL_REV_PROTO1) {
enable_za = true;
} else if (!hk3_get_opr(ctx, &opr)) {
enable_za = (opr > HK3_ZA_THRESHOLD_OPR);
} else {
dev_warn(ctx->dev, "Unable to update za\n");
return;
}
}
if (spanel->hw_za_enabled != enable_za) {
/* LP setting - 0x21 or 0x11: 7.5%, 0x00: off */
u8 val = 0;
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x01, 0x6C, 0x92);
if (enable_za)
val = (ctx->panel_rev == PANEL_REV_PROTO1) ? 0x21 : 0x11;
EXYNOS_DCS_BUF_ADD(ctx, 0x92, val);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
spanel->hw_za_enabled = enable_za;
dev_info(ctx->dev, "%s: %s\n", __func__, enable_za ? "on" : "off");
}
}
#define HK3_ACL_ZA_THRESHOLD_DBV_P1_0 3917
#define HK3_ACL_ZA_THRESHOLD_DBV_P1_1 3781
#define HK3_ACL_ENHANCED_THRESHOLD_DBV 3865
#define HK3_ACL_NORMAL_THRESHOLD_DBV_1 3570
#define HK3_ACL_NORMAL_THRESHOLD_DBV_2 3963
/* updated za when acl mode changed */
static void hk3_set_acl_mode(struct exynos_panel *ctx, enum exynos_acl_mode mode)
{
struct hk3_panel *spanel = to_spanel(ctx);
u16 dbv_th = 0;
u8 setting = 0;
bool enable_acl = false;
/*
* ACL mode and setting:
*
* P1.0
* NORMAL/ENHANCED- 5% (0x01)
* P1.1
* NORMAL/ENHANCED- 7.5% (0x02)
*
* EVT1 and later
* ENHANCED - 17% (0x03)
* NORMAL - 12% (0x02)
* - 7.5% (0x01)
*
* Set 0x00 to disable it
*/
if (ctx->panel_rev == PANEL_REV_PROTO1) {
dbv_th = HK3_ACL_ZA_THRESHOLD_DBV_P1_0;
setting = 0x01;
} else if (ctx->panel_rev == PANEL_REV_PROTO1_1) {
dbv_th = HK3_ACL_ZA_THRESHOLD_DBV_P1_1;
setting = 0x02;
} else {
if (mode == ACL_ENHANCED) {
dbv_th = HK3_ACL_ENHANCED_THRESHOLD_DBV;
setting = 0x03;
} else if (mode == ACL_NORMAL) {
if (spanel->hw_dbv >= HK3_ACL_NORMAL_THRESHOLD_DBV_1 &&
spanel->hw_dbv < HK3_ACL_NORMAL_THRESHOLD_DBV_2) {
dbv_th = HK3_ACL_NORMAL_THRESHOLD_DBV_1;
setting = 0x01;
} else if (spanel->hw_dbv >= HK3_ACL_NORMAL_THRESHOLD_DBV_2) {
dbv_th = HK3_ACL_NORMAL_THRESHOLD_DBV_2;
setting = 0x02;
}
}
}
enable_acl = (spanel->hw_dbv >= dbv_th && IS_HBM_ON(ctx->hbm_mode) && mode != ACL_OFF);
if (enable_acl == false)
setting = 0;
if (spanel->hw_acl_setting != setting) {
EXYNOS_DCS_WRITE_SEQ(ctx, 0x55, setting);
spanel->hw_acl_setting = setting;
dev_info(ctx->dev, "%s: %d\n", __func__, setting);
/* Keep ZA off after EVT1 */
if (ctx->panel_rev < PANEL_REV_EVT1)
hk3_update_za(ctx);
}
}
static int hk3_set_brightness(struct exynos_panel *ctx, u16 br)
{
int ret;
u16 brightness;
struct hk3_panel *spanel = to_spanel(ctx);
if (ctx->current_mode->exynos_mode.is_lp_mode) {
const struct exynos_panel_funcs *funcs;
/* don't stay at pixel-off state in AOD, or black screen is possibly seen */
if (spanel->is_pixel_off) {
EXYNOS_DCS_WRITE_SEQ(ctx, MIPI_DCS_ENTER_NORMAL_MODE);
spanel->is_pixel_off = false;
}
funcs = ctx->desc->exynos_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) {
EXYNOS_DCS_WRITE_TABLE(ctx, pixel_off);
spanel->is_pixel_off = true;
dev_dbg(ctx->dev, "%s: pixel off instead of dbv 0\n", __func__);
}
return 0;
} else if (br && spanel->is_pixel_off) {
EXYNOS_DCS_WRITE_SEQ(ctx, MIPI_DCS_ENTER_NORMAL_MODE);
spanel->is_pixel_off = false;
}
brightness = (br & 0xff) << 8 | br >> 8;
ret = exynos_dcs_set_brightness(ctx, brightness);
if (!ret) {
spanel->hw_dbv = br;
hk3_set_acl_mode(ctx, ctx->acl_mode);
}
return ret;
}
static const struct exynos_dsi_cmd hk3_display_on_cmds[] = {
EXYNOS_DSI_CMD0(unlock_cmd_f0),
EXYNOS_DSI_CMD0(sync_begin),
/* AMP type change (return) */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x4F, 0xF4),
EXYNOS_DSI_CMD_SEQ(0xF4, 0x70),
/* Vreg = 7.1V (return) */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x31, 0xF4),
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_DVT1), 0xF4, 0x1A, 0x1A, 0x1A, 0x1A, 0x1A),
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_DVT1), 0xF4, 0x1B, 0x1B, 0x1B, 0x1B, 0x1B),
EXYNOS_DSI_CMD0(sync_end),
EXYNOS_DSI_CMD0(lock_cmd_f0),
EXYNOS_DSI_CMD_SEQ(MIPI_DCS_SET_DISPLAY_ON),
};
static DEFINE_EXYNOS_CMD_SET(hk3_display_on);
static const struct exynos_dsi_cmd hk3_display_off_cmds[] = {
EXYNOS_DSI_CMD_SEQ(MIPI_DCS_SET_DISPLAY_OFF),
EXYNOS_DSI_CMD0(unlock_cmd_f0),
EXYNOS_DSI_CMD0(sync_begin),
/* AMP type change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x4F, 0xF4),
EXYNOS_DSI_CMD_SEQ(0xF4, 0x50),
/* Vreg = 4.5 */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x31, 0xF4),
EXYNOS_DSI_CMD_SEQ(0xF4, 0x00, 0x00, 0x00, 0x00, 0x00),
EXYNOS_DSI_CMD0(sync_end),
EXYNOS_DSI_CMD0(lock_cmd_f0),
};
static DEFINE_EXYNOS_CMD_SET(hk3_display_off);
static unsigned int hk3_get_te_usec(struct exynos_panel *ctx,
const struct exynos_panel_mode *pmode)
{
struct hk3_panel *spanel = to_spanel(ctx);
if (spanel->hw_vrefresh != 60)
return pmode->exynos_mode.te_usec;
else
return (test_bit(FEAT_OP_NS, spanel->feat) ? HK3_TE_USEC_60HZ_NS :
HK3_TE_USEC_60HZ_HS);
}
static u32 hk3_get_te_width_usec(u32 vrefresh, bool is_ns)
{
/* TODO: update this line if supporting 30 Hz normal mode in the future */
if (vrefresh == 30)
return HK3_TE_USEC_AOD;
else if (vrefresh == 120)
return HK3_TE_USEC_120HZ;
else
return is_ns ? HK3_TE_USEC_60HZ_NS : HK3_TE_USEC_60HZ_HS;
}
static void hk3_wait_for_vsync_done(struct exynos_panel *ctx, u32 vrefresh, bool is_ns)
{
u32 te_width_us = hk3_get_te_width_usec(vrefresh, is_ns);
dev_dbg(ctx->dev, "%s: %dhz\n", __func__, vrefresh);
DPU_ATRACE_BEGIN(__func__);
exynos_panel_wait_for_vsync_done(ctx, te_width_us,
EXYNOS_VREFRESH_TO_PERIOD_USEC(vrefresh));
/* add 1ms tolerance */
exynos_panel_msleep(1);
DPU_ATRACE_END(__func__);
}
/**
* hk3_wait_for_vsync_done_changeable - wait for finishing vsync for changeable TE to avoid
* fake TE at transition from fixed TE to changeable TE.
* @ctx: panel struct
* @vrefresh: current refresh rate
*/
static void hk3_wait_for_vsync_done_changeable(struct exynos_panel *ctx, u32 vrefresh, bool is_ns)
{
int i = 0;
const int timeout = 5;
u32 te_width_us = hk3_get_te_width_usec(vrefresh, is_ns);
while (i++ < timeout) {
ktime_t t;
s64 delta_us;
int period_us = EXYNOS_VREFRESH_TO_PERIOD_USEC(vrefresh);
exynos_panel_wait_for_vblank(ctx);
t = ktime_get();
exynos_panel_wait_for_vblank(ctx);
delta_us = ktime_us_delta(ktime_get(), t);
if (abs(delta_us - period_us) < HK3_TE_PERIOD_DELTA_TOLERANCE_USEC)
break;
}
if (i >= timeout)
dev_warn(ctx->dev, "timeout of waiting for changeable TE @ %d Hz\n", vrefresh);
usleep_range(te_width_us, te_width_us + 10);
}
static bool hk3_is_peak_vrefresh(u32 vrefresh, bool is_ns)
{
return (is_ns && vrefresh == 60) || (!is_ns && vrefresh == 120);
}
static void hk3_set_lp_mode(struct exynos_panel *ctx, const struct exynos_panel_mode *pmode)
{
struct hk3_panel *spanel = to_spanel(ctx);
const u16 brightness = exynos_panel_get_brightness(ctx);
bool is_changeable_te = !test_bit(FEAT_EARLY_EXIT, spanel->feat);
bool is_ns = test_bit(FEAT_OP_NS, spanel->feat);
bool panel_enabled = is_panel_enabled(ctx);
u32 vrefresh = panel_enabled ? spanel->hw_vrefresh : 60;
dev_dbg(ctx->dev, "%s: panel: %s\n", __func__, panel_enabled ? "ON" : "OFF");
DPU_ATRACE_BEGIN(__func__);
hk3_disable_panel_feat(ctx, vrefresh);
if (panel_enabled) {
/* init sequence has sent display-off command already */
if (!hk3_is_peak_vrefresh(vrefresh, is_ns) && is_changeable_te)
hk3_wait_for_vsync_done_changeable(ctx, vrefresh, is_ns);
else
hk3_wait_for_vsync_done(ctx, vrefresh, is_ns);
exynos_panel_send_cmd_set(ctx, &hk3_display_off_cmd_set);
}
/* display should be off here, set dbv before entering lp mode */
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, aod_dbv);
hk3_wait_for_vsync_done(ctx, vrefresh, false);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, aod_on);
exynos_panel_set_binned_lp(ctx, brightness);
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
/* Fixed TE: sync on */
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, 0x51);
/* Default TE pulse width 693us */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x08, 0xB9);
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, 0x0B, 0xE0, 0x00, 0x2F, 0x0B, 0xE0, 0x00, 0x2F);
/* Frequency set for AOD */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x02, 0xB9);
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, 0x00);
/* Auto frame insertion: 1Hz */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x18, 0xBD);
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x04, 0x00, 0x74);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0xB8, 0xBD);
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00, 0x08);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0xC8, 0xBD);
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x03);
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0xA7);
/* Enable early exit */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0xE8, 0xBD);
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x00);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x10, 0xBD);
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x22);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x82, 0xBD);
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x22, 0x22, 0x22, 0x22);
EXYNOS_DCS_BUF_ADD_SET(ctx, freq_update);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
exynos_panel_send_cmd_set(ctx, &hk3_display_on_cmd_set);
spanel->hw_vrefresh = 30;
DPU_ATRACE_END(__func__);
dev_info(ctx->dev, "enter %dhz LP mode\n", drm_mode_vrefresh(&pmode->mode));
}
static void hk3_set_nolp_mode(struct exynos_panel *ctx,
const struct exynos_panel_mode *pmode)
{
struct hk3_panel *spanel = to_spanel(ctx);
dev_dbg(ctx->dev, "%s\n", __func__);
DPU_ATRACE_BEGIN(__func__);
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
/* manual mode */
EXYNOS_DCS_BUF_ADD(ctx, 0xBD, 0x21);
/* Changeable TE is a must to ensure command sync */
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, 0x04);
/* Changeable TE width setting and frequency */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x04, 0xB9);
/* width 693us in AOD mode */
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, 0x0B, 0xE0, 0x00, 0x2F);
/* AOD 30Hz */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x01, 0x60);
EXYNOS_DCS_BUF_ADD(ctx, 0x60, 0x00);
EXYNOS_DCS_BUF_ADD_SET(ctx, freq_update);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
spanel->hw_idle_vrefresh = 0;
hk3_wait_for_vsync_done(ctx, 30, false);
exynos_panel_send_cmd_set(ctx, &hk3_display_off_cmd_set);
hk3_wait_for_vsync_done(ctx, 30, false);
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
/* TE width setting */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x04, 0xB9);
EXYNOS_DCS_BUF_ADD(ctx, 0xB9, 0x0B, 0xBB, 0x00, 0x2F, /* changeable TE */
0x0B, 0xBB, 0x00, 0x2F, 0x0B, 0xBB, 0x00, 0x2F); /* fixed TE */
/* disabling AOD low Mode is a must before aod-off */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x52, 0x94);
EXYNOS_DCS_BUF_ADD(ctx, 0x94, 0x00);
EXYNOS_DCS_BUF_ADD_SET(ctx, lock_cmd_f0);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, aod_off);
hk3_update_panel_feat(ctx, drm_mode_vrefresh(&pmode->mode), true);
/* backlight control and dimming */
hk3_write_display_mode(ctx, &pmode->mode);
hk3_change_frequency(ctx, pmode);
exynos_panel_send_cmd_set(ctx, &hk3_display_on_cmd_set);
DPU_ATRACE_END(__func__);
dev_info(ctx->dev, "exit LP mode\n");
}
static const struct exynos_dsi_cmd hk3_init_cmds[] = {
EXYNOS_DSI_CMD_SEQ_DELAY(10, MIPI_DCS_EXIT_SLEEP_MODE),
EXYNOS_DSI_CMD0(unlock_cmd_f0),
/* AMP type change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x4F, 0xF4),
EXYNOS_DSI_CMD_SEQ(0xF4, 0x50),
/* VREG 4.5V */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x31, 0xF4),
EXYNOS_DSI_CMD_SEQ(0xF4, 0x00, 0x00, 0x00, 0x00, 0x00),
EXYNOS_DSI_CMD(lock_cmd_f0, 110),
/* Enable TE*/
EXYNOS_DSI_CMD_SEQ(MIPI_DCS_SET_TEAR_ON),
EXYNOS_DSI_CMD0(unlock_cmd_f0),
/* AOD Transition Set */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_DVT1), 0xB0, 0x00, 0x03, 0xBB),
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_DVT1), 0xBB, 0x41),
/* TSP SYNC Enable (Auto Set) */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x3C, 0xB9),
EXYNOS_DSI_CMD_SEQ(0xB9, 0x19, 0x09),
/* FFC: off, 165MHz, MIPI Speed 1368 Mbps */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x36, 0xC5),
EXYNOS_DSI_CMD_SEQ(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),
/* TE width setting */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x04, 0xB9),
EXYNOS_DSI_CMD_SEQ(0xB9, 0x0B, 0xBB, 0x00, 0x2F, /* changeable TE */
0x0B, 0xBB, 0x00, 0x2F, 0x0B, 0xBB, 0x00, 0x2F), /* fixed TE */
/* enable OPEC (auto still IMG detect off) */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_MP), 0xB0, 0x00, 0x1D, 0x63),
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_MP), 0x63, 0x02, 0x18),
/* PMIC Fast Discharge off */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x18, 0xB1),
EXYNOS_DSI_CMD_SEQ(0xB1, 0x55, 0x01),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x13, 0xB1),
EXYNOS_DSI_CMD_SEQ(0xB1, 0x80),
EXYNOS_DSI_CMD0(freq_update),
EXYNOS_DSI_CMD0(lock_cmd_f0),
/* CASET: 1343 */
EXYNOS_DSI_CMD_SEQ(MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x05, 0x3F),
/* PASET: 2991 */
EXYNOS_DSI_CMD_SEQ(MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x0B, 0xAF),
};
static DEFINE_EXYNOS_CMD_SET(hk3_init);
static const struct exynos_dsi_cmd hk3_ns_gamma_fix_cmds[] = {
EXYNOS_DSI_CMD0(unlock_cmd_f0),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x02, 0x3F, 0xCB),
EXYNOS_DSI_CMD_SEQ(0xCB, 0x0A),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x02, 0x45, 0xCB),
EXYNOS_DSI_CMD_SEQ(0xCB, 0x0A),
EXYNOS_DSI_CMD0(freq_update),
EXYNOS_DSI_CMD0(lock_cmd_f0),
};
static DEFINE_EXYNOS_CMD_SET(hk3_ns_gamma_fix);
static void hk3_lhbm_luminance_opr_setting(struct exynos_panel *ctx)
{
struct hk3_panel *spanel = to_spanel(ctx);
bool is_ns_mode = test_bit(FEAT_OP_NS, spanel->feat);
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x02, 0xF9, 0x95);
/* DBV setting */
EXYNOS_DCS_BUF_ADD(ctx, 0x95, 0x00, 0x40, 0x0C, 0x01, 0x90, 0x33, 0x06, 0x60,
0xCC, 0x11, 0x92, 0x7F);
EXYNOS_DCS_BUF_ADD(ctx, 0x71, 0xC6, 0x00, 0x00, 0x19);
/* 120Hz base (HS) offset */
EXYNOS_DCS_BUF_ADD(ctx, 0x6C, 0x9C, 0x9F, 0x59, 0x58, 0x50, 0x2F, 0x2B, 0x2E);
EXYNOS_DCS_BUF_ADD(ctx, 0x71, 0xC6, 0x00, 0x00, 0x6A);
/* 60Hz base (NS) offset */
EXYNOS_DCS_BUF_ADD(ctx, 0x6C, 0xA0, 0xA7, 0x57, 0x5C, 0x52, 0x37, 0x37, 0x40);
/* Target frequency */
EXYNOS_DCS_BUF_ADD(ctx, 0x60, is_ns_mode ? 0x18 : 0x00);
EXYNOS_DCS_BUF_ADD_SET(ctx, freq_update);
/* Opposite setting of target frequency */
EXYNOS_DCS_BUF_ADD(ctx, 0x60, is_ns_mode ? 0x00 : 0x18);
EXYNOS_DCS_BUF_ADD_SET(ctx, freq_update);
/* Target frequency */
EXYNOS_DCS_BUF_ADD(ctx, 0x60, is_ns_mode ? 0x18 : 0x00);
EXYNOS_DCS_BUF_ADD_SET(ctx, freq_update);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
}
static void hk3_negative_field_setting(struct exynos_panel *ctx)
{
/* all settings will take effect in AOD mode automatically */
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
/* Vint -3V */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x21, 0xF4);
EXYNOS_DCS_BUF_ADD(ctx, 0xF4, 0x1E);
/* Vaint -4V */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x69, 0xF4);
EXYNOS_DCS_BUF_ADD(ctx, 0xF4, 0x78);
/* VGL -8V */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x17, 0xF4);
EXYNOS_DCS_BUF_ADD(ctx, 0xF4, 0x1E);
EXYNOS_DCS_BUF_ADD_SET(ctx, freq_update);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
}
static int hk3_enable(struct drm_panel *panel)
{
struct exynos_panel *ctx = container_of(panel, struct exynos_panel, panel);
const struct exynos_panel_mode *pmode = ctx->current_mode;
const struct drm_display_mode *mode;
struct hk3_panel *spanel = to_spanel(ctx);
const bool needs_reset = !is_panel_enabled(ctx);
bool is_ns = needs_reset ? false : test_bit(FEAT_OP_NS, spanel->feat);
struct drm_dsc_picture_parameter_set pps_payload;
bool is_fhd;
u32 vrefresh;
if (!pmode) {
dev_err(ctx->dev, "no current mode set\n");
return -EINVAL;
}
mode = &pmode->mode;
is_fhd = mode->hdisplay == 1008;
vrefresh = drm_mode_vrefresh(mode);
dev_info(ctx->dev, "%s (%s)\n", __func__, is_fhd ? "fhd" : "wqhd");
DPU_ATRACE_BEGIN(__func__);
if (needs_reset)
exynos_panel_reset(ctx);
if (ctx->mode_in_progress == MODE_RES_IN_PROGRESS) {
u32 te_width_us = hk3_get_te_width_usec(vrefresh, is_ns);
exynos_panel_wait_for_vsync_done(ctx, te_width_us,
EXYNOS_VREFRESH_TO_PERIOD_USEC(vrefresh));
} else if (ctx->mode_in_progress == MODE_RES_AND_RR_IN_PROGRESS) {
u32 te_width_us = hk3_get_te_width_usec(ctx->last_rr, is_ns);
exynos_panel_wait_for_vsync_done(ctx, te_width_us,
EXYNOS_VREFRESH_TO_PERIOD_USEC(ctx->last_rr));
}
PANEL_SEQ_LABEL_BEGIN("init");
/* DSC related configuration */
drm_dsc_pps_payload_pack(&pps_payload,
is_fhd ? &fhd_pps_config : &wqhd_pps_config);
EXYNOS_DCS_WRITE_SEQ(ctx, 0x9D, 0x01);
EXYNOS_PPS_WRITE_BUF(ctx, &pps_payload);
if (needs_reset) {
exynos_panel_send_cmd_set(ctx, &hk3_init_cmd_set);
if (ctx->panel_rev == PANEL_REV_PROTO1)
hk3_lhbm_luminance_opr_setting(ctx);
if (ctx->panel_rev >= PANEL_REV_DVT1)
hk3_negative_field_setting(ctx);
spanel->is_pixel_off = false;
ctx->dsi_hs_clk = MIPI_DSI_FREQ_DEFAULT;
}
PANEL_SEQ_LABEL_END("init");
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
EXYNOS_DCS_BUF_ADD(ctx, 0xC3, is_fhd ? 0x0D : 0x0C);
/* 8/10bit config for QHD/FHD */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x01, 0xF2);
EXYNOS_DCS_BUF_ADD(ctx, 0xF2, is_fhd ? 0x81 : 0x01);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
if (needs_reset && spanel->material == MATERIAL_E7_DOE)
exynos_panel_send_cmd_set(ctx, &hk3_ns_gamma_fix_cmd_set);
if (pmode->exynos_mode.is_lp_mode) {
hk3_set_lp_mode(ctx, pmode);
} else {
hk3_update_panel_feat(ctx, vrefresh, true);
hk3_write_display_mode(ctx, mode); /* dimming and HBM */
hk3_change_frequency(ctx, pmode);
if (needs_reset || (ctx->panel_state == PANEL_STATE_BLANK)) {
hk3_wait_for_vsync_done(ctx, needs_reset ? 60 : vrefresh, is_ns);
exynos_panel_send_cmd_set(ctx, &hk3_display_on_cmd_set);
}
}
spanel->lhbm_ctl.hist_roi_configured = false;
DPU_ATRACE_END(__func__);
return 0;
}
static int hk3_disable(struct drm_panel *panel)
{
struct exynos_panel *ctx = container_of(panel, struct exynos_panel, panel);
struct hk3_panel *spanel = to_spanel(ctx);
u32 vrefresh = spanel->hw_vrefresh;
int ret;
dev_info(ctx->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_RES_AND_RR_IN_PROGRESS) {
dev_dbg(ctx->dev, "%s: RRS in progress, skip\n", __func__);
return 0;
}
ret = exynos_panel_disable(panel);
if (ret)
return ret;
hk3_disable_panel_feat(ctx, 60);
/*
* can't get crtc pointer here, fallback to sleep. hk3_disable_panel_feat() sends freq
* update command to trigger early exit if auto mode is enabled before, waiting for one
* frame (for either auto or manual mode) should be sufficient to make sure the previous
* commands become effective.
*/
exynos_panel_msleep(EXYNOS_VREFRESH_TO_PERIOD_USEC(vrefresh) / 1000 + 1);
exynos_panel_send_cmd_set(ctx, &hk3_display_off_cmd_set);
exynos_panel_msleep(20);
if (ctx->panel_state == PANEL_STATE_OFF)
EXYNOS_DCS_WRITE_SEQ_DELAY(ctx, 100, MIPI_DCS_ENTER_SLEEP_MODE);
/* panel register state gets reset after disabling hardware */
bitmap_clear(spanel->hw_feat, 0, FEAT_MAX);
spanel->hw_vrefresh = 60;
spanel->hw_idle_vrefresh = 0;
spanel->hw_acl_setting = 0;
spanel->hw_za_enabled = false;
spanel->hw_dbv = 0;
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
/**
* hk3_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 hk3_update_idle_state(struct exynos_panel *ctx)
{
s64 delta_us;
struct hk3_panel *spanel = to_spanel(ctx);
ctx->panel_idle_vrefresh = 0;
if (!test_bit(FEAT_FRAME_AUTO, spanel->feat))
return;
delta_us = ktime_us_delta(ktime_get(), ctx->last_commit_ts);
if (delta_us < EARLY_EXIT_THRESHOLD_US) {
dev_dbg(ctx->dev, "skip early exit. %lldus since last commit\n",
delta_us);
return;
}
/* triggering early exit causes a switch to 120hz */
ctx->last_mode_set_ts = ktime_get();
DPU_ATRACE_BEGIN(__func__);
if (!ctx->idle_delay_ms && spanel->force_changeable_te) {
dev_dbg(ctx->dev, "sending early exit out cmd\n");
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
EXYNOS_DCS_BUF_ADD_SET(ctx, freq_update);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
} else {
/* turn off auto mode to prevent panel from lowering frequency too fast */
hk3_update_refresh_mode(ctx, ctx->current_mode, 0);
}
DPU_ATRACE_END(__func__);
}
static void hk3_commit_done(struct exynos_panel *ctx)
{
struct hk3_panel *spanel = to_spanel(ctx);
if (ctx->current_mode->exynos_mode.is_lp_mode)
return;
/* 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;
}
hk3_update_idle_state(ctx);
hk3_update_za(ctx);
if (spanel->pending_temp_update)
hk3_update_disp_therm(ctx);
}
static void hk3_set_hbm_mode(struct exynos_panel *ctx,
enum exynos_hbm_mode mode)
{
struct hk3_panel *spanel = to_spanel(ctx);
const struct exynos_panel_mode *pmode = ctx->current_mode;
if (mode == ctx->hbm_mode)
return;
if (unlikely(!pmode))
return;
ctx->hbm_mode = mode;
if (IS_HBM_ON(mode)) {
set_bit(FEAT_HBM, spanel->feat);
/* enforce IRC on for factory builds */
#ifndef PANEL_FACTORY_BUILD
if (mode == HBM_ON_IRC_ON)
clear_bit(ctx->panel_rev >= PANEL_REV_EVT1 ?
FEAT_IRC_Z_MODE : FEAT_IRC_OFF, spanel->feat);
else
set_bit(ctx->panel_rev >= PANEL_REV_EVT1 ?
FEAT_IRC_Z_MODE : FEAT_IRC_OFF, spanel->feat);
#endif
} else {
clear_bit(FEAT_HBM, spanel->feat);
clear_bit(ctx->panel_rev >= PANEL_REV_EVT1 ?
FEAT_IRC_Z_MODE : FEAT_IRC_OFF, spanel->feat);
}
if (ctx->panel_state == PANEL_STATE_NORMAL) {
if (!IS_HBM_ON(mode))
hk3_write_display_mode(ctx, &pmode->mode);
hk3_update_panel_feat(ctx, drm_mode_vrefresh(&pmode->mode), false);
if (IS_HBM_ON(mode))
hk3_write_display_mode(ctx, &pmode->mode);
}
}
static void hk3_set_dimming_on(struct exynos_panel *ctx,
bool dimming_on)
{
const struct exynos_panel_mode *pmode = ctx->current_mode;
ctx->dimming_on = dimming_on;
if (pmode->exynos_mode.is_lp_mode) {
dev_info(ctx->dev,"in lp mode, skip to update");
return;
}
hk3_write_display_mode(ctx, &pmode->mode);
}
static void hk3_set_local_hbm_brightness(struct exynos_panel *ctx, bool is_first_stage)
{
struct hk3_panel *spanel = to_spanel(ctx);
struct hk3_lhbm_ctl *ctl = &spanel->lhbm_ctl;
const u8 *brt;
enum hk3_lhbm_brt_overdrive_group group = LHBM_OVERDRIVE_GRP_MAX;
static u8 cmd[LHBM_BRT_CMD_LEN];
int i;
if (!is_local_hbm_post_enabling_supported(ctx))
return;
dev_info(ctx->dev, "set LHBM brightness at %s stage\n", is_first_stage ? "1st" : "2nd");
if (is_first_stage) {
u32 gray = exynos_drm_connector_get_lhbm_gray_level(&ctx->exynos_connector);
u32 dbv = exynos_panel_get_brightness(ctx);
u32 normal_dbv_max = ctx->desc->brt_capability->normal.level.max;
u32 normal_nit_max = ctx->desc->brt_capability->normal.nits.max;
u32 luma = 0;
if (gray < 15) {
group = LHBM_OVERDRIVE_GRP_0_NIT;
} else {
if (dbv <= normal_dbv_max)
luma = panel_cmn_calc_gamma_2_2_luminance(dbv, normal_dbv_max,
normal_nit_max);
else
luma = panel_cmn_calc_linear_luminance(dbv, 700, -1271);
luma = panel_cmn_calc_gamma_2_2_luminance(gray, 255, luma);
if (luma < 6)
group = LHBM_OVERDRIVE_GRP_6_NIT;
else if (luma < 50)
group = LHBM_OVERDRIVE_GRP_50_NIT;
else if (luma < 300)
group = LHBM_OVERDRIVE_GRP_300_NIT;
else
group = LHBM_OVERDRIVE_GRP_MAX;
}
dev_dbg(ctx->dev, "check LHBM overdrive condition | gray=%u dbv=%u luma=%u\n",
gray, dbv, luma);
}
if (group < LHBM_OVERDRIVE_GRP_MAX) {
brt = ctl->brt_overdrive[group];
ctl->overdrived = true;
} else {
brt = ctl->brt_normal;
ctl->overdrived = false;
}
cmd[0] = lhbm_brightness_reg;
for (i = 0; i < LHBM_BRT_LEN; i++)
cmd[i+1] = brt[i];
dev_dbg(ctx->dev, "set %s brightness: [%d] %*ph\n",
ctl->overdrived ? "overdrive" : "normal",
ctl->overdrived ? group : -1, LHBM_BRT_LEN, brt);
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
EXYNOS_DCS_BUF_ADD_SET(ctx, lhbm_brightness_index);
EXYNOS_DCS_BUF_ADD_SET(ctx, cmd);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
}
static void hk3_set_local_hbm_mode(struct exynos_panel *ctx,
bool local_hbm_en)
{
const struct exynos_panel_mode *pmode = ctx->current_mode;
/* TODO: LHBM Position & Size */
hk3_write_display_mode(ctx, &pmode->mode);
if (local_hbm_en)
hk3_set_local_hbm_brightness(ctx, true);
}
static void hk3_set_local_hbm_mode_post(struct exynos_panel *ctx)
{
const struct hk3_panel *spanel = to_spanel(ctx);
if (spanel->lhbm_ctl.overdrived)
hk3_set_local_hbm_brightness(ctx, false);
}
static void hk3_mode_set(struct exynos_panel *ctx,
const struct exynos_panel_mode *pmode)
{
hk3_change_frequency(ctx, pmode);
}
static bool hk3_is_mode_seamless(const struct exynos_panel *ctx,
const struct exynos_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) &&
(c->flags == n->flags);
}
static int hk3_set_op_hz(struct exynos_panel *ctx, unsigned int hz)
{
struct hk3_panel *spanel = to_spanel(ctx);
u32 vrefresh = drm_mode_vrefresh(&ctx->current_mode->mode);
if (vrefresh > hz || (hz != 60 && hz != 120)) {
dev_err(ctx->dev, "invalid op_hz=%d for vrefresh=%d\n",
hz, vrefresh);
return -EINVAL;
}
DPU_ATRACE_BEGIN(__func__);
ctx->op_hz = hz;
if (hz == 60)
set_bit(FEAT_OP_NS, spanel->feat);
else
clear_bit(FEAT_OP_NS, spanel->feat);
if (is_panel_active(ctx))
hk3_update_panel_feat(ctx, vrefresh, false);
dev_info(ctx->dev, "%s op_hz at %d\n",
is_panel_active(ctx) ? "set" : "cache", hz);
DPU_ATRACE_END(__func__);
return 0;
}
static int hk3_read_id(struct exynos_panel *ctx)
{
return exynos_panel_read_ddic_id(ctx);
}
/* Note the format is 0x<DAh><DBh><DCh> which is reverse of bootloader (0x<DCh><DBh><DAh>) */
static void hk3_get_panel_material(struct exynos_panel *ctx, u32 id)
{
struct hk3_panel *spanel = to_spanel(ctx);
switch (id) {
case 0x000A4000:
spanel->material = MATERIAL_E6;
break;
case 0x000A4020:
spanel->material = MATERIAL_E7_DOE;
break;
case 0x000A4420:
spanel->material = MATERIAL_E7;
break;
case 0x000A4520:
spanel->material = MATERIAL_LPC5;
break;
default:
dev_warn(ctx->dev, "unknown material from panel (%#x), default to E7\n", id);
spanel->material = MATERIAL_E7;
break;
}
dev_info(ctx->dev, "%s: %d\n", __func__, spanel->material);
}
static void hk3_get_panel_rev(struct exynos_panel *ctx, u32 id)
{
/* extract command 0xDB */
u8 build_code = (id & 0xFF00) >> 8;
u8 rev = ((build_code & 0xE0) >> 3) | ((build_code & 0x0C) >> 2);
exynos_panel_get_panel_rev(ctx, rev);
hk3_get_panel_material(ctx, id);
}
static void hk3_normal_mode_work(struct exynos_panel *ctx)
{
if (ctx->self_refresh_active) {
hk3_update_disp_therm(ctx);
} else {
struct hk3_panel *spanel = to_spanel(ctx);
spanel->pending_temp_update = true;
}
}
static void hk3_pre_update_ffc(struct exynos_panel *ctx)
{
dev_dbg(ctx->dev, "%s\n", __func__);
DPU_ATRACE_BEGIN(__func__);
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
/* FFC off */
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x36, 0xC5);
EXYNOS_DCS_BUF_ADD(ctx, 0xC5, 0x10);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
DPU_ATRACE_END(__func__);
}
static void hk3_update_ffc(struct exynos_panel *ctx, unsigned int hs_clk)
{
dev_dbg(ctx->dev, "%s: hs_clk: current=%d, target=%d\n",
__func__, ctx->dsi_hs_clk, hs_clk);
DPU_ATRACE_BEGIN(__func__);
if (hs_clk != MIPI_DSI_FREQ_DEFAULT && hs_clk != MIPI_DSI_FREQ_ALTERNATIVE) {
dev_warn(ctx->dev, "%s: invalid hs_clk=%d for FFC\n", __func__, hs_clk);
} else if (ctx->dsi_hs_clk != hs_clk) {
dev_info(ctx->dev, "%s: updating for hs_clk=%d\n", __func__, hs_clk);
ctx->dsi_hs_clk = hs_clk;
/* Update FFC */
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x37, 0xC5);
if (hs_clk == MIPI_DSI_FREQ_DEFAULT)
EXYNOS_DCS_BUF_ADD(ctx, 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_ALTERNATIVE */
EXYNOS_DCS_BUF_ADD(ctx, 0xC5, 0x10, 0x50, 0x05, 0x4E, 0x74, 0x40, 0x00,
0x40, 0x00, 0x40, 0x00, 0x4E, 0x74, 0x40, 0x00,
0x40, 0x00, 0x40, 0x00, 0x4E, 0x74, 0x40, 0x00,
0x40, 0x00, 0x40, 0x00, 0x4E, 0x74, 0x40, 0x00,
0x40, 0x00, 0x40, 0x00);
EXYNOS_DCS_BUF_ADD_SET(ctx, lock_cmd_f0);
}
/* FFC on */
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x36, 0xC5);
EXYNOS_DCS_BUF_ADD(ctx, 0xC5, 0x11);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
DPU_ATRACE_END(__func__);
}
static const struct exynos_display_underrun_param underrun_param = {
.te_idle_us = 350,
.te_var = 1,
};
static const u32 hk3_bl_range[] = {
94, 180, 270, 360, 3307
};
#define HK3_WQHD_DSC {\
.enabled = true,\
.dsc_count = 2,\
.slice_count = 2,\
.slice_height = 187,\
.cfg = &wqhd_pps_config,\
}
#define HK3_FHD_DSC {\
.enabled = true,\
.dsc_count = 2,\
.slice_count = 2,\
.slice_height = 187,\
.cfg = &fhd_pps_config,\
}
static const struct exynos_panel_mode hk3_modes[] = {
#ifdef PANEL_FACTORY_BUILD
{
.mode = {
.name = "1344x2992x1",
.clock = 4545,
.hdisplay = 1344,
.hsync_start = 1344 + 80, // add hfp
.hsync_end = 1344 + 80 + 24, // add hsa
.htotal = 1344 + 80 + 24 + 52, // add hbp
.vdisplay = 2992,
.vsync_start = 2992 + 12, // add vfp
.vsync_end = 2992 + 12 + 4, // add vsa
.vtotal = 2992 + 12 + 4 + 22, // add vbp
.flags = 0,
.width_mm = 70,
.height_mm = 155,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = HK3_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = HK3_TE2_RISING_EDGE_OFFSET,
.falling_edge = HK3_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = IDLE_MODE_UNSUPPORTED,
},
{
.mode = {
.name = "1344x2992x5",
.clock = 22725,
.hdisplay = 1344,
.hsync_start = 1344 + 80, // add hfp
.hsync_end = 1344 + 80 + 24, // add hsa
.htotal = 1344 + 80 + 24 + 52, // add hbp
.vdisplay = 2992,
.vsync_start = 2992 + 12, // add vfp
.vsync_end = 2992 + 12 + 4, // add vsa
.vtotal = 2992 + 12 + 4 + 22, // add vbp
.flags = 0,
.width_mm = 70,
.height_mm = 155,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = HK3_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = HK3_TE2_RISING_EDGE_OFFSET,
.falling_edge = HK3_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = IDLE_MODE_UNSUPPORTED,
},
{
.mode = {
.name = "1344x2992x10",
.clock = 45147,
.hdisplay = 1344,
.hsync_start = 1344 + 80, // add hfp
.hsync_end = 1344 + 80 + 24, // add hsa
.htotal = 1344 + 80 + 24 + 42, // add hbp
.vdisplay = 2992,
.vsync_start = 2992 + 12, // add vfp
.vsync_end = 2992 + 12 + 4, // add vsa
.vtotal = 2992 + 12 + 4 + 22, // add vbp
.flags = 0,
.width_mm = 70,
.height_mm = 155,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = HK3_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = HK3_TE2_RISING_EDGE_OFFSET,
.falling_edge = HK3_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = IDLE_MODE_UNSUPPORTED,
},
{
.mode = {
.name = "1344x2992x30",
.clock = 135441,
.hdisplay = 1344,
.hsync_start = 1344 + 80, // add hfp
/* change hsa and hbp to avoid conflicting to LP mode 30Hz */
.hsync_end = 1344 + 80 + 22, // add hsa
.htotal = 1344 + 80 + 22 + 44, // add hbp
.vdisplay = 2992,
.vsync_start = 2992 + 12, // add vfp
.vsync_end = 2992 + 12 + 4, // add vsa
.vtotal = 2992 + 12 + 4 + 22, // add vbp
.flags = 0,
.width_mm = 70,
.height_mm = 155,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = HK3_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = HK3_TE2_RISING_EDGE_OFFSET,
.falling_edge = HK3_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = IDLE_MODE_UNSUPPORTED,
},
#endif
{
.mode = {
.name = "1344x2992x60",
.clock = 270882,
.hdisplay = 1344,
.hsync_start = 1344 + 80, // add hfp
.hsync_end = 1344 + 80 + 24, // add hsa
.htotal = 1344 + 80 + 24 + 42, // add hbp
.vdisplay = 2992,
.vsync_start = 2992 + 12, // add vfp
.vsync_end = 2992 + 12 + 4, // add vsa
.vtotal = 2992 + 12 + 4 + 22, // add vbp
.flags = 0,
.type = DRM_MODE_TYPE_PREFERRED,
.width_mm = 70,
.height_mm = 155,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = HK3_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = HK3_TE2_RISING_EDGE_OFFSET,
.falling_edge = HK3_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = IDLE_MODE_ON_SELF_REFRESH,
},
{
.mode = {
.name = "1344x2992x120",
.clock = 541764,
.hdisplay = 1344,
.hsync_start = 1344 + 80, // add hfp
.hsync_end = 1344 + 80 + 24, // add hsa
.htotal = 1344 + 80 + 24 + 42, // add hbp
.vdisplay = 2992,
.vsync_start = 2992 + 12, // add vfp
.vsync_end = 2992 + 12 + 4, // add vsa
.vtotal = 2992 + 12 + 4 + 22, // add vbp
.flags = 0,
.width_mm = 70,
.height_mm = 155,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = HK3_TE_USEC_120HZ,
.bpc = 8,
.dsc = HK3_WQHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = HK3_TE2_RISING_EDGE_OFFSET,
.falling_edge = HK3_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = IDLE_MODE_ON_INACTIVITY,
},
#ifndef PANEL_FACTORY_BUILD
{
.mode = {
.name = "1008x2244x60",
.clock = 157320,
.hdisplay = 1008,
.hsync_start = 1008 + 80, // add hfp
.hsync_end = 1008 + 80 + 24, // add hsa
.htotal = 1008 + 80 + 24 + 38, // add hbp
.vdisplay = 2244,
.vsync_start = 2244 + 12, // add vfp
.vsync_end = 2244 + 12 + 4, // add vsa
.vtotal = 2244 + 12 + 4 + 20, // add vbp
.flags = 0,
.width_mm = 70,
.height_mm = 155,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = HK3_FHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = HK3_TE2_RISING_EDGE_OFFSET,
.falling_edge = HK3_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = IDLE_MODE_ON_SELF_REFRESH,
},
{
.mode = {
.name = "1008x2244x120",
.clock = 314640,
.hdisplay = 1008,
.hsync_start = 1008 + 80, // add hfp
.hsync_end = 1008 + 80 + 24, // add hsa
.htotal = 1008 + 80 + 24 + 38, // add hbp
.vdisplay = 2244,
.vsync_start = 2244 + 12, // add vfp
.vsync_end = 2244 + 12 + 4, // add vsa
.vtotal = 2244 + 12 + 4 + 20, // add vbp
.flags = 0,
.width_mm = 70,
.height_mm = 155,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = HK3_TE_USEC_120HZ,
.bpc = 8,
.dsc = HK3_FHD_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = HK3_TE2_RISING_EDGE_OFFSET,
.falling_edge = HK3_TE2_FALLING_EDGE_OFFSET,
},
.idle_mode = IDLE_MODE_ON_INACTIVITY,
},
#endif
};
static const struct exynos_panel_mode hk3_lp_modes[] = {
{
.mode = {
.name = "1344x2992x30",
.clock = 135441,
.hdisplay = 1344,
.hsync_start = 1344 + 80, // add hfp
.hsync_end = 1344 + 80 + 24, // add hsa
.htotal = 1344 + 80 + 24 + 42, // add hbp
.vdisplay = 2992,
.vsync_start = 2992 + 12, // add vfp
.vsync_end = 2992 + 12 + 4, // add vsa
.vtotal = 2992 + 12 + 4 + 22, // add vbp
.flags = 0,
.width_mm = 70,
.height_mm = 155,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = HK3_TE_USEC_AOD,
.bpc = 8,
.dsc = HK3_WQHD_DSC,
.underrun_param = &underrun_param,
.is_lp_mode = true,
},
},
#ifndef PANEL_FACTORY_BUILD
{
.mode = {
.name = "1008x2244x30",
.clock = 78660,
.hdisplay = 1008,
.hsync_start = 1008 + 80, // add hfp
.hsync_end = 1008 + 80 + 24, // add hsa
.htotal = 1008 + 80 + 24 + 38, // add hbp
.vdisplay = 2244,
.vsync_start = 2244 + 12, // add vfp
.vsync_end = 2244 + 12 + 4, // add vsa
.vtotal = 2244 + 12 + 4 + 20, // add vbp
.flags = 0,
.width_mm = 70,
.height_mm = 155,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = HK3_TE_USEC_AOD,
.bpc = 8,
.dsc = HK3_FHD_DSC,
.underrun_param = &underrun_param,
.is_lp_mode = true,
},
},
#endif
};
static void hk3_calc_lhbm_od_brightness(u8 n_fine, u8 n_coarse,
u8 *o_fine, u8 *o_coarse,
u8 fine_offset_0, u8 fine_offset_1,
u8 coarse_offset_0, u8 coarse_offset_1)
{
if (((int)n_fine + (int)fine_offset_0) <= 0xFF) {
*o_coarse = n_coarse + coarse_offset_0;
*o_fine = n_fine + fine_offset_0;
} else {
*o_coarse = n_coarse + coarse_offset_1;
*o_fine = n_fine - fine_offset_1;
}
}
static void hk3_lhbm_brightness_init(struct exynos_panel *ctx)
{
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
struct hk3_panel *spanel = to_spanel(ctx);
struct hk3_lhbm_ctl *ctl = &spanel->lhbm_ctl;
int ret;
u8 g_coarse, b_coarse;
u8 *p_norm = ctl->brt_normal;
u8 *p_over;
enum hk3_lhbm_brt_overdrive_group grp;
EXYNOS_DCS_WRITE_TABLE(ctx, unlock_cmd_f0);
EXYNOS_DCS_WRITE_TABLE(ctx, lhbm_brightness_index);
ret = mipi_dsi_dcs_read(dsi, lhbm_brightness_reg, p_norm, LHBM_BRT_LEN);
EXYNOS_DCS_WRITE_TABLE(ctx, lock_cmd_f0);
if (ret != LHBM_BRT_LEN) {
dev_err(ctx->dev, "failed to read lhbm brightness ret=%d\n", ret);
return;
}
dev_dbg(ctx->dev, "lhbm normal brightness: %*ph\n", LHBM_BRT_LEN, p_norm);
/* 0 nit */
grp = LHBM_OVERDRIVE_GRP_0_NIT;
p_over = ctl->brt_overdrive[grp];
hk3_calc_lhbm_od_brightness(p_norm[LHBM_R_FINE], p_norm[LHBM_R_COARSE],
&p_over[LHBM_R_FINE], &p_over[LHBM_R_COARSE],
0x00, 0x00, 0x01, 0x01);
hk3_calc_lhbm_od_brightness(p_norm[LHBM_G_FINE], p_norm[LHBM_GB_COARSE],
&p_over[LHBM_G_FINE], &g_coarse,
0x00, 0x00, 0x10, 0x10);
hk3_calc_lhbm_od_brightness(p_norm[LHBM_B_FINE], p_norm[LHBM_GB_COARSE],
&p_over[LHBM_B_FINE], &b_coarse,
0x5C, 0x79, 0x01, 0x02);
p_over[LHBM_GB_COARSE] = (g_coarse & 0xF0) | (b_coarse & 0x0F);
/* 0 - 6 nits */
grp = LHBM_OVERDRIVE_GRP_6_NIT;
p_over = ctl->brt_overdrive[grp];
hk3_calc_lhbm_od_brightness(p_norm[LHBM_R_FINE], p_norm[LHBM_R_COARSE],
&p_over[LHBM_R_FINE], &p_over[LHBM_R_COARSE],
0x63, 0x7A, 0x00, 0x01);
hk3_calc_lhbm_od_brightness(p_norm[LHBM_G_FINE], p_norm[LHBM_GB_COARSE],
&p_over[LHBM_G_FINE], &g_coarse,
0x70, 0x80, 0x00, 0x10);
hk3_calc_lhbm_od_brightness(p_norm[LHBM_B_FINE], p_norm[LHBM_GB_COARSE],
&p_over[LHBM_B_FINE], &b_coarse,
0x90, 0x43, 0x00, 0x01);
p_over[LHBM_GB_COARSE] = (g_coarse & 0xF0) | (b_coarse & 0x0F);
/* 6 - 100 nits */
grp = LHBM_OVERDRIVE_GRP_50_NIT;
p_over = ctl->brt_overdrive[grp];
hk3_calc_lhbm_od_brightness(p_norm[LHBM_R_FINE], p_norm[LHBM_R_COARSE],
&p_over[LHBM_R_FINE], &p_over[LHBM_R_COARSE],
0x45, 0x8F, 0x00, 0x01);
hk3_calc_lhbm_od_brightness(p_norm[LHBM_G_FINE], p_norm[LHBM_GB_COARSE],
&p_over[LHBM_G_FINE], &g_coarse,
0x55, 0x98, 0x00, 0x10);
hk3_calc_lhbm_od_brightness(p_norm[LHBM_B_FINE], p_norm[LHBM_GB_COARSE],
&p_over[LHBM_B_FINE], &b_coarse,
0x75, 0x58, 0x00, 0x01);
p_over[LHBM_GB_COARSE] = (g_coarse & 0xF0) | (b_coarse & 0x0F);
/* 100 - 300 nits */
grp = LHBM_OVERDRIVE_GRP_300_NIT;
p_over = ctl->brt_overdrive[grp];
hk3_calc_lhbm_od_brightness(p_norm[LHBM_R_FINE], p_norm[LHBM_R_COARSE],
&p_over[LHBM_R_FINE], &p_over[LHBM_R_COARSE],
0x44, 0xA2, 0x00, 0x01);
hk3_calc_lhbm_od_brightness(p_norm[LHBM_G_FINE], p_norm[LHBM_GB_COARSE],
&p_over[LHBM_G_FINE], &g_coarse,
0x41, 0xAC, 0x00, 0x10);
hk3_calc_lhbm_od_brightness(p_norm[LHBM_B_FINE], p_norm[LHBM_GB_COARSE],
&p_over[LHBM_B_FINE], &b_coarse,
0x55, 0x78, 0x00, 0x01);
p_over[LHBM_GB_COARSE] = (g_coarse & 0xF0) | (b_coarse & 0x0F);
for (grp = 0; grp < LHBM_OVERDRIVE_GRP_MAX; grp++) {
dev_dbg(ctx->dev, "lhbm overdrive brightness[%d]: %*ph\n",
grp, LHBM_BRT_LEN, ctl->brt_overdrive[grp]);
}
}
static void hk3_panel_init(struct exynos_panel *ctx)
{
#ifdef CONFIG_DEBUG_FS
struct dentry *csroot = ctx->debugfs_cmdset_entry;
struct hk3_panel *spanel = to_spanel(ctx);
exynos_panel_debugfs_create_cmdset(ctx, csroot, &hk3_init_cmd_set, "init");
debugfs_create_bool("force_changeable_te", 0644, ctx->debugfs_entry,
&spanel->force_changeable_te);
debugfs_create_bool("force_changeable_te2", 0644, ctx->debugfs_entry,
&spanel->force_changeable_te2);
debugfs_create_bool("force_za_off", 0644, ctx->debugfs_entry,
&spanel->force_za_off);
debugfs_create_u8("hw_acl_setting", 0644, ctx->debugfs_entry,
&spanel->hw_acl_setting);
#endif
#ifdef PANEL_FACTORY_BUILD
ctx->panel_idle_enabled = false;
#endif
hk3_lhbm_brightness_init(ctx);
if (ctx->panel_rev < PANEL_REV_DVT1) {
/* AOD Transition Set */
EXYNOS_DCS_BUF_ADD_SET(ctx, unlock_cmd_f0);
EXYNOS_DCS_BUF_ADD(ctx, 0xB0, 0x00, 0x03, 0xBB);
EXYNOS_DCS_BUF_ADD(ctx, 0xBB, 0x41);
EXYNOS_DCS_BUF_ADD_SET_AND_FLUSH(ctx, lock_cmd_f0);
}
if (ctx->panel_rev >= PANEL_REV_DVT1)
hk3_negative_field_setting(ctx);
spanel->tz = thermal_zone_get_zone_by_name("disp_therm");
if (IS_ERR_OR_NULL(spanel->tz))
dev_err(ctx->dev, "%s: failed to get thermal zone disp_therm\n",
__func__);
}
static int hk3_panel_probe(struct mipi_dsi_device *dsi)
{
struct hk3_panel *spanel;
spanel = devm_kzalloc(&dsi->dev, sizeof(*spanel), GFP_KERNEL);
if (!spanel)
return -ENOMEM;
spanel->base.op_hz = 120;
spanel->hw_vrefresh = 60;
spanel->hw_acl_setting = 0;
spanel->hw_za_enabled = false;
spanel->hw_dbv = 0;
/* ddic default temp */
spanel->hw_temp = 25;
spanel->pending_temp_update = false;
spanel->is_pixel_off = false;
return exynos_panel_common_init(dsi, &spanel->base);
}
static int hk3_panel_config(struct exynos_panel *ctx)
{
exynos_panel_model_init(ctx, PROJECT, 0);
return 0;
}
static const struct drm_panel_funcs hk3_drm_funcs = {
.disable = hk3_disable,
.unprepare = exynos_panel_unprepare,
.prepare = exynos_panel_prepare,
.enable = hk3_enable,
.get_modes = exynos_panel_get_modes,
};
static const struct exynos_panel_funcs hk3_exynos_funcs = {
.set_brightness = hk3_set_brightness,
.set_lp_mode = hk3_set_lp_mode,
.set_nolp_mode = hk3_set_nolp_mode,
.set_binned_lp = exynos_panel_set_binned_lp,
.set_hbm_mode = hk3_set_hbm_mode,
.set_dimming_on = hk3_set_dimming_on,
.set_local_hbm_mode = hk3_set_local_hbm_mode,
.set_local_hbm_mode_post = hk3_set_local_hbm_mode_post,
.is_mode_seamless = hk3_is_mode_seamless,
.mode_set = hk3_mode_set,
.panel_init = hk3_panel_init,
.panel_config = hk3_panel_config,
.get_panel_rev = hk3_get_panel_rev,
.get_te2_edges = exynos_panel_get_te2_edges,
.configure_te2_edges = exynos_panel_configure_te2_edges,
.update_te2 = hk3_update_te2,
.commit_done = hk3_commit_done,
.atomic_check = hk3_atomic_check,
.set_self_refresh = hk3_set_self_refresh,
.set_op_hz = hk3_set_op_hz,
.read_id = hk3_read_id,
.get_te_usec = hk3_get_te_usec,
.set_acl_mode = hk3_set_acl_mode,
.run_normal_mode_work = hk3_normal_mode_work,
.pre_update_ffc = hk3_pre_update_ffc,
.update_ffc = hk3_update_ffc,
};
const struct brightness_capability hk3_brightness_capability = {
.normal = {
.nits = {
.min = 2,
.max = 1000,
},
.level = {
.min = 196,
.max = 3307,
},
.percentage = {
.min = 0,
.max = 63,
},
},
.hbm = {
.nits = {
.min = 1000,
.max = 1600,
},
.level = {
.min = 3308,
.max = 4095,
},
.percentage = {
.min = 63,
.max = 100,
},
},
};
const struct exynos_panel_desc google_hk3 = {
.data_lane_cnt = 4,
.max_brightness = 4095,
.dft_brightness = 1353, /* 140 nits */
.brt_capability = &hk3_brightness_capability,
.dbv_extra_frame = true,
/* supported HDR format bitmask : 1(DOLBY_VISION), 2(HDR10), 3(HLG) */
.hdr_formats = BIT(2) | BIT(3),
.max_luminance = 10000000,
.max_avg_luminance = 1200000,
.min_luminance = 5,
.bl_range = hk3_bl_range,
.bl_num_ranges = ARRAY_SIZE(hk3_bl_range),
.modes = hk3_modes,
.num_modes = ARRAY_SIZE(hk3_modes),
.lp_mode = hk3_lp_modes,
.lp_mode_count = ARRAY_SIZE(hk3_lp_modes),
.binned_lp = hk3_binned_lp,
.num_binned_lp = ARRAY_SIZE(hk3_binned_lp),
.is_panel_idle_supported = true,
.no_lhbm_rr_constraints = true,
.use_async_notify = true,
.panel_func = &hk3_drm_funcs,
.exynos_panel_func = &hk3_exynos_funcs,
.lhbm_effective_delay_frames = 1,
.lhbm_post_cmd_delay_frames = 1,
.normal_mode_work_delay_ms = 30000,
.default_dsi_hs_clk = MIPI_DSI_FREQ_DEFAULT,
.reset_timing_ms = {1, 1, 5},
.reg_ctrl_enable = {
{PANEL_REG_ID_VDDI, 1},
{PANEL_REG_ID_VCI, 10},
},
.reg_ctrl_post_enable = {
{PANEL_REG_ID_VDDD, 1},
},
.reg_ctrl_pre_disable = {
{PANEL_REG_ID_VDDD, 1},
},
.reg_ctrl_disable = {
{PANEL_REG_ID_VCI, 1},
{PANEL_REG_ID_VDDI, 1},
},
};
static const struct of_device_id exynos_panel_of_match[] = {
{ .compatible = "google,hk3", .data = &google_hk3 },
{ }
};
MODULE_DEVICE_TABLE(of, exynos_panel_of_match);
static struct mipi_dsi_driver exynos_panel_driver = {
.probe = hk3_panel_probe,
.remove = exynos_panel_remove,<