blob: c4ca9d5a4de26fa9a4e558893249ca7896b611d6 [file] [log] [blame]
/* SPDX-License-Identifier: MIT */
/*
* MIPI-DSI based CT3D panel driver.
*
* Copyright 2024 Google LLC
*
* Use of this source code is governed by an MIT-style
* license that can be found in the LICENSE file or at
* https://opensource.org/licenses/MIT.
*/
#include <drm/display/drm_dsc_helper.h>
#include <linux/debugfs.h>
#include <linux/delay.h>
#include <linux/module.h>
#include <linux/of_platform.h>
#include <linux/thermal.h>
#include <video/mipi_display.h>
#include "trace/panel_trace.h"
#include "gs_panel/drm_panel_funcs_defaults.h"
#include "gs_panel/gs_panel.h"
#include "gs_panel/gs_panel_funcs_defaults.h"
#define CT3D_DDIC_ID_LEN 8
#define CT3D_DIMMING_FRAME 32
#define WIDTH_MM 64
#define HEIGHT_MM 145
#define MIPI_DSI_FREQ_MBPS_DEFAULT 865
#define MIPI_DSI_FREQ_MBPS_ALTERNATIVE 756
#define PROJECT "CT3D"
/**
* struct ct3d_panel - panel specific runtime info
*
* This struct maintains ct3d panel specific runtime info, any fixed details about panel
* should most likely go into struct gs_panel_desc.
*/
struct ct3d_panel {
/** @base: base panel struct */
struct gs_panel base;
/** @is_hbm2_enabled: indicates panel is running in HBM mode 2 */
bool is_hbm2_enabled;
};
#define to_spanel(ctx) container_of(ctx, struct ct3d_panel, base)
static const struct gs_dsi_cmd ct3d_lp_cmds[] = {
/* Disable the Black insertion in AoD */
GS_DSI_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00),
GS_DSI_CMD(0xC0, 0x54),
/* disable dimming */
GS_DSI_CMD(0x53, 0x20),
/* enter AOD */
GS_DSI_CMD(MIPI_DCS_ENTER_IDLE_MODE),
/* Settings AOD Hclk */
GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x81),
GS_DSI_CMD(0x6F, 0x0E),
GS_DSI_CMD(0xF5, 0x20),
/* Lock TE2 30Hz */
GS_DSI_CMD(0x5A, 0x04),
};
static DEFINE_GS_CMDSET(ct3d_lp);
static const struct gs_dsi_cmd ct3d_lp_off_cmds[] = {
GS_DSI_CMD(0x6F, 0x04),
GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x00, 0x00),
};
static const struct gs_dsi_cmd ct3d_lp_night_cmds[] = {
/* 2 nit */
GS_DSI_CMD(0x6F, 0x04),
GS_DSI_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1_1),
MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x00, 0x03),
GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1_1),
MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x03, 0x33),
};
static const struct gs_dsi_cmd ct3d_lp_low_cmds[] = {
/* 10 nit */
GS_DSI_CMD(0x6F, 0x04),
GS_DSI_REV_CMD(PANEL_REV_GE(PANEL_REV_EVT1_1),
MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x07, 0xB2),
GS_DSI_REV_CMD(PANEL_REV_LT(PANEL_REV_EVT1_1),
MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x03, 0x33),
};
static const struct gs_dsi_cmd ct3d_lp_high_cmds[] = {
/* 50 nit */
GS_DSI_CMD(0x6F, 0x04),
GS_DSI_CMD(MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x0F, 0xFE),
};
static const struct gs_binned_lp ct3d_binned_lp[] = {
BINNED_LP_MODE("off", 0, ct3d_lp_off_cmds),
/* night threshold 4 nits */
BINNED_LP_MODE_TIMING("night", 105, ct3d_lp_night_cmds, 0, 32),
/* low threshold 40 nits */
BINNED_LP_MODE_TIMING("low", 871, ct3d_lp_low_cmds, 0, 32),
/* rising = 0, falling = 32 */
BINNED_LP_MODE_TIMING("high", 3628, ct3d_lp_high_cmds, 0, 32),
};
static const struct gs_dsi_cmd ct3d_off_cmds[] = {
GS_DSI_DELAY_CMD(100, MIPI_DCS_SET_DISPLAY_OFF),
GS_DSI_DELAY_CMD(120, MIPI_DCS_ENTER_SLEEP_MODE),
};
static DEFINE_GS_CMDSET(ct3d_off);
static const struct gs_dsi_cmd ct3d_init_cmds[] = {
/* CMD2, Page0 */
GS_DSI_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00),
GS_DSI_CMD(0x6F, 0x06),
GS_DSI_CMD(0xB5, 0x7F, 0x00, 0x2C, 0x00),
GS_DSI_CMD(0x6F, 0x11),
GS_DSI_CMD(0xB5, 0x2C, 0x2C, 0x2C, 0x2C, 0x2C),
GS_DSI_CMD(0x6F, 0x1B),
GS_DSI_CMD(0xBA, 0x18),
GS_DSI_CMD(0x6F, 0x2D),
GS_DSI_CMD(0xB5, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A,
0x2A, 0x2A, 0x2A, 0x25, 0x25, 0x1B, 0x1B, 0x13, 0x13, 0x0C, 0x0C,
0x0C, 0x0C, 0x07),
GS_DSI_CMD(0x6F, 0x44),
GS_DSI_CMD(0xB5, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A, 0x2A,
0x2A, 0x2A, 0x2A, 0x25, 0x25, 0x1B, 0x1B, 0x13, 0x13, 0x0C, 0x0C,
0x0C, 0x0C, 0x07),
/* CMD2, Page1 */
GS_DSI_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01),
GS_DSI_CMD(0x6F, 0x05),
GS_DSI_CMD(0xC5, 0x15, 0x15, 0x15, 0xDD),
/* FFC Off */
GS_DSI_CMD(0xC3, 0x00),
/* FFC setting (MIPI: 865Mbps) */
GS_DSI_CMD(0xC3, 0xDD, 0x06, 0x20, 0x0E, 0xFF,
0x00, 0x06, 0x20, 0x0E, 0xFF, 0x00,
0x04, 0x79, 0x0E, 0x06, 0x12, 0x13,
0x04, 0x79, 0x0E, 0x06, 0x12, 0x13,
0x04, 0x79, 0x0E, 0x06, 0x12, 0x13,
0x04, 0x79, 0x0E, 0x06, 0x12, 0x13,
0x04, 0x79, 0x0E, 0x06, 0x12, 0x13),
/* CMD2, Page3 */
GS_DSI_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x03),
/* Extend AOD TE width to 1.9ms */
GS_DSI_CMD(0x6F, 0x22),
GS_DSI_CMD(0xB3, 0x70, 0x7F),
/* CMD2, Page4 */
GS_DSI_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x04),
/* Extend DBI flash data update cycle time */
GS_DSI_CMD(0xBB, 0xB3, 0x01, 0xBC),
/* CMD2, Page7 */
GS_DSI_CMD(0xF0, 0x55, 0xAA, 0x52, 0x08, 0x07),
/* Round algorithm OFF */
GS_DSI_CMD(0xC0, 0x00),
/* CMD3, Page0 */
GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x80),
GS_DSI_CMD(0x6F, 0x19),
GS_DSI_CMD(0xF2, 0x00),
GS_DSI_CMD(0x6F, 0x1A),
GS_DSI_CMD(0xF4, 0x55),
GS_DSI_CMD(0x6F, 0x2D),
GS_DSI_CMD(0xFC, 0x44),
GS_DSI_CMD(0x6F, 0x11),
GS_DSI_CMD(0xF8, 0x01, 0x7B),
GS_DSI_CMD(0x6F, 0x2D),
GS_DSI_CMD(0xF8, 0x01, 0x1D),
/* CMD3, Page1 */
GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x81),
GS_DSI_CMD(0x6F, 0x05),
GS_DSI_CMD(0xFE, 0x3C),
GS_DSI_CMD(0x6F, 0x02),
GS_DSI_CMD(0xF9, 0x04),
GS_DSI_CMD(0x6F, 0x1E),
GS_DSI_CMD(0xFB, 0x0F),
GS_DSI_CMD(0x6F, 0x0D),
GS_DSI_CMD(0xFB, 0x84),
GS_DSI_CMD(0x6F, 0x0F),
GS_DSI_CMD(0xF5, 0x20),
/* CMD3, Page2 */
GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x82),
GS_DSI_CMD(0x6F, 0x09),
GS_DSI_CMD(0xF2, 0x55),
/* CMD3, Page3 */
GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x83),
GS_DSI_CMD(0x6F, 0x12),
GS_DSI_CMD(0xFE, 0x41),
/* CMD, Disable */
GS_DSI_CMD(0xFF, 0xAA, 0x55, 0xA5, 0x00),
GS_DSI_CMD(MIPI_DCS_SET_TEAR_SCANLINE, 0x00, 0x00),
GS_DSI_CMD(MIPI_DCS_SET_TEAR_ON, 0x00, 0x2D),
/* BC Dimming OFF */
GS_DSI_CMD(MIPI_DCS_WRITE_CONTROL_DISPLAY, 0x20),
GS_DSI_CMD(MIPI_DCS_SET_COLUMN_ADDRESS, 0x00, 0x00, 0x04, 0x37),
GS_DSI_CMD(MIPI_DCS_SET_PAGE_ADDRESS, 0x00, 0x00, 0x09, 0x77),
/* Normal GMA */
GS_DSI_CMD(MIPI_DCS_SET_GAMMA_CURVE, 0x00),
/* CMD1, DPC Temperature 25 */
GS_DSI_CMD(0x81, 0x01, 0x19),
GS_DSI_CMD(0x03, 0x01),
GS_DSI_CMD(0x90, 0x03, 0x03),
/* 2DSC & slice high 24 DSC v1.2a */
GS_DSI_CMD(0x91, 0x89, 0xA8, 0x00, 0x18, 0xD2, 0x00, 0x02, 0x25, 0x02,
0x35, 0x00, 0x07, 0x04, 0x86, 0x04, 0x3D, 0x10, 0xF0),
GS_DSI_CMD(0x2F, 0x02),
GS_DSI_DELAY_CMD(60, MIPI_DCS_EXIT_SLEEP_MODE)
};
static DEFINE_GS_CMDSET(ct3d_init);
static void ct3d_update_te2(struct gs_panel *ctx)
{
struct gs_panel_te2_timing timing;
struct device *dev = ctx->dev;
u8 width = 0x2D; /* default width */
u32 rising = 0, falling;
int ret;
ret = gs_panel_get_current_mode_te2(ctx, &timing);
if (!ret) {
falling = timing.falling_edge;
if (falling >= timing.rising_edge) {
rising = timing.rising_edge;
width = falling - rising;
} else {
dev_warn(dev, "invalid timing, use default setting\n");
}
} else if (ret == -EAGAIN) {
dev_dbg(dev, "Panel is not ready, use default setting\n");
} else {
return;
}
dev_dbg(dev, "TE2 updated: rising= 0x%x, width= 0x%x", rising, width);
GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_TEAR_SCANLINE, 0x00, rising);
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_SET_TEAR_ON, 0x00, width);
}
static void ct3d_update_irc(struct gs_panel *ctx,
const enum gs_hbm_mode hbm_mode,
const int vrefresh)
{
struct device *dev = ctx->dev;
struct ct3d_panel *spanel = to_spanel(ctx);
const u16 level = gs_panel_get_brightness(ctx);
if (GS_IS_HBM_ON_IRC_OFF(hbm_mode)) {
/* sync from bigSurf panel_rev >= PANEL_REV_EVT */
if (level == ctx->desc->brightness_desc->brt_capability->hbm.level.max) {
/* set brightness to hbm2 */
GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x0F, 0xFF);
spanel->is_hbm2_enabled = true;
/* set ACD Level 3 */
GS_DCS_BUF_ADD_CMD(dev, 0x55, 0x04);
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x0C);
GS_DCS_BUF_ADD_CMD(dev, 0xB0, 0x0E, 0x2C, 0x32);
} else {
if (spanel->is_hbm2_enabled) {
/* set ACD off */
GS_DCS_BUF_ADD_CMD(dev, 0x55, 0x00);
}
spanel->is_hbm2_enabled = false;
}
dev_info(ctx->dev, "%s: is HBM2 enabled: %d\n",
__func__, spanel->is_hbm2_enabled);
GS_DCS_BUF_ADD_CMD(dev, 0x5F, 0x01);
if (vrefresh == 120) {
GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x00);
GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_GAMMA_CURVE, 0x02);
if (ctx->panel_rev < PANEL_REV_PVT) {
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x03);
GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x40);
}
} else {
GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x02);
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x03);
GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x40);
}
} else {
const u8 val1 = level >> 8;
const u8 val2 = level & 0xff;
GS_DCS_BUF_ADD_CMD(dev, 0x5F, 0x00);
if (vrefresh == 120) {
GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x00);
GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_GAMMA_CURVE, 0x00);
if (ctx->panel_rev < PANEL_REV_PVT) {
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x03);
GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x10);
}
} else {
GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x02);
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x03);
GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x10);
}
/* sync from bigSurf panel_rev >= PANEL_REV_EVT */
GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, val1, val2);
}
/* Empty command is for flush */
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0x00);
}
static void ct3d_change_frequency(struct gs_panel *ctx,
const struct gs_panel_mode *pmode)
{
struct device *dev = ctx->dev;
int vrefresh = drm_mode_vrefresh(&pmode->mode);
if (vrefresh != 60 && vrefresh != 120)
return;
if (!GS_IS_HBM_ON(ctx->hbm_mode)) {
if (vrefresh == 120) {
GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x00);
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_SET_GAMMA_CURVE, 0x00);
} else {
GS_DCS_BUF_ADD_CMD(dev, 0x2F, 0x02);
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x03);
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xC0, 0x10);
}
} else {
ct3d_update_irc(ctx, ctx->hbm_mode, vrefresh);
}
dev_dbg(dev, "%s: change to %uhz\n", __func__, vrefresh);
}
static void ct3d_set_dimming(struct gs_panel *ctx,
bool dimming_on)
{
const struct gs_panel_mode *pmode = ctx->current_mode;
struct device *dev = ctx->dev;
ctx->dimming_on = dimming_on;
if (pmode->gs_mode.is_lp_mode) {
dev_warn(dev, "in lp mode, skip dimming update\n");
return;
}
GS_DCS_WRITE_CMD(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY,
ctx->dimming_on ? 0x28 : 0x20);
dev_dbg(dev, "%s dimming_on=%d\n", __func__, dimming_on);
}
static void ct3d_set_nolp_mode(struct gs_panel *ctx,
const struct gs_panel_mode *pmode)
{
struct device *dev = ctx->dev;
int vrefresh = drm_mode_vrefresh(&pmode->mode);
if (!gs_is_panel_active(ctx))
return;
/* exit AOD */
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0xC0, 0x54);
GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_EXIT_IDLE_MODE);
GS_DCS_BUF_ADD_CMD(dev, 0xFF, 0xAA, 0x55, 0xA5, 0x81);
GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x0E);
GS_DCS_BUF_ADD_CMD(dev, 0xF5, 0x2B);
GS_DCS_BUF_ADD_CMD(dev, 0x5A, 0x04);
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_WRITE_CONTROL_DISPLAY,
ctx->dimming_on ? 0x28 : 0x20);
ct3d_change_frequency(ctx, pmode);
ctx->timestamps.idle_exit_dimming_delay_ts = ktime_add_us(
ktime_get(), 100 + GS_VREFRESH_TO_PERIOD_USEC(vrefresh) * 2);
dev_info(dev, "exit LP mode\n");
}
static void ct3d_dimming_frame_setting(struct gs_panel *ctx, u8 dimming_frame)
{
struct device *dev = ctx->dev;
/* Fixed time 1 frame */
if (!dimming_frame)
dimming_frame = 0x01;
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0xB2, 0x19);
GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x05);
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xB2, dimming_frame, dimming_frame);
}
static int ct3d_enable(struct drm_panel *panel)
{
struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
struct device *dev = ctx->dev;
const struct gs_panel_mode *pmode = ctx->current_mode;
if (!pmode) {
dev_err(dev, "no current mode set\n");
return -EINVAL;
}
dev_dbg(dev, "%s\n", __func__);
gs_panel_reset_helper(ctx);
gs_panel_send_cmdset(ctx, &ct3d_init_cmdset);
ct3d_change_frequency(ctx, pmode);
ct3d_dimming_frame_setting(ctx, CT3D_DIMMING_FRAME);
if (pmode->gs_mode.is_lp_mode)
gs_panel_set_lp_mode_helper(ctx, pmode);
GS_DCS_WRITE_CMD(dev, MIPI_DCS_SET_DISPLAY_ON);
ctx->dsi_hs_clk_mbps = MIPI_DSI_FREQ_MBPS_DEFAULT;
return 0;
}
static int ct3d_disable(struct drm_panel *panel)
{
struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
struct ct3d_panel *spanel = to_spanel(ctx);
int ret;
spanel->is_hbm2_enabled = false;
ret = gs_panel_disable(panel);
if (ret)
return ret;
return 0;
}
static int ct3d_atomic_check(struct gs_panel *ctx, struct drm_atomic_state *state)
{
struct drm_connector *conn = &ctx->gs_connector->base;
struct drm_connector_state *new_conn_state =
drm_atomic_get_new_connector_state(state, conn);
struct drm_crtc_state *old_crtc_state, *new_crtc_state;
struct device *dev = ctx->dev;
if (!ctx->current_mode || drm_mode_vrefresh(&ctx->current_mode->mode) == 120 ||
!new_conn_state || !new_conn_state->crtc)
return 0;
new_crtc_state = drm_atomic_get_new_crtc_state(state, new_conn_state->crtc);
old_crtc_state = drm_atomic_get_old_crtc_state(state, new_conn_state->crtc);
if (!old_crtc_state || !new_crtc_state || !new_crtc_state->active)
return 0;
if (!drm_atomic_crtc_effectively_active(old_crtc_state) ||
(ctx->current_mode->gs_mode.is_lp_mode &&
drm_mode_vrefresh(&new_crtc_state->mode) == 60)) {
struct drm_display_mode *mode = &new_crtc_state->adjusted_mode;
mode->clock = mode->htotal * mode->vtotal * 120 / 1000;
if (mode->clock != new_crtc_state->mode.clock) {
new_crtc_state->mode_changed = true;
ctx->gs_connector->needs_commit = true;
dev_dbg(dev, "raise mode (%s) clock to 120hz on %s\n",
mode->name,
!drm_atomic_crtc_effectively_active(old_crtc_state) ?
"resume" : "lp exit");
}
} else if (old_crtc_state->adjusted_mode.clock != old_crtc_state->mode.clock) {
/* clock hacked in last commit due to resume or lp exit, undo that */
new_crtc_state->mode_changed = true;
new_crtc_state->adjusted_mode.clock = new_crtc_state->mode.clock;
ctx->gs_connector->needs_commit = false;
dev_dbg(dev, "restore mode (%s) clock after resume or lp exit\n",
new_crtc_state->mode.name);
}
return 0;
}
static void ct3d_pre_update_ffc(struct gs_panel *ctx)
{
struct device *dev = ctx->dev;
dev_dbg(ctx->dev, "%s\n", __func__);
/* FFC off */
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01);
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xC3, 0x00);
}
static void ct3d_update_ffc(struct gs_panel *ctx, unsigned int hs_clk_mbps)
{
struct device *dev = ctx->dev;
dev_dbg(ctx->dev, "%s: hs_clk_mbps: current=%d, target=%d\n",
__func__, ctx->dsi_hs_clk_mbps, hs_clk_mbps);
if (hs_clk_mbps != MIPI_DSI_FREQ_MBPS_DEFAULT &&
hs_clk_mbps != MIPI_DSI_FREQ_MBPS_ALTERNATIVE) {
dev_warn(ctx->dev, "invalid hs_clk_mbps=%d for FFC\n", hs_clk_mbps);
} else if (ctx->dsi_hs_clk_mbps != hs_clk_mbps) {
dev_info(ctx->dev, "%s: updating for hs_clk_mbps=%d\n", __func__, hs_clk_mbps);
ctx->dsi_hs_clk_mbps = hs_clk_mbps;
/* Update FFC */
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01);
if (hs_clk_mbps == MIPI_DSI_FREQ_MBPS_DEFAULT)
GS_DCS_BUF_ADD_CMD(dev, 0xC3, 0xDD, 0x06, 0x20, 0x0E, 0xFF,
0x00, 0x06, 0x20, 0x0E, 0xFF, 0x00,
0x04, 0x79, 0x0E, 0x06, 0x12, 0x13,
0x04, 0x79, 0x0E, 0x06, 0x12, 0x13,
0x04, 0x79, 0x0E, 0x06, 0x12, 0x13,
0x04, 0x79, 0x0E, 0x06, 0x12, 0x13,
0x04, 0x79, 0x0E, 0x06, 0x12, 0x13);
else /* MIPI_DSI_FREQ_MBPS_ALTERNATIVE */
GS_DCS_BUF_ADD_CMD(dev, 0xC3, 0xDD, 0x06, 0x20, 0x0C, 0xFF,
0x00, 0x06, 0x20, 0x0C, 0xFF, 0x00,
0x04, 0x63, 0x0C, 0x05, 0xD9, 0x10,
0x04, 0x63, 0x0C, 0x05, 0xD9, 0x10,
0x04, 0x63, 0x0C, 0x05, 0xD9, 0x10,
0x04, 0x63, 0x0C, 0x05, 0xD9, 0x10,
0x04, 0x63, 0x0C, 0x05, 0xD9, 0x10);
}
/* FFC on */
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x01);
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xC3, 0xDD);
}
static int ct3d_set_brightness(struct gs_panel *ctx, u16 br)
{
struct device *dev = ctx->dev;
struct ct3d_panel *spanel = to_spanel(ctx);
if (ctx->current_mode->gs_mode.is_lp_mode) {
if (gs_panel_has_func(ctx, set_binned_lp))
ctx->desc->gs_panel_func->set_binned_lp(ctx, br);
return 0;
}
if (GS_IS_HBM_ON_IRC_OFF(ctx->hbm_mode)
&& br == ctx->desc->brightness_desc->brt_capability->hbm.level.max) {
/* set brightness to hbm2 */
GS_DCS_BUF_ADD_CMD(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS, 0x0F, 0xFF);
spanel->is_hbm2_enabled = true;
/* set ACD Level 3 */
GS_DCS_BUF_ADD_CMD(dev, 0x55, 0x04);
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x0C);
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xB0, 0x0E, 0x2C, 0x32);
dev_info(ctx->dev, "%s: is HBM2 enabled : %d\n",
__func__, spanel->is_hbm2_enabled);
} else {
if (spanel->is_hbm2_enabled) {
/* set ACD off */
GS_DCS_BUF_ADD_CMD(dev, 0x55, 0x00);
dev_info(ctx->dev, "%s: is HBM2 enabled: off\n", __func__);
}
spanel->is_hbm2_enabled = false;
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, MIPI_DCS_SET_DISPLAY_BRIGHTNESS,
br >> 8, br & 0xff);
}
return 0;
}
static void ct3d_set_hbm_mode(struct gs_panel *ctx,
enum gs_hbm_mode hbm_mode)
{
const struct gs_panel_mode *pmode = ctx->current_mode;
struct device *dev = ctx->dev;
int vrefresh = drm_mode_vrefresh(&pmode->mode);
if (ctx->hbm_mode == hbm_mode)
return;
if (hbm_mode == GS_HBM_OFF) {
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x11);
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xB2, 0x01, 0x01, 0x43);
} else {
GS_DCS_BUF_ADD_CMD(dev, 0xF0, 0x55, 0xAA, 0x52, 0x08, 0x00);
GS_DCS_BUF_ADD_CMD(dev, 0x6F, 0x11);
GS_DCS_BUF_ADD_CMD_AND_FLUSH(dev, 0xB2, 0x00, 0x00, 0x41);
}
ct3d_update_irc(ctx, hbm_mode, vrefresh);
ctx->hbm_mode = hbm_mode;
dev_info(dev, "hbm_on=%d hbm_ircoff=%d\n", GS_IS_HBM_ON(ctx->hbm_mode),
GS_IS_HBM_ON_IRC_OFF(ctx->hbm_mode));
}
static void ct3d_mode_set(struct gs_panel *ctx,
const struct gs_panel_mode *pmode)
{
ct3d_change_frequency(ctx, pmode);
}
static void ct3d_get_panel_rev(struct gs_panel *ctx, u32 id)
{
/* extract command 0xDB */
const u8 build_code = (id & 0xFF00) >> 8;
const u8 main = (build_code & 0xE0) >> 3;
const u8 sub = (build_code & 0x0C) >> 2;
gs_panel_get_panel_rev(ctx, main | sub);
}
static int ct3d_read_id(struct gs_panel *ctx)
{
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
struct device *dev = ctx->dev;
char buf[CT3D_DDIC_ID_LEN] = {0};
int ret;
GS_DCS_WRITE_CMD(dev, 0xFF, 0xAA, 0x55, 0xA5, 0x81);
ret = mipi_dsi_dcs_read(dsi, 0xF2, buf, CT3D_DDIC_ID_LEN);
if (ret != CT3D_DDIC_ID_LEN) {
dev_warn(dev, "Unable to read DDIC id (%d)\n", ret);
goto done;
} else {
ret = 0;
}
bin2hex(ctx->panel_id, buf, CT3D_DDIC_ID_LEN);
done:
GS_DCS_WRITE_CMD(dev, 0xFF, 0xAA, 0x55, 0xA5, 0x00);
return ret;
}
static const struct gs_display_underrun_param underrun_param = {
.te_idle_us = 350,
.te_var = 1,
};
/* Truncate 8-bit signed value to 6-bit signed value */
#define TO_6BIT_SIGNED(v) ((v) & 0x3F)
static const struct drm_dsc_config ct3d_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)},
},
/* Used DSC v1.2 */
.dsc_version_major = 1,
.dsc_version_minor = 2,
.slice_count = 2,
.slice_height = 24,
};
#define CT3D_DSC { \
.enabled = true, \
.dsc_count = 1, \
.cfg = &ct3d_dsc_cfg, \
}
static const struct gs_panel_mode_array ct3d_modes = {
.num_modes = 2,
.modes = {
{
.mode = {
.name = "1080x2424@60:60",
DRM_MODE_TIMING(60, 1080, 32, 12, 16, 2424, 12, 4, 15),
/* aligned to bootloader setting */
.type = DRM_MODE_TYPE_PREFERRED,
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = 8604,
.bpc = 8,
.dsc = CT3D_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = 0,
.falling_edge = 45,
},
},
{
.mode = {
.name = "1080x2424@120:120",
DRM_MODE_TIMING(120, 1080, 32, 12, 16, 2424, 12, 4, 15),
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.te_usec = 274,
.bpc = 8,
.dsc = CT3D_DSC,
.underrun_param = &underrun_param,
},
.te2_timing = {
.rising_edge = 0,
.falling_edge = 45,
},
},
},
};
static const struct gs_panel_mode_array ct3d_lp_modes = {
.num_modes = 1,
.modes = {
{
.mode = {
.name = "1080x2424@30:30",
DRM_MODE_TIMING(30, 1080, 32, 12, 16, 2424, 12, 4, 15),
.type = DRM_MODE_TYPE_DRIVER,
.width_mm = WIDTH_MM,
.height_mm = HEIGHT_MM,
},
.gs_mode = {
.mode_flags = MIPI_DSI_CLOCK_NON_CONTINUOUS,
.vblank_usec = 120,
.bpc = 8,
.dsc = CT3D_DSC,
.underrun_param = &underrun_param,
.is_lp_mode = true,
},
},
},
};
static void ct3d_debugfs_init(struct drm_panel *panel, struct dentry *root)
{
struct gs_panel *ctx = container_of(panel, struct gs_panel, base);
struct dentry *panel_root, *csroot;
if (!ctx)
return;
panel_root = debugfs_lookup("panel", root);
if (!panel_root)
return;
csroot = debugfs_lookup("cmdsets", panel_root);
if (!csroot)
goto panel_out;
gs_panel_debugfs_create_cmdset(csroot, &ct3d_init_cmdset, "init");
dput(csroot);
panel_out:
dput(panel_root);
}
static void ct3d_panel_init(struct gs_panel *ctx)
{
ct3d_dimming_frame_setting(ctx, CT3D_DIMMING_FRAME);
}
static int ct3d_panel_probe(struct mipi_dsi_device *dsi)
{
struct ct3d_panel *spanel;
spanel = devm_kzalloc(&dsi->dev, sizeof(*spanel), GFP_KERNEL);
if (!spanel)
return -ENOMEM;
spanel->is_hbm2_enabled = false;
return gs_dsi_panel_common_init(dsi, &spanel->base);
}
static const struct drm_panel_funcs ct3d_drm_funcs = {
.disable = ct3d_disable,
.unprepare = gs_panel_unprepare,
.prepare = gs_panel_prepare,
.enable = ct3d_enable,
.get_modes = gs_panel_get_modes,
.debugfs_init = ct3d_debugfs_init,
};
static int ct3d_panel_config(struct gs_panel *ctx);
static const struct gs_panel_funcs ct3d_gs_funcs = {
.set_brightness = ct3d_set_brightness,
.set_lp_mode = gs_panel_set_lp_mode_helper,
.set_nolp_mode = ct3d_set_nolp_mode,
.set_binned_lp = gs_panel_set_binned_lp_helper,
.set_hbm_mode = ct3d_set_hbm_mode,
.set_dimming = ct3d_set_dimming,
.is_mode_seamless = gs_panel_is_mode_seamless_helper,
.mode_set = ct3d_mode_set,
.panel_init = ct3d_panel_init,
.panel_config = ct3d_panel_config,
.get_panel_rev = ct3d_get_panel_rev,
.get_te2_edges = gs_panel_get_te2_edges_helper,
.set_te2_edges = gs_panel_set_te2_edges_helper,
.update_te2 = ct3d_update_te2,
.read_id = ct3d_read_id,
.atomic_check = ct3d_atomic_check,
.pre_update_ffc = ct3d_pre_update_ffc,
.update_ffc = ct3d_update_ffc,
};
static const struct gs_brightness_configuration ct3d_btr_configs[] = {
{
.panel_rev = PANEL_REV_LATEST,
.default_brightness = 1816,
.brt_capability = {
.normal = {
.nits = {
.min = 2,
.max = 1200,
},
.level = {
.min = 1,
.max = 3628,
},
.percentage = {
.min = 0,
.max = 67,
},
},
.hbm = {
.nits = {
.min = 1200,
.max = 1800,
},
.level = {
.min = 3629,
.max = 3939,
},
.percentage = {
.min = 67,
.max = 100,
},
},
},
},
};
static struct gs_panel_brightness_desc ct3d_brightness_desc = {
.max_luminance = 10000000,
.max_avg_luminance = 10000000,
.min_luminance = 5,
};
static struct gs_panel_reg_ctrl_desc ct3d_reg_ctrl_desc = {
.reg_ctrl_enable = {
{PANEL_REG_ID_VDDI, 0},
{PANEL_REG_ID_VCI, 0},
{PANEL_REG_ID_VDDD, 10},
},
.reg_ctrl_disable = {
{PANEL_REG_ID_VDDD, 0},
{PANEL_REG_ID_VCI, 0},
{PANEL_REG_ID_VDDI, 0},
},
};
struct gs_panel_desc google_ct3d = {
.data_lane_cnt = 4,
/* supported HDR format bitmask : 1(DOLBY_VISION), 2(HDR10), 3(HLG) */
.hdr_formats = BIT(2) | BIT(3),
.brightness_desc = &ct3d_brightness_desc,
.modes = &ct3d_modes,
.off_cmdset = &ct3d_off_cmdset,
.lp_modes = &ct3d_lp_modes,
.lp_cmdset = &ct3d_lp_cmdset,
.binned_lp = ct3d_binned_lp,
.num_binned_lp = ARRAY_SIZE(ct3d_binned_lp),
.has_off_binned_lp_entry = true,
.reg_ctrl_desc = &ct3d_reg_ctrl_desc,
.panel_func = &ct3d_drm_funcs,
.gs_panel_func = &ct3d_gs_funcs,
.default_dsi_hs_clk_mbps = MIPI_DSI_FREQ_MBPS_DEFAULT,
.reset_timing_ms = {1, 1, 20},
.refresh_on_lp = true,
};
static int ct3d_panel_config(struct gs_panel *ctx)
{
int ret;
/* b/300383405 Currently, we can't support multiple
* displays in `display_layout_configuration.xml`.
*/
/* gs_panel_model_init(ctx, PROJECT, 0); */
ret = gs_panel_update_brightness_desc(&ct3d_brightness_desc, ct3d_btr_configs,
ARRAY_SIZE(ct3d_btr_configs), ctx->panel_rev);
return ret;
}
static const struct of_device_id gs_panel_of_match[] = {
{ .compatible = "google,gs-ct3d", .data = &google_ct3d },
{ }
};
MODULE_DEVICE_TABLE(of, gs_panel_of_match);
static struct mipi_dsi_driver gs_panel_driver = {
.probe = ct3d_panel_probe,
.remove = gs_dsi_panel_common_remove,
.driver = {
.name = "panel-gs-ct3d",
.of_match_table = gs_panel_of_match,
},
};
module_mipi_dsi_driver(gs_panel_driver);
MODULE_AUTHOR("Gil Liu <[email protected]>");
MODULE_DESCRIPTION("MIPI-DSI based Google ct3d panel driver");
MODULE_LICENSE("Dual MIT/GPL");