blob: 23f64aa1dbfe4ef2f113ad386749e455344c9f14 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* MIPI-DSI based s6e3fc3 AMOLED LCD panel driver.
*
* Copyright (c) 2020 Samsung Electronics Co., Ltd
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2 as
* published by the Free Software Foundation.
*/
#include <drm/drm_vblank.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <video/mipi_display.h>
#include "panel-samsung-drv.h"
static const unsigned char PPS_SETTING[] = {
0x11, 0x00, 0x00, 0x89, 0x30, 0x80, 0x09, 0x60,
0x04, 0x38, 0x00, 0x30, 0x02, 0x1C, 0x02, 0x1C,
0x02, 0x00, 0x02, 0x0E, 0x00, 0x20, 0x04, 0xA6,
0x00, 0x07, 0x00, 0x0C, 0x02, 0x0B, 0x02, 0x1F,
0x18, 0x00, 0x10, 0xF0, 0x03, 0x0C, 0x20, 0x00,
0x06, 0x0B, 0x0B, 0x33, 0x0E, 0x1C, 0x2A, 0x38,
0x46, 0x54, 0x62, 0x69, 0x70, 0x77, 0x79, 0x7B,
0x7D, 0x7E, 0x01, 0x02, 0x01, 0x00, 0x09, 0x40,
0x09, 0xBE, 0x19, 0xFC, 0x19, 0xFA, 0x19, 0xF8,
0x1A, 0x38, 0x1A, 0x78, 0x1A, 0xB6, 0x2A, 0xF6,
0x2B, 0x34, 0x2B, 0x74, 0x3B, 0x74, 0x6B, 0xF4,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00
};
#define S6E3FC3_WRCTRLD_DIMMING_BIT 0x08
#define S6E3FC3_WRCTRLD_BCTRL_BIT 0x20
#define S6E3FC3_WRCTRLD_HBM_BIT 0xC0
#define S6E3FC3_WRCTRLD_LOCAL_HBM_BIT 0x10
static const u8 display_off[] = { 0x28 };
static const u8 display_on[] = { 0x29 };
static const u8 test_key_on_f0[] = { 0xF0, 0x5A, 0x5A };
static const u8 test_key_off_f0[] = { 0xF0, 0xA5, 0xA5 };
static const u8 test_key_on_f1[] = { 0xF1, 0x5A, 0x5A };
static const u8 test_key_off_f1[] = { 0xF1, 0xA5, 0xA5 };
static const u8 freq_update[] = { 0xF7, 0x0F };
static const struct exynos_dsi_cmd s6e3fc3_off_cmds[] = {
EXYNOS_DSI_CMD(display_off, 0),
EXYNOS_DSI_CMD_SEQ_DELAY(120, 0x10), /* sleep in */
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_off);
static const struct exynos_dsi_cmd s6e3fc3_lp_cmds[] = {
EXYNOS_DSI_CMD(display_off, 0),
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_lp);
static const struct exynos_dsi_cmd s6e3fc3_lp_off_cmds[] = {
EXYNOS_DSI_CMD(display_off, 0)
};
static const struct exynos_dsi_cmd s6e3fc3_lp_low_cmds[] = {
EXYNOS_DSI_CMD_SEQ_DELAY(17, 0x53, 0x25), /* AOD 10 nit */
EXYNOS_DSI_CMD(display_on, 0)
};
static const struct exynos_dsi_cmd s6e3fc3_lp_high_cmds[] = {
EXYNOS_DSI_CMD_SEQ_DELAY(17, 0x53, 0x24), /* AOD 50 nit */
EXYNOS_DSI_CMD(display_on, 0)
};
static const struct exynos_dsi_cmd s6e3fc3_1_pwm_cmds[] = {
EXYNOS_DSI_CMD0(test_key_on_f0),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x01, 0xF2, 0x65),
EXYNOS_DSI_CMD_SEQ(0x65, 0x00, 0x72),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x01, 0xD2, 0x65),
EXYNOS_DSI_CMD_SEQ(0x65, 0x00, 0x72),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x02, 0x33, 0x65),
EXYNOS_DSI_CMD_SEQ(0x65, 0x01, 0x02, 0x22),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x02, 0x38, 0x65),
EXYNOS_DSI_CMD_SEQ(0x65, 0x01, 0x00, 0x01, 0x00),
EXYNOS_DSI_CMD0(freq_update),
EXYNOS_DSI_CMD0(test_key_off_f0)
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_1_pwm);
static const struct exynos_dsi_cmd s6e3fc3_4_pwm_cmds[] = {
EXYNOS_DSI_CMD0(test_key_on_f0),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x01, 0xF2, 0x65),
EXYNOS_DSI_CMD_SEQ(0x65, 0x01, 0xC4),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x01, 0xD2, 0x65),
EXYNOS_DSI_CMD_SEQ(0x65, 0x01, 0xC4),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x02, 0x33, 0x65),
EXYNOS_DSI_CMD_SEQ(0x65, 0x01, 0x02, 0x22),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x02, 0x38, 0x65),
EXYNOS_DSI_CMD_SEQ(0x65, 0x01, 0x00, 0x01, 0x00),
EXYNOS_DSI_CMD0(freq_update),
EXYNOS_DSI_CMD0(test_key_off_f0)
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_4_pwm);
static const struct exynos_binned_lp s6e3fc3_binned_lp[] = {
BINNED_LP_MODE("off", 0, s6e3fc3_lp_off_cmds),
/* rising time = delay = 0, falling time = delay + width = 0 + 16 */
BINNED_LP_MODE_TIMING("low", 80, s6e3fc3_lp_low_cmds, 0, 0 + 16),
BINNED_LP_MODE_TIMING("high", 2047, s6e3fc3_lp_high_cmds, 0, 0 + 16)
};
static const struct exynos_dsi_cmd s6e3fc3_init_cmds[] = {
EXYNOS_DSI_CMD_SEQ_DELAY(120, 0x11), /* sleep out */
EXYNOS_DSI_CMD_SEQ(0x35), /* TE on */
EXYNOS_DSI_CMD_SEQ(0x2A, 0x00, 0x00, 0x04, 0x37), /* CASET */
EXYNOS_DSI_CMD_SEQ(0x2B, 0x00, 0x00, 0x09, 0x5F), /* PASET */
EXYNOS_DSI_CMD0(test_key_on_f0),
/* TE rising time */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1),
0xB9, 0x01, 0x09, 0x5C, 0x00, 0x0B),
/* FQ CON setting */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x27, 0xF2),
EXYNOS_DSI_CMD_SEQ(0xF2, 0x00),
EXYNOS_DSI_CMD0(freq_update),
/* IRC setting for HBM */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_PROTO1, 0xB0, 0x0B, 0x8F),
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_PROTO1, 0x8F, 0x2B),
/* Enable FD in display PMIC for ELVDD and ELVSS */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0xB0, 0x0B, 0xF4),
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0xF4, 0x1C),
/* Local HBM circle location setting */
EXYNOS_DSI_CMD0(test_key_on_f1),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x28, 0xF2),
EXYNOS_DSI_CMD_SEQ(0xF2, 0xCC),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x01, 0x34, 0x68),
EXYNOS_DSI_CMD_SEQ(0x68, 0x21, 0xC6, 0xE9),
EXYNOS_DSI_CMD0(test_key_off_f1),
EXYNOS_DSI_CMD0(test_key_off_f0)
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_init);
static void s6e3fc3_get_te2_setting(struct exynos_panel_te2_timing *timing,
u8 *setting)
{
u8 delay_low_byte, delay_high_byte;
u8 width_low_byte, width_high_byte;
u32 rising, falling;
if (!timing || !setting)
return;
rising = timing->rising_edge;
falling = timing->falling_edge;
delay_low_byte = rising & 0xFF;
delay_high_byte = (rising >> 8) & 0xF;
width_low_byte = (falling - rising) & 0xFF;
width_high_byte = ((falling - rising) >> 8) & 0xF;
setting[0] = (delay_high_byte << 4) | width_high_byte;
setting[1] = delay_low_byte;
setting[2] = width_low_byte;
}
static void s6e3fc3_update_te2(struct exynos_panel *ctx)
{
struct exynos_panel_te2_timing timing;
u8 setting[2][4] = {
{0xCB, 0x00, 0x00, 0x30}, // normal 60Hz
{0xCB, 0x00, 0x00, 0x30}, // normal 90Hz
};
u8 lp_setting[4] = {0xCB, 0x00, 0x00, 0x10}; // lp low/high
int ret, i;
if (!ctx)
return;
if (ctx->panel_rev == PANEL_REV_PROTO1) {
dev_dbg(ctx->dev, "No need to send TE2 commands on P1.0\n");
return;
}
/* normal mode */
for (i = 0; i < 2; i++) {
timing.rising_edge = ctx->te2.mode_data[i].timing.rising_edge;
timing.falling_edge = ctx->te2.mode_data[i].timing.falling_edge;
s6e3fc3_get_te2_setting(&timing, &setting[i][1]);
dev_dbg(ctx->dev, "TE2 updated normal %dHz: 0xcb 0x%x 0x%x 0x%x\n",
(i == 0) ? 60 : 90,
setting[i][1], setting[i][2], setting[i][3]);
}
/* LP mode */
if (ctx->current_mode->exynos_mode.is_lp_mode) {
ret = exynos_panel_get_current_mode_te2(ctx, &timing);
if (!ret)
s6e3fc3_get_te2_setting(&timing, &lp_setting[1]);
else if (ret == -EAGAIN)
dev_dbg(ctx->dev,
"Panel is not ready, use default setting\n");
else
return;
dev_dbg(ctx->dev, "TE2 updated LP: 0xcb 0x%x 0x%x 0x%x\n",
lp_setting[1], lp_setting[2], lp_setting[3]);
}
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_on_f0);
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x00, 0x26, 0xF2); /* global para */
EXYNOS_DCS_WRITE_SEQ(ctx, 0xF2, 0x03, 0x14); /* TE2 on */
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x00, 0xAF, 0xCB); /* global para */
EXYNOS_DCS_WRITE_TABLE(ctx, setting[0]); /* 60Hz control */
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x01, 0x2F, 0xCB); /* global para */
EXYNOS_DCS_WRITE_TABLE(ctx, setting[1]); /* 90Hz control */
if (ctx->current_mode->exynos_mode.is_lp_mode) {
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x01, 0xAF, 0xCB); /* global para */
EXYNOS_DCS_WRITE_TABLE(ctx, lp_setting); /* HLPM mode */
}
EXYNOS_DCS_WRITE_TABLE(ctx, freq_update); /* LTPS update */
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_off_f0);
}
static void s6e3fc3_change_frequency(struct exynos_panel *ctx,
unsigned int vrefresh)
{
if (!ctx || (vrefresh != 60 && vrefresh != 90))
return;
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_on_f0);
EXYNOS_DCS_WRITE_SEQ(ctx, 0x60, (vrefresh == 90) ? 0x08 : 0x00);
EXYNOS_DCS_WRITE_TABLE(ctx, freq_update);
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_off_f0);
dev_dbg(ctx->dev, "%s: change to %uhz\n", __func__, vrefresh);
}
static void s6e3fc3_update_wrctrld(struct exynos_panel *ctx)
{
u8 val = S6E3FC3_WRCTRLD_BCTRL_BIT;
if (IS_HBM_ON(ctx->hbm_mode))
val |= S6E3FC3_WRCTRLD_HBM_BIT;
if (ctx->hbm.local_hbm.enabled)
val |= S6E3FC3_WRCTRLD_LOCAL_HBM_BIT;
if (ctx->dimming_on)
val |= S6E3FC3_WRCTRLD_DIMMING_BIT;
dev_dbg(ctx->dev,
"%s(wrctrld:0x%x, hbm: %s, dimming: %s, local_hbm: %s)\n",
__func__, val, IS_HBM_ON(ctx->hbm_mode) ? "on" : "off",
ctx->dimming_on ? "on" : "off",
ctx->hbm.local_hbm.enabled ? "on" : "off");
EXYNOS_DCS_WRITE_SEQ(ctx, MIPI_DCS_WRITE_CONTROL_DISPLAY, val);
/* TODO: need to perform gamma updates */
}
static void s6e3fc3_set_nolp_mode(struct exynos_panel *ctx,
const struct exynos_panel_mode *pmode)
{
unsigned int vrefresh = drm_mode_vrefresh(&pmode->mode);
u32 delay_us = mult_frac(1000, 1020, vrefresh);
if (!ctx->enabled)
return;
EXYNOS_DCS_WRITE_TABLE(ctx, display_off);
/* backlight control and dimming */
s6e3fc3_update_wrctrld(ctx);
s6e3fc3_change_frequency(ctx, vrefresh);
usleep_range(delay_us, delay_us + 10);
EXYNOS_DCS_WRITE_TABLE(ctx, display_on);
dev_info(ctx->dev, "exit LP mode\n");
}
#define S6E3FC3_LOCAL_HBM_GAMMA_CMD_SIZE 6
static int s6e3fc3_lhbm_gamma_read(struct exynos_panel *ctx)
{
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
u8 *gamma_cmd = ctx->hbm.local_hbm.gamma_cmd;
int ret;
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_on_f0);
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x00, 0x22, 0xD8); /* global para */
ret = mipi_dsi_dcs_read(dsi, 0xD8, gamma_cmd + 1, S6E3FC3_LOCAL_HBM_GAMMA_CMD_SIZE - 1);
if (ret == (S6E3FC3_LOCAL_HBM_GAMMA_CMD_SIZE - 1)) {
gamma_cmd[0] = 0x65;
ctx->hbm.local_hbm.gamma_para_ready = true;
ret = 0;
} else {
dev_err(ctx->dev, "fail to read LHBM gamma\n");
ret = -EIO;
}
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_off_f0);
return ret;
}
static void s6e3fc3_lhbm_gamma_write(struct exynos_panel *ctx)
{
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_on_f0);
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x03, 0xCD, 0x65); /* global para */
exynos_dcs_write(ctx, ctx->hbm.local_hbm.gamma_cmd,
S6E3FC3_LOCAL_HBM_GAMMA_CMD_SIZE); /* write gamma */
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_off_f0);
}
static int s6e3fc3_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;
if (!pmode) {
dev_err(ctx->dev, "no current mode set\n");
return -EINVAL;
}
mode = &pmode->mode;
dev_dbg(ctx->dev, "%s\n", __func__);
exynos_panel_reset(ctx);
exynos_panel_send_cmd_set(ctx, &s6e3fc3_init_cmd_set);
s6e3fc3_change_frequency(ctx, drm_mode_vrefresh(mode));
if (ctx->panel_rev == PANEL_REV_PROTO1_1)
exynos_panel_send_cmd_set(ctx, &s6e3fc3_4_pwm_cmd_set);
else if (ctx->panel_rev >= PANEL_REV_EVT1_1)
if (ctx->hbm.local_hbm.gamma_para_ready)
s6e3fc3_lhbm_gamma_write(ctx);
/* DSC related configuration */
exynos_dcs_compression_mode(ctx, 0x1); /* DSC_DEC_ON */
EXYNOS_PPS_LONG_WRITE(ctx); /* PPS_SETTING */
EXYNOS_DCS_WRITE_SEQ(ctx, 0xC2, 0x14); /* PPS_MIC_OFF */
EXYNOS_DCS_WRITE_SEQ(ctx, 0x9D, 0x01); /* PPS_DSC_EN */
s6e3fc3_update_wrctrld(ctx); /* dimming and HBM */
ctx->enabled = true;
if (pmode->exynos_mode.is_lp_mode)
exynos_panel_set_lp_mode(ctx, pmode);
else
EXYNOS_DCS_WRITE_SEQ(ctx, 0x29); /* display on */
return 0;
}
static void s6e3fc3_set_hbm_mode(struct exynos_panel *exynos_panel,
enum exynos_hbm_mode mode)
{
const bool hbm_update =
(IS_HBM_ON(exynos_panel->hbm_mode) != IS_HBM_ON(mode));
const bool irc_update =
(IS_HBM_ON_IRC_OFF(exynos_panel->hbm_mode) != IS_HBM_ON_IRC_OFF(mode));
exynos_panel->hbm_mode = mode;
if (hbm_update) {
if (exynos_panel->panel_rev == PANEL_REV_PROTO1_1) {
if (IS_HBM_ON(mode))
exynos_panel_send_cmd_set(exynos_panel, &s6e3fc3_1_pwm_cmd_set);
else
exynos_panel_send_cmd_set(exynos_panel, &s6e3fc3_4_pwm_cmd_set);
}
s6e3fc3_update_wrctrld(exynos_panel);
}
if (irc_update) {
EXYNOS_DCS_WRITE_SEQ(exynos_panel, 0xF0, 0x5A, 0x5A);
EXYNOS_DCS_WRITE_SEQ(exynos_panel, 0xB0, 0x00, 0x03, 0x8F);
EXYNOS_DCS_WRITE_SEQ(exynos_panel, 0x8F, IS_HBM_ON_IRC_OFF(mode) ? 0x05 : 0x25);
EXYNOS_DCS_WRITE_SEQ(exynos_panel, 0xF0, 0xA5, 0xA5);
}
dev_info(exynos_panel->dev, "hbm_on=%d hbm_ircoff=%d\n", IS_HBM_ON(exynos_panel->hbm_mode),
IS_HBM_ON_IRC_OFF(exynos_panel->hbm_mode));
}
static void s6e3fc3_set_dimming_on(struct exynos_panel *exynos_panel,
bool dimming_on)
{
const struct exynos_panel_mode *pmode = exynos_panel->current_mode;
exynos_panel->dimming_on = dimming_on;
if (pmode->exynos_mode.is_lp_mode) {
dev_info(exynos_panel->dev,"in lp mode, skip to update");
return;
}
s6e3fc3_update_wrctrld(exynos_panel);
}
static void s6e3fc3_set_local_hbm_mode(struct exynos_panel *exynos_panel,
bool local_hbm_en)
{
s6e3fc3_update_wrctrld(exynos_panel);
}
static void s6e3fc3_mode_set(struct exynos_panel *ctx,
const struct exynos_panel_mode *pmode)
{
s6e3fc3_change_frequency(ctx, drm_mode_vrefresh(&pmode->mode));
}
static bool s6e3fc3_is_mode_seamless(const struct exynos_panel *ctx,
const struct exynos_panel_mode *pmode)
{
/* seamless mode switch is possible if only changing refresh rate */
return drm_mode_equal_no_clocks(&ctx->current_mode->mode, &pmode->mode);
}
static void s6e3fc3_panel_init(struct exynos_panel *ctx)
{
struct dentry *csroot = ctx->debugfs_cmdset_entry;
exynos_panel_debugfs_create_cmdset(ctx, csroot,
&s6e3fc3_init_cmd_set, "init");
if (ctx->panel_rev >= PANEL_REV_EVT1_1)
if (!s6e3fc3_lhbm_gamma_read(ctx))
s6e3fc3_lhbm_gamma_write(ctx);
}
static void s6e3fc3_get_panel_rev(struct exynos_panel *ctx, u32 id)
{
/* extract command 0xDB */
u8 build_code = (id & 0xFF00) >> 8;
u8 rev = ((build_code & 0xE0) >> 3) | ((build_code & 0x0C) >> 2);
exynos_panel_get_panel_rev(ctx, rev);
}
static const struct exynos_display_underrun_param underrun_param = {
.te_idle_us = 700,
.te_var = 1,
};
static const u32 s6e3fc3_bl_range[] = {
95, 205, 315, 400, 2047
};
static const struct exynos_panel_mode s6e3fc3_modes[] = {
{
.mode = {
.name = "1080x2400x60",
.clock = 168498,
.hdisplay = 1080,
.hsync_start = 1080 + 32, // add hfp
.hsync_end = 1080 + 32 + 12, // add hsa
.htotal = 1080 + 32 + 12 + 26, // add hbp
.vdisplay = 2400,
.vsync_start = 2400 + 12, // add vfp
.vsync_end = 2400 + 12 + 4, // add vsa
.vtotal = 2400 + 12 + 4 + 26, // add vbp
.flags = 0,
.width_mm = 67,
.height_mm = 148,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = 5630,
.bpc = 8,
.dsc = {
.enabled = true,
.dsc_count = 2,
.slice_count = 2,
.slice_height = 48,
},
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = 0,
.falling_edge = 0 + 48,
},
},
{
.mode = {
.name = "1080x2400x90",
.clock = 252747,
.hdisplay = 1080,
.hsync_start = 1080 + 32, // add hfp
.hsync_end = 1080 + 32 + 12, // add hsa
.htotal = 1080 + 32 + 12 + 26, // add hbp
.vdisplay = 2400,
.vsync_start = 2400 + 12, // add vfp
.vsync_end = 2400 + 12 + 4, // add vsa
.vtotal = 2400 + 12 + 4 + 26, // add vbp
.flags = 0,
.width_mm = 67,
.height_mm = 148,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = 140,
.bpc = 8,
.dsc = {
.enabled = true,
.dsc_count = 2,
.slice_count = 2,
.slice_height = 48,
},
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = 0,
.falling_edge = 0 + 48,
},
},
};
static const struct exynos_panel_mode s6e3fc3_lp_mode = {
.mode = {
.name = "1080x2400x30",
.clock = 84249,
.hdisplay = 1080,
.hsync_start = 1080 + 32, // add hfp
.hsync_end = 1080 + 32 + 12, // add hsa
.htotal = 1080 + 32 + 12 + 26, // add hbp
.vdisplay = 2400,
.vsync_start = 2400 + 12, // add vfp
.vsync_end = 2400 + 12 + 4, // add vsa
.vtotal = 2400 + 12 + 4 + 26, // add vbp
.flags = 0,
.type = DRM_MODE_TYPE_DRIVER,
.width_mm = 67,
.height_mm = 148,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = {
.enabled = true,
.dsc_count = 2,
.slice_count = 2,
.slice_height = 48,
},
.underrun_param = &underrun_param,
.is_lp_mode = true,
}
};
static const struct drm_panel_funcs s6e3fc3_drm_funcs = {
.disable = exynos_panel_disable,
.unprepare = exynos_panel_unprepare,
.prepare = exynos_panel_prepare,
.enable = s6e3fc3_enable,
.get_modes = exynos_panel_get_modes,
};
static const struct exynos_panel_funcs s6e3fc3_exynos_funcs = {
.set_brightness = exynos_panel_set_brightness,
.set_lp_mode = exynos_panel_set_lp_mode,
.set_nolp_mode = s6e3fc3_set_nolp_mode,
.set_binned_lp = exynos_panel_set_binned_lp,
.set_hbm_mode = s6e3fc3_set_hbm_mode,
.set_dimming_on = s6e3fc3_set_dimming_on,
.set_local_hbm_mode = s6e3fc3_set_local_hbm_mode,
.is_mode_seamless = s6e3fc3_is_mode_seamless,
.mode_set = s6e3fc3_mode_set,
.panel_init = s6e3fc3_panel_init,
.get_panel_rev = s6e3fc3_get_panel_rev,
.get_te2_edges = exynos_panel_get_te2_edges,
.configure_te2_edges = exynos_panel_configure_te2_edges,
.update_te2 = s6e3fc3_update_te2,
};
const struct brightness_capability s6e3fc3_brightness_capability = {
.normal = {
.nits = {
.min = 2,
.max = 500,
},
.level = {
.min = 4,
.max = 2047,
},
.percentage = {
.min = 0,
.max = 62,
},
},
.hbm = {
.nits = {
.min = 550,
.max = 800,
},
.level = {
.min = 2048,
.max = 4095,
},
.percentage = {
.min = 62,
.max = 100,
},
},
};
const struct exynos_panel_desc samsung_s6e3fc3 = {
.dsc_pps = PPS_SETTING,
.dsc_pps_len = ARRAY_SIZE(PPS_SETTING),
.data_lane_cnt = 4,
.max_brightness = 4095,
.min_brightness = 4,
.dft_brightness = 1023,
.brt_capability = &s6e3fc3_brightness_capability,
/* supported HDR format bitmask : 1(DOLBY_VISION), 2(HDR10), 3(HLG) */
.hdr_formats = BIT(2) | BIT(3),
.max_luminance = 8000000,
.max_avg_luminance = 1200000,
.min_luminance = 5,
.bl_range = s6e3fc3_bl_range,
.bl_num_ranges = ARRAY_SIZE(s6e3fc3_bl_range),
.modes = s6e3fc3_modes,
.num_modes = ARRAY_SIZE(s6e3fc3_modes),
.off_cmd_set = &s6e3fc3_off_cmd_set,
.lp_mode = &s6e3fc3_lp_mode,
.lp_cmd_set = &s6e3fc3_lp_cmd_set,
.binned_lp = s6e3fc3_binned_lp,
.num_binned_lp = ARRAY_SIZE(s6e3fc3_binned_lp),
.panel_func = &s6e3fc3_drm_funcs,
.exynos_panel_func = &s6e3fc3_exynos_funcs,
};
static const struct of_device_id exynos_panel_of_match[] = {
{ .compatible = "samsung,s6e3fc3", .data = &samsung_s6e3fc3 },
{ }
};
MODULE_DEVICE_TABLE(of, exynos_panel_of_match);
static struct mipi_dsi_driver exynos_panel_driver = {
.probe = exynos_panel_probe,
.remove = exynos_panel_remove,
.driver = {
.name = "panel-samsung-s6e3fc3",
.of_match_table = exynos_panel_of_match,
},
};
module_mipi_dsi_driver(exynos_panel_driver);
MODULE_AUTHOR("Jiun Yu <jiun.yu@samsung.com>");
MODULE_DESCRIPTION("MIPI-DSI based Samsung s6e3fc3 panel driver");
MODULE_LICENSE("GPL");