blob: 1a7ff25bef09170bc3de406afc8d7c78b54c877a [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/*
* MIPI-DSI based s6e3fc3-p10 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 <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_P10_WRCTRLD_DIMMING_BIT 0x08
#define S6E3FC3_P10_WRCTRLD_BCTRL_BIT 0x20
#define S6E3FC3_P10_WRCTRLD_HBM_BIT 0xC0
#define S6E3FC3_P10_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 u8 new_gamma_ip_bypass[] = { 0x91, 0x01 };
static const u8 new_gamma_ip_enable[] = { 0x91, 0x02 };
static const struct exynos_dsi_cmd s6e3fc3_p10_off_cmds[] = {
EXYNOS_DSI_CMD(display_off, 0),
EXYNOS_DSI_CMD_SEQ_DELAY(120, 0x10), /* sleep in */
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_p10_off);
static const struct exynos_dsi_cmd s6e3fc3_p10_lp_cmds[] = {
EXYNOS_DSI_CMD(display_off, 0),
EXYNOS_DSI_CMD0_REV(test_key_on_f0, PANEL_REV_GE(PANEL_REV_EVT1)),
EXYNOS_DSI_CMD0_REV(new_gamma_ip_bypass, PANEL_REV_GE(PANEL_REV_EVT1)),
EXYNOS_DSI_CMD0_REV(test_key_off_f0, PANEL_REV_GE(PANEL_REV_EVT1)),
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_p10_lp);
static const struct exynos_dsi_cmd s6e3fc3_p10_lp_off_cmds[] = {
EXYNOS_DSI_CMD(display_off, 0)
};
static const struct exynos_dsi_cmd s6e3fc3_p10_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_p10_lp_high_cmds[] = {
EXYNOS_DSI_CMD_SEQ_DELAY(17, 0x53, 0x24), /* AOD 50 nit */
EXYNOS_DSI_CMD(display_on, 0)
};
static const struct exynos_binned_lp s6e3fc3_p10_binned_lp[] = {
BINNED_LP_MODE("off", 0, s6e3fc3_p10_lp_off_cmds),
/* rising time = delay = 0, falling time = delay + width = 0 + 16 */
BINNED_LP_MODE_TIMING("low", 80, s6e3fc3_p10_lp_low_cmds, 0, 0 + 16),
BINNED_LP_MODE_TIMING("high", 2047, s6e3fc3_p10_lp_high_cmds, 0, 0 + 16)
};
static const struct exynos_dsi_cmd s6e3fc3_p10_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),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x27, 0xF2), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF2, 0x00), /* FQ CON */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x0E, 0xF2), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF2, 0x04, 0xD4), /* HW, V-total */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x4C, 0xF6), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF6, 0x48, 0x21), /* Source Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x88, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x2D, 0x22), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x8E, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x2D, 0x22), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0xA6, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x05, 0x18, 0x22, 0x21), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0xBF, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x09, 0x28), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0xC5, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x09, 0x28), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xB0, 0xDF, 0x66),
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x66, 0x71), /* EM off setting */
EXYNOS_DSI_CMD0(freq_update),
EXYNOS_DSI_CMD0(test_key_off_f0)
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_p10_init);
static const struct exynos_dsi_cmd s6e3fc3_p10_evt1_irc_setting_cmds[] = {
EXYNOS_DSI_CMD0(test_key_on_f0),
/* global para */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x28, 0xF2),
/* 10 bits */
EXYNOS_DSI_CMD_SEQ(0xF2, 0xCC),
/* global para */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x01, 0x18, 0x68),
/* IRC setting */
EXYNOS_DSI_CMD_SEQ(0x68, 0xE2, 0x0A, 0x0A, 0x0A, 0x82, 0x0E, 0xCB, 0x80, 0x00,
0x00, 0x00, 0x00, 0xFF, 0x90),
/* global para */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x28, 0xF2),
/* 8 bits */
EXYNOS_DSI_CMD_SEQ(0xF2, 0xC4),
EXYNOS_DSI_CMD0(freq_update),
EXYNOS_DSI_CMD0(test_key_off_f0)
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_p10_evt1_irc_setting);
static const struct exynos_dsi_cmd s6e3fc3_p10_lhbm_location_cmds[] = {
EXYNOS_DSI_CMD0(test_key_on_f0),
EXYNOS_DSI_CMD0(test_key_on_f1),
/* global para */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x28, 0xF2),
/* 10 bits */
EXYNOS_DSI_CMD_SEQ(0xF2, 0xCC),
/* global para */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x01, 0x33, 0x68),
/* box location */
EXYNOS_DSI_CMD_SEQ(0x68, 0xCC),
/* global para */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x01, 0x34, 0x68),
/* center position set, x: 0x21C, y: 0x6B1, size: 0x63 */
EXYNOS_DSI_CMD_SEQ(0x68, 0x21, 0xC6, 0xB1, 0x63),
/* global para */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x00, 0x28, 0xF2),
/* 8 bits */
EXYNOS_DSI_CMD_SEQ(0xF2, 0xC4),
EXYNOS_DSI_CMD0(test_key_off_f1),
EXYNOS_DSI_CMD0(test_key_off_f0)
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_p10_lhbm_location);
static const struct exynos_dsi_cmd s6e3fc3_p10_mode_ns_60_cmds[] = {
EXYNOS_DSI_CMD0(test_key_on_f0),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x27, 0xF2), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF2, 0x80), /* FQ CON */
EXYNOS_DSI_CMD_SEQ(0x60, 0x00), /* 60 hz NS */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x0E, 0xF2), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF2, 0x00, 0x14), /* HW, V-total */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x4C, 0xF6), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF6, 0x48, 0x15), /* Source Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x88, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0xCB, 0x1D, 0x30), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xCB, 0x1D, 0x16), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x8E, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0xCB, 0x1D, 0x30), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xCB, 0x1D, 0x16), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0xA6, 0xCB), /* Global Para */
/* LTPS Change */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0xCB, 0x03, 0x10, 0x16, 0x30),
/* LTPS Change */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xCB, 0x03, 0x10, 0x16, 0x16),
EXYNOS_DSI_CMD_SEQ(0xB0, 0xBF, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0xCB, 0x06, 0x29), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xCB, 0x06, 0x34), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0xC5, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_LT(PANEL_REV_EVT1), 0xCB, 0x06, 0x29), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xCB, 0x06, 0x34), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xB0, 0xDF, 0x66),
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x66, 0x4C), /* EM off setting */
EXYNOS_DSI_CMD0(freq_update),
EXYNOS_DSI_CMD0(test_key_off_f0)
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_p10_mode_ns_60);
static const struct exynos_dsi_cmd s6e3fc3_p10_mode_hs_60_cmds[] = {
EXYNOS_DSI_CMD0(test_key_on_f0),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x27, 0xF2), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF2, 0x00), /* FQ CON */
EXYNOS_DSI_CMD_SEQ(0x60, 0x00), /* 60 hz HS */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x0E, 0xF2), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF2, 0x04, 0xD4), /* HW, V-total */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x4C, 0xF6), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF6, 0x48, 0x21), /* Source Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x88, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x2D, 0x22), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x8E, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x2D, 0x22), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0xA6, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x05, 0x18, 0x22, 0x21), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0xBF, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x09, 0x28), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0xC5, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x09, 0x28), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xB0, 0xDF, 0x66),
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x66, 0x71), /* EM off setting */
EXYNOS_DSI_CMD0(freq_update),
EXYNOS_DSI_CMD0(test_key_off_f0)
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_p10_mode_hs_60);
static const struct exynos_dsi_cmd s6e3fc3_p10_mode_hs_90_cmds[] = {
EXYNOS_DSI_CMD0(test_key_on_f0),
EXYNOS_DSI_CMD_SEQ(0xB0, 0x27, 0xF2), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF2, 0x00), /* FQ CON */
EXYNOS_DSI_CMD_SEQ(0x60, 0x08), /* 90 hz HS */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x0E, 0xF2), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF2, 0x04, 0xD4), /* HW, V-total */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x4C, 0xF6), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xF6, 0x48, 0x21), /* Source Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x88, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x2D, 0x22), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0x8E, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x2D, 0x22), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0xA6, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x05, 0x18, 0x22, 0x21), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0xBF, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x09, 0x28), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ(0xB0, 0xC5, 0xCB), /* Global Para */
EXYNOS_DSI_CMD_SEQ(0xCB, 0x09, 0x28), /* LTPS Change */
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0xB0, 0xDF, 0x66),
EXYNOS_DSI_CMD_SEQ_REV(PANEL_REV_GE(PANEL_REV_EVT1), 0x66, 0x71), /* EM off setting */
EXYNOS_DSI_CMD0(freq_update),
EXYNOS_DSI_CMD0(test_key_off_f0)
};
static DEFINE_EXYNOS_CMD_SET(s6e3fc3_p10_mode_hs_90);
#define LHBM_GAMMA_CMD_SIZE 6
/**
* struct s6e3fc3_p10_panel - panel specific runtime info
*
* This struct maintains s6e3fc3_p10 panel specific runtime info, any fixed details about panel
* should most likely go into struct exynos_panel_desc
*/
struct s6e3fc3_p10_panel {
/** @base: base panel struct */
struct exynos_panel base;
/** @local_hbm_gamma: lhbm gamma data */
struct local_hbm_gamma {
u8 hs_cmd[LHBM_GAMMA_CMD_SIZE];
u8 ns_cmd[LHBM_GAMMA_CMD_SIZE];
u8 aod_cmd[LHBM_GAMMA_CMD_SIZE];
} local_hbm_gamma;
};
#define to_spanel(ctx) container_of(ctx, struct s6e3fc3_p10_panel, base)
static void s6e3fc3_p10_lhbm_gamma_read(struct exynos_panel *ctx)
{
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
struct s6e3fc3_p10_panel *spanel = to_spanel(ctx);
int ret;
u8 *hs_cmd = spanel->local_hbm_gamma.hs_cmd;
u8 *ns_cmd = spanel->local_hbm_gamma.ns_cmd;
u8 *aod_cmd = spanel->local_hbm_gamma.aod_cmd;
u8 buf[LHBM_GAMMA_CMD_SIZE * 2] = {0};
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_on_f0);
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x28, 0xF2); /* global para */
EXYNOS_DCS_WRITE_SEQ(ctx, 0xF2, 0xCC); /* 10 bit */
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x00, 0x22, 0xD8); /* global para */
ret = mipi_dsi_dcs_read(dsi, 0xD8, hs_cmd + 1, LHBM_GAMMA_CMD_SIZE - 1);
if (ret == (LHBM_GAMMA_CMD_SIZE - 1)) {
/* fill in gamma write command 0x65 in offset 0 */
hs_cmd[0] = 0x65;
exynos_bin2hex(hs_cmd + 1, LHBM_GAMMA_CMD_SIZE - 1,
buf, sizeof(buf));
dev_info(ctx->dev, "%s: hs_gamma: %s\n", __func__, buf);
} else {
dev_err(ctx->dev, "fail to read LHBM gamma for HS\n");
}
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x00, 0x1D, 0xD8); /* global para */
ret = mipi_dsi_dcs_read(dsi, 0xD8, ns_cmd + 1, LHBM_GAMMA_CMD_SIZE - 1);
if (ret == (LHBM_GAMMA_CMD_SIZE - 1)) {
/* fill in gamma write command 0x65 in offset 0 */
ns_cmd[0] = 0x65;
exynos_bin2hex(ns_cmd + 1, LHBM_GAMMA_CMD_SIZE - 1,
buf, sizeof(buf));
dev_info(ctx->dev, "%s: ns_gamma: %s\n", __func__, buf);
} else {
dev_err(ctx->dev, "fail to read LHBM gamma for NS\n");
}
if (ctx->panel_rev >= PANEL_REV_EVT1) {
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x00, 0x18, 0xD8); /* global para */
ret = mipi_dsi_dcs_read(dsi, 0xD8, aod_cmd + 1, LHBM_GAMMA_CMD_SIZE - 1);
if (ret == (LHBM_GAMMA_CMD_SIZE - 1)) {
/* fill in gamma write command 0x65 in offset 0 */
aod_cmd[0] = 0x65;
exynos_bin2hex(aod_cmd + 1, LHBM_GAMMA_CMD_SIZE - 1,
buf, sizeof(buf));
dev_info(ctx->dev, "%s: aod_gamma: %s\n", __func__, buf);
} else {
dev_err(ctx->dev, "fail to read LHBM gamma for AOD\n");
}
}
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x00, 0x28, 0xF2); /* global para */
EXYNOS_DCS_WRITE_SEQ(ctx, 0xF2, 0xC4); /* 8 bit */
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_off_f0);
}
static void s6e3fc3_p10_lhbm_gamma_write(struct exynos_panel *ctx)
{
struct s6e3fc3_p10_panel *spanel = to_spanel(ctx);
const u8 *hs_cmd = spanel->local_hbm_gamma.hs_cmd;
const u8 *ns_cmd = spanel->local_hbm_gamma.ns_cmd;
const u8 *aod_cmd = spanel->local_hbm_gamma.aod_cmd;
if (!hs_cmd[0] && !ns_cmd[0] && !aod_cmd[0]) {
dev_err(ctx->dev, "%s: no lhbm gamma!\n", __func__);
return;
}
dev_dbg(ctx->dev, "%s\n", __func__);
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_on_f0);
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x28, 0xF2); /* global para */
EXYNOS_DCS_WRITE_SEQ(ctx, 0xF2, 0xCC); /* 10 bit */
if (hs_cmd[0]) {
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x03, 0xCD, 0x65); /* global para */
exynos_dcs_write(ctx, hs_cmd, LHBM_GAMMA_CMD_SIZE); /* write gamma */
}
if (ns_cmd[0]) {
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x03, 0xC8, 0x65); /* global para */
exynos_dcs_write(ctx, ns_cmd, LHBM_GAMMA_CMD_SIZE); /* write gamma */
}
if (aod_cmd[0]) {
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x03, 0xD7, 0x65); /* global para */
exynos_dcs_write(ctx, aod_cmd, LHBM_GAMMA_CMD_SIZE); /* write gamma */
}
EXYNOS_DCS_WRITE_SEQ(ctx, 0xB0, 0x00, 0x28, 0xF2); /* global para */
EXYNOS_DCS_WRITE_SEQ(ctx, 0xF2, 0xC4); /* 8 bit */
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_off_f0);
}
static void s6e3fc3_p10_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_p10_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;
/* 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_p10_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_p10_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, 0x28, 0xF2); /* global para */
EXYNOS_DCS_WRITE_SEQ(ctx, 0xF2, 0xCC); /* global para 10bit */
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_SEQ(ctx, 0xB0, 0x00, 0x28, 0xF2); /* global para */
EXYNOS_DCS_WRITE_SEQ(ctx, 0xF2, 0xC4); /* global para 8bit */
EXYNOS_DCS_WRITE_TABLE(ctx, freq_update); /* LTPS update */
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_off_f0);
}
static void s6e3fc3_p10_change_frequency(struct exynos_panel *ctx,
const 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 int s6e3fc3_p10_set_op_hz(struct exynos_panel *ctx, unsigned int hz)
{
const unsigned int vrefresh = drm_mode_vrefresh(&ctx->current_mode->mode);
if ((vrefresh > hz) || ((hz != 60) && (hz != 90))) {
dev_err(ctx->dev, "invalid op_hz=%u for vrefresh=%u\n",
hz, vrefresh);
return -EINVAL;
}
ctx->op_hz = hz;
if (ctx->op_hz == 60) {
exynos_panel_send_cmd_set(ctx,
&s6e3fc3_p10_mode_ns_60_cmd_set);
} else {
if (vrefresh == 60) {
exynos_panel_send_cmd_set(ctx,
&s6e3fc3_p10_mode_hs_60_cmd_set);
} else {
exynos_panel_send_cmd_set(ctx,
&s6e3fc3_p10_mode_hs_90_cmd_set);
}
}
dev_info(ctx->dev, "set op_hz at %u\n", hz);
return 0;
}
static void s6e3fc3_p10_update_wrctrld(struct exynos_panel *ctx)
{
u8 val = S6E3FC3_P10_WRCTRLD_BCTRL_BIT;
if (IS_HBM_ON(ctx->hbm_mode))
val |= S6E3FC3_P10_WRCTRLD_HBM_BIT;
if (ctx->hbm.local_hbm.enabled)
val |= S6E3FC3_P10_WRCTRLD_LOCAL_HBM_BIT;
if (ctx->dimming_on)
val |= S6E3FC3_P10_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_p10_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);
if (ctx->panel_rev >= PANEL_REV_EVT1) {
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_on_f0);
EXYNOS_DCS_WRITE_TABLE(ctx, new_gamma_ip_enable);
}
/* backlight control and dimming */
s6e3fc3_p10_update_wrctrld(ctx);
if (ctx->panel_rev >= PANEL_REV_EVT1)
EXYNOS_DCS_WRITE_TABLE(ctx, test_key_off_f0);
s6e3fc3_p10_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");
}
static int s6e3fc3_p10_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_p10_init_cmd_set);
if (ctx->panel_rev == PANEL_REV_EVT1)
exynos_panel_send_cmd_set(ctx, &s6e3fc3_p10_evt1_irc_setting_cmd_set);
s6e3fc3_p10_change_frequency(ctx, drm_mode_vrefresh(mode));
s6e3fc3_p10_lhbm_gamma_write(ctx);
exynos_panel_send_cmd_set(ctx, &s6e3fc3_p10_lhbm_location_cmd_set);
/* 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_p10_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_p10_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)
s6e3fc3_p10_update_wrctrld(exynos_panel);
if (irc_update) {
EXYNOS_DCS_WRITE_SEQ(exynos_panel, 0xF0, 0x5A, 0x5A);
EXYNOS_DCS_WRITE_SEQ(exynos_panel, 0xB0, 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_p10_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_p10_update_wrctrld(exynos_panel);
}
static void s6e3fc3_p10_set_local_hbm_mode(struct exynos_panel *exynos_panel,
bool local_hbm_en)
{
if (exynos_panel->hbm.local_hbm.enabled == local_hbm_en)
return;
exynos_panel->hbm.local_hbm.enabled = local_hbm_en;
s6e3fc3_p10_update_wrctrld(exynos_panel);
}
static void s6e3fc3_p10_mode_set(struct exynos_panel *ctx,
const struct exynos_panel_mode *pmode)
{
if (!ctx->enabled)
return;
s6e3fc3_p10_change_frequency(ctx, drm_mode_vrefresh(&pmode->mode));
}
static bool s6e3fc3_p10_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_p10_panel_init(struct exynos_panel *ctx)
{
struct dentry *csroot = ctx->debugfs_cmdset_entry;
exynos_panel_debugfs_create_cmdset(ctx, csroot,
&s6e3fc3_p10_init_cmd_set, "init");
s6e3fc3_p10_lhbm_gamma_read(ctx);
s6e3fc3_p10_lhbm_gamma_write(ctx);
}
static int s6e3fc3_p10_read_id(struct exynos_panel *ctx)
{
if (ctx->panel_rev < PANEL_REV_DVT1)
return exynos_panel_read_id(ctx);
return exynos_panel_read_ddic_id(ctx);
}
static void s6e3fc3_p10_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 int s6e3fc3_p10_panel_probe(struct mipi_dsi_device *dsi)
{
struct s6e3fc3_p10_panel *spanel;
spanel = devm_kzalloc(&dsi->dev, sizeof(*spanel), GFP_KERNEL);
if (!spanel)
return -ENOMEM;
spanel->base.op_hz = 90;
return exynos_panel_common_init(dsi, &spanel->base);
}
static const struct exynos_display_underrun_param underrun_param = {
.te_idle_us = 1000,
.te_var = 1,
};
static const u32 s6e3fc3_p10_bl_range[] = {
95, 205, 315, 400, 2047
};
static const struct exynos_panel_mode s6e3fc3_p10_modes[] = {
{
/* 1080x2400 @ 60Hz */
.mode = {
.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 = 66,
.height_mm = 146,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = 5673,
.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,
},
},
{
/* 1080x2400 @ 90Hz */
.mode = {
.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 = 66,
.height_mm = 146,
},
.exynos_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = 144,
.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_p10_lp_mode = {
.mode = {
/* 1080x2400 @ 30Hz */
.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 = 66,
.height_mm = 146,
},
.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_p10_drm_funcs = {
.disable = exynos_panel_disable,
.unprepare = exynos_panel_unprepare,
.prepare = exynos_panel_prepare,
.enable = s6e3fc3_p10_enable,
.get_modes = exynos_panel_get_modes,
};
static const struct exynos_panel_funcs s6e3fc3_p10_exynos_funcs = {
.set_brightness = exynos_panel_set_brightness,
.set_lp_mode = exynos_panel_set_lp_mode,
.set_nolp_mode = s6e3fc3_p10_set_nolp_mode,
.set_binned_lp = exynos_panel_set_binned_lp,
.set_hbm_mode = s6e3fc3_p10_set_hbm_mode,
.set_dimming_on = s6e3fc3_p10_set_dimming_on,
.set_local_hbm_mode = s6e3fc3_p10_set_local_hbm_mode,
.is_mode_seamless = s6e3fc3_p10_is_mode_seamless,
.mode_set = s6e3fc3_p10_mode_set,
.panel_init = s6e3fc3_p10_panel_init,
.get_panel_rev = s6e3fc3_p10_get_panel_rev,
.get_te2_edges = exynos_panel_get_te2_edges,
.configure_te2_edges = exynos_panel_configure_te2_edges,
.update_te2 = s6e3fc3_p10_update_te2,
.set_op_hz = s6e3fc3_p10_set_op_hz,
.read_id = s6e3fc3_p10_read_id,
};
const struct brightness_capability s6e3fc3_p10_brightness_capability = {
.normal = {
.nits = {
.min = 2,
.max = 500,
},
.level = {
.min = 4,
.max = 2047,
},
.percentage = {
.min = 0,
.max = 50,
},
},
.hbm = {
.nits = {
.min = 550,
.max = 1000,
},
.level = {
.min = 2048,
.max = 4095,
},
.percentage = {
.min = 50,
.max = 100,
},
},
};
const struct exynos_panel_desc samsung_s6e3fc3_p10 = {
.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_p10_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 = s6e3fc3_p10_bl_range,
.bl_num_ranges = ARRAY_SIZE(s6e3fc3_p10_bl_range),
.modes = s6e3fc3_p10_modes,
.num_modes = ARRAY_SIZE(s6e3fc3_p10_modes),
.off_cmd_set = &s6e3fc3_p10_off_cmd_set,
.lp_mode = &s6e3fc3_p10_lp_mode,
.lp_cmd_set = &s6e3fc3_p10_lp_cmd_set,
.binned_lp = s6e3fc3_p10_binned_lp,
.num_binned_lp = ARRAY_SIZE(s6e3fc3_p10_binned_lp),
.panel_func = &s6e3fc3_p10_drm_funcs,
.exynos_panel_func = &s6e3fc3_p10_exynos_funcs,
};
static const struct of_device_id exynos_panel_of_match[] = {
{ .compatible = "samsung,s6e3fc3-p10", .data = &samsung_s6e3fc3_p10 },
{ }
};
MODULE_DEVICE_TABLE(of, exynos_panel_of_match);
static struct mipi_dsi_driver exynos_panel_driver = {
.probe = s6e3fc3_p10_panel_probe,
.remove = exynos_panel_remove,
.driver = {
.name = "panel-samsung-s6e3fc3-p10",
.of_match_table = exynos_panel_of_match,
},
};
module_mipi_dsi_driver(exynos_panel_driver);
MODULE_AUTHOR("William Tai <williamtai@google.com>");
MODULE_DESCRIPTION("MIPI-DSI based Samsung s6e3fc3-p10 panel driver");
MODULE_LICENSE("GPL");