| // SPDX-License-Identifier: GPL-2.0-only |
| /* |
| * MIPI-DSI based nt37290 AMOLED LCD panel driver. |
| * |
| * Copyright (c) 2021 Google LLC |
| * |
| * This program is free software; you can redistribute it and/or modify |
| * it under the terms of the GNU General Public License version 2 as |
| * published by the Free Software Foundation. |
| */ |
| |
| #include <linux/debugfs.h> |
| #include <linux/module.h> |
| #include <linux/of_platform.h> |
| #include <trace/dpu_trace.h> |
| #include <video/mipi_display.h> |
| |
| #include "panel-samsung-drv.h" |
| |
| /* when refresh rate can go lower than this value (in auto mode), fixed TE2 should be enabled */ |
| #define NT37290_TE2_MIN_RATE 30 |
| #define NT37290_TE2_CHANGEABLE 0x02 |
| #define NT37290_TE2_FIXED 0x22 |
| |
| #define NT37290_DDIC_ID_LEN 8 |
| |
| /** |
| * enum nt37290_panel_feature - features supported by this panel |
| * @G10_FEAT_EARLY_EXIT: early exit from a long frame |
| * @G10_FEAT_FRAME_AUTO: automatic (not manual) frame control |
| * @G10_FEAT_MAX: placeholder, counter for number of features |
| * |
| * The following features are correlated, if one or more of them change, the others need |
| * to be updated unconditionally. |
| */ |
| enum nt37290_panel_feature { |
| G10_FEAT_EARLY_EXIT = 0, |
| G10_FEAT_FRAME_AUTO, |
| G10_FEAT_MAX, |
| }; |
| |
| /** |
| * enum nt37290_dfc_mode - dynamic frame rate control mode |
| * @DFC_MODE_MANUAL: manual mode, running at based frame rate |
| * @DFC_MODE_PANEL_LP: low power mode, running at lower frame rates |
| * |
| * Select DDIC frame rate control mode. For 120Hz based, MANUAL will use 120Hz, and |
| * PANEL_LP will use 60/30/10Hz. For 30Hz based (AOD), MANUAL will use 30Hz, and |
| * PANEL_LP will use 10Hz. |
| */ |
| enum nt37290_dfc_mode { |
| DFC_MODE_MANUAL = 0, |
| DFC_MODE_PANEL_LP = 3, |
| }; |
| |
| /** |
| * struct nt37290_panel - panel specific runtime info |
| * |
| * This struct maintains nt37290 panel specific runtime info, any fixed details about panel |
| * should most likely go into struct exynos_panel_desc. 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 nt37290_panel { |
| /** @base: base panel struct */ |
| struct exynos_panel base; |
| /** @feat: software/working correlated features, not guaranteed to be effective in panel */ |
| DECLARE_BITMAP(feat, G10_FEAT_MAX); |
| /** @hw_feat: correlated states effective in panel */ |
| DECLARE_BITMAP(hw_feat, G10_FEAT_MAX); |
| /** @hw_vrefresh: vrefresh rate effective in panel */ |
| int hw_vrefresh; |
| /** @hw_idle_vrefresh: idle vrefresh rate effective in panel */ |
| int hw_idle_vrefresh; |
| /** @hw_dbv: hw brightness value sent to panel */ |
| u16 hw_dbv; |
| /** |
| * @auto_mode_vrefresh: indicates current minimum refresh rate while in auto mode, |
| * if 0 it means that auto mode is not enabled |
| */ |
| u32 auto_mode_vrefresh; |
| /** |
| * @delayed_idle: indicates idle mode set is delayed due to idle_delay_ms, |
| * we should avoid changing idle_mode when it's true |
| */ |
| bool delayed_idle; |
| /** @hw_osc2_clk_idx: current index of OSC2 clock table in panel */ |
| int hw_osc2_clk_idx; |
| }; |
| |
| #define to_spanel(ctx) container_of(ctx, struct nt37290_panel, base) |
| |
| /** |
| * struct nt37290_osc2_clk_data - OSC2 clock info in panel |
| * |
| * This struct includes the panel OSC2 clock (kHz) and its value (FCON[2:0]) of frame |
| * rate selection command (2Fh). Selecting appropriate clock dynamically in normal and |
| * AOD modes can reduce radio interferences. |
| */ |
| struct nt37290_osc2_clk_data { |
| /** @clk_khz: the clock in kHz */ |
| unsigned int clk_khz; |
| /** @fcon_val: the value of FCON[2:0] in command 2Fh */ |
| u8 fcon_val; |
| }; |
| |
| static const u8 display_off[] = { 0x28 }; |
| static const u8 display_on[] = { 0x29 }; |
| static const u8 cmd2_page0[] = { 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00 }; |
| static const u8 stream_2c[] = { 0x2C }; |
| |
| static const struct exynos_dsi_cmd nt37290_lp_cmds[] = { |
| /* enter AOD */ |
| EXYNOS_DSI_CMD_SEQ(0x39), |
| }; |
| static DEFINE_EXYNOS_CMD_SET(nt37290_lp); |
| |
| static const struct exynos_dsi_cmd nt37290_lp_off_cmds[] = { |
| EXYNOS_DSI_CMD0(display_off), |
| }; |
| |
| static const struct exynos_dsi_cmd nt37290_lp_low_cmds[] = { |
| /* 10 nit */ |
| EXYNOS_DSI_CMD_SEQ_DELAY(9, 0x51, 0x00, 0x00, 0x00, 0x00, 0x03, 0x33), |
| /* 2Ch needs to be sent twice in next 2 vsync */ |
| EXYNOS_DSI_CMD(stream_2c, 9), |
| EXYNOS_DSI_CMD0(stream_2c), |
| EXYNOS_DSI_CMD0(display_on), |
| }; |
| |
| static const struct exynos_dsi_cmd nt37290_lp_high_cmds[] = { |
| /* 50 nit */ |
| EXYNOS_DSI_CMD_SEQ_DELAY(9, 0x51, 0x00, 0x00, 0x00, 0x00, 0x0F, 0xFE), |
| /* 2Ch needs to be sent twice in next 2 vsync */ |
| EXYNOS_DSI_CMD(stream_2c, 9), |
| EXYNOS_DSI_CMD0(stream_2c), |
| EXYNOS_DSI_CMD0(display_on), |
| }; |
| |
| static const struct exynos_binned_lp nt37290_binned_lp[] = { |
| BINNED_LP_MODE("off", 0, nt37290_lp_off_cmds), |
| /* rising = 0, falling = 48 */ |
| BINNED_LP_MODE_TIMING("low", 80, nt37290_lp_low_cmds, 0, 48), |
| BINNED_LP_MODE_TIMING("high", 3584, nt37290_lp_high_cmds, 0, 48), |
| }; |
| |
| static const struct exynos_dsi_cmd nt37290_off_cmds[] = { |
| EXYNOS_DSI_CMD(display_off, 100), |
| EXYNOS_DSI_CMD_SEQ_DELAY(120, 0x10), |
| }; |
| static DEFINE_EXYNOS_CMD_SET(nt37290_off); |
| |
| static const struct exynos_dsi_cmd nt37290_lhbm_on_setting_cmds[] = { |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x07), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xC0, 0xB1), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x08), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xC0, 0x55), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xD5, 0x21, 0x00, 0x39, 0x31, 0x39, |
| 0x31, 0x00, 0x00, 0x3F, 0xC9, 0xEF, 0xAE, 0x3F, 0xC9, 0xEF, 0xAE, |
| 0x00, 0x0C, 0xC6, 0xDB, 0x61, 0x23, 0x00, 0x00, 0x79, 0x00, 0x00, |
| 0x79, 0x33, 0xF0, 0x87, 0x87, 0x39, 0x31, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xD6, 0x27, 0x00, 0x39, 0x31, 0x39, |
| 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x3F, 0xC9, 0xEF, 0xAE, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x23, 0x00, 0x7A, 0xF3, 0x00, 0x00, |
| 0x79, 0x33, 0x30, 0x79, 0x87, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xD7, 0x2B, 0x00, 0x39, 0x31, 0x39, |
| 0x31, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x7F, 0xF3, 0x39, 0x24, 0x9F, 0x55, 0x00, 0x7A, 0xF3, 0x00, 0x7A, |
| 0xF3, 0x33, 0x0F, 0x79, 0x79, 0xC6, 0xCF, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xD8, 0x2D, 0x00, 0x39, 0x31, 0x39, |
| 0x31, 0x00, 0x00, 0x3F, 0xC9, 0xEF, 0xAE, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x55, 0x00, 0x00, 0x79, 0x00, 0x7A, |
| 0xF3, 0x33, 0xC0, 0x87, 0x79, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 0x00, 0x00, 0x00, 0x00, 0x00, 0x00), |
| EXYNOS_DSI_CMD0_REV(cmd2_page0, PANEL_REV_GE(PANEL_REV_EVT1)), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x05), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x01), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x00), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x02), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x00), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x13), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x00, 0x7A, 0x00, 0x7A), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x1B), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x00, 0x00, 0x00, 0x00), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x1F), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x00, 0xF3, 0x00, 0xF3), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x2B), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x3F, 0xFF, 0x3F, 0xFF, 0x3F, |
| 0xFF), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x31), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x22), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x32), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x2A), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x33), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x2A), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x34), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x16), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x35), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x00), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x36), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x02), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x37), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x01), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x38), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x0C, 0x38), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x3A), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x01, 0x1F, 0x00, 0x61, 0x00, |
| 0x93), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x40), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x00, 0xF8, 0x01, 0x07, 0x00, |
| 0x2E), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x46), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x00, 0x99, 0x00, 0x29, 0x00, |
| 0x88), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x4C), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x1F, 0xFC, 0x1F, 0xFC, 0x1F, |
| 0xFC), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x52), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x0A, 0x99, 0x22, 0xDA, 0x3E, |
| 0xB5), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x58), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x3D, 0xDC, 0x28, 0xD5, 0x1D, |
| 0x52), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x5E), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x13, 0x51, 0x13, 0xCD, 0x0D, |
| 0x4E), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x64), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x3B, 0x3F, 0x2E, 0x39, 0x35, |
| 0xF2), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x6A), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x25, 0x35, 0x18, 0x3C, 0x30, |
| 0xCF), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x70), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x3E, 0xD6, 0x03, 0xE4, 0x3F, |
| 0xF5), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x76), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x23, 0x19, 0x1C, 0x89, 0x37, |
| 0x4B), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x7C), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x3F, 0x69, 0x0A, 0xC7, 0x3C, |
| 0xB5), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x82), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x13, 0x61, 0x1E, 0x2E, 0x03, |
| 0xA9), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x88), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xDF, 0x40), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x01), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x87, 0x07, 0x5E), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x03), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x87, 0x07, 0x5E), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x6F, 0x05), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x87, 0x07, 0x5E, 0x07, 0x5E, 0x07, |
| 0x5E, 0x07, 0x5E, 0x07, 0x5E, 0x07, 0x5E, 0x07, 0x5E, 0x07, 0x5E), |
| |
| EXYNOS_DSI_CMD_SEQ(0x88, 0x01), /* enable */ |
| /* circle center: x=720, y=2361 */ |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x01), |
| EXYNOS_DSI_CMD_SEQ(0x88, 0x02, 0xD0, 0x09, 0x39), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x15), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x0A, 0x86), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x17), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x0F, 0xFF), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x19), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x01, 0x4F, 0x06, 0x45, 0x0B, 0x98, 0x01, 0x96, 0x08, 0x19, 0x0A, |
| 0xFD, 0x01, 0x55, 0x05, 0x84), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x3D), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x01, 0x4A), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x3F), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x08, 0xBB), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x41), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x08, 0xF4, 0x0C, 0xAB, 0x00, 0xD4, 0x08, 0x80, 0x09, 0x91, 0x0A, |
| 0x87, 0x04, 0x1D, 0x0B, 0x9C), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x65), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x07, 0x68), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x67), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x01, 0x1C), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x69), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x0B, 0x3C, 0x0D, 0x16, 0x04, 0x32, 0x07, 0x83, 0x0D, 0x92, 0x0C, |
| 0x87, 0x07, 0x4B, 0x07, 0x18), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x29), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x09, 0xBE), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x2B), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x0D, 0x95), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x2D), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x0E, 0x45, 0x07, 0xCE, 0x04, 0x18, 0x03, 0x47, 0x0B, 0x52, 0x00, |
| 0x7C, 0x0D, 0x90, 0x0A, 0x8B), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x51), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x02, 0x10), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x53), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x07, 0x9D), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x55), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x01, 0x11, 0x04, 0x28, 0x00, 0xF0, 0x0B, 0x8C, 0x0C, 0xC0, 0x04, |
| 0x0F, 0x05, 0x1F, 0x0E, 0x89), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x79), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x07, 0x8C), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x7B), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x0C, 0xE2), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x7D), |
| EXYNOS_DSI_CMD_SEQ(0x87, 0x09, 0x08, 0x02, 0xF9, 0x01, 0x08, 0x0D, 0x17, 0x04, 0x6B, 0x00, |
| 0xD0, 0x04, 0x77, 0x05, 0x7D), |
| |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0x51, 0x3F, 0xFF), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0x53, 0x20), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0xFF, 0xAA, 0x55, 0xA5, 0x84), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0x6F, 0x7C), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0xF3, 0x01), |
| }; |
| static DEFINE_EXYNOS_CMD_SET(nt37290_lhbm_on_setting); |
| |
| static const struct exynos_dsi_cmd nt37290_dsc_wqhd_cmds[] = { |
| /* scaling off (1440x3120) */ |
| EXYNOS_DSI_CMD_SEQ(0x8F, 0x00), |
| |
| /* row address */ |
| EXYNOS_DSI_CMD_SEQ(0x2B, 0x00, 0x00, 0x0C, 0x2F), |
| /* slice 24, 2 decoder */ |
| EXYNOS_DSI_CMD_SEQ(0x90, 0x03, 0x03), |
| EXYNOS_DSI_CMD_SEQ(0x91, 0x89, 0x28, 0x00, 0x18, 0xD2, 0x00, 0x02, |
| 0x86, 0x02, 0x83, 0x00, 0x0A, 0x04, 0x86, 0x03, |
| 0x2E, 0x10, 0xF0), |
| }; |
| static DEFINE_EXYNOS_CMD_SET(nt37290_dsc_wqhd); |
| |
| static const struct exynos_dsi_cmd nt37290_dsc_fhd_cmds[] = { |
| /* scaling up 1.33x (1080x2340) */ |
| EXYNOS_DSI_CMD_SEQ(0x8F, 0x01), |
| EXYNOS_DSI_CMD0(cmd2_page0), |
| /* horizontal display resolution selection */ |
| EXYNOS_DSI_CMD_SEQ(0xB9, 0x00, 0x05, 0xA0), |
| /* set the display vertical scan line */ |
| EXYNOS_DSI_CMD_SEQ(0xBD, 0x0C, 0x30), |
| |
| /* row address */ |
| EXYNOS_DSI_CMD_SEQ(0x2B, 0x00, 0x00, 0x09, 0x23), |
| /* slice 30, 2 decoder */ |
| EXYNOS_DSI_CMD_SEQ(0x90, 0x03, 0x03), |
| EXYNOS_DSI_CMD_SEQ(0x91, 0x89, 0x28, 0x00, 0x1E, 0xD2, 0x00, 0x02, |
| 0x25, 0x02, 0xC5, 0x00, 0x07, 0x03, 0x97, 0x03, |
| 0x64, 0x10, 0xF0), |
| }; |
| static DEFINE_EXYNOS_CMD_SET(nt37290_dsc_fhd); |
| |
| static const struct exynos_dsi_cmd nt37290_init_cmds[] = { |
| /* CMD1 */ |
| /* set for higher MIPI speed: 1346Mbps */ |
| EXYNOS_DSI_CMD_SEQ(0x1F, 0xF0), |
| /* gamma curve */ |
| EXYNOS_DSI_CMD_SEQ(0x26, 0x00), |
| /* TE output line */ |
| EXYNOS_DSI_CMD_SEQ(0x35), |
| /* select brightness value */ |
| EXYNOS_DSI_CMD_SEQ(0x51, 0x03, 0xF8, 0x03, 0xF8, 0x0F, 0xFE), |
| /* control brightness */ |
| EXYNOS_DSI_CMD_SEQ(0x53, 0x20), |
| EXYNOS_DSI_CMD_SEQ(0x5A, 0x01), |
| /* change refresh frame to 1 after 2Ch command in skip mode */ |
| EXYNOS_DSI_CMD0(cmd2_page0), |
| EXYNOS_DSI_CMD_SEQ(0xBA, 0x00), |
| /* dimming config: 32 frame */ |
| EXYNOS_DSI_CMD_SEQ(0xB2, 0x19), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x05), |
| EXYNOS_DSI_CMD_SEQ(0xB2, 0x20), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x06), |
| EXYNOS_DSI_CMD_SEQ(0xB2, 0x20), |
| |
| /* CMD2 Page 1 */ |
| EXYNOS_DSI_CMD_SEQ(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01), |
| EXYNOS_DSI_CMD_SEQ(0xC5, 0x00, 0x0B, 0x0B, 0x0B), |
| /* CMD2 Page 0: display driving voltage Vkeep 6.35V->5.95V */ |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_DVT1, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_DVT1, 0x6F, 0x14), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_DVT1, 0xC2, 0x00, 0x50), |
| |
| /* CMD3 Page 0 */ |
| EXYNOS_DSI_CMD_SEQ(0xFF, 0xAA, 0x55, 0xA5, 0x80), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x1B), |
| EXYNOS_DSI_CMD_SEQ(0xF4, 0x55), |
| /* CMD3 Page 1 */ |
| EXYNOS_DSI_CMD_SEQ(0xFF, 0xAA, 0x55, 0xA5, 0x81), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x12), |
| EXYNOS_DSI_CMD_SEQ(0xF5, 0x00), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x09), |
| EXYNOS_DSI_CMD_SEQ(0xF9, 0x10), |
| /* CMD3 Page 2: MIPI termination resistor 90ohm */ |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xFF, 0xAA, 0x55, 0xA5, 0x82), |
| EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xF2, 0x3F), |
| /* CMD3 Page 3 */ |
| EXYNOS_DSI_CMD_SEQ(0xFF, 0xAA, 0x55, 0xA5, 0x83), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x14), |
| EXYNOS_DSI_CMD_SEQ(0xF8, 0x0D), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x01), |
| EXYNOS_DSI_CMD_SEQ(0xF9, 0x06), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x01), |
| EXYNOS_DSI_CMD_SEQ(0xFA, 0x06), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x01), |
| EXYNOS_DSI_CMD_SEQ(0xFB, 0x06), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x01), |
| EXYNOS_DSI_CMD_SEQ(0xFC, 0x06), |
| /* CMD3 Page 4 */ |
| EXYNOS_DSI_CMD_SEQ(0xFF, 0xAA, 0x55, 0xA5, 0x84), |
| EXYNOS_DSI_CMD_SEQ(0x6F, 0x1C), |
| EXYNOS_DSI_CMD_SEQ(0xF8, 0x3A), |
| |
| EXYNOS_DSI_CMD_SEQ_DELAY(120, 0x11), |
| }; |
| static DEFINE_EXYNOS_CMD_SET(nt37290_init); |
| |
| static u8 nt37290_get_te2_option(struct exynos_panel *ctx) |
| { |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| |
| if (!ctx || !ctx->current_mode) |
| return NT37290_TE2_CHANGEABLE; |
| |
| /* AOD mode only supports fixed TE2 */ |
| if (ctx->current_mode->exynos_mode.is_lp_mode || |
| (spanel->hw_idle_vrefresh > 0 && spanel->hw_idle_vrefresh < NT37290_TE2_MIN_RATE)) |
| return NT37290_TE2_FIXED; |
| |
| return NT37290_TE2_CHANGEABLE; |
| } |
| |
| static void nt37290_update_te2(struct exynos_panel *ctx) |
| { |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| struct exynos_panel_te2_timing timing; |
| /* default timing */ |
| u8 rising = 0, falling = 0x30; |
| u8 option = nt37290_get_te2_option(ctx); |
| int ret; |
| |
| if (!ctx) |
| return; |
| |
| ret = exynos_panel_get_current_mode_te2(ctx, &timing); |
| if (!ret) { |
| rising = timing.rising_edge & 0xFF; |
| falling = timing.falling_edge & 0xFF; |
| } else if (ret == -EAGAIN) { |
| dev_dbg(ctx->dev, "Panel is not ready, use default timing\n"); |
| } else { |
| dev_warn(ctx->dev, "Failed to get current timing\n"); |
| return; |
| } |
| |
| ctx->te2.option = (option == NT37290_TE2_FIXED) ? TE2_OPT_FIXED : TE2_OPT_CHANGEABLE; |
| |
| /* option */ |
| EXYNOS_DCS_BUF_ADD(ctx, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x03); |
| EXYNOS_DCS_BUF_ADD(ctx, 0xC3, option); |
| EXYNOS_DCS_BUF_ADD(ctx, 0x6F, 0x04); |
| EXYNOS_DCS_BUF_ADD(ctx, 0xC3, option); |
| /* timing */ |
| EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, 0xC4, 0x00, 0x00, 0x00, 0x00, |
| 0x00, rising, 0x10, falling); |
| |
| dev_dbg(ctx->dev, |
| "TE2 updated: option %s, idle mode %s, rising 0x%x, falling 0x%x\n", |
| (option == NT37290_TE2_CHANGEABLE) ? "changeable" : "fixed", |
| spanel->hw_idle_vrefresh ? "enabled" : "disabled", |
| rising, falling); |
| } |
| |
| static inline bool is_auto_mode_allowed(struct exynos_panel *ctx) |
| { |
| /* don't want to enable auto mode/early exit during hbm or dimming on */ |
| if (IS_HBM_ON(ctx->hbm_mode) || ctx->dimming_on) |
| return false; |
| |
| return ctx->panel_idle_enabled; |
| } |
| |
| static void nt37290_update_min_idle_vrefresh(struct exynos_panel *ctx, |
| const struct exynos_panel_mode *pmode) |
| { |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| const int vrefresh = drm_mode_vrefresh(&pmode->mode); |
| int idle_vrefresh = ctx->min_vrefresh; |
| |
| if (!idle_vrefresh || !is_auto_mode_allowed(ctx) || |
| pmode->idle_mode == IDLE_MODE_UNSUPPORTED) |
| idle_vrefresh = 0; |
| else if (idle_vrefresh <= 10) |
| idle_vrefresh = 10; |
| else if (idle_vrefresh <= 30) |
| idle_vrefresh = 30; |
| else if (idle_vrefresh <= 60) |
| idle_vrefresh = 60; |
| else /* 120hz: no idle available */ |
| idle_vrefresh = 0; |
| |
| if (idle_vrefresh >= vrefresh) { |
| dev_dbg(ctx->dev, "idle vrefresh (%d) higher than target (%d)\n", |
| idle_vrefresh, vrefresh); |
| idle_vrefresh = 0; |
| } |
| |
| if (idle_vrefresh && ctx->idle_delay_ms && |
| (panel_get_idle_time_delta(ctx) < ctx->idle_delay_ms)) { |
| spanel->delayed_idle = true; |
| idle_vrefresh = 0; |
| } else { |
| spanel->delayed_idle = false; |
| } |
| |
| spanel->auto_mode_vrefresh = idle_vrefresh; |
| } |
| |
| static const struct nt37290_osc2_clk_data osc2_clk_data[] = { |
| { .clk_khz = 170500, .fcon_val = 0 }, |
| { .clk_khz = 165400, .fcon_val = 1 }, |
| }; |
| |
| static inline u8 nt37290_get_frame_rate_ctrl(struct exynos_panel *ctx, |
| enum nt37290_dfc_mode mode) |
| { |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| u8 fcon = osc2_clk_data[spanel->hw_osc2_clk_idx].fcon_val; |
| u8 val = ((mode & 0x7) << 4) | (fcon & 0x7); |
| |
| return val; |
| } |
| |
| static bool nt37290_update_panel_feat(struct exynos_panel *ctx, |
| const struct exynos_panel_mode *pmode, bool enforce) |
| { |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| int vrefresh; |
| int idle_vrefresh = spanel->auto_mode_vrefresh; |
| DECLARE_BITMAP(changed_feat, G10_FEAT_MAX); |
| bool ee, fi; |
| |
| if (pmode) |
| vrefresh = drm_mode_vrefresh(&pmode->mode); |
| else |
| vrefresh = drm_mode_vrefresh(&ctx->current_mode->mode); |
| |
| /* when panel feat func is called, idle effect should be disabled */ |
| ctx->panel_idle_vrefresh = 0; |
| |
| if (enforce) { |
| bitmap_fill(changed_feat, G10_FEAT_MAX); |
| } else { |
| bitmap_xor(changed_feat, spanel->feat, spanel->hw_feat, G10_FEAT_MAX); |
| if (bitmap_empty(changed_feat, G10_FEAT_MAX) && |
| vrefresh == spanel->hw_vrefresh && |
| idle_vrefresh == spanel->hw_idle_vrefresh) |
| return false; |
| } |
| |
| spanel->hw_vrefresh = vrefresh; |
| spanel->hw_idle_vrefresh = idle_vrefresh; |
| bitmap_copy(spanel->hw_feat, spanel->feat, G10_FEAT_MAX); |
| ee = test_bit(G10_FEAT_EARLY_EXIT, spanel->feat); |
| fi = test_bit(G10_FEAT_FRAME_AUTO, spanel->feat); |
| |
| dev_dbg(ctx->dev, "ee=%s fi=%s vrefresh=%d idle_vrefresh=%d osc2=%u\n", |
| ee ? "on" : "off", fi ? "auto" : "manual", |
| vrefresh, idle_vrefresh, ctx->osc2_clk_khz); |
| |
| DPU_ATRACE_BEGIN(__func__); |
| |
| if (vrefresh == 120 && !fi) { |
| /* DFC mode manual */ |
| EXYNOS_DCS_BUF_ADD(ctx, 0x2F, |
| nt37290_get_frame_rate_ctrl(ctx, DFC_MODE_MANUAL)); |
| /* 120Hz gamma band */ |
| if (ctx->panel_rev >= PANEL_REV_EVT1_1) |
| EXYNOS_DCS_BUF_ADD(ctx, 0x26, 0x00); |
| /* restore TE timing (no shift) */ |
| EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, 0x44, 0x00, 0x00); |
| } else { |
| /* DFC mode panel low power */ |
| EXYNOS_DCS_BUF_ADD(ctx, 0x2F, |
| nt37290_get_frame_rate_ctrl(ctx, DFC_MODE_PANEL_LP)); |
| /* early exit */ |
| EXYNOS_DCS_BUF_ADD(ctx, 0x5A, !ee); |
| |
| /* set auto frame insertion */ |
| EXYNOS_DCS_BUF_ADD_SET(ctx, cmd2_page0); |
| EXYNOS_DCS_BUF_ADD(ctx, 0x6F, 0x1C); |
| /* auto frame insertion off (manual) */ |
| if (!fi) { |
| if (vrefresh == 60) |
| EXYNOS_DCS_BUF_ADD(ctx, |
| 0xBA, 0x91, 0x01, 0x01, 0x00, 0x01, 0x01, 0x01, 0x00); |
| else |
| dev_warn(ctx->dev, |
| "Unsupported vrefresh %dHz for manual mode\n", vrefresh); |
| /* auto frame insertion on */ |
| } else { |
| if (vrefresh == 60) { |
| if (idle_vrefresh == 10) |
| EXYNOS_DCS_BUF_ADD(ctx, 0xBA, 0x91, 0x09, 0x03, 0x00, 0x11, |
| 0x0B, 0x0B, 0x00, 0x06); |
| else if (idle_vrefresh == 30) |
| EXYNOS_DCS_BUF_ADD(ctx, 0xBA, 0x91, 0x03, 0x02, 0x00, 0x11, |
| 0x03, 0x03, 0x00, 0x04); |
| else |
| dev_warn(ctx->dev, |
| "Unsupported idle_vrefresh %dHz for auto mode\n", |
| idle_vrefresh); |
| } else if (vrefresh == 120) { |
| /* two 120Hz frames are removed after EVT1.1 */ |
| u8 val = (ctx->panel_rev >= PANEL_REV_EVT1_1) ? 0x91 : 0x93; |
| |
| if (idle_vrefresh == 10) |
| EXYNOS_DCS_BUF_ADD(ctx, 0xBA, val, 0x09, 0x03, 0x00, 0x31, |
| 0x0B, 0x0B, 0x00, 0x06); |
| else if (idle_vrefresh == 30) |
| EXYNOS_DCS_BUF_ADD(ctx, 0xBA, val, 0x03, 0x02, 0x00, 0x11, |
| 0x03, 0x03, 0x00, 0x04); |
| else if (idle_vrefresh == 60) |
| EXYNOS_DCS_BUF_ADD(ctx, 0xBA, 0x93, 0x01, 0x01, 0x00, 0x01, |
| 0x01, 0x01, 0x00, 0x00); |
| else |
| dev_warn(ctx->dev, |
| "Unsupported idle_vrefresh %dHz for auto mode\n", |
| idle_vrefresh); |
| } else { |
| dev_warn(ctx->dev, "Unsupported vrefresh %dHz for auto mode\n", |
| vrefresh); |
| } |
| } |
| |
| EXYNOS_DCS_BUF_ADD(ctx, 0x2C); |
| |
| if (ctx->panel_rev >= PANEL_REV_EVT1_1) { |
| EXYNOS_DCS_BUF_ADD(ctx, 0x6F, 0x03); |
| EXYNOS_DCS_BUF_ADD(ctx, 0xC0, |
| (spanel->hw_osc2_clk_idx == 1) ? 0x21 : 0x20); |
| } |
| |
| /* VRR gamma band (60~10Hz) */ |
| if (ctx->panel_rev >= PANEL_REV_EVT1_1) |
| EXYNOS_DCS_BUF_ADD(ctx, 0x26, 0x01); |
| |
| if (vrefresh == 120) |
| /* restore TE timing (no shift) */ |
| EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, 0x44, 0x00, 0x00); |
| else |
| /* TE shift 8.2ms */ |
| EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, 0x44, 0x00, 0x01); |
| } |
| |
| DPU_ATRACE_END(__func__); |
| |
| return true; |
| } |
| |
| static bool nt37290_change_frequency(struct exynos_panel *ctx, |
| const struct exynos_panel_mode *pmode) |
| { |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| int vrefresh = drm_mode_vrefresh(&pmode->mode); |
| bool idle_active = false; |
| bool was_lp_mode = ctx->current_mode->exynos_mode.is_lp_mode; |
| bool updated; |
| |
| nt37290_update_min_idle_vrefresh(ctx, pmode); |
| |
| if (spanel->auto_mode_vrefresh && |
| (pmode->idle_mode == IDLE_MODE_ON_INACTIVITY || |
| (pmode->idle_mode == IDLE_MODE_ON_SELF_REFRESH && ctx->self_refresh_active))) |
| idle_active = true; |
| |
| if (idle_active) { |
| set_bit(G10_FEAT_EARLY_EXIT, spanel->feat); |
| set_bit(G10_FEAT_FRAME_AUTO, spanel->feat); |
| } else { |
| clear_bit(G10_FEAT_EARLY_EXIT, spanel->feat); |
| clear_bit(G10_FEAT_FRAME_AUTO, spanel->feat); |
| } |
| |
| /* need to update 2Fh command while exiting AOD */ |
| updated = nt37290_update_panel_feat(ctx, pmode, was_lp_mode); |
| |
| ctx->panel_idle_vrefresh = ctx->self_refresh_active ? spanel->hw_idle_vrefresh : 0; |
| |
| if (updated) { |
| backlight_state_changed(ctx->bl); |
| te2_state_changed(ctx->bl); |
| |
| dev_dbg(ctx->dev, "change to %dHz, idle %s, was_lp_mode %d\n", |
| vrefresh, idle_active ? "active" : "deactive", was_lp_mode); |
| } |
| |
| return updated; |
| } |
| |
| static bool nt37290_set_self_refresh(struct exynos_panel *ctx, bool enable) |
| { |
| const struct exynos_panel_mode *pmode = ctx->current_mode; |
| bool updated; |
| |
| if (unlikely(!pmode)) |
| return false; |
| |
| /* self refresh is not supported in lp mode since that always makes use of early exit */ |
| if (pmode->exynos_mode.is_lp_mode) |
| return false; |
| |
| DPU_ATRACE_BEGIN(__func__); |
| |
| updated = nt37290_change_frequency(ctx, pmode); |
| |
| if (pmode->idle_mode == IDLE_MODE_ON_SELF_REFRESH) { |
| dev_dbg(ctx->dev, "%s: %s idle (%dHz) for mode %s\n", |
| __func__, enable ? "enter" : "exit", |
| ctx->panel_idle_vrefresh ? : drm_mode_vrefresh(&pmode->mode), |
| pmode->mode.name); |
| } |
| |
| DPU_ATRACE_END(__func__); |
| |
| return updated; |
| } |
| |
| /** |
| * 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 |
| /** |
| * Use a threshold to avoid disabling idle auto mode too frequently while continuously |
| * updating frames. Considering the hibernation time for this scenario. |
| */ |
| #define IDLE_DELAY_THRESHOLD_US 50000 |
| |
| /** |
| * nt37290_trigger_early_exit - trigger early exit command to panel |
| * @ctx: panel struct |
| * |
| * Sends a command to panel to indicate a frame is about to come in case its been a while since |
| * the last frame update and auto mode may have started to take effect and lowering refresh rate |
| */ |
| static void nt37290_trigger_early_exit(struct exynos_panel *ctx) |
| { |
| const ktime_t delta = ktime_sub(ktime_get(), ctx->last_commit_ts); |
| const s64 delta_us = ktime_to_us(delta); |
| |
| if (delta_us < EARLY_EXIT_THRESHOLD_US) { |
| dev_dbg(ctx->dev, "skip early exit. %lldus since last commit\n", |
| delta_us); |
| return; |
| } |
| |
| /* triggering early exit causes a switch to 120hz */ |
| ctx->last_mode_set_ts = ktime_get(); |
| |
| DPU_ATRACE_BEGIN(__func__); |
| |
| if (ctx->idle_delay_ms && delta_us > IDLE_DELAY_THRESHOLD_US) { |
| const struct exynos_panel_mode *pmode = ctx->current_mode; |
| |
| dev_dbg(ctx->dev, "%s: disable auto idle mode for %s\n", |
| __func__, pmode->mode.name); |
| nt37290_change_frequency(ctx, pmode); |
| } else { |
| EXYNOS_DCS_WRITE_TABLE(ctx, stream_2c); |
| } |
| |
| DPU_ATRACE_END(__func__); |
| } |
| |
| static void nt37290_commit_done(struct exynos_panel *ctx) |
| { |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| const struct exynos_panel_mode *pmode = ctx->current_mode; |
| |
| if (!is_panel_active(ctx) || !pmode) |
| return; |
| |
| if (test_bit(G10_FEAT_EARLY_EXIT, spanel->feat)) |
| nt37290_trigger_early_exit(ctx); |
| /** |
| * For IDLE_MODE_ON_INACTIVITY, we should go back to auto mode again |
| * after the delay time has elapsed. |
| */ |
| else if (pmode->idle_mode == IDLE_MODE_ON_INACTIVITY && |
| spanel->delayed_idle) |
| nt37290_change_frequency(ctx, pmode); |
| } |
| |
| static void nt37290_set_lp_mode(struct exynos_panel *ctx, |
| const struct exynos_panel_mode *pmode) |
| { |
| exynos_panel_set_lp_mode(ctx, pmode); |
| |
| /* enable early exit and auto frame insertion (10Hz) */ |
| EXYNOS_DCS_BUF_ADD(ctx, 0x2F, |
| nt37290_get_frame_rate_ctrl(ctx, DFC_MODE_PANEL_LP)); |
| EXYNOS_DCS_BUF_ADD(ctx, 0x5A, 0x00); |
| EXYNOS_DCS_BUF_ADD_SET(ctx, cmd2_page0); |
| EXYNOS_DCS_BUF_ADD(ctx, 0x6F, 0x1C); |
| EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, 0xBA, 0x95, 0x02, 0x02, 0x00, 0x11, 0x02, 0x02, 0x00); |
| |
| dev_dbg(ctx->dev, "%s: done\n", __func__); |
| } |
| |
| static void nt37290_set_nolp_mode(struct exynos_panel *ctx, |
| const struct exynos_panel_mode *pmode) |
| { |
| if (!is_panel_active(ctx)) |
| return; |
| |
| /* exit AOD */ |
| EXYNOS_DCS_WRITE_SEQ_DELAY(ctx, 34, 0x38); |
| |
| nt37290_change_frequency(ctx, pmode); |
| |
| /* 2Ch needs to be sent twice in next 2 vsync */ |
| EXYNOS_DCS_WRITE_TABLE_DELAY(ctx, 34, stream_2c); |
| EXYNOS_DCS_WRITE_TABLE(ctx, stream_2c); |
| EXYNOS_DCS_WRITE_TABLE(ctx, display_on); |
| |
| dev_info(ctx->dev, "exit LP mode\n"); |
| } |
| |
| static int nt37290_enable(struct drm_panel *panel) |
| { |
| struct exynos_panel *ctx = container_of(panel, struct exynos_panel, panel); |
| const struct exynos_panel_mode *pmode = ctx->current_mode; |
| const struct drm_display_mode *mode; |
| bool is_fhd; |
| |
| if (!pmode) { |
| dev_err(ctx->dev, "no current mode set\n"); |
| return -EINVAL; |
| } |
| |
| mode = &pmode->mode; |
| is_fhd = mode->hdisplay == 1080; |
| |
| dev_dbg(ctx->dev, "%s (%s)\n", __func__, is_fhd ? "fhd" : "wqhd"); |
| |
| exynos_panel_reset(ctx); |
| exynos_panel_send_cmd_set(ctx, &nt37290_init_cmd_set); |
| exynos_panel_send_cmd_set(ctx, |
| is_fhd ? &nt37290_dsc_fhd_cmd_set : &nt37290_dsc_wqhd_cmd_set); |
| exynos_panel_send_cmd_set(ctx, &nt37290_lhbm_on_setting_cmd_set); |
| |
| nt37290_update_panel_feat(ctx, pmode, true); |
| |
| if (!pmode->exynos_mode.is_lp_mode) |
| EXYNOS_DCS_WRITE_TABLE(ctx, display_on); |
| else |
| nt37290_set_lp_mode(ctx, pmode); |
| |
| return 0; |
| } |
| |
| static int nt37290_disable(struct drm_panel *panel) |
| { |
| struct exynos_panel *ctx = container_of(panel, struct exynos_panel, panel); |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| |
| /* panel register state gets reset after disabling hardware */ |
| bitmap_clear(spanel->hw_feat, 0, G10_FEAT_MAX); |
| spanel->hw_vrefresh = 60; |
| spanel->hw_idle_vrefresh = 0; |
| |
| return exynos_panel_disable(panel); |
| } |
| |
| /** |
| * struct brightness_data - nits and DBV in each brightness band |
| * |
| * @nits: brightness in nits. |
| * @level: brightness in DBV. |
| */ |
| struct brightness_data { |
| u16 nits; |
| u16 level; |
| }; |
| |
| /** |
| * struct brightness_settings - brightness data of all bands in hbm and normal mode. |
| * |
| * @hbm_band_data: pointer to brightness data in hbm bands. |
| * @num_of_hbm_bands: number of brightness bands in hbm. |
| * @normal_band_data: pointer to brightness data in normal bands. |
| * @num_of_normal_bands: number of brightness bands in normal mode. |
| */ |
| struct brightness_settings { |
| const struct brightness_data * const hbm_band_data; |
| const unsigned int num_of_hbm_bands; |
| const struct brightness_data * const normal_band_data; |
| const unsigned int num_of_normal_bands; |
| }; |
| |
| static const struct brightness_data evt1_1_hbm_band_data[] = { |
| { .nits = 1000, .level = 4094 }, /* band 0 */ |
| { .nits = 600, .level = 3585 }, /* band 1 */ |
| }; |
| |
| static const struct brightness_data evt1_1_normal_band_data[] = { |
| { .nits = 600, .level = 3584 }, /* band 0 */ |
| { .nits = 300, .level = 3072 }, /* band 1 */ |
| { .nits = 200, .level = 2560 }, /* band 2 */ |
| { .nits = 120, .level = 2048 }, /* band 3 */ |
| { .nits = 80, .level = 1536 }, /* band 4 */ |
| { .nits = 50, .level = 1024 }, /* band 5 */ |
| { .nits = 25, .level = 512 }, /* band 6 */ |
| { .nits = 5, .level = 256 }, /* band 7 */ |
| { .nits = 2, .level = 3 }, /* band 8 */ |
| }; |
| |
| static const struct brightness_settings evt1_1_br_settings = { |
| .hbm_band_data = evt1_1_hbm_band_data, |
| .num_of_hbm_bands = ARRAY_SIZE(evt1_1_hbm_band_data), |
| .normal_band_data = evt1_1_normal_band_data, |
| .num_of_normal_bands = ARRAY_SIZE(evt1_1_normal_band_data), |
| }; |
| |
| static const struct brightness_data evt1_hbm_band_data[] = { |
| { .nits = 1000, .level = 4094 }, /* band 0 */ |
| { .nits = 500, .level = 2048 }, /* band 1 */ |
| }; |
| |
| static const struct brightness_data evt1_normal_band_data[] = { |
| { .nits = 500, .level = 2047 }, /* band 0 */ |
| /* skip band 1 to 7 due to linear brightness in evt1 normal mode */ |
| { .nits = 2, .level = 3 }, /* band 8 */ |
| }; |
| |
| static const struct brightness_settings evt1_br_settings = { |
| .hbm_band_data = evt1_hbm_band_data, |
| .num_of_hbm_bands = ARRAY_SIZE(evt1_hbm_band_data), |
| .normal_band_data = evt1_normal_band_data, |
| .num_of_normal_bands = ARRAY_SIZE(evt1_normal_band_data), |
| }; |
| |
| /** |
| * linear_interpolation - linear interpolation for given x |
| * @x1: x coordinate |
| * @x2: x coordinate, greater than x1 |
| * @y1: y coordinate |
| * @y2: y coordinate, greater than y1 |
| * @x: x coordinate, in the interval (x1, x2) |
| * |
| * Given x, do linear interpolation according to x1, x2, y1, and y2. |
| * Return a new value after calculation. Return negative if the inputs are invalid. |
| */ |
| |
| static inline u16 linear_interpolation(x1, x2, y1, y2, x) |
| { |
| if (x == x1) |
| return y1; |
| else if (x == x2) |
| return y2; |
| else |
| return (y1 + DIV_ROUND_CLOSEST((x - x1) * (y2 - y1), x2 - x1)); |
| } |
| |
| static u16 nt37290_convert_to_evt1_1_nonlinear_br(struct exynos_panel *ctx, u16 br) |
| { |
| u16 i = 0, nits = 0, level = 0, band = 0; |
| u16 num_band = evt1_1_br_settings.num_of_normal_bands; |
| |
| nits = linear_interpolation(evt1_1_br_settings.normal_band_data[num_band - 1].level, |
| evt1_1_br_settings.normal_band_data[0].level, |
| evt1_1_br_settings.normal_band_data[num_band - 1].nits, |
| evt1_1_br_settings.normal_band_data[0].nits, |
| br); |
| for (i = 1; i < num_band; i++) { |
| if (nits >= evt1_1_br_settings.normal_band_data[i].nits) { |
| band = i; |
| break; |
| } |
| } |
| level = linear_interpolation(evt1_1_br_settings.normal_band_data[band].nits, |
| evt1_1_br_settings.normal_band_data[band - 1].nits, |
| evt1_1_br_settings.normal_band_data[band].level, |
| evt1_1_br_settings.normal_band_data[band - 1].level, |
| nits); |
| |
| dev_dbg(ctx->dev, "%s: nits %u, band %u, level %u->%u\n", |
| __func__, nits, band, br, level); |
| |
| return level; |
| } |
| |
| static u16 nt37290_convert_to_evt1_br(struct exynos_panel *ctx, u16 br) |
| { |
| u16 x1, x2, y1, y2, num_band, level; |
| |
| if (br <= evt1_1_br_settings.normal_band_data[0].level) { |
| num_band = evt1_1_br_settings.num_of_normal_bands; |
| x1 = evt1_1_br_settings.normal_band_data[num_band - 1].level; |
| x2 = evt1_1_br_settings.normal_band_data[0].level; |
| num_band = evt1_br_settings.num_of_normal_bands; |
| y1 = evt1_br_settings.normal_band_data[num_band - 1].level; |
| y2 = evt1_br_settings.normal_band_data[0].level; |
| } else { |
| num_band = evt1_1_br_settings.num_of_hbm_bands; |
| x1 = evt1_1_br_settings.hbm_band_data[num_band - 1].level; |
| x2 = evt1_1_br_settings.hbm_band_data[0].level; |
| num_band = evt1_br_settings.num_of_hbm_bands; |
| y1 = evt1_br_settings.hbm_band_data[num_band - 1].level; |
| y2 = evt1_br_settings.hbm_band_data[0].level; |
| } |
| |
| level = linear_interpolation(x1, x2, y1, y2, br); |
| dev_dbg(ctx->dev, "%s: level %u->%u\n", __func__, br, level); |
| return level; |
| } |
| |
| static int nt37290_set_brightness(struct exynos_panel *ctx, u16 br) |
| { |
| u16 brightness; |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| |
| if (ctx->current_mode->exynos_mode.is_lp_mode) { |
| const struct exynos_panel_funcs *funcs; |
| |
| funcs = ctx->desc->exynos_panel_func; |
| if (funcs && funcs->set_binned_lp) |
| funcs->set_binned_lp(ctx, br); |
| return 0; |
| } |
| |
| spanel->hw_dbv = br; |
| if (spanel->hw_dbv == 0) { |
| // turn off panel and set brightness directly. |
| return exynos_dcs_set_brightness(ctx, 0); |
| } |
| |
| if (ctx->panel_rev >= PANEL_REV_EVT1_1) { |
| if (br <= evt1_1_br_settings.normal_band_data[0].level) |
| spanel->hw_dbv = nt37290_convert_to_evt1_1_nonlinear_br(ctx, br); |
| } else { |
| spanel->hw_dbv = nt37290_convert_to_evt1_br(ctx, br); |
| } |
| |
| if (ctx->panel_rev >= PANEL_REV_EVT1 && ctx->hbm.local_hbm.enabled) { |
| u16 level = spanel->hw_dbv * 4; |
| u8 val1 = level >> 8; |
| u8 val2 = level & 0xff; |
| |
| /* LHBM DBV value write */ |
| EXYNOS_DCS_BUF_ADD_SET(ctx, cmd2_page0); |
| EXYNOS_DCS_BUF_ADD(ctx, 0x6F, 0x4C); |
| EXYNOS_DCS_BUF_ADD(ctx, 0xDF, val1, val2, val1, val2, val1, val2); |
| } |
| |
| brightness = (spanel->hw_dbv & 0xff) << 8 | spanel->hw_dbv >> 8; |
| |
| return exynos_dcs_set_brightness(ctx, brightness); |
| } |
| |
| static void nt37290_set_hbm_mode(struct exynos_panel *ctx, |
| enum exynos_hbm_mode mode) |
| { |
| if (ctx->hbm_mode == mode) |
| return; |
| |
| if (IS_HBM_ON(ctx->hbm_mode) != IS_HBM_ON(mode)) { |
| EXYNOS_DCS_BUF_ADD_SET(ctx, cmd2_page0); |
| EXYNOS_DCS_BUF_ADD(ctx, 0x6F, 0x11); |
| EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, 0xB2, mode ? 0x00 : 0x01); |
| } |
| |
| if (IS_HBM_ON_IRC_OFF(ctx->hbm_mode) != IS_HBM_ON_IRC_OFF(mode)) { |
| EXYNOS_DCS_BUF_ADD(ctx, 0xFF, 0xAA, 0x55, 0xA5, 0x84); |
| EXYNOS_DCS_BUF_ADD(ctx, 0x6F, 0x02); |
| EXYNOS_DCS_BUF_ADD(ctx, 0xF5, 0x01); |
| EXYNOS_DCS_BUF_ADD(ctx, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x08); |
| EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, 0xB9, IS_HBM_ON_IRC_OFF(mode) ? 0x00 : 0x01); |
| } |
| |
| ctx->hbm_mode = mode; |
| dev_info(ctx->dev, "hbm_on=%d hbm_ircoff=%d\n", IS_HBM_ON(ctx->hbm_mode), |
| IS_HBM_ON_IRC_OFF(ctx->hbm_mode)); |
| } |
| |
| static void nt37290_set_local_hbm_mode(struct exynos_panel *ctx, |
| bool local_hbm_en) |
| { |
| const struct exynos_panel_mode *pmode; |
| |
| if (ctx->hbm.local_hbm.enabled == local_hbm_en) |
| return; |
| |
| pmode = ctx->current_mode; |
| if (unlikely(pmode == NULL)) { |
| dev_err(ctx->dev, "%s: unknown current mode\n", __func__); |
| return; |
| } |
| |
| if (local_hbm_en) { |
| const int vrefresh = drm_mode_vrefresh(&pmode->mode); |
| /* Add check to turn on LHBM @ 120hz only to comply with HW requirement */ |
| if (vrefresh != 120) { |
| dev_err(ctx->dev, "unexpected mode `%s` while enabling LHBM, give up\n", |
| pmode->mode.name); |
| return; |
| } |
| } |
| |
| ctx->hbm.local_hbm.enabled = local_hbm_en; |
| |
| if (local_hbm_en) { |
| if (ctx->panel_rev >= PANEL_REV_EVT1) { |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| u16 level = spanel->hw_dbv * 4; |
| u8 val1 = level >> 8; |
| u8 val2 = level & 0xff; |
| |
| /* LHBM DBV value write */ |
| EXYNOS_DCS_BUF_ADD_SET(ctx, cmd2_page0); |
| EXYNOS_DCS_BUF_ADD(ctx, 0x6F, 0x4C); |
| EXYNOS_DCS_BUF_ADD(ctx, 0xDF, val1, val2, val1, val2, val1, val2); |
| /* FPS gamma timing */ |
| EXYNOS_DCS_BUF_ADD(ctx, 0x2F, 0x02); |
| /* Enter FPS mode */ |
| EXYNOS_DCS_BUF_ADD(ctx, 0x87, 0x01); |
| } else { |
| EXYNOS_DCS_BUF_ADD(ctx, 0x87, 0x21); |
| } |
| /* LHBM on */ |
| EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, 0x85); |
| } else { |
| /* LHBM off */ |
| EXYNOS_DCS_BUF_ADD(ctx, 0x86); |
| if (ctx->panel_rev >= PANEL_REV_EVT1) { |
| /* Exit FPS mode */ |
| EXYNOS_DCS_BUF_ADD(ctx, 0x87, 0x00); |
| /* normal gamma timing */ |
| EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, 0x2F, 0x00); |
| } else { |
| EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, 0x87, 0x20); |
| } |
| } |
| } |
| |
| static void nt37290_mode_set(struct exynos_panel *ctx, |
| const struct exynos_panel_mode *pmode) |
| { |
| if (!is_panel_active(ctx)) |
| return; |
| |
| nt37290_change_frequency(ctx, pmode); |
| } |
| |
| static bool nt37290_is_mode_seamless(const struct exynos_panel *ctx, |
| const struct exynos_panel_mode *pmode) |
| { |
| const struct drm_display_mode *c = &ctx->current_mode->mode; |
| const struct drm_display_mode *n = &pmode->mode; |
| |
| /* seamless mode set can happen if active region resolution is same */ |
| return (c->vdisplay == n->vdisplay) && (c->hdisplay == n->hdisplay) && |
| (c->flags == n->flags); |
| } |
| |
| static void nt37290_get_panel_rev(struct exynos_panel *ctx, u32 id) |
| { |
| /* extract command 0xDB */ |
| u8 build_code = (id & 0xFF00) >> 8; |
| u8 main = (build_code & 0xE0) >> 3; |
| u8 sub = 0; |
| |
| if ((build_code & 0x03) == 0 && (build_code & 0x0C) != 0) |
| /* bit[3:2] */ |
| sub = (build_code & 0x0C) >> 2; |
| else if ((build_code & 0x03) != 0 && (build_code & 0x0C) == 0) |
| /* bit[1:0] */ |
| sub = build_code & 0x03; |
| |
| exynos_panel_get_panel_rev(ctx, main | sub); |
| } |
| |
| static void nt37290_set_osc2_clk_khz(struct exynos_panel *ctx, unsigned int clk_khz) |
| { |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| const struct exynos_panel_mode *pmode = ctx->current_mode; |
| int num_clk = ARRAY_SIZE(osc2_clk_data); |
| int idx = 0; |
| int i; |
| |
| /* only EVT1.1 and later versions are allowed to change OSC2 clock */ |
| if (ctx->panel_rev < PANEL_REV_EVT1_1) |
| return; |
| |
| if (!pmode) |
| return; |
| |
| /* don't change OSC2 clock in AOD mode */ |
| if (pmode->exynos_mode.is_lp_mode) |
| return; |
| |
| for (i = 0; i < num_clk; i++) { |
| if (clk_khz == osc2_clk_data[i].clk_khz) { |
| idx = i; |
| break; |
| } |
| } |
| |
| if (i == num_clk) { |
| dev_warn(ctx->dev, "Invalid OSC2 clock (%u)\n", clk_khz); |
| return; |
| } |
| |
| if (idx != spanel->hw_osc2_clk_idx) { |
| spanel->hw_osc2_clk_idx = idx; |
| ctx->osc2_clk_khz = clk_khz; |
| |
| /* trigger update since OSC2 clock is changed */ |
| nt37290_update_panel_feat(ctx, pmode, true); |
| dev_dbg(ctx->dev, "OSC2 clock is changed to %u (idx %d)\n", clk_khz, idx); |
| } |
| } |
| |
| static ssize_t nt37290_list_osc2_clk_khz(struct exynos_panel *ctx, char *buf) |
| { |
| ssize_t len = 0; |
| int i; |
| |
| for (i = 0; i < ARRAY_SIZE(osc2_clk_data); i++) |
| len += scnprintf(buf + len, PAGE_SIZE - len, "%u\n", |
| osc2_clk_data[i].clk_khz); |
| |
| return len; |
| } |
| |
| static void nt37290_set_dimming_on(struct exynos_panel *ctx, |
| bool dimming_on) |
| { |
| ctx->dimming_on = dimming_on; |
| EXYNOS_DCS_WRITE_SEQ(ctx, 0x53, ctx->dimming_on ? 0x28 : 0x20); |
| dev_dbg(ctx->dev, "%s dimming_on=%d \n", __func__, dimming_on); |
| } |
| |
| static int nt37290_read_id(struct exynos_panel *ctx) |
| { |
| struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev); |
| char buf[NT37290_DDIC_ID_LEN] = {0}; |
| int ret; |
| |
| if (ctx->panel_rev < PANEL_REV_DVT1) |
| return exynos_panel_read_id(ctx); |
| |
| EXYNOS_DCS_BUF_ADD_AND_FLUSH(ctx, |
| 0xFF, 0xAA, 0x55, 0xA5, 0x81); |
| ret = mipi_dsi_dcs_read(dsi, 0xF2, buf, NT37290_DDIC_ID_LEN); |
| if (ret != NT37290_DDIC_ID_LEN) { |
| dev_warn(ctx->dev, "Unable to read DDIC id (%d)\n", ret); |
| return ret; |
| } |
| |
| exynos_bin2hex(buf, NT37290_DDIC_ID_LEN, |
| ctx->panel_id, sizeof(ctx->panel_id)); |
| return 0; |
| } |
| |
| static const struct exynos_display_underrun_param underrun_param = { |
| .te_idle_us = 350, |
| .te_var = 1, |
| }; |
| |
| static const u32 nt37290_bl_range[] = { |
| 94, 180, 270, 360, 3584 |
| }; |
| |
| /* Truncate 8-bit signed value to 6-bit signed value */ |
| #define TO_6BIT_SIGNED(v) (v & 0x3F) |
| |
| static const struct drm_dsc_config nt37290_dsc_cfg = { |
| .first_line_bpg_offset = 13, |
| .rc_range_params = { |
| {0, 0, 0}, |
| {0, 0, 0}, |
| {0, 0, 0}, |
| {0, 0, 0}, |
| {0, 0, 0}, |
| {0, 0, 0}, |
| {0, 0, 0}, |
| {0, 0, 0}, |
| {0, 0, 0}, |
| {4, 10, TO_6BIT_SIGNED(-10)}, |
| {5, 10, TO_6BIT_SIGNED(-10)}, |
| {5, 11, TO_6BIT_SIGNED(-10)}, |
| {5, 11, TO_6BIT_SIGNED(-12)}, |
| {8, 12, TO_6BIT_SIGNED(-12)}, |
| {12, 13, TO_6BIT_SIGNED(-12)}, |
| }, |
| }; |
| |
| #define NT37290_DSC_WQHD_CONFIG \ |
| .dsc = { \ |
| .enabled = true, \ |
| .dsc_count = 2, \ |
| .slice_count = 2, \ |
| .slice_height = 24, \ |
| .cfg = &nt37290_dsc_cfg, \ |
| } |
| |
| #define NT37290_DSC_FHD_CONFIG \ |
| .dsc = { \ |
| .enabled = true, \ |
| .dsc_count = 2, \ |
| .slice_count = 2, \ |
| .slice_height = 30, \ |
| .cfg = &nt37290_dsc_cfg, \ |
| } |
| |
| static const struct exynos_panel_mode nt37290_modes[] = { |
| { |
| /* 1440x3120 @ 60Hz */ |
| .mode = { |
| .name = "1440x3120x60", |
| .clock = 298620, |
| .hdisplay = 1440, |
| .hsync_start = 1440 + 80, // add hfp |
| .hsync_end = 1440 + 80 + 24, // add hsa |
| .htotal = 1440 + 80 + 24 + 36, // add hbp |
| .vdisplay = 3120, |
| .vsync_start = 3120 + 12, // add vfp |
| .vsync_end = 3120 + 12 + 4, // add vsa |
| .vtotal = 3120 + 12 + 4 + 14, // add vbp |
| .flags = 0, |
| .width_mm = 71, |
| .height_mm = 155, |
| }, |
| .exynos_mode = { |
| .mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS, |
| .vblank_usec = 120, |
| .bpc = 8, |
| NT37290_DSC_WQHD_CONFIG, |
| .underrun_param = &underrun_param, |
| }, |
| .te2_timing = { |
| .rising_edge = 0, |
| .falling_edge = 48, |
| }, |
| .idle_mode = IDLE_MODE_ON_SELF_REFRESH, |
| }, |
| { |
| /* 1440x3120 @ 120Hz */ |
| .mode = { |
| .name = "1440x3120x120", |
| .clock = 597240, |
| .hdisplay = 1440, |
| .hsync_start = 1440 + 80, // add hfp |
| .hsync_end = 1440 + 80 + 24, // add hsa |
| .htotal = 1440 + 80 + 24 + 36, // add hbp |
| .vdisplay = 3120, |
| .vsync_start = 3120 + 12, // add vfp |
| .vsync_end = 3120 + 12 + 4, // add vsa |
| .vtotal = 3120 + 12 + 4 + 14, // add vbp |
| .flags = 0, |
| .width_mm = 71, |
| .height_mm = 155, |
| }, |
| .exynos_mode = { |
| .mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS, |
| .vblank_usec = 120, |
| .bpc = 8, |
| NT37290_DSC_WQHD_CONFIG, |
| .underrun_param = &underrun_param, |
| }, |
| .te2_timing = { |
| .rising_edge = 0, |
| .falling_edge = 48, |
| }, |
| .idle_mode = IDLE_MODE_ON_SELF_REFRESH, |
| }, |
| { |
| /* 1080x2340 @ 60Hz */ |
| .mode = { |
| .name = "1080x2340x60", |
| .clock = 173484, |
| .hdisplay = 1080, |
| .hsync_start = 1080 + 80, // add hfp |
| .hsync_end = 1080 + 80 + 24, // add hsa |
| .htotal = 1080 + 80 + 24 + 36, // add hbp |
| .vdisplay = 2340, |
| .vsync_start = 2340 + 12, // add vfp |
| .vsync_end = 2340 + 12 + 4, // add vsa |
| .vtotal = 2340 + 12 + 4 + 14, // add vbp |
| .flags = 0, |
| .width_mm = 71, |
| .height_mm = 155, |
| }, |
| .exynos_mode = { |
| .mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS, |
| .vblank_usec = 120, |
| .bpc = 8, |
| NT37290_DSC_FHD_CONFIG, |
| .underrun_param = &underrun_param, |
| }, |
| .te2_timing = { |
| .rising_edge = 0, |
| .falling_edge = 48, |
| }, |
| .idle_mode = IDLE_MODE_ON_SELF_REFRESH, |
| }, |
| { |
| /* 1080x2340 @ 120Hz */ |
| .mode = { |
| .name = "1080x2340x120", |
| .clock = 346968, |
| .hdisplay = 1080, |
| .hsync_start = 1080 + 80, // add hfp |
| .hsync_end = 1080 + 80 + 24, // add hsa |
| .htotal = 1080 + 80 + 24 + 36, // add hbp |
| .vdisplay = 2340, |
| .vsync_start = 2340 + 12, // add vfp |
| .vsync_end = 2340 + 12 + 4, // add vsa |
| .vtotal = 2340 + 12 + 4 + 14, // add vbp |
| .flags = 0, |
| .width_mm = 71, |
| .height_mm = 155, |
| }, |
| .exynos_mode = { |
| .mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS, |
| .vblank_usec = 120, |
| .bpc = 8, |
| NT37290_DSC_FHD_CONFIG, |
| .underrun_param = &underrun_param, |
| }, |
| .te2_timing = { |
| .rising_edge = 0, |
| .falling_edge = 48, |
| }, |
| .idle_mode = IDLE_MODE_ON_SELF_REFRESH, |
| }, |
| }; |
| |
| static const struct exynos_panel_mode nt37290_lp_modes[] = { |
| { |
| .mode = { |
| /* 1440x3120 @ 30Hz */ |
| .name = "1440x3120x30", |
| .clock = 149310, |
| .hdisplay = 1440, |
| .hsync_start = 1440 + 80, // add hfp |
| .hsync_end = 1440 + 80 + 24, // add hsa |
| .htotal = 1440 + 80 + 24 + 36, // add hbp |
| .vdisplay = 3120, |
| .vsync_start = 3120 + 12, // add vfp |
| .vsync_end = 3120 + 12 + 4, // add vsa |
| .vtotal = 3120 + 12 + 4 + 14, // add vbp |
| .flags = 0, |
| .width_mm = 71, |
| .height_mm = 155, |
| }, |
| .exynos_mode = { |
| .mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS, |
| .vblank_usec = 120, |
| .bpc = 8, |
| NT37290_DSC_WQHD_CONFIG, |
| .underrun_param = &underrun_param, |
| .is_lp_mode = true, |
| } |
| }, |
| { |
| .mode = { |
| /* 1080x2340 @ 30Hz */ |
| .name = "1080x2340x30", |
| .clock = 86742, |
| .hdisplay = 1080, |
| .hsync_start = 1080 + 80, // add hfp |
| .hsync_end = 1080 + 80 + 24, // add hsa |
| .htotal = 1080 + 80 + 24 + 36, // add hbp |
| .vdisplay = 2340, |
| .vsync_start = 2340 + 12, // add vfp |
| .vsync_end = 2340 + 12 + 4, // add vsa |
| .vtotal = 2340 + 12 + 4 + 14, // add vbp |
| .flags = 0, |
| .width_mm = 71, |
| .height_mm = 155, |
| }, |
| .exynos_mode = { |
| .mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS, |
| .vblank_usec = 120, |
| .bpc = 8, |
| NT37290_DSC_FHD_CONFIG, |
| .underrun_param = &underrun_param, |
| .is_lp_mode = true, |
| } |
| }, |
| }; |
| |
| static void nt37290_panel_init(struct exynos_panel *ctx) |
| { |
| struct nt37290_panel *spanel = to_spanel(ctx); |
| struct dentry *csroot = ctx->debugfs_cmdset_entry; |
| |
| exynos_panel_debugfs_create_cmdset(ctx, csroot, &nt37290_init_cmd_set, "init"); |
| exynos_panel_send_cmd_set(ctx, &nt37290_lhbm_on_setting_cmd_set); |
| |
| ctx->osc2_clk_khz = osc2_clk_data[spanel->hw_osc2_clk_idx].clk_khz; |
| } |
| |
| static int nt37290_panel_probe(struct mipi_dsi_device *dsi) |
| { |
| struct nt37290_panel *spanel; |
| |
| spanel = devm_kzalloc(&dsi->dev, sizeof(*spanel), GFP_KERNEL); |
| if (!spanel) |
| return -ENOMEM; |
| |
| spanel->hw_vrefresh = 60; |
| spanel->hw_idle_vrefresh = 0; |
| spanel->auto_mode_vrefresh = 0; |
| spanel->delayed_idle = false; |
| spanel->hw_osc2_clk_idx = 0; |
| |
| return exynos_panel_common_init(dsi, &spanel->base); |
| } |
| |
| static const struct drm_panel_funcs nt37290_drm_funcs = { |
| .disable = nt37290_disable, |
| .unprepare = exynos_panel_unprepare, |
| .prepare = exynos_panel_prepare, |
| .enable = nt37290_enable, |
| .get_modes = exynos_panel_get_modes, |
| }; |
| |
| static const struct exynos_panel_funcs nt37290_exynos_funcs = { |
| .set_brightness = nt37290_set_brightness, |
| .set_lp_mode = nt37290_set_lp_mode, |
| .set_nolp_mode = nt37290_set_nolp_mode, |
| .set_binned_lp = exynos_panel_set_binned_lp, |
| .set_hbm_mode = nt37290_set_hbm_mode, |
| .set_dimming_on = nt37290_set_dimming_on, |
| .set_local_hbm_mode = nt37290_set_local_hbm_mode, |
| .is_mode_seamless = nt37290_is_mode_seamless, |
| .mode_set = nt37290_mode_set, |
| .panel_init = nt37290_panel_init, |
| .get_panel_rev = nt37290_get_panel_rev, |
| .get_te2_edges = exynos_panel_get_te2_edges, |
| .configure_te2_edges = exynos_panel_configure_te2_edges, |
| .update_te2 = nt37290_update_te2, |
| .set_self_refresh = nt37290_set_self_refresh, |
| .commit_done = nt37290_commit_done, |
| .set_osc2_clk_khz = nt37290_set_osc2_clk_khz, |
| .list_osc2_clk_khz = nt37290_list_osc2_clk_khz, |
| .read_id = nt37290_read_id, |
| }; |
| |
| const struct brightness_capability nt37290_brightness_capability = { |
| .normal = { |
| .nits = { |
| .min = 2, |
| .max = 600, |
| }, |
| .level = { |
| .min = 3, |
| .max = 3584, |
| }, |
| .percentage = { |
| .min = 0, |
| .max = 60, |
| }, |
| }, |
| .hbm = { |
| .nits = { |
| .min = 600, |
| .max = 1000, |
| }, |
| .level = { |
| .min = 3585, |
| .max = 4094, |
| }, |
| .percentage = { |
| .min = 60, |
| .max = 100, |
| }, |
| }, |
| }; |
| |
| const struct exynos_panel_desc boe_nt37290 = { |
| .panel_id_reg = 0xAC, |
| .data_lane_cnt = 4, |
| .max_brightness = 4094, |
| .min_brightness = 3, |
| .dft_brightness = 1023, |
| .brt_capability = &nt37290_brightness_capability, |
| /* supported HDR format bitmask : 1(DOLBY_VISION), 2(HDR10), 3(HLG) */ |
| .hdr_formats = BIT(2) | BIT(3), |
| .max_luminance = 10000000, |
| .max_avg_luminance = 1200000, |
| .min_luminance = 5, |
| .bl_range = nt37290_bl_range, |
| .bl_num_ranges = ARRAY_SIZE(nt37290_bl_range), |
| .modes = nt37290_modes, |
| .num_modes = ARRAY_SIZE(nt37290_modes), |
| .off_cmd_set = &nt37290_off_cmd_set, |
| .lp_mode = nt37290_lp_modes, |
| .lp_mode_count = ARRAY_SIZE(nt37290_lp_modes), |
| .lp_cmd_set = &nt37290_lp_cmd_set, |
| .binned_lp = nt37290_binned_lp, |
| .num_binned_lp = ARRAY_SIZE(nt37290_binned_lp), |
| .panel_func = &nt37290_drm_funcs, |
| .exynos_panel_func = &nt37290_exynos_funcs, |
| }; |
| |
| static const struct of_device_id exynos_panel_of_match[] = { |
| { .compatible = "boe,nt37290", .data = &boe_nt37290 }, |
| { } |
| }; |
| MODULE_DEVICE_TABLE(of, exynos_panel_of_match); |
| |
| static struct mipi_dsi_driver exynos_panel_driver = { |
| .probe = nt37290_panel_probe, |
| .remove = exynos_panel_remove, |
| .driver = { |
| .name = "panel-boe-nt37290", |
| .of_match_table = exynos_panel_of_match, |
| }, |
| }; |
| module_mipi_dsi_driver(exynos_panel_driver); |
| |
| MODULE_AUTHOR("Chris Lu <luchris@google.com>"); |
| MODULE_DESCRIPTION("MIPI-DSI based BOE nt37290 panel driver"); |
| MODULE_LICENSE("GPL"); |