2772 lines
70 KiB
C
Executable file
2772 lines
70 KiB
C
Executable file
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* MIPI-DSI based Samsung common panel driver.
|
|
*
|
|
* Copyright (c) 2019 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 <linux/debugfs.h>
|
|
#include <linux/init.h>
|
|
#include <linux/of_platform.h>
|
|
#include <linux/of_gpio.h>
|
|
#include <linux/platform_device.h>
|
|
#include <linux/regulator/consumer.h>
|
|
|
|
#include <drm/drm_panel.h>
|
|
#include <drm/drm_encoder.h>
|
|
#include <drm/drm_atomic.h>
|
|
#include <drm/drm_atomic_helper.h>
|
|
#include <drm/drm_probe_helper.h>
|
|
#include <drm/drm_edid.h>
|
|
#include <drm/drm_print.h>
|
|
#include <drm/drm_vblank.h>
|
|
|
|
#include <exynos_drm_decon.h>
|
|
#include <exynos_display_common.h>
|
|
#include "../exynos_drm_crtc.h"
|
|
#include <exynos_drm_connector.h>
|
|
#include <exynos_drm_dsim.h>
|
|
#include <exynos_drm_tui.h>
|
|
#include <mcd_drm_decon.h>
|
|
#include <mcd_drm_dsim.h>
|
|
#include "../exynos_drm_dqe.h"
|
|
#include "panel-samsung-drv.h"
|
|
#include "mcd-panel-samsung-helper.h"
|
|
#include "panel_drv.h"
|
|
#if IS_ENABLED(CONFIG_PANEL_FREQ_HOP) || IS_ENABLED(CONFIG_USDM_PANEL_FREQ_HOP)
|
|
#include "panel_freq_hop.h"
|
|
#endif
|
|
|
|
#define MCD_PANEL_PROBE_DELAY_MSEC (5000)
|
|
|
|
#define call_mcd_panel_func(p, func, args...) \
|
|
(((p) && (p)->funcs && (p)->funcs->func) ? (p)->funcs->func(p, ##args) : -EINVAL)
|
|
|
|
static int panel_log_level = 6;
|
|
module_param(panel_log_level, int, 0644);
|
|
MODULE_PARM_DESC(panel_log_level, "log level for panel drv [default : 6]");
|
|
|
|
int get_panel_log_level(void)
|
|
{
|
|
return panel_log_level;
|
|
}
|
|
EXPORT_SYMBOL(get_panel_log_level);
|
|
|
|
static int panel_cmd_log;
|
|
module_param(panel_cmd_log, int, 0644);
|
|
MODULE_PARM_DESC(panel_cmd_log, "log level for panel command [default : 0]");
|
|
|
|
static int exynos_panel_get_bts_fps(const struct exynos_panel *ctx,
|
|
const struct exynos_panel_mode *pmode);
|
|
static int mcd_drm_panel_set_display_mode(struct exynos_panel *ctx,
|
|
const struct exynos_panel_mode *pmode);
|
|
|
|
struct edid panel_edid;
|
|
|
|
static inline bool need_panel_recovery(struct exynos_panel *ctx)
|
|
{
|
|
const struct drm_connector_state *conn_state =
|
|
ctx->exynos_connector.base.state;
|
|
struct drm_crtc *crtc = conn_state->crtc;
|
|
struct exynos_drm_crtc *exynos_crtc;
|
|
struct exynos_drm_connector *exynos_conn;
|
|
struct exynos_drm_connector_state *exynos_conn_state;
|
|
|
|
if (!crtc)
|
|
return false;
|
|
|
|
exynos_crtc = to_exynos_crtc(crtc);
|
|
|
|
if (!exynos_crtc || !exynos_crtc->ops)
|
|
return false;
|
|
|
|
exynos_conn = &ctx->exynos_connector;
|
|
if (!exynos_conn)
|
|
return false;
|
|
|
|
exynos_conn_state = to_exynos_connector_state(exynos_conn->base.state);
|
|
if (!exynos_conn_state)
|
|
return false;
|
|
|
|
if (exynos_crtc->ops->is_recovering &&
|
|
exynos_crtc->ops->is_recovering(exynos_crtc) &&
|
|
!exynos_conn_state->requested_panel_recovery) {
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
static inline bool is_panel_tui(struct exynos_panel *ctx)
|
|
{
|
|
const struct drm_connector_state *conn_state =
|
|
ctx->exynos_connector.base.state;
|
|
|
|
if (conn_state && conn_state->crtc)
|
|
return is_tui_trans(conn_state->crtc->state);
|
|
|
|
return false;
|
|
}
|
|
|
|
int mcd_drm_panel_get_modes(struct drm_panel *panel, struct drm_connector *conn)
|
|
{
|
|
struct exynos_panel *ctx =
|
|
container_of(panel, struct exynos_panel, panel);
|
|
struct drm_display_mode *preferred_mode = NULL;
|
|
int i;
|
|
|
|
panel_debug(ctx, "+\n");
|
|
|
|
for (i = 0; i < (int)ctx->desc->num_modes; i++) {
|
|
const struct exynos_panel_mode *pmode = &ctx->desc->modes[i];
|
|
struct drm_display_mode *mode;
|
|
|
|
mode = drm_mode_duplicate(conn->dev, &pmode->mode);
|
|
if (!mode) {
|
|
panel_err(ctx, "failed to add mode %s\n", pmode->mode.name);
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mode->type |= DRM_MODE_TYPE_DRIVER;
|
|
drm_mode_probed_add(conn, mode);
|
|
|
|
panel_debug(ctx, "added display mode: %s\n", mode->name);
|
|
|
|
if (!preferred_mode || (mode->type & DRM_MODE_TYPE_PREFERRED))
|
|
preferred_mode = mode;
|
|
}
|
|
|
|
if (preferred_mode) {
|
|
panel_debug(ctx, "preferred display mode: %s\n",
|
|
preferred_mode->name);
|
|
preferred_mode->type |= DRM_MODE_TYPE_PREFERRED;
|
|
conn->display_info.width_mm = preferred_mode->width_mm;
|
|
conn->display_info.height_mm = preferred_mode->height_mm;
|
|
}
|
|
|
|
panel_debug(ctx, "-\n");
|
|
|
|
return i;
|
|
}
|
|
EXPORT_SYMBOL(mcd_drm_panel_get_modes);
|
|
|
|
void exynos_panel_set_lp_mode(struct exynos_panel *ctx, const struct exynos_panel_mode *pmode)
|
|
{
|
|
int ret;
|
|
|
|
if (!ctx->enabled) {
|
|
panel_info(ctx, "exynos panel is disabed\n");
|
|
return;
|
|
}
|
|
|
|
panel_info(ctx, "enter %dhz LP mode\n", drm_mode_vrefresh(&pmode->mode));
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, doze);
|
|
if (ret < 0)
|
|
panel_err(ctx, "mcd_panel doze failed(ret:%d)\n", ret);
|
|
}
|
|
EXPORT_SYMBOL(exynos_panel_set_lp_mode);
|
|
|
|
static void exynos_panel_connector_print_state(struct drm_printer *p,
|
|
const struct exynos_drm_connector_state *exynos_conn_state)
|
|
{
|
|
const struct exynos_drm_connector *exynos_conn =
|
|
to_exynos_connector(exynos_conn_state->base.connector);
|
|
const struct exynos_display_mode *exynos_mode =
|
|
&exynos_conn_state->exynos_mode;
|
|
const struct exynos_panel *ctx = exynos_connector_to_panel(exynos_conn);
|
|
const struct exynos_panel_desc *desc = ctx->desc;
|
|
|
|
drm_printf(p, "\tenabled: %d\n", ctx->enabled);
|
|
if (ctx->current_mode) {
|
|
const struct drm_display_mode *m = &ctx->current_mode->mode;
|
|
|
|
drm_printf(p, " \tcurrent mode: %s\n", m->name);
|
|
}
|
|
if (exynos_mode) {
|
|
const struct exynos_display_dsc *dsc = &exynos_mode->dsc;
|
|
|
|
drm_printf(p, " \tcurrent exynos_mode:\n");
|
|
drm_printf(p, " \t\tdsc: en=%d dsc_cnt=%d slice_cnt=%d slice_h=%d\n",
|
|
dsc->enabled, dsc->dsc_count, dsc->slice_count,
|
|
dsc->slice_height);
|
|
drm_printf(p, " \t\tpanel bpc: %d\n", exynos_mode->bpc);
|
|
drm_printf(p, " \t\top_mode: %s\n", exynos_mode->mode_flags &
|
|
MIPI_DSI_MODE_VIDEO ? "video" : "cmd");
|
|
drm_printf(p, " \t\tlp_mode_state: %d\n", exynos_mode->is_lp_mode);
|
|
drm_printf(p, " \t\tbts_fps: %d\n", exynos_mode->bts_fps);
|
|
}
|
|
drm_printf(p, "\tluminance: [%u, %u] avg: %u\n",
|
|
desc->min_luminance, desc->max_luminance,
|
|
desc->max_avg_luminance);
|
|
drm_printf(p, "\thdr_formats: 0x%x\n", desc->hdr_formats);
|
|
drm_printf(p, "\tadjusted_fps: %d\n", exynos_conn_state->adjusted_fps);
|
|
#if IS_ENABLED(CONFIG_SUPPORT_MASK_LAYER) || IS_ENABLED(CONFIG_USDM_PANEL_MASK_LAYER)
|
|
drm_printf(p, "\tfingerprint_mask_req: 0x%x\n", exynos_conn_state->fingerprint_mask);
|
|
#endif
|
|
}
|
|
|
|
int exynos_drm_cmdset_add(struct exynos_panel *ctx, u8 type, size_t size,
|
|
const u8 *data)
|
|
{
|
|
u8 *buf;
|
|
int index;
|
|
|
|
if (data == NULL || size <= 0)
|
|
return -EINVAL;
|
|
|
|
if (ctx->cmdset_msg_total >= MAX_CMDSET_NUM ||
|
|
ctx->cmdset_payload_total + size >= MAX_CMDSET_PAYLOAD) {
|
|
panel_err(ctx, "Command set buffer is full\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
buf = kzalloc(sizeof(u8) * size, GFP_KERNEL);
|
|
if (buf == NULL)
|
|
return -ENOMEM;
|
|
|
|
memcpy(buf, data, size);
|
|
index = ctx->cmdset_msg_total++;
|
|
ctx->msg[index].type = type;
|
|
ctx->msg[index].tx_buf = buf;
|
|
ctx->msg[index].tx_len = size;
|
|
ctx->cmdset_payload_total += size;
|
|
|
|
panel_debug(ctx, "%d msgs, %d payload\n",
|
|
ctx->cmdset_msg_total, ctx->cmdset_payload_total);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(exynos_drm_cmdset_add);
|
|
|
|
int exynos_drm_cmdset_cleanup(struct exynos_panel *ctx)
|
|
{
|
|
int i;
|
|
int msg_total = ctx->cmdset_msg_total;
|
|
|
|
panel_debug(ctx, "msg:%d\n", ctx->cmdset_msg_total);
|
|
|
|
for (i = 0; i < msg_total; i++) {
|
|
if (ctx->msg[i].tx_buf != NULL)
|
|
kfree(ctx->msg[i].tx_buf);
|
|
ctx->msg[i].tx_buf = NULL;
|
|
ctx->msg[i].type = 0;
|
|
ctx->msg[i].tx_len = 0;
|
|
ctx->msg[i].flags = 0;
|
|
}
|
|
ctx->cmdset_msg_total = 0;
|
|
ctx->cmdset_payload_total = 0;
|
|
|
|
panel_debug(ctx, "-\n");
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(exynos_drm_cmdset_cleanup);
|
|
|
|
int exynos_drm_cmdset_flush(struct exynos_panel *ctx, bool wait_vsync, bool wait_fifo)
|
|
{
|
|
int ret;
|
|
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
|
|
struct mipi_dsi_msg *msg_head;
|
|
|
|
if (!ctx->cmdset_msg_total) {
|
|
dev_err(ctx->dev, "there is no MIPI command to transfer\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
msg_head = ctx->msg;
|
|
if (dsi->mode_flags & MIPI_DSI_MODE_LPM)
|
|
msg_head->flags |= MIPI_DSI_MSG_USE_LPM;
|
|
|
|
ret = dsim_host_cmdset_transfer(dsi->host, ctx->msg, ctx->cmdset_msg_total, wait_vsync, wait_fifo);
|
|
if (ret < 0)
|
|
panel_err(ctx, "failed to tx command set data\n");
|
|
|
|
ret = exynos_drm_cmdset_cleanup(ctx);
|
|
if (ret < 0)
|
|
panel_err(ctx, "failed to cleanup command set data\n");
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(exynos_drm_cmdset_flush);
|
|
|
|
static int
|
|
exynos_panel_connector_set_property(struct exynos_drm_connector *exynos_conn,
|
|
struct exynos_drm_connector_state *exynos_conn_state,
|
|
struct drm_property *property, uint64_t val)
|
|
{
|
|
#if IS_ENABLED(CONFIG_SUPPORT_MASK_LAYER) || IS_ENABLED(CONFIG_USDM_PANEL_MASK_LAYER)
|
|
struct exynos_panel *ctx = exynos_connector_to_panel(exynos_conn);
|
|
const struct exynos_drm_connector_properties *p =
|
|
exynos_drm_connector_get_properties(&ctx->exynos_connector);
|
|
|
|
if (property == p->fingerprint_mask)
|
|
exynos_conn_state->fingerprint_mask = val;
|
|
#endif
|
|
return 0;
|
|
}
|
|
|
|
static int
|
|
exynos_panel_connector_get_property(struct exynos_drm_connector *exynos_conn,
|
|
const struct exynos_drm_connector_state *exynos_conn_state,
|
|
struct drm_property *property, uint64_t *val)
|
|
{
|
|
struct exynos_panel *ctx = exynos_connector_to_panel(exynos_conn);
|
|
const struct exynos_drm_connector_properties *p =
|
|
exynos_drm_connector_get_properties(&ctx->exynos_connector);
|
|
|
|
if (property == p->max_luminance)
|
|
*val = ctx->desc->max_luminance;
|
|
else if (property == p->max_avg_luminance)
|
|
*val = ctx->desc->max_avg_luminance;
|
|
else if (property == p->min_luminance)
|
|
*val = ctx->desc->min_luminance;
|
|
else if (property == p->hdr_formats)
|
|
*val = ctx->desc->hdr_formats;
|
|
else if (property == p->adjusted_fps)
|
|
*val = exynos_conn_state->adjusted_fps;
|
|
#if IS_ENABLED(CONFIG_SUPPORT_MASK_LAYER) || IS_ENABLED(CONFIG_USDM_PANEL_MASK_LAYER)
|
|
else if (property == p->fingerprint_mask)
|
|
*val = ctx->fingerprint_mask;
|
|
#endif
|
|
else
|
|
return -EINVAL;
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct exynos_drm_connector_funcs exynos_panel_connector_funcs = {
|
|
.atomic_print_state = exynos_panel_connector_print_state,
|
|
.atomic_set_property = exynos_panel_connector_set_property,
|
|
.atomic_get_property = exynos_panel_connector_get_property,
|
|
};
|
|
|
|
static int exynos_drm_connector_modes(struct drm_connector *connector)
|
|
{
|
|
struct exynos_panel *ctx = connector_to_exynos_panel(connector);
|
|
int ret;
|
|
|
|
ret = drm_panel_get_modes(&ctx->panel, connector);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to get panel display modes\n");
|
|
return ret;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static const struct exynos_panel_mode *
|
|
exynos_panel_get_mode(struct exynos_panel *ctx,
|
|
const struct drm_display_mode *mode)
|
|
{
|
|
int i;
|
|
const struct exynos_panel_mode *pmode;
|
|
|
|
for (i = 0; i < (int)ctx->desc->num_modes; i++) {
|
|
pmode = &ctx->desc->modes[i];
|
|
|
|
if (drm_mode_equal(&pmode->mode, mode))
|
|
return pmode;
|
|
}
|
|
|
|
for (i = 0; i < (int)ctx->desc->num_lp_modes; i++) {
|
|
pmode = &ctx->desc->lp_modes[i];
|
|
|
|
if (pmode && drm_mode_equal(&pmode->mode, mode))
|
|
return pmode;
|
|
}
|
|
|
|
panel_err(ctx, "fail to get panel mode matching w/ mode(%s)\n",
|
|
mode->name);
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static void exynos_drm_connector_check_seamless_modeset(
|
|
struct exynos_drm_connector_state *exynos_conn_state,
|
|
const struct exynos_panel_mode *new_pmode,
|
|
const struct exynos_panel_mode *old_pmode)
|
|
{
|
|
const struct drm_display_mode *new_mode;
|
|
const struct drm_display_mode *old_mode;
|
|
|
|
if (!old_pmode)
|
|
return;
|
|
|
|
new_mode = &new_pmode->mode;
|
|
old_mode = &old_pmode->mode;
|
|
|
|
if (!drm_mode_match(new_mode, old_mode, DRM_MODE_MATCH_TIMINGS))
|
|
exynos_conn_state->seamless_modeset |= SEAMLESS_MODESET_MRES;
|
|
if (drm_mode_vrefresh(new_mode) != drm_mode_vrefresh(old_mode))
|
|
exynos_conn_state->seamless_modeset |= SEAMLESS_MODESET_VREF;
|
|
if (new_pmode->exynos_mode.is_lp_mode != old_pmode->exynos_mode.is_lp_mode)
|
|
exynos_conn_state->seamless_modeset |= SEAMLESS_MODESET_LP;
|
|
}
|
|
|
|
static inline bool is_seamless_mode_change(struct drm_crtc_state *crtc_state,
|
|
const struct exynos_panel_mode *pmode)
|
|
{
|
|
return (crtc_state->active && !crtc_state->active_changed);
|
|
}
|
|
|
|
static int exynos_drm_connector_atomic_check(struct drm_connector *connector,
|
|
struct drm_atomic_state *state)
|
|
{
|
|
struct drm_connector_state *connector_state =
|
|
drm_atomic_get_new_connector_state(state, connector);
|
|
struct exynos_drm_connector_state *exynos_conn_state =
|
|
to_exynos_connector_state(connector_state);
|
|
struct drm_crtc_state *new_crtc_state, *old_crtc_state;
|
|
struct exynos_panel *ctx = connector_to_exynos_panel(connector);
|
|
const struct exynos_panel_mode *pmode;
|
|
const struct exynos_panel_mode *old_pmode;
|
|
|
|
if (!connector_state->best_encoder) {
|
|
panel_err(ctx, "encoder is null\n");
|
|
return 0;
|
|
}
|
|
|
|
old_crtc_state = drm_atomic_get_old_crtc_state(state, connector_state->crtc);
|
|
new_crtc_state = drm_atomic_get_new_crtc_state(state, connector_state->crtc);
|
|
|
|
pmode = exynos_panel_get_mode(ctx, &new_crtc_state->mode);
|
|
if (!pmode) {
|
|
panel_err(ctx, "%s can't support none panel mode\n",
|
|
new_crtc_state->mode.name);
|
|
return -EINVAL;
|
|
}
|
|
|
|
old_pmode = exynos_panel_get_mode(ctx, &old_crtc_state->mode);
|
|
if (old_pmode) {
|
|
if ((old_pmode->exynos_mode.is_lp_mode && pmode->exynos_mode.is_lp_mode) &&
|
|
((old_crtc_state->active == 1 && new_crtc_state->active == 0) || /* doze->doze_suspend */
|
|
(old_crtc_state->active == 0 && new_crtc_state->active == 1))) /* doze_suspend->doze */
|
|
exynos_conn_state->is_lp_transition = 1;
|
|
|
|
panel_debug(ctx, "old lp_mode(%d), old crtc active(%d)\n",
|
|
old_pmode->exynos_mode.is_lp_mode, old_crtc_state->active);
|
|
panel_debug(ctx, "new lp_mode(%d), new crtc active(%d)\n",
|
|
pmode->exynos_mode.is_lp_mode, new_crtc_state->active);
|
|
panel_debug(ctx, "is_lp_transition(%d)\n",
|
|
exynos_conn_state->is_lp_transition);
|
|
}
|
|
|
|
/* display state : doze_suspend */
|
|
if (pmode->exynos_mode.is_lp_mode && !new_crtc_state->active) {
|
|
down_write(&ctx->panel_drm_state_lock);
|
|
ctx->panel_drm_state = PANEL_DRM_STATE_LPM_DISABLED;
|
|
up_write(&ctx->panel_drm_state_lock);
|
|
}
|
|
|
|
if (!new_crtc_state->mode_changed)
|
|
return 0;
|
|
|
|
exynos_conn_state->exynos_mode = pmode->exynos_mode;
|
|
if (!exynos_conn_state->exynos_mode.bts_fps)
|
|
exynos_conn_state->exynos_mode.bts_fps =
|
|
exynos_panel_get_bts_fps(ctx, pmode);
|
|
|
|
if (is_seamless_mode_change(new_crtc_state, pmode)) {
|
|
exynos_drm_connector_check_seamless_modeset(
|
|
exynos_conn_state, pmode, old_pmode);
|
|
}
|
|
|
|
panel_debug(ctx, "old mode_changed(%d), active_changed(%d)\n",
|
|
old_crtc_state->mode_changed,
|
|
old_crtc_state->active_changed);
|
|
panel_debug(ctx, "new mode_changed(%d), active_changed(%d)\n",
|
|
new_crtc_state->mode_changed,
|
|
new_crtc_state->active_changed);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct drm_connector_helper_funcs exynos_connector_helper_funcs = {
|
|
.atomic_check = exynos_drm_connector_atomic_check,
|
|
.get_modes = exynos_drm_connector_modes,
|
|
};
|
|
|
|
static u8 exynos_drm_connector_edid_get_checksum(const u8 *raw_edid)
|
|
{
|
|
int i;
|
|
u8 csum = 0;
|
|
|
|
for (i = 0; i < EDID_LENGTH; i++)
|
|
csum += raw_edid[i];
|
|
|
|
return csum;
|
|
}
|
|
|
|
static void exynos_drm_connector_edid(struct drm_connector *connector)
|
|
{
|
|
int ret;
|
|
struct edid *edid = &panel_edid;
|
|
const u8 edid_header[] = {0x00, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0x00};
|
|
const u8 edid_display_name[] = {0x73, 0x61, 0x6d, 0x73, 0x75, 0x6e, 0x67, 0x20, 0x6c, 0x63, 0x64, 0x20, 0x20};
|
|
|
|
memset(edid, 0, sizeof(struct edid));
|
|
memcpy(edid, edid_header, sizeof(edid_header));
|
|
|
|
/*
|
|
* If you want to manipulate EDID information, use member variables
|
|
* of edid structure in here.
|
|
*
|
|
* ex) edid.width_cm = xx; edid.height_cm = yy
|
|
*/
|
|
edid->mfg_id[0] = 0x4C; // manufacturer ID for samsung
|
|
edid->mfg_id[1] = 0x2D;
|
|
edid->mfg_week = 0x10; /* 16 week */
|
|
edid->mfg_year = 0x1E; /* 1990 + 30 = 2020 year */
|
|
edid->detailed_timings[0].data.other_data.type = 0xfc; // for display name
|
|
memcpy(edid->detailed_timings[0].data.other_data.data.str.str, edid_display_name, 13);
|
|
/* sum of all 128 bytes should equal 0 (mod 0x100) */
|
|
edid->checksum = 0x100 - exynos_drm_connector_edid_get_checksum((const u8 *)edid);
|
|
|
|
pr_info("%s: checksum(0x%x)\n", __func__,
|
|
exynos_drm_connector_edid_get_checksum((const u8 *)edid));
|
|
|
|
connector->override_edid = false;
|
|
ret = drm_connector_update_edid_property(connector, edid);
|
|
}
|
|
|
|
static int exynos_panel_attach_lp_mode(struct exynos_drm_connector *exynos_conn,
|
|
const struct exynos_panel_desc *desc)
|
|
{
|
|
struct exynos_drm_connector_properties *p =
|
|
exynos_drm_connector_get_properties(exynos_conn);
|
|
struct drm_mode_modeinfo *umodes;
|
|
struct drm_property_blob *blob;
|
|
const struct exynos_panel_mode *lp_modes = desc->lp_modes;
|
|
const size_t num_lp_modes = desc->num_lp_modes;
|
|
int ret = 0, i = 0;
|
|
|
|
if (!lp_modes) {
|
|
ret = -ENOENT;
|
|
goto err;
|
|
}
|
|
|
|
umodes = kzalloc(num_lp_modes * sizeof(struct drm_mode_modeinfo), GFP_KERNEL);
|
|
if (!umodes) {
|
|
ret = -ENOMEM;
|
|
goto err;
|
|
}
|
|
for (i = 0; i < (int)num_lp_modes; i++)
|
|
drm_mode_convert_to_umode(&umodes[i], &lp_modes[i].mode);
|
|
|
|
blob = drm_property_create_blob(exynos_conn->base.dev,
|
|
num_lp_modes * sizeof(struct drm_mode_modeinfo), umodes);
|
|
if (IS_ERR(blob)) {
|
|
ret = PTR_ERR(blob);
|
|
goto err_blob;
|
|
}
|
|
|
|
drm_object_attach_property(&exynos_conn->base.base, p->lp_mode, blob->base.id);
|
|
|
|
err_blob:
|
|
kfree(umodes);
|
|
err:
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_panel_attach_properties(struct exynos_panel *ctx)
|
|
{
|
|
const struct exynos_drm_connector_properties *p =
|
|
exynos_drm_connector_get_properties(&ctx->exynos_connector);
|
|
struct drm_mode_object *obj = &ctx->exynos_connector.base.base;
|
|
const struct exynos_panel_desc *desc = ctx->desc;
|
|
int ret = 0;
|
|
|
|
if (!p || !desc)
|
|
return -ENOENT;
|
|
|
|
drm_object_attach_property(obj, p->min_luminance, 2);
|
|
drm_object_attach_property(obj, p->max_luminance, 500);
|
|
drm_object_attach_property(obj, p->max_avg_luminance, 0);
|
|
drm_object_attach_property(obj, p->hdr_formats, 0);
|
|
drm_object_attach_property(obj, p->adjusted_fps, 0);
|
|
#if IS_ENABLED(CONFIG_SUPPORT_MASK_LAYER) || IS_ENABLED(CONFIG_USDM_PANEL_MASK_LAYER)
|
|
drm_object_attach_property(obj, p->fingerprint_mask, 0);
|
|
#endif
|
|
|
|
if (IS_ENABLED(CONFIG_DRM_SAMSUNG_DOZE)) {
|
|
ret = exynos_panel_attach_lp_mode(&ctx->exynos_connector, desc);
|
|
if (ret)
|
|
panel_err(ctx, "Failed to attach lp mode (%d)\n", ret);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int exynos_panel_bridge_attach(struct drm_bridge *bridge,
|
|
enum drm_bridge_attach_flags flags)
|
|
{
|
|
struct exynos_panel *ctx = bridge_to_exynos_panel(bridge);
|
|
struct drm_connector *connector = &ctx->exynos_connector.base;
|
|
int ret;
|
|
|
|
ret = exynos_drm_connector_init(bridge->dev, &ctx->exynos_connector,
|
|
&exynos_panel_connector_funcs,
|
|
DRM_MODE_CONNECTOR_DSI);
|
|
if (ret) {
|
|
panel_err(ctx, "failed to initialize connector with drm\n");
|
|
return ret;
|
|
}
|
|
|
|
ret = exynos_panel_attach_properties(ctx);
|
|
if (ret) {
|
|
panel_err(ctx, "failed to attach connector properties\n");
|
|
return ret;
|
|
}
|
|
|
|
drm_connector_helper_add(connector, &exynos_connector_helper_funcs);
|
|
|
|
ret = drm_connector_register(connector);
|
|
if (ret)
|
|
goto err;
|
|
|
|
drm_connector_attach_encoder(connector, bridge->encoder);
|
|
connector->funcs->reset(connector);
|
|
connector->status = connector_status_connected;
|
|
|
|
drm_kms_helper_hotplug_event(connector->dev);
|
|
|
|
exynos_drm_connector_edid(connector);
|
|
|
|
return 0;
|
|
|
|
err:
|
|
drm_connector_unregister(connector);
|
|
drm_connector_cleanup(connector);
|
|
return ret;
|
|
}
|
|
|
|
static void exynos_panel_bridge_detach(struct drm_bridge *bridge)
|
|
{
|
|
struct exynos_panel *ctx = bridge_to_exynos_panel(bridge);
|
|
|
|
drm_connector_unregister(&ctx->exynos_connector.base);
|
|
drm_connector_cleanup(&ctx->exynos_connector.base);
|
|
}
|
|
|
|
static void exynos_panel_enable(struct drm_bridge *bridge)
|
|
{
|
|
struct exynos_panel *ctx = bridge_to_exynos_panel(bridge);
|
|
struct exynos_drm_connector *exynos_conn = &ctx->exynos_connector;
|
|
struct exynos_drm_connector_state *exynos_conn_state =
|
|
to_exynos_connector_state(exynos_conn->base.state);
|
|
const struct drm_display_mode *current_mode = &ctx->current_mode->mode;
|
|
|
|
if (exynos_conn_state->is_lp_transition && ctx->enabled) {
|
|
panel_info(ctx, "skip in lp mode transition\n");
|
|
return;
|
|
}
|
|
|
|
if (is_panel_tui(ctx) || need_panel_recovery(ctx)) {
|
|
panel_info(ctx, "tui transition : skip\n");
|
|
return;
|
|
}
|
|
|
|
if (ctx->enabled) {
|
|
panel_info(ctx, "panel is already initialized\n");
|
|
return;
|
|
}
|
|
|
|
if (ctx->desc->lp11_reset) {
|
|
panel_info(ctx, "%s lp11_reset:%d\n", __func__, ctx->desc->lp11_reset);
|
|
|
|
/* 1. RST HIGH in LP11 */
|
|
call_mcd_panel_func(ctx->mcd_panel_dev, reset_lp11);
|
|
|
|
/* 2. LP11 -> HS CLK (dsim) */
|
|
dsim_atomic_activate(bridge->encoder);
|
|
}
|
|
|
|
if (drm_panel_enable(&ctx->panel))
|
|
return;
|
|
|
|
exynos_conn_state->adjusted_fps = drm_mode_vrefresh(current_mode);
|
|
}
|
|
|
|
static void exynos_panel_pre_enable(struct drm_bridge *bridge)
|
|
{
|
|
struct exynos_panel *ctx = bridge_to_exynos_panel(bridge);
|
|
struct exynos_drm_connector *exynos_conn = &ctx->exynos_connector;
|
|
struct exynos_drm_connector_state *exynos_conn_state =
|
|
to_exynos_connector_state(exynos_conn->base.state);
|
|
|
|
if (exynos_conn_state->is_lp_transition && ctx->enabled) {
|
|
panel_info(ctx, "skip in lp mode transition\n");
|
|
/* display state : doze */
|
|
down_write(&ctx->panel_drm_state_lock);
|
|
ctx->panel_drm_state = PANEL_DRM_STATE_LPM_ENABLED;
|
|
up_write(&ctx->panel_drm_state_lock);
|
|
return;
|
|
}
|
|
|
|
if (ctx->enabled) {
|
|
panel_info(ctx, "panel is already initialized\n");
|
|
return;
|
|
}
|
|
|
|
if (is_panel_tui(ctx) || need_panel_recovery(ctx)) {
|
|
panel_info(ctx, "tui transition : skip\n");
|
|
return;
|
|
}
|
|
|
|
drm_panel_prepare(&ctx->panel);
|
|
|
|
down_write(&ctx->panel_drm_state_lock);
|
|
ctx->panel_drm_state = PANEL_DRM_STATE_ENABLED;
|
|
up_write(&ctx->panel_drm_state_lock);
|
|
}
|
|
|
|
static void exynos_panel_disable(struct drm_bridge *bridge)
|
|
{
|
|
struct exynos_panel *ctx = bridge_to_exynos_panel(bridge);
|
|
struct exynos_drm_connector *exynos_conn = &ctx->exynos_connector;
|
|
struct exynos_drm_connector_state *exynos_conn_state =
|
|
to_exynos_connector_state(exynos_conn->base.state);
|
|
|
|
if (exynos_conn_state->is_lp_transition) {
|
|
panel_info(ctx, "skip in lp mode transition\n");
|
|
return;
|
|
}
|
|
|
|
if (is_panel_tui(ctx) || need_panel_recovery(ctx)) {
|
|
panel_info(ctx, "tui transition : skip\n");
|
|
return;
|
|
}
|
|
|
|
drm_panel_disable(&ctx->panel);
|
|
}
|
|
|
|
static void exynos_panel_post_disable(struct drm_bridge *bridge)
|
|
{
|
|
struct exynos_panel *ctx = bridge_to_exynos_panel(bridge);
|
|
struct exynos_drm_connector *exynos_conn = &ctx->exynos_connector;
|
|
struct exynos_drm_connector_state *exynos_conn_state =
|
|
to_exynos_connector_state(exynos_conn->base.state);
|
|
|
|
if (exynos_conn_state->is_lp_transition) {
|
|
panel_info(ctx, "skip in lp mode transition\n");
|
|
return;
|
|
}
|
|
|
|
if (is_panel_tui(ctx) || need_panel_recovery(ctx)) {
|
|
panel_info(ctx, "tui transition : skip\n");
|
|
return;
|
|
}
|
|
|
|
down_write(&ctx->panel_drm_state_lock);
|
|
ctx->panel_drm_state = PANEL_DRM_STATE_DISABLED;
|
|
up_write(&ctx->panel_drm_state_lock);
|
|
|
|
drm_panel_unprepare(&ctx->panel);
|
|
}
|
|
|
|
static void exynos_panel_bridge_mode_set(struct drm_bridge *bridge,
|
|
const struct drm_display_mode *mode,
|
|
const struct drm_display_mode *adjusted_mode)
|
|
{
|
|
struct exynos_panel *ctx = bridge_to_exynos_panel(bridge);
|
|
struct mipi_dsi_device *dsi = to_mipi_dsi_device(ctx->dev);
|
|
const struct exynos_panel_funcs *funcs = ctx->desc->exynos_panel_func;
|
|
const struct exynos_panel_mode *curr_pmode = ctx->current_mode;
|
|
const struct exynos_panel_mode *pmode =
|
|
exynos_panel_get_mode(ctx, adjusted_mode);
|
|
|
|
panel_debug(ctx, "+\n");
|
|
|
|
if (WARN_ON(!pmode))
|
|
return;
|
|
|
|
dsi->mode_flags = pmode->exynos_mode.mode_flags;
|
|
|
|
if (funcs) {
|
|
const bool is_lp_mode = pmode->exynos_mode.is_lp_mode;
|
|
const struct drm_connector *conn = &ctx->exynos_connector.base;
|
|
struct exynos_drm_connector_state *exynos_state =
|
|
to_exynos_connector_state(conn->state);
|
|
|
|
if (is_lp_mode && funcs->set_lp_mode)
|
|
funcs->set_lp_mode(ctx, pmode);
|
|
else if (funcs->mode_set) {
|
|
|
|
if (!curr_pmode)
|
|
exynos_state->seamless_modeset |= SEAMLESS_MODESET_VREF;
|
|
|
|
funcs->mode_set(ctx, pmode, exynos_state->seamless_modeset);
|
|
}
|
|
|
|
if (SEAMLESS_MODESET_VREF & exynos_state->seamless_modeset &&
|
|
ctx->enabled)
|
|
exynos_state->adjusted_fps = drm_mode_vrefresh(&pmode->mode);
|
|
}
|
|
|
|
panel_info(ctx, "change the panel(%s) display mode (%s -> %s)\n",
|
|
ctx->enabled ? "on" : "off", curr_pmode ?
|
|
curr_pmode->mode.name : "none", pmode->mode.name);
|
|
ctx->current_mode = pmode;
|
|
|
|
panel_debug(ctx, "-\n");
|
|
}
|
|
|
|
static const struct drm_bridge_funcs exynos_panel_bridge_funcs = {
|
|
.attach = exynos_panel_bridge_attach,
|
|
.detach = exynos_panel_bridge_detach,
|
|
.pre_enable = exynos_panel_pre_enable,
|
|
.enable = exynos_panel_enable,
|
|
.disable = exynos_panel_disable,
|
|
.post_disable = exynos_panel_post_disable,
|
|
.mode_set = exynos_panel_bridge_mode_set,
|
|
};
|
|
|
|
static void exynos_panel_parse_vendor_pps(struct exynos_panel *ctx)
|
|
{
|
|
struct device_node *np;
|
|
struct drm_device *drm_dev = NULL;
|
|
struct drm_crtc *crtc;
|
|
struct exynos_drm_crtc *exynos_crtc = NULL;
|
|
struct decon_device *decon;
|
|
|
|
if (!ctx->mcd_panel_dev) {
|
|
panel_err(ctx, "mcd_panel_dev has null\n");
|
|
return;
|
|
}
|
|
|
|
drm_dev = ctx->exynos_connector.base.dev;
|
|
if (!drm_dev) {
|
|
panel_info(ctx, "drm_dev has null\n");
|
|
return;
|
|
}
|
|
|
|
drm_for_each_crtc(crtc, drm_dev)
|
|
if (to_exynos_crtc(crtc)->possible_type & EXYNOS_DISPLAY_TYPE_DSI) {
|
|
exynos_crtc = to_exynos_crtc(crtc);
|
|
break;
|
|
}
|
|
|
|
if (!exynos_crtc) {
|
|
panel_info(ctx, "exynos_crtc has null\n");
|
|
return;
|
|
}
|
|
|
|
decon = exynos_crtc->ctx;
|
|
if (!decon) {
|
|
panel_info(ctx, "decon has null\n");
|
|
return;
|
|
}
|
|
|
|
np = ctx->mcd_panel_dev->ap_vendor_setting_node;
|
|
if (!np) {
|
|
panel_err(ctx, "mcd_panel ddi-node is null\n");
|
|
return;
|
|
}
|
|
|
|
/* get vendor pps parameter from panel dt node */
|
|
of_property_read_u32(np, "initial_xmit_delay",
|
|
&decon->config.vendor_pps.initial_xmit_delay);
|
|
of_property_read_u32(np, "initial_dec_delay",
|
|
&decon->config.vendor_pps.initial_dec_delay);
|
|
of_property_read_u32(np, "scale_increment_interval",
|
|
&decon->config.vendor_pps.scale_increment_interval);
|
|
of_property_read_u32(np, "final_offset",
|
|
&decon->config.vendor_pps.final_offset);
|
|
}
|
|
|
|
static void exynos_panel_parse_vfp_detail(struct exynos_panel *ctx)
|
|
{
|
|
struct device_node *np;
|
|
struct mipi_dsi_device *dsi;
|
|
struct dsim_device *dsim;
|
|
|
|
if (!ctx->mcd_panel_dev) {
|
|
panel_err(ctx, "mcd_panel_dev has null\n");
|
|
return;
|
|
}
|
|
|
|
np = ctx->mcd_panel_dev->ap_vendor_setting_node;
|
|
if (!np) {
|
|
panel_err(ctx, "mcd_panel ddi-node is null\n");
|
|
return;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi->host) {
|
|
panel_err(ctx, "invalid dsi host\n");
|
|
return;
|
|
}
|
|
|
|
dsim = container_of(dsi->host, struct dsim_device, dsi_host);
|
|
if (dsim != NULL) {
|
|
if (of_property_read_u32(np, "lines-cmd-allow",
|
|
&dsim->config.line_cmd_allow))
|
|
dsim->config.line_cmd_allow = 4;
|
|
|
|
if (of_property_read_u32(np, "lines-stable-vfp",
|
|
&dsim->config.line_stable_vfp))
|
|
dsim->config.line_stable_vfp = 2;
|
|
} else {
|
|
panel_err(ctx, "DSIM is not found\n");
|
|
}
|
|
}
|
|
|
|
static int exynos_panel_get_bts_fps(const struct exynos_panel *ctx,
|
|
const struct exynos_panel_mode *pmode)
|
|
{
|
|
size_t num_modes = ctx->desc->num_modes;
|
|
int i;
|
|
int mode_fps, max_fps = 0;
|
|
|
|
/* @command mode */
|
|
if (!(pmode->exynos_mode.mode_flags & MIPI_DSI_MODE_VIDEO)) {
|
|
#if !IS_ENABLED(CONFIG_DRM_MCD_COMMON)
|
|
return drm_mode_vrefresh(&pmode->mode);
|
|
#else
|
|
return mcd_decon_get_bts_fps(&pmode->mode);
|
|
#endif
|
|
}
|
|
|
|
/*
|
|
* In order to frame rate in video mode.
|
|
* VRR can be supported through changing VFP value.
|
|
* video processing time is always required as max fps.
|
|
* so bts.fps value have to be fixed in video mode operation.
|
|
*/
|
|
for (i = 0; i < num_modes; i++) {
|
|
mode_fps = drm_mode_vrefresh(&ctx->desc->modes[i].mode);
|
|
if (max_fps < mode_fps)
|
|
max_fps = mode_fps;
|
|
}
|
|
panel_info(ctx, "[Video Mode] max_fps(%d)\n", max_fps);
|
|
|
|
return max_fps;
|
|
}
|
|
|
|
static void exynos_panel_set_dqe_xml(struct device *dev, struct exynos_panel *ctx)
|
|
{
|
|
struct drm_device *drm_dev = NULL;
|
|
struct drm_crtc *crtc;
|
|
struct exynos_drm_crtc *exynos_crtc = NULL;
|
|
struct exynos_dqe *dqe;
|
|
|
|
drm_dev = ctx->exynos_connector.base.dev;
|
|
if (!drm_dev) {
|
|
panel_info(ctx, "drm_dev has null\n");
|
|
return;
|
|
}
|
|
|
|
drm_for_each_crtc(crtc, drm_dev)
|
|
if (to_exynos_crtc(crtc)->possible_type & EXYNOS_DISPLAY_TYPE_DSI) {
|
|
exynos_crtc = to_exynos_crtc(crtc);
|
|
break;
|
|
}
|
|
|
|
if (!exynos_crtc) {
|
|
panel_info(ctx, "exynos_crtc has null\n");
|
|
return;
|
|
}
|
|
|
|
dqe = exynos_crtc->dqe;
|
|
if (!dqe) {
|
|
panel_info(ctx, "dqe has null\n");
|
|
return;
|
|
}
|
|
|
|
if (ctx->desc->xml_suffix == NULL)
|
|
return;
|
|
strncpy(dqe->xml_suffix, ctx->desc->xml_suffix, DQE_XML_SUFFIX_SIZE - 1);
|
|
|
|
panel_info(ctx, "DQE XML Suffix (%s)\n", dqe->xml_suffix);
|
|
}
|
|
|
|
int mcd_drm_panel_check_probe(struct exynos_panel *ctx)
|
|
{
|
|
int ret = 0;
|
|
|
|
if (!ctx || !ctx->mcd_panel_dev)
|
|
return -ENODEV;
|
|
|
|
mutex_lock(&ctx->probe_lock);
|
|
if (ctx->mcd_panel_probed)
|
|
goto out;
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, probe);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "mcd_panel probe failed(ret:%d)\n", ret);
|
|
goto out;
|
|
}
|
|
ctx->mcd_panel_probed = true;
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, get_ddi_props, &ctx->ddi_props);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "mcd_panel get ddi props failed(ret:%d)\n", ret);
|
|
goto out;
|
|
}
|
|
|
|
out:
|
|
mutex_unlock(&ctx->probe_lock);
|
|
return ret;
|
|
}
|
|
|
|
static int mcd_drm_panel_display_on(struct exynos_panel *ctx)
|
|
{
|
|
if (!ctx)
|
|
return -ENODEV;
|
|
|
|
queue_delayed_work(ctx->wqs[MCD_DRM_DRV_WQ_DISPON].wq,
|
|
&ctx->wqs[MCD_DRM_DRV_WQ_DISPON].dwork, msecs_to_jiffies(0));
|
|
panel_info(ctx, "MCD_DRM_DRV_WQ_DISPON queued\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mcd_drm_panel_disable(struct drm_panel *panel)
|
|
{
|
|
struct exynos_panel *ctx;
|
|
int ret;
|
|
|
|
if (!panel) {
|
|
pr_err("%s: invalid drm_panel\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx = container_of(panel, struct exynos_panel, panel);
|
|
if (!ctx->mcd_panel_dev)
|
|
return -ENODEV;
|
|
|
|
panel_info(ctx, "+\n");
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, sleep_in);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to sleep in(ret:%d)\n", ret);
|
|
return ret;
|
|
}
|
|
ctx->enabled = false;
|
|
panel_info(ctx, "-\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mcd_drm_panel_enable(struct drm_panel *panel)
|
|
{
|
|
struct exynos_panel *ctx;
|
|
const struct exynos_panel_mode *pmode;
|
|
const struct exynos_panel_funcs *funcs;
|
|
int ret;
|
|
|
|
if (!panel) {
|
|
pr_err("%s: invalid drm_panel\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx = container_of(panel, struct exynos_panel, panel);
|
|
if (!ctx->mcd_panel_dev)
|
|
return -ENODEV;
|
|
|
|
pmode = ctx->current_mode;
|
|
if (!pmode) {
|
|
dev_err(ctx->dev, "no current mode set\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = mcd_drm_panel_check_probe(ctx);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
if (!pmode->exynos_mode.is_lp_mode) {
|
|
ret = mcd_drm_panel_set_display_mode(ctx, pmode);
|
|
if (ret < 0)
|
|
panel_err(ctx, "failed to set display mode(%s)\n",
|
|
pmode->mode.name);
|
|
}
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, sleep_out);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to sleep out(ret:%d)\n", ret);
|
|
return ret;
|
|
}
|
|
ctx->enabled = true;
|
|
|
|
funcs = ctx->desc->exynos_panel_func;
|
|
if (pmode->exynos_mode.is_lp_mode && funcs->set_lp_mode)
|
|
funcs->set_lp_mode(ctx, pmode);
|
|
|
|
ret = mcd_drm_panel_display_on(ctx);
|
|
if (ret < 0)
|
|
panel_err(ctx, "failed to display on(ret:%d)\n", ret);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mcd_drm_panel_unprepare(struct drm_panel *panel)
|
|
{
|
|
struct exynos_panel *ctx;
|
|
int ret;
|
|
|
|
if (!panel) {
|
|
pr_err("%s: invalid drm_panel\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx = container_of(panel, struct exynos_panel, panel);
|
|
if (!ctx->mcd_panel_dev)
|
|
return -ENODEV;
|
|
|
|
panel_info(ctx, "+\n");
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, power_off);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to power off(ret:%d)\n", ret);
|
|
return ret;
|
|
}
|
|
panel_info(ctx, "-\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mcd_drm_panel_prepare(struct drm_panel *panel)
|
|
{
|
|
struct exynos_panel *ctx;
|
|
int ret;
|
|
|
|
if (!panel) {
|
|
pr_err("%s: invalid drm_panel\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ctx = container_of(panel, struct exynos_panel, panel);
|
|
if (!ctx->mcd_panel_dev)
|
|
return -ENODEV;
|
|
|
|
ret = mcd_drm_panel_check_probe(ctx);
|
|
if (ret < 0)
|
|
return ret;
|
|
|
|
panel_info(ctx, "+\n");
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, power_on);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to power on(ret:%d)\n", ret);
|
|
return ret;
|
|
}
|
|
panel_info(ctx, "-\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mcd_drm_panel_set_display_mode(struct exynos_panel *ctx,
|
|
const struct exynos_panel_mode *pmode)
|
|
{
|
|
const struct drm_display_mode *mode;
|
|
struct panel_display_modes *pdms;
|
|
struct panel_display_mode *pdm;
|
|
int ret;
|
|
|
|
if (!ctx || !pmode)
|
|
return -EINVAL;
|
|
|
|
mode = &pmode->mode;
|
|
|
|
panel_debug(ctx, "+\n");
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, get_display_mode, &pdms);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to get display mode(ret:%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
pdm = exynos_panel_find_panel_mode(pdms, mode);
|
|
if (pdm == NULL) {
|
|
panel_err(ctx, "panel_mode(%dx%d@%d) is not found\n",
|
|
mode->hdisplay, mode->vdisplay,
|
|
drm_mode_vrefresh(mode));
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, set_display_mode, pdm);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to set display mode(ret:%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
panel_info(ctx, "drm-mode:%s, panel-mode:%s\n",
|
|
mode->name, pdm->name);
|
|
|
|
panel_debug(ctx, "-\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void mcd_drm_panel_mode_set(struct exynos_panel *ctx,
|
|
const struct exynos_panel_mode *pmode, unsigned int flags)
|
|
{
|
|
int ret;
|
|
|
|
panel_info(ctx, "+\n");
|
|
|
|
if (!ctx->enabled)
|
|
return;
|
|
|
|
if (ctx->current_mode->exynos_mode.is_lp_mode) {
|
|
panel_info(ctx, "exit lp mode\n");
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, sleep_out);
|
|
if (ret < 0)
|
|
panel_err(ctx, "failed to sleep_out(ret:%d)\n", ret);
|
|
}
|
|
|
|
if (SEAMLESS_MODESET_MRES & flags || SEAMLESS_MODESET_VREF & flags) {
|
|
ret = mcd_drm_panel_set_display_mode(ctx, pmode);
|
|
if (ret < 0)
|
|
panel_err(ctx, "failed to set vrefresh\n");
|
|
}
|
|
|
|
if (ctx->current_mode->exynos_mode.is_lp_mode) {
|
|
ret = mcd_drm_panel_display_on(ctx);
|
|
if (ret < 0)
|
|
panel_err(ctx, "failed to display_on(ret:%d)\n", ret);
|
|
}
|
|
|
|
panel_info(ctx, "-\n");
|
|
}
|
|
|
|
static void mcd_drm_request_set_clock(struct exynos_panel *ctx, void *arg)
|
|
{
|
|
int ret;
|
|
|
|
if (!ctx || !arg) {
|
|
pr_err("%s invalid param\n", __func__);
|
|
return;
|
|
}
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, req_set_clock, arg);
|
|
if (ret < 0)
|
|
panel_err(ctx, "mcd_panel req_set_clock failed(ret:%d)\n", ret);
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_SUPPORT_MASK_LAYER) || IS_ENABLED(CONFIG_USDM_PANEL_MASK_LAYER)
|
|
static int mcd_drm_panel_set_fingermask_layer(struct exynos_panel *ctx, u32 after)
|
|
{
|
|
struct mask_layer_data data;
|
|
const struct drm_connector *conn = &ctx->exynos_connector.base;
|
|
struct exynos_drm_connector_state *exynos_state =
|
|
to_exynos_connector_state(conn->state);
|
|
|
|
int ret = 0;
|
|
|
|
if (ctx->fingerprint_mask == exynos_state->fingerprint_mask)
|
|
return 0;
|
|
|
|
data.trigger_time = after;
|
|
data.req_mask_layer = exynos_state->fingerprint_mask;
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, set_mask_layer, &data);
|
|
|
|
if (after)
|
|
ctx->fingerprint_mask = exynos_state->fingerprint_mask;
|
|
|
|
if (ret < 0) {
|
|
panel_err(ctx, "mcd_panel set_mask_layer failed(ret:%d)\n", ret);
|
|
return -EINVAL;
|
|
}
|
|
|
|
panel_info(ctx, "(%s)(%s)\n",
|
|
data.trigger_time ? "after" : "before",
|
|
data.req_mask_layer ? "enable" : "disable");
|
|
|
|
return ret;
|
|
}
|
|
#endif
|
|
|
|
static int _mcd_drm_mipi_write_exec(struct mipi_dsi_device *dsi, const u8 *buf, int len)
|
|
{
|
|
int ret;
|
|
|
|
if (!dsi) {
|
|
pr_err("%s: invalid dsi device\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!dsi->host) {
|
|
dev_err(&dsi->dev, "%s: invalid dsi host\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = mipi_dsi_dcs_write_buffer(dsi, buf, len);
|
|
if (ret < 0) {
|
|
dev_err(&dsi->dev, "%s failed to write %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
|
|
/* w/a for mipi_write returned 0 when success, request fix to lsi */
|
|
ret = len;
|
|
|
|
if (ret != len)
|
|
dev_warn(&dsi->dev, "%s req %d, written %d bytes\n", __func__, len, ret);
|
|
|
|
return len;
|
|
}
|
|
|
|
static int _mcd_drm_mipi_read_exec(struct mipi_dsi_device *dsi, u8 cmd, u8 *buf, int len)
|
|
{
|
|
int ret;
|
|
|
|
if (!dsi) {
|
|
pr_err("%s: invalid dsi device\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!dsi->host) {
|
|
dev_err(&dsi->dev, "%s: invalid dsi host\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = mipi_dsi_dcs_read(dsi, cmd, buf, len);
|
|
if (ret < 0) {
|
|
dev_err(&dsi->dev, "%s failed to write %d\n", __func__, ret);
|
|
return ret;
|
|
}
|
|
if (ret != len)
|
|
dev_warn(&dsi->dev, "%s req %d, written %d bytes\n", __func__, len, ret);
|
|
|
|
return ret;
|
|
}
|
|
|
|
static void print_tx(u8 cmd_id, const u8 *cmd, int size)
|
|
{
|
|
char data[256];
|
|
int i, len;
|
|
bool newline = false;
|
|
|
|
len = snprintf(data, ARRAY_SIZE(data), "(%02X) ", cmd_id);
|
|
for (i = 0; i < min_t(int, size, 256); i++) {
|
|
if (newline)
|
|
len += snprintf(data + len, ARRAY_SIZE(data) - len, " ");
|
|
newline = (!((i + 1) % 16) || (i + 1 == size)) ? true : false;
|
|
len += snprintf(data + len, ARRAY_SIZE(data) - len,
|
|
"%02X%s", cmd[i], newline ? "\n" : " ");
|
|
if (newline) {
|
|
pr_info("%s: %s", __func__, data);
|
|
len = 0;
|
|
}
|
|
}
|
|
}
|
|
|
|
static void print_dsim_cmd(const struct cmd_set *cmd_set, int size)
|
|
{
|
|
int i;
|
|
|
|
for (i = 0; i < size; i++)
|
|
print_tx(cmd_set[i].cmd_id,
|
|
cmd_set[i].buf,
|
|
cmd_set[i].size);
|
|
}
|
|
|
|
int mcd_drm_mipi_write(void *_ctx, u8 cmd_id, const u8 *cmd, u32 offset, int size, u32 option)
|
|
{
|
|
struct exynos_panel *ctx = (struct exynos_panel *)_ctx;
|
|
struct mipi_dsi_device *dsi;
|
|
u8 gpara[4] = { 0xB0, 0x00, };
|
|
int gpara_len = 1;
|
|
int ret;
|
|
|
|
if (!ctx || !ctx->dev) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!cmd) {
|
|
panel_err(ctx, "invalid cmd data\n");
|
|
return -ENODATA;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi->host) {
|
|
panel_err(ctx, "invalid dsi host\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (offset) {
|
|
if (option & DSIM_OPTION_2BYTE_GPARA)
|
|
gpara[gpara_len++] = (offset >> 8) & 0xFF;
|
|
|
|
gpara[gpara_len++] = offset & 0xFF;
|
|
if (option & DSIM_OPTION_POINT_GPARA)
|
|
gpara[gpara_len++] = cmd[0];
|
|
if (panel_cmd_log)
|
|
print_tx(MIPI_DSI_DCS_LONG_WRITE, gpara, gpara_len);
|
|
|
|
ret = _mcd_drm_mipi_write_exec(dsi, gpara, gpara_len);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
if (cmd_id == MIPI_DSI_WR_PPS_CMD) {
|
|
ret = mipi_dsi_picture_parameter_set(dsi, (struct drm_dsc_picture_parameter_set *)cmd);
|
|
|
|
if (panel_cmd_log)
|
|
print_tx(MIPI_DSI_PICTURE_PARAMETER_SET, cmd, size);
|
|
if (ret == 0)
|
|
ret = size;
|
|
} else if (cmd_id == MIPI_DSI_WR_DSC_CMD) {
|
|
ret = mipi_dsi_compression_mode(dsi, (*cmd) ? true : false);
|
|
if (panel_cmd_log)
|
|
print_tx(MIPI_DSI_COMPRESSION_MODE, cmd, size);
|
|
|
|
if (ret == 0)
|
|
ret = size;
|
|
} else {
|
|
ret = _mcd_drm_mipi_write_exec(dsi, cmd, size);
|
|
if (panel_cmd_log)
|
|
print_tx(MIPI_DSI_DCS_LONG_WRITE, cmd, size);
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
#if defined(CONFIG_EXYNOS_DMA_DSIMFC)
|
|
#define FCMD_DATA_MAX_SIZE 0x00100000
|
|
struct dsim_fcmd *create_dsim_fast_cmd(void *vaddr, const u8 *payload, u32 size, u32 align)
|
|
{
|
|
struct dsim_fcmd *fcmd;
|
|
struct mipi_dsi_msg *msg;
|
|
u32 padd_u = 0, padd_c, padd_t;
|
|
int xfer_cnt, xfer_sz, xfer_unit, xfer_unit_aligned;
|
|
u32 payload_xfer_unit;
|
|
u32 remaineder = size;
|
|
u32 size_aligned;
|
|
u8 *tx_buf = (u8 *)vaddr;
|
|
int i;
|
|
|
|
if (!vaddr || !payload)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
if (size == 0 || size > FCMD_DATA_MAX_SIZE)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
fcmd = kzalloc(sizeof(*fcmd), GFP_KERNEL);
|
|
if (!fcmd)
|
|
return ERR_PTR(-ENOMEM);
|
|
|
|
/* minimum alignment size is 1 */
|
|
align = max(1U, align);
|
|
size_aligned = roundup(size, align);
|
|
|
|
payload_xfer_unit = rounddown(min((u32)(DSIM_PL_FIFO_THRESHOLD - 1), size_aligned), align);
|
|
xfer_cnt = roundup(size_aligned, payload_xfer_unit) / payload_xfer_unit;
|
|
xfer_unit = payload_xfer_unit + 1;
|
|
xfer_unit_aligned = roundup(xfer_unit, DSIM_FCMD_ALIGN_CONSTRAINT);
|
|
|
|
/* add address(e.g. 4C ,5C..) payload count */
|
|
xfer_sz = size_aligned + xfer_cnt;
|
|
|
|
/* padding is necessary if next xfer payload exists. so add (xfer_cnt - 1) times */
|
|
if (xfer_unit % DSIM_FCMD_ALIGN_CONSTRAINT) {
|
|
padd_u = DSIM_FCMD_ALIGN_CONSTRAINT - (xfer_unit % DSIM_FCMD_ALIGN_CONSTRAINT);
|
|
padd_c = xfer_cnt - 1;
|
|
padd_t = padd_u * padd_c;
|
|
xfer_sz += padd_t;
|
|
}
|
|
|
|
for (i = 0; i < xfer_cnt; i++) {
|
|
int copy_size = min(remaineder, payload_xfer_unit);
|
|
|
|
tx_buf[i * xfer_unit_aligned] = (i == 0) ? 0x4C : 0x5C;
|
|
memcpy(&tx_buf[i * xfer_unit_aligned + 1],
|
|
&payload[i * payload_xfer_unit], copy_size);
|
|
remaineder -= copy_size;
|
|
}
|
|
fcmd->xfer_unit = xfer_unit;
|
|
msg = &fcmd->msg;
|
|
msg->type = MIPI_DSI_DCS_LONG_WRITE;
|
|
msg->tx_buf = (const u8 *)tx_buf;
|
|
msg->tx_len = xfer_sz;
|
|
|
|
return fcmd;
|
|
}
|
|
|
|
void destroy_dsim_fast_cmd(struct dsim_fcmd *fcmd)
|
|
{
|
|
kfree(fcmd);
|
|
}
|
|
|
|
int drm_mipi_fcmd_write(void *_ctx, const u8 *payload, int size, u32 align)
|
|
{
|
|
struct exynos_panel *ctx = (struct exynos_panel *)_ctx;
|
|
struct mipi_dsi_device *dsi;
|
|
struct dsim_device *dsim;
|
|
struct dsim_fcmd *fcmd;
|
|
int ret = 0;
|
|
static DEFINE_MUTEX(lock);
|
|
|
|
if (!ctx || !ctx->dev) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!payload) {
|
|
panel_err(ctx, "invalid payload\n");
|
|
return -ENODATA;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi->host) {
|
|
panel_err(ctx, "invalid dsi host\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsim = container_of(dsi->host, struct dsim_device, dsi_host);
|
|
if (!dsim->fcmd_buf_allocated) {
|
|
panel_err(ctx, "fcmd_buf not allocated\n");
|
|
return -ENOMEM;
|
|
}
|
|
|
|
mutex_lock(&lock);
|
|
fcmd = create_dsim_fast_cmd(dsim->fcmd_buf_vaddr, payload, size, align);
|
|
if (IS_ERR(fcmd)) {
|
|
panel_err(ctx, "failed to create fast-command\n");
|
|
ret = PTR_ERR(fcmd);
|
|
goto out;
|
|
}
|
|
|
|
ret = dsim_host_fcmd_transfer(dsi->host, &fcmd->msg);
|
|
if (ret < 0)
|
|
panel_err(ctx, "failed to transfer fast-command\n");
|
|
|
|
out:
|
|
mutex_unlock(&lock);
|
|
destroy_dsim_fast_cmd(fcmd);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(drm_mipi_fcmd_write);
|
|
|
|
static inline int mcd_drm_mipi_get_spsram_constrain_align_size(u32 option)
|
|
{
|
|
return (option & PKT_OPTION_SR_ALIGN_12) ? 12 : 16;
|
|
}
|
|
|
|
int mcd_drm_mipi_sr_write(void *_ctx, u8 cmd_id, const u8 *payload, u32 offset, int size, u32 option)
|
|
{
|
|
return drm_mipi_fcmd_write((struct exynos_panel *)_ctx, payload, size,
|
|
mcd_drm_mipi_get_spsram_constrain_align_size(option));
|
|
}
|
|
#endif
|
|
|
|
int mcd_drm_mipi_read(void *_ctx, u8 addr, u32 offset, u8 *buf, int size, u32 option)
|
|
{
|
|
struct exynos_panel *ctx = (struct exynos_panel *)_ctx;
|
|
struct mipi_dsi_device *dsi;
|
|
u8 gpara[4] = { 0xB0, 0x00, };
|
|
int gpara_len = 1;
|
|
int ret;
|
|
|
|
if (!ctx || !ctx->dev) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!buf) {
|
|
panel_err(ctx, "invalid read buffer\n");
|
|
return -ENODATA;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi->host) {
|
|
panel_err(ctx, "invalid dsi host\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (offset) {
|
|
if (option & DSIM_OPTION_2BYTE_GPARA)
|
|
gpara[gpara_len++] = (offset >> 8) & 0xFF;
|
|
|
|
gpara[gpara_len++] = offset & 0xFF;
|
|
if (option & DSIM_OPTION_POINT_GPARA)
|
|
gpara[gpara_len++] = addr;
|
|
|
|
ret = _mcd_drm_mipi_write_exec(dsi, gpara, gpara_len);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
|
|
ret = _mcd_drm_mipi_read_exec(dsi, addr, buf, size);
|
|
return ret;
|
|
}
|
|
|
|
#define DSIM_TX_FLOW_CONTROL
|
|
#define MAX_DSIM_PL_SIZE (DSIM_PL_FIFO_THRESHOLD)
|
|
#define MAX_CMD_SET_SIZE (MAX_PANEL_CMD_QUEUE)
|
|
|
|
static int mipi_write_table(void *_ctx, const struct cmd_set *cmd, int size, u32 option)
|
|
{
|
|
struct exynos_panel *ctx = (struct exynos_panel *)_ctx;
|
|
int ret, total_size = 0;
|
|
int i, sz_pl = 0;
|
|
#if defined(DSIM_TX_FLOW_CONTROL)
|
|
int from = 0;
|
|
#endif
|
|
s64 elapsed_usec;
|
|
struct timespec64 cur_ts, last_ts, delta_ts;
|
|
|
|
/* Todo: get flag from panel-drv */
|
|
bool wait_tx_done = (option & DSIM_OPTION_WAIT_TX_DONE);
|
|
// bool wait_tx_done = false;
|
|
bool wait_vsync = false;
|
|
|
|
if (!cmd) {
|
|
pr_err("%s: cmd is null\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (size <= 0) {
|
|
panel_err(ctx, "invalid cmd size %d\n", size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (size > MAX_CMD_SET_SIZE) {
|
|
panel_err(ctx, "exceeded MAX_CMD_SET_SIZE(%d) (size:%d)\n",
|
|
MAX_CMD_SET_SIZE, size);
|
|
return -EINVAL;
|
|
}
|
|
|
|
/* 1. Cleanup Buffer */
|
|
exynos_drm_cmdset_cleanup(ctx);
|
|
|
|
ktime_get_ts64(&last_ts);
|
|
|
|
/* 2. Queue command set Buffer */
|
|
for (i = 0; i < size; i++) {
|
|
if (cmd[i].buf == NULL) {
|
|
panel_err(ctx, "cmd[%d].buf is null\n", i);
|
|
continue;
|
|
}
|
|
|
|
#if defined(DSIM_TX_FLOW_CONTROL)
|
|
/* If FIFO has no space to stack cmd, then flush first */
|
|
if ((i - from >= (MAX_CMDSET_NUM - 1)) ||
|
|
(sz_pl + ALIGN(cmd[i].size, 4) >= MAX_CMDSET_PAYLOAD)) {
|
|
|
|
/* 3. Flush Command Set Buffer */
|
|
if (exynos_drm_cmdset_flush(ctx, wait_vsync, wait_tx_done)) {
|
|
panel_err(ctx, "failed to exynos_drm_cmdset_flush\n");
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
|
|
if (panel_cmd_log)
|
|
print_dsim_cmd(&cmd[from], i - from);
|
|
|
|
panel_debug(ctx, "cmd_set:%d pl:%d\n", i - from, sz_pl);
|
|
|
|
from = i;
|
|
sz_pl = 0;
|
|
}
|
|
#endif
|
|
|
|
if (cmd[i].cmd_id == MIPI_DSI_WR_DSC_CMD) {
|
|
ret = exynos_drm_cmdset_add(ctx, MIPI_DSI_COMPRESSION_MODE, cmd[i].size, cmd[i].buf);
|
|
} else if (cmd[i].cmd_id == MIPI_DSI_WR_PPS_CMD) {
|
|
ret = exynos_drm_cmdset_add(ctx, MIPI_DSI_PICTURE_PARAMETER_SET, cmd[i].size, cmd[i].buf);
|
|
} else if (cmd[i].cmd_id == MIPI_DSI_WR_GEN_CMD) {
|
|
if (cmd[i].size == 1)
|
|
ret = exynos_drm_cmdset_add(ctx, MIPI_DSI_DCS_SHORT_WRITE, cmd[i].size, cmd[i].buf);
|
|
else
|
|
ret = exynos_drm_cmdset_add(ctx, MIPI_DSI_DCS_LONG_WRITE, cmd[i].size, cmd[i].buf);
|
|
} else {
|
|
panel_err(ctx, "invalid cmd_id %d\n", cmd[i].cmd_id);
|
|
ret = -EINVAL;
|
|
goto error;
|
|
}
|
|
|
|
if (ret) {
|
|
panel_err(ctx, "failed to exynos_drm_cmdset_add\n");
|
|
goto error;
|
|
}
|
|
|
|
sz_pl += ALIGN(cmd[i].size, 4);
|
|
total_size += cmd[i].size;
|
|
}
|
|
|
|
/* 3. Flush Command Set Buffer */
|
|
if (exynos_drm_cmdset_flush(ctx, wait_vsync, wait_tx_done)) {
|
|
panel_err(ctx, "failed to exynos_drm_cmdset_flush\n");
|
|
ret = -EIO;
|
|
goto error;
|
|
}
|
|
|
|
if (panel_cmd_log)
|
|
print_dsim_cmd(&cmd[from], i - from);
|
|
|
|
ktime_get_ts64(&cur_ts);
|
|
delta_ts = timespec64_sub(cur_ts, last_ts);
|
|
elapsed_usec = timespec64_to_ns(&delta_ts) / 1000;
|
|
panel_debug(ctx, "done (cmd_set:%d size:%d elapsed %2lld.%03lld msec)\n",
|
|
size, total_size,
|
|
elapsed_usec / 1000, elapsed_usec % 1000);
|
|
|
|
ret = total_size;
|
|
|
|
error:
|
|
|
|
return ret;
|
|
}
|
|
|
|
|
|
int mcd_drm_mipi_get_state(void *_ctx)
|
|
{
|
|
struct exynos_panel *ctx = (struct exynos_panel *)_ctx;
|
|
struct mipi_dsi_device *dsi;
|
|
struct dsim_device *dsim;
|
|
|
|
if (!ctx || !ctx->dev) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi->host) {
|
|
panel_err(ctx, "invalid dsi host\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsim = container_of(dsi->host, struct dsim_device, dsi_host);
|
|
|
|
return (dsim->state == DSIM_STATE_SUSPEND && !dsim->lp_mode_state) ?
|
|
CTRL_INTERFACE_STATE_INACTIVE : CTRL_INTERFACE_STATE_ACTIVE;
|
|
}
|
|
|
|
static int parse_hdr_info(struct device_node *node, struct panel_hdr_info *hdr)
|
|
{
|
|
int i;
|
|
int ret;
|
|
u32 hdr_num = 0;
|
|
u32 hdr_type[MAX_HDR_TYPE] = {0, };
|
|
u32 formats = 0;
|
|
|
|
if (!node) {
|
|
pr_err("%s node is null\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!hdr) {
|
|
pr_err("%s hdr is null\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = of_property_read_u32(node, "hdr_num", &hdr_num);
|
|
if ((ret) || (hdr_num == 0)) {
|
|
pr_info("%s : Not support hdr foramt(ret:%d, hdr_num: %d)\n", __func__, ret, hdr_num);
|
|
return 0;
|
|
}
|
|
if (hdr_num > MAX_HDR_TYPE) {
|
|
pr_err("%s: exceed supporting hdr type: %d\n", __func__, hdr_num);
|
|
hdr_num = MAX_HDR_TYPE;
|
|
}
|
|
|
|
ret = of_property_read_u32_array(node, "hdr_type", hdr_type, hdr_num);
|
|
if (ret) {
|
|
pr_err("%s: wrong hdr_type count(%d:%d)\n", __func__, hdr_num, ret);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < hdr_num; i++)
|
|
formats |= (1 << hdr_type[i]);
|
|
|
|
hdr->formats = formats;
|
|
of_property_read_u32(node, "hdr_max_luma", &hdr->max_luma);
|
|
of_property_read_u32(node, "hdr_max_avg_luma", &hdr->max_avg_luma);
|
|
of_property_read_u32(node, "hdr_min_luma", &hdr->min_luma);
|
|
|
|
pr_info("support hdr info: num: %d, formats: %x, luma max: %d, avg: %d, min:%d\n",
|
|
hdr_num, formats, hdr->max_luma, hdr->max_avg_luma, hdr->min_luma);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int parse_panel_info(struct exynos_panel *ctx, struct device_node *np)
|
|
{
|
|
int ret;
|
|
struct panel_device *panel;
|
|
|
|
if (!ctx) {
|
|
pr_err("%s ctx is null\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
panel = ctx->mcd_panel_dev;
|
|
if (!panel) {
|
|
panel_err(ctx, "panel is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = parse_hdr_info(np, &panel->hdr);
|
|
if (ret)
|
|
panel_err(ctx, "failed to parse hdr info\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mcd_drm_parse_dt(void *_ctx, struct device_node *np)
|
|
{
|
|
int ret;
|
|
struct exynos_panel *ctx = (struct exynos_panel *)_ctx;
|
|
struct mipi_dsi_device *dsi;
|
|
|
|
if (!ctx || !ctx->dev) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!np) {
|
|
panel_err(ctx, "no device tree\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
panel_info(ctx, "+\n");
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
ret = parse_panel_info(ctx, np);
|
|
if (ret)
|
|
panel_err(ctx, "failed to parse panel info\n");
|
|
panel_info(ctx, "-\n");
|
|
|
|
return 0;
|
|
}
|
|
|
|
void wq_vblank_handler(struct work_struct *data)
|
|
{
|
|
struct mcd_drm_drv_wq *w;
|
|
struct exynos_panel *ctx;
|
|
struct mipi_dsi_device *dsi;
|
|
struct dsim_device *dsim;
|
|
struct drm_crtc *crtc;
|
|
int ret = 0;
|
|
|
|
if (!data) {
|
|
pr_err("%s: invalid work_struct\n", __func__);
|
|
return;
|
|
}
|
|
|
|
w = container_of(to_delayed_work(data), struct mcd_drm_drv_wq, dwork);
|
|
ctx = wq_to_exynos_panel(w);
|
|
|
|
panel_debug(ctx, "+\n");
|
|
down_read(&ctx->panel_drm_state_lock);
|
|
if (ctx->panel_drm_state == PANEL_DRM_STATE_DISABLED) {
|
|
panel_err(ctx, "panel drm is disabled state\n");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
if (ctx->panel_drm_state == PANEL_DRM_STATE_LPM_DISABLED) {
|
|
/* TODO: get vsync period from drm_display_mode */
|
|
usleep_range(33400, 33500);
|
|
goto out;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi->host) {
|
|
panel_err(ctx, "invalid dsi host\n");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
dsim = container_of(dsi->host, struct dsim_device, dsi_host);
|
|
crtc = dsim->encoder.crtc;
|
|
if (!crtc) {
|
|
panel_err(ctx, "invalid crtc\n");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
ret = drm_crtc_vblank_get(crtc);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to get vblank\n");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
drm_crtc_wait_one_vblank(crtc);
|
|
drm_crtc_vblank_put(crtc);
|
|
|
|
out:
|
|
atomic_inc(&w->count);
|
|
wake_up_interruptible_all(&w->wait);
|
|
|
|
err:
|
|
up_read(&ctx->panel_drm_state_lock);
|
|
w->ret = ret;
|
|
panel_debug(ctx, "- (ret:%d)\n", w->ret);
|
|
}
|
|
|
|
void wq_framedone_handler(struct work_struct *data)
|
|
{
|
|
struct mcd_drm_drv_wq *w;
|
|
struct exynos_panel *ctx;
|
|
struct mipi_dsi_device *dsi;
|
|
struct dsim_device *dsim;
|
|
struct drm_crtc *crtc;
|
|
struct exynos_drm_crtc *exynos_crtc;
|
|
const struct exynos_drm_crtc_ops *crtc_ops;
|
|
struct drm_crtc_state *crtc_state;
|
|
int ret = 0;
|
|
|
|
if (!data) {
|
|
pr_err("%s: invalid work_struct\n", __func__);
|
|
return;
|
|
}
|
|
|
|
w = container_of(to_delayed_work(data), struct mcd_drm_drv_wq, dwork);
|
|
ctx = wq_to_exynos_panel(w);
|
|
|
|
panel_debug(ctx, "+\n");
|
|
down_read(&ctx->panel_drm_state_lock);
|
|
if (PANEL_DRM_STATE_IS_DISABLED(ctx->panel_drm_state)) {
|
|
panel_err(ctx, "panel drm is disabled state\n");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi->host) {
|
|
panel_err(ctx, "invalid dsi host\n");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
dsim = container_of(dsi->host, struct dsim_device, dsi_host);
|
|
crtc = dsim->encoder.crtc;
|
|
if (!crtc) {
|
|
panel_err(ctx, "invalid crtc\n");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
exynos_crtc = to_exynos_crtc(crtc);
|
|
crtc_state = crtc->state;
|
|
|
|
/* The following code prevents garbage screen in the first frame
|
|
* when it is turned on in the connected state
|
|
* after the panel is turned off without connection.
|
|
*/
|
|
if (crtc_state->no_vblank)
|
|
usleep_range(100000, 100100);
|
|
|
|
crtc_ops = exynos_crtc->ops;
|
|
if (!crtc_ops || !crtc_ops->wait_framestart) {
|
|
panel_err(ctx, "invalid crtc_ops\n");
|
|
ret = -EINVAL;
|
|
goto err;
|
|
}
|
|
|
|
crtc_ops->wait_framestart(exynos_crtc);
|
|
|
|
atomic_inc(&w->count);
|
|
|
|
err:
|
|
up_read(&ctx->panel_drm_state_lock);
|
|
w->ret = ret;
|
|
panel_debug(ctx, "- (ret:%d)\n", w->ret);
|
|
}
|
|
|
|
void wq_framedone_handler_fsync(struct work_struct *data)
|
|
{
|
|
struct mcd_drm_drv_wq *w = container_of(to_delayed_work(data), struct mcd_drm_drv_wq, dwork);
|
|
|
|
wq_framedone_handler(data);
|
|
wake_up_interruptible_all(&w->wait);
|
|
}
|
|
|
|
void wq_framedone_handler_dispon(struct work_struct *data)
|
|
{
|
|
struct mcd_drm_drv_wq *w = container_of(to_delayed_work(data), struct mcd_drm_drv_wq, dwork);
|
|
struct exynos_panel *ctx = container_of(w, struct exynos_panel, wqs[MCD_DRM_DRV_WQ_DISPON]);
|
|
int ret;
|
|
|
|
panel_info(ctx, "+\n");
|
|
if (!ctx->ddi_props.support_avoid_sandstorm)
|
|
wq_framedone_handler(data);
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, display_on);
|
|
if (ret < 0)
|
|
panel_err(ctx, "failed to display_on(ret:%d)\n", ret);
|
|
panel_info(ctx, "-\n");
|
|
}
|
|
|
|
void mcd_panel_probe_handler(struct work_struct *data)
|
|
{
|
|
struct mcd_drm_drv_wq *w = container_of(to_delayed_work(data), struct mcd_drm_drv_wq, dwork);
|
|
struct exynos_panel *ctx = container_of(w, struct exynos_panel, wqs[MCD_DRM_DRV_WQ_PANEL_PROBE]);
|
|
int ret;
|
|
|
|
panel_info(ctx, "+\n");
|
|
ret = mcd_drm_panel_check_probe(ctx);
|
|
if (ret < 0)
|
|
panel_err(ctx, "mcd-panel not probed(ret:%d)\n", ret);
|
|
panel_info(ctx, "-\n");
|
|
}
|
|
|
|
__visible_for_testing int mcd_drm_wait_work(void *_ctx, u32 work_idx, u32 timeout)
|
|
{
|
|
struct exynos_panel *ctx = (struct exynos_panel *)_ctx;
|
|
struct mcd_drm_drv_wq *w;
|
|
unsigned int count;
|
|
int ret;
|
|
|
|
if (!ctx) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (work_idx >= MAX_MCD_DRM_DRV_WQ) {
|
|
panel_err(ctx, "invalid work index(%d)\n", work_idx);
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!timeout) {
|
|
panel_err(ctx, "timeout must be greater than 0\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
w = &ctx->wqs[work_idx];
|
|
if (!w->wq) {
|
|
panel_err(ctx, "invalid workqueue\n");
|
|
return -EFAULT;
|
|
}
|
|
|
|
count = atomic_read(&w->count);
|
|
panel_debug(ctx, "+ count:%u\n", count);
|
|
if (!queue_delayed_work(w->wq, &w->dwork, msecs_to_jiffies(0))) {
|
|
panel_err(ctx, "failed to queueing work\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
ret = wait_event_interruptible_timeout(w->wait,
|
|
count < atomic_read(&w->count), msecs_to_jiffies(timeout));
|
|
if (ret == 0) {
|
|
panel_warn(ctx, "timeout(ret:%d, %dms)\n", ret, timeout);
|
|
return -ETIMEDOUT;
|
|
}
|
|
panel_debug(ctx, "- count:%u\n", atomic_read(&w->count));
|
|
|
|
return 0;
|
|
}
|
|
|
|
__visible_for_testing inline int mcd_drm_wait_for_vsync(void *ctx, u32 timeout)
|
|
{
|
|
return mcd_drm_wait_work(ctx, MCD_DRM_DRV_WQ_VSYNC, timeout);
|
|
}
|
|
|
|
__visible_for_testing inline int mcd_drm_wait_for_fsync(void *ctx, u32 timeout)
|
|
{
|
|
return mcd_drm_wait_work(ctx, MCD_DRM_DRV_WQ_FSYNC, timeout);
|
|
}
|
|
|
|
__visible_for_testing int mcd_drm_set_bypass(void *ctx, bool on)
|
|
{
|
|
pr_info("%s %s\n", __func__, on ? "on" : "off");
|
|
bypass_display = (on ? 1 : 0);
|
|
return 0;
|
|
}
|
|
|
|
__visible_for_testing int mcd_drm_set_commit_retry(void *ctx, bool on)
|
|
{
|
|
pr_info("%s %s\n", __func__, on ? "on" : "off");
|
|
commit_retry = (on ? 1 : 0);
|
|
return 0;
|
|
}
|
|
|
|
__visible_for_testing int mcd_drm_get_bypass(void *ctx)
|
|
{
|
|
return (bypass_display > 0 ? 1 : 0);
|
|
}
|
|
|
|
__visible_for_testing int mcd_drm_set_lpdt(void *_ctx, bool on)
|
|
{
|
|
struct mipi_dsi_device *dsi;
|
|
struct dsim_device *dsim;
|
|
struct exynos_panel *ctx = (struct exynos_panel *)_ctx;
|
|
|
|
if (!ctx || !ctx->dev) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi->host) {
|
|
panel_err(ctx, "invalid dsi host\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsim = container_of(dsi->host, struct dsim_device, dsi_host);
|
|
panel_info(ctx, "%s\n", on ? "on" : "off");
|
|
|
|
if (on)
|
|
dsi->mode_flags |= MIPI_DSI_MODE_LPM;
|
|
else
|
|
dsi->mode_flags &= ~MIPI_DSI_MODE_LPM;
|
|
|
|
return 0;
|
|
}
|
|
|
|
__visible_for_testing int mcd_drm_dpu_register_dump(void *_ctx)
|
|
{
|
|
struct mipi_dsi_device *dsi;
|
|
struct dsim_device *dsim;
|
|
struct exynos_panel *ctx = (struct exynos_panel *)_ctx;
|
|
const struct drm_crtc *crtc;
|
|
struct exynos_drm_crtc *exynos_crtc;
|
|
const struct exynos_drm_crtc_ops *crtc_ops;
|
|
|
|
if (!ctx || !ctx->dev) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi->host) {
|
|
panel_err(ctx, "invalid dsi host\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsim = container_of(dsi->host, struct dsim_device, dsi_host);
|
|
crtc = dsim->encoder.crtc;
|
|
if (!crtc) {
|
|
panel_err(ctx, "invalid crtc\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
exynos_crtc = to_exynos_crtc(crtc);
|
|
crtc_ops = exynos_crtc->ops;
|
|
if (!crtc_ops) {
|
|
panel_err(ctx, "invalid crtc_ops\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!crtc_ops->dump_register) {
|
|
panel_err(ctx, "no dump_register handler\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
crtc_ops->dump_register(exynos_crtc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
__visible_for_testing int mcd_drm_dpu_event_log_print(void *_ctx)
|
|
{
|
|
struct mipi_dsi_device *dsi;
|
|
struct dsim_device *dsim;
|
|
struct exynos_panel *ctx = (struct exynos_panel *)_ctx;
|
|
const struct drm_crtc *crtc;
|
|
struct exynos_drm_crtc *exynos_crtc;
|
|
const struct exynos_drm_crtc_ops *crtc_ops;
|
|
|
|
if (!ctx || !ctx->dev) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi->host) {
|
|
panel_err(ctx, "invalid dsi host\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsim = container_of(dsi->host, struct dsim_device, dsi_host);
|
|
crtc = dsim->encoder.crtc;
|
|
if (!crtc) {
|
|
panel_err(ctx, "invalid crtc\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
exynos_crtc = to_exynos_crtc(crtc);
|
|
crtc_ops = exynos_crtc->ops;
|
|
if (!crtc_ops) {
|
|
panel_err(ctx, "invalid crtc_ops\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!crtc_ops->dump_event_log) {
|
|
panel_err(ctx, "no dump_event_log handler\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
crtc_ops->dump_event_log(exynos_crtc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
__visible_for_testing int mcd_drm_emergency_off(void *_ctx)
|
|
{
|
|
struct mipi_dsi_device *dsi;
|
|
struct dsim_device *dsim;
|
|
struct exynos_panel *ctx = (struct exynos_panel *)_ctx;
|
|
const struct drm_crtc *crtc;
|
|
struct exynos_drm_crtc *exynos_crtc;
|
|
const struct exynos_drm_crtc_ops *crtc_ops;
|
|
|
|
if (!ctx || !ctx->dev) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi->host) {
|
|
panel_err(ctx, "invalid dsi host\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsim = container_of(dsi->host, struct dsim_device, dsi_host);
|
|
crtc = dsim->encoder.crtc;
|
|
if (!crtc) {
|
|
panel_err(ctx, "invalid crtc\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
exynos_crtc = to_exynos_crtc(crtc);
|
|
crtc_ops = exynos_crtc->ops;
|
|
if (!crtc_ops) {
|
|
panel_err(ctx, "invalid crtc_ops\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (!crtc_ops->emergency_off) {
|
|
panel_err(ctx, "no emergency_off handler\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (exynos_crtc->ops->emergency_off)
|
|
exynos_crtc->ops->emergency_off(exynos_crtc);
|
|
|
|
return 0;
|
|
}
|
|
|
|
#if IS_ENABLED(CONFIG_PANEL_FREQ_HOP) || IS_ENABLED(CONFIG_USDM_PANEL_FREQ_HOP)
|
|
static int mcd_drm_panel_set_osc(struct exynos_panel *ctx, u32 frequency)
|
|
{
|
|
struct panel_clock_info info;
|
|
int ret;
|
|
|
|
if (!ctx) {
|
|
pr_err("FREQ_HOP: ERR:%s: invalid param\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
info.clock_id = CLOCK_ID_OSC;
|
|
info.clock_rate = frequency;
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev,
|
|
req_set_clock, &info);
|
|
if (ret < 0) {
|
|
pr_err("%s: failed to set ffc\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static int mcd_drm_panel_set_ffc(struct exynos_panel *ctx, u32 frequency)
|
|
{
|
|
struct panel_clock_info info;
|
|
int ret;
|
|
|
|
if (!ctx) {
|
|
pr_err("FREQ_HOP: ERR:%s: invalid param\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
info.clock_id = CLOCK_ID_DSI;
|
|
info.clock_rate = frequency;
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev,
|
|
req_set_clock, &info);
|
|
if (ret < 0) {
|
|
pr_err("%s: failed to set ffc\n", __func__);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
__visible_for_testing int mcd_drm_set_freq_hop(void *_ctx,
|
|
struct freq_hop_param *param)
|
|
{
|
|
struct mipi_dsi_device *dsi;
|
|
struct dsim_device *dsim;
|
|
struct exynos_panel *ctx = _ctx;
|
|
int ret;
|
|
|
|
if (!ctx || !ctx->dev) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi) {
|
|
pr_err("%s: invalid dsi\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
dsim = container_of(dsi->host, struct dsim_device, dsi_host);
|
|
ret = mcd_dsim_update_dsi_freq(dsim, param->dsi_freq);
|
|
if (ret < 0) {
|
|
pr_err("%s: failed to update dsi_freq(%d)\n",
|
|
__func__, param->dsi_freq);
|
|
return ret;
|
|
}
|
|
|
|
ret = mcd_drm_panel_set_ffc(ctx, param->dsi_freq);
|
|
if (ret < 0) {
|
|
pr_err("%s: failed to set ffc(%d)\n",
|
|
__func__, param->dsi_freq);
|
|
return ret;
|
|
}
|
|
|
|
ret = mcd_drm_panel_set_osc(ctx, param->osc_freq);
|
|
if (ret < 0) {
|
|
pr_err("%s: failed to set osc(%d)\n",
|
|
__func__, param->osc_freq);
|
|
return ret;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
#endif
|
|
|
|
struct panel_adapter_funcs mcd_panel_adapter_funcs = {
|
|
.read = mcd_drm_mipi_read,
|
|
.write = mcd_drm_mipi_write,
|
|
#if defined(CONFIG_EXYNOS_DMA_DSIMFC)
|
|
.sr_write = mcd_drm_mipi_sr_write,
|
|
#else
|
|
.sr_write = NULL,
|
|
#endif
|
|
.write_table = mipi_write_table,
|
|
.get_state = mcd_drm_mipi_get_state,
|
|
.parse_dt = mcd_drm_parse_dt,
|
|
.wait_for_vsync = mcd_drm_wait_for_vsync,
|
|
.wait_for_fsync = mcd_drm_wait_for_fsync,
|
|
.set_bypass = mcd_drm_set_bypass,
|
|
.get_bypass = mcd_drm_get_bypass,
|
|
.set_lpdt = mcd_drm_set_lpdt,
|
|
.dpu_register_dump = mcd_drm_dpu_register_dump,
|
|
.dpu_event_log_print = mcd_drm_dpu_event_log_print,
|
|
.set_commit_retry = mcd_drm_set_commit_retry,
|
|
.emergency_off = mcd_drm_emergency_off,
|
|
#if IS_ENABLED(CONFIG_PANEL_FREQ_HOP) || IS_ENABLED(CONFIG_USDM_PANEL_FREQ_HOP)
|
|
.set_freq_hop = mcd_drm_set_freq_hop,
|
|
#endif
|
|
};
|
|
|
|
static const struct drm_panel_funcs mcd_drm_panel_funcs = {
|
|
.disable = mcd_drm_panel_disable,
|
|
.unprepare = mcd_drm_panel_unprepare,
|
|
.prepare = mcd_drm_panel_prepare,
|
|
.enable = mcd_drm_panel_enable,
|
|
.get_modes = mcd_drm_panel_get_modes,
|
|
};
|
|
|
|
static const struct exynos_panel_funcs mcd_exynos_panel_funcs = {
|
|
.set_lp_mode = exynos_panel_set_lp_mode,
|
|
.mode_set = mcd_drm_panel_mode_set,
|
|
.req_set_clock = mcd_drm_request_set_clock,
|
|
#if IS_ENABLED(CONFIG_SUPPORT_MASK_LAYER) || IS_ENABLED(CONFIG_USDM_PANEL_MASK_LAYER)
|
|
.set_fingermask_layer = mcd_drm_panel_set_fingermask_layer,
|
|
#endif
|
|
};
|
|
static const struct exynos_panel_mode exynos_panel_modes[] = {
|
|
#if IS_ENABLED(CONFIG_SOC_S5E9925)
|
|
{
|
|
.mode = { DRM_MODE("1080x2316@120", DRM_MODE_TYPE_DRIVER, 303196,
|
|
1080, 1081, 1082, 1083, 0, 2316, 2331, 2332, 2333, 0, 0) },
|
|
.exynos_mode = { EXYNOS_MODE(MIPI_DSI_CLOCK_NON_CONTINUOUS,
|
|
8, false, true, 2, 2, 193) },
|
|
},
|
|
{
|
|
.mode = { DRM_MODE("1080x2340@120", DRM_MODE_TYPE_DRIVER, 306315,
|
|
1080, 1081, 1082, 1083, 0, 2340, 2355, 2356, 2357, 0, 0) },
|
|
.exynos_mode = { EXYNOS_MODE(MIPI_DSI_CLOCK_NON_CONTINUOUS,
|
|
8, false, true, 2, 2, 117) },
|
|
},
|
|
{
|
|
.mode = { DRM_MODE("1080x2400@120", DRM_MODE_TYPE_DRIVER, 314113,
|
|
1080, 1081, 1082, 1083, 0, 2400, 2415, 2416, 2417, 0, 0) },
|
|
.exynos_mode = { EXYNOS_MODE(MIPI_DSI_CLOCK_NON_CONTINUOUS,
|
|
8, false, true, 2, 2, 120) },
|
|
},
|
|
#else
|
|
{
|
|
.mode = { DRM_MODE("1080x2400@120", DRM_MODE_TYPE_DRIVER, 314113,
|
|
1080, 1081, 1082, 1083, 0, 2400, 2415, 2416, 2417, 0, 0) },
|
|
.exynos_mode = { EXYNOS_MODE(MIPI_DSI_CLOCK_NON_CONTINUOUS,
|
|
8, false, true, 1, 2, 40) },
|
|
},
|
|
#endif
|
|
};
|
|
static const struct exynos_panel_mode exynos_panel_lp_mode[] = {
|
|
#if IS_ENABLED(CONFIG_SOC_S5E9925)
|
|
{
|
|
.mode = { DRM_MODE("1080x2400@30", DRM_MODE_TYPE_DRIVER, 78528,
|
|
1080, 1081, 1082, 1083, 0, 2400, 2415, 2416, 2417, 0, 0) },
|
|
.exynos_mode = { EXYNOS_MODE(MIPI_DSI_CLOCK_NON_CONTINUOUS,
|
|
8, true, true, 2, 2, 120) },
|
|
},
|
|
#else
|
|
{
|
|
.mode = { DRM_MODE("1080x2400@30", DRM_MODE_TYPE_DRIVER, 78528,
|
|
1080, 1081, 1082, 1083, 0, 2400, 2415, 2416, 2417, 0, 0) },
|
|
.exynos_mode = { EXYNOS_MODE(MIPI_DSI_CLOCK_NON_CONTINUOUS,
|
|
8, true, true, 1, 2, 40) },
|
|
},
|
|
#endif
|
|
};
|
|
|
|
const struct exynos_panel_desc default_mcd_samsung_panel_desc = {
|
|
.data_lane_cnt = 4,
|
|
.max_brightness = 1023, /* TODO check */
|
|
.dft_brightness = 511, /* TODO check */
|
|
.modes = exynos_panel_modes,
|
|
.num_modes = ARRAY_SIZE(exynos_panel_modes),
|
|
.lp_modes = exynos_panel_lp_mode,
|
|
.num_lp_modes = ARRAY_SIZE(exynos_panel_lp_mode),
|
|
.panel_func = &mcd_drm_panel_funcs,
|
|
.exynos_panel_func = &mcd_exynos_panel_funcs,
|
|
};
|
|
|
|
work_func_t mcd_drm_drv_wq_fns[MAX_MCD_DRM_DRV_WQ] = {
|
|
[MCD_DRM_DRV_WQ_VSYNC] = wq_vblank_handler,
|
|
[MCD_DRM_DRV_WQ_FSYNC] = wq_framedone_handler_fsync,
|
|
[MCD_DRM_DRV_WQ_DISPON] = wq_framedone_handler_dispon,
|
|
[MCD_DRM_DRV_WQ_PANEL_PROBE] = mcd_panel_probe_handler,
|
|
};
|
|
|
|
char *mcd_drm_drv_wq_names[MAX_MCD_DRM_DRV_WQ] = {
|
|
[MCD_DRM_DRV_WQ_VSYNC] = "wq_vsync",
|
|
[MCD_DRM_DRV_WQ_FSYNC] = "wq_fsync",
|
|
[MCD_DRM_DRV_WQ_DISPON] = "wq_dispon",
|
|
[MCD_DRM_DRV_WQ_PANEL_PROBE] = "wq_panel_probe",
|
|
};
|
|
|
|
int mcd_drm_drv_wq_exit(struct exynos_panel *ctx)
|
|
{
|
|
struct mcd_drm_drv_wq *w;
|
|
int i;
|
|
|
|
if (!ctx) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < MAX_MCD_DRM_DRV_WQ; i++) {
|
|
if (!mcd_drm_drv_wq_fns[i])
|
|
continue;
|
|
|
|
w = &ctx->wqs[i];
|
|
if (w->wq) {
|
|
destroy_workqueue(w->wq);
|
|
w->wq = NULL;
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int mcd_drm_drv_wq_init(struct exynos_panel *ctx)
|
|
{
|
|
struct mcd_drm_drv_wq *w;
|
|
int ret, i;
|
|
|
|
if (!ctx) {
|
|
pr_err("%s: invalid ctx\n", __func__);
|
|
return -EINVAL;
|
|
}
|
|
|
|
for (i = 0; i < MAX_MCD_DRM_DRV_WQ; i++) {
|
|
if (!mcd_drm_drv_wq_fns[i])
|
|
continue;
|
|
|
|
w = &ctx->wqs[i];
|
|
w->index = i;
|
|
INIT_DELAYED_WORK(&w->dwork, mcd_drm_drv_wq_fns[i]);
|
|
w->wq = create_singlethread_workqueue(mcd_drm_drv_wq_names[i]);
|
|
if (!w->wq) {
|
|
panel_err(ctx, "failed to workqueue(%s) initialize\n",
|
|
mcd_drm_drv_wq_names[i]);
|
|
ret = mcd_drm_drv_wq_exit(ctx);
|
|
if (ret < 0)
|
|
panel_err(ctx, "failed to workqueue(%s) exit(ret:%d)\n",
|
|
mcd_drm_drv_wq_names[i], ret);
|
|
return -EINVAL;
|
|
}
|
|
w->name = mcd_drm_drv_wq_names[i];
|
|
init_waitqueue_head(&w->wait);
|
|
atomic_set(&w->count, 0);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
int mcd_drm_panel_get_size_mm(struct exynos_panel *ctx,
|
|
unsigned int *width_mm, unsigned int *height_mm)
|
|
{
|
|
struct device_node *np;
|
|
unsigned int size_mm[2];
|
|
|
|
if (!ctx || !ctx->dev || !ctx->mcd_panel_dev)
|
|
return -ENODEV;
|
|
|
|
/* TODO: get width_mm, height_mm from mcd-panel */
|
|
|
|
/* temporary get width_mm, height_mm directly */
|
|
np = ctx->mcd_panel_dev->ap_vendor_setting_node;
|
|
if (!np) {
|
|
panel_err(ctx, "mcd_panel ddi-node is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
of_property_read_u32_array(np, "size", size_mm, 2);
|
|
|
|
*width_mm = size_mm[0];
|
|
*height_mm = size_mm[1];
|
|
|
|
return 0;
|
|
}
|
|
|
|
bool mcd_drm_panel_get_lp11_reset(struct exynos_panel *ctx)
|
|
{
|
|
struct device_node *np;
|
|
bool ret = false;
|
|
u32 reg = 0;
|
|
|
|
if (!ctx || !ctx->dev || !ctx->mcd_panel_dev)
|
|
return -ENODEV;
|
|
|
|
np = ctx->mcd_panel_dev->ap_vendor_setting_node;
|
|
if (!np) {
|
|
panel_err(ctx, "mcd_panel ddi-node is null\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
of_property_read_u32(np, "lp11_reset", ®);
|
|
|
|
ret = reg ? true : false;
|
|
|
|
panel_info(ctx, "lp11_reset:(%d)\n", reg);
|
|
|
|
return ret;
|
|
}
|
|
|
|
struct exynos_panel_desc *
|
|
mcd_drm_panel_get_exynos_panel_desc(struct exynos_panel *ctx)
|
|
{
|
|
struct panel_display_modes *pdms;
|
|
struct exynos_panel_desc *desc;
|
|
unsigned int width_mm = 0, height_mm = 0;
|
|
struct exynos_panel_mode *epms;
|
|
int i, ret;
|
|
|
|
if (!ctx)
|
|
return ERR_PTR(-EINVAL);
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, get_display_mode, &pdms);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to get_display_mode(ret:%d)\n", ret);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
/* create exynos_panel_desc from mcd-panel driver */
|
|
desc = exynos_panel_desc_create_from_panel_display_modes(ctx, pdms);
|
|
desc->panel_func = &mcd_drm_panel_funcs;
|
|
desc->exynos_panel_func = &mcd_exynos_panel_funcs;
|
|
desc->lp11_reset = mcd_drm_panel_get_lp11_reset(ctx);
|
|
|
|
/* set width_mm, height_mm on exynos_panel_mode */
|
|
ret = mcd_drm_panel_get_size_mm(ctx, &width_mm, &height_mm);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to get width_mm, height_mm(ret:%d)\n", ret);
|
|
kfree(desc);
|
|
return ERR_PTR(ret);
|
|
}
|
|
|
|
epms = (struct exynos_panel_mode *)desc->modes;
|
|
for (i = 0; i < (int)desc->num_modes; i++) {
|
|
epms[i].mode.width_mm = width_mm;
|
|
epms[i].mode.height_mm = height_mm;
|
|
}
|
|
|
|
epms = (struct exynos_panel_mode *)desc->lp_modes;
|
|
for (i = 0; i < (int)desc->num_lp_modes; i++) {
|
|
epms[i].mode.width_mm = width_mm;
|
|
epms[i].mode.height_mm = height_mm;
|
|
}
|
|
|
|
return desc;
|
|
}
|
|
|
|
int mcd_drm_panel_probe(struct exynos_panel *ctx)
|
|
{
|
|
struct platform_device *pdev;
|
|
struct device_node *np;
|
|
struct panel_adapter adapter = {
|
|
.ctx = ctx,
|
|
.fifo_size = DSIM_PL_FIFO_THRESHOLD,
|
|
.funcs = &mcd_panel_adapter_funcs,
|
|
};
|
|
int ret;
|
|
|
|
if (!ctx)
|
|
return -EINVAL;
|
|
|
|
init_rwsem(&ctx->panel_drm_state_lock);
|
|
mutex_init(&ctx->probe_lock);
|
|
ctx->panel_drm_state = PANEL_DRM_STATE_DISABLED;
|
|
|
|
np = of_find_compatible_node(NULL, NULL, "samsung,panel-drv");
|
|
if (!np) {
|
|
panel_err(ctx, "compatible(\"samsung,panel-drv\") node not found\n");
|
|
return -ENOENT;
|
|
}
|
|
|
|
pdev = of_find_device_by_node(np);
|
|
of_node_put(np);
|
|
if (!pdev) {
|
|
panel_err(ctx, "mcd-panel device not found\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ctx->mcd_panel_dev = (struct panel_device *)platform_get_drvdata(pdev);
|
|
if (!ctx->mcd_panel_dev) {
|
|
panel_err(ctx, "failed to get panel_device\n");
|
|
return -ENODEV;
|
|
}
|
|
|
|
ret = call_mcd_panel_func(ctx->mcd_panel_dev, attach_adapter, &adapter);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "mcd_panel call attach_adapter failed %d", ret);
|
|
return ret;
|
|
}
|
|
|
|
ret = mcd_drm_drv_wq_init(ctx);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "mcd_panel initialize workqueue failed(ret:%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
queue_delayed_work(ctx->wqs[MCD_DRM_DRV_WQ_PANEL_PROBE].wq,
|
|
&ctx->wqs[MCD_DRM_DRV_WQ_PANEL_PROBE].dwork,
|
|
msecs_to_jiffies(MCD_PANEL_PROBE_DELAY_MSEC));
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void notify_lp11_reset(struct exynos_panel *ctx, bool en)
|
|
{
|
|
struct mipi_dsi_device *dsi;
|
|
struct dsim_device *dsim;
|
|
|
|
dsi = to_mipi_dsi_device(ctx->dev);
|
|
if (!dsi || !dsi->host) {
|
|
panel_err(ctx, "dsi not attached yet\n");
|
|
return;
|
|
}
|
|
|
|
dsim = container_of(dsi->host, struct dsim_device, dsi_host);
|
|
|
|
dsim->lp11_reset = en;
|
|
}
|
|
|
|
int exynos_panel_probe(struct mipi_dsi_device *dsi)
|
|
{
|
|
struct device *dev = &dsi->dev;
|
|
struct exynos_panel *ctx;
|
|
int ret = 0;
|
|
|
|
ctx = devm_kzalloc(dev, sizeof(struct exynos_panel), GFP_KERNEL);
|
|
if (!ctx)
|
|
return -ENOMEM;
|
|
|
|
pr_info("%s: %s+\n", dev->driver->name, __func__);
|
|
|
|
mipi_dsi_set_drvdata(dsi, ctx);
|
|
ctx->dev = dev;
|
|
|
|
ret = mcd_drm_panel_probe(ctx);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to probe mcd_drm_panel(ret:%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
ctx->desc = mcd_drm_panel_get_exynos_panel_desc(ctx);
|
|
if (IS_ERR_OR_NULL(ctx->desc)) {
|
|
panel_err(ctx, "failed to get exynos_panel_desc (ret:%ld)\n",
|
|
PTR_ERR(ctx->desc));
|
|
return PTR_ERR(ctx->desc);
|
|
}
|
|
|
|
dsi->lanes = ctx->desc->data_lane_cnt;
|
|
dsi->format = MIPI_DSI_FMT_RGB888;
|
|
|
|
notify_lp11_reset(ctx, ctx->desc->lp11_reset);
|
|
|
|
drm_panel_init(&ctx->panel, dev,
|
|
&mcd_drm_panel_funcs, DRM_MODE_CONNECTOR_DSI);
|
|
drm_panel_add(&ctx->panel);
|
|
|
|
ctx->bridge.funcs = &exynos_panel_bridge_funcs;
|
|
#ifdef CONFIG_OF
|
|
ctx->bridge.of_node = ctx->dev->of_node;
|
|
#endif
|
|
drm_bridge_add(&ctx->bridge);
|
|
|
|
ret = mipi_dsi_attach(dsi);
|
|
if (ret < 0) {
|
|
panel_err(ctx, "failed to attach mipi dsi(ret:%d)\n", ret);
|
|
return ret;
|
|
}
|
|
|
|
exynos_panel_set_dqe_xml(dev, ctx);
|
|
|
|
exynos_panel_parse_vendor_pps(ctx);
|
|
|
|
exynos_panel_parse_vfp_detail(ctx);
|
|
|
|
panel_info(ctx, "mcd common panel driver has been probed\n");
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(exynos_panel_probe);
|
|
|
|
int exynos_panel_remove(struct mipi_dsi_device *dsi)
|
|
{
|
|
struct exynos_panel *ctx = mipi_dsi_get_drvdata(dsi);
|
|
|
|
mipi_dsi_detach(dsi);
|
|
drm_panel_remove(&ctx->panel);
|
|
drm_bridge_remove(&ctx->bridge);
|
|
devm_backlight_device_unregister(ctx->dev, ctx->bl);
|
|
exynos_panel_desc_destroy(ctx, (struct exynos_panel_desc *)ctx->desc);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL(exynos_panel_remove);
|
|
|
|
static const struct of_device_id exynos_panel_of_match[] = {
|
|
{ .compatible = "samsung,mcd-panel-samsung-drv" },
|
|
{ }
|
|
};
|
|
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 = "mcd-panel-samsung-drv",
|
|
.of_match_table = exynos_panel_of_match,
|
|
},
|
|
};
|
|
module_mipi_dsi_driver(exynos_panel_driver);
|
|
|
|
MODULE_SOFTDEP("pre: mcd-panel");
|
|
|
|
MODULE_AUTHOR("Jiun Yu <jiun.yu@samsung.com>");
|
|
MODULE_DESCRIPTION("MIPI-DSI based Samsung common panel driver");
|
|
MODULE_LICENSE("GPL");
|