| /* 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"); |