blob: 6a5987a201124d90072e4ad6df76de835559c9b5 [file] [log] [blame]
// SPDX-License-Identifier: GPL-2.0-only
/* exynos_drm_crtc.c
*
* Copyright (c) 2011 Samsung Electronics Co., Ltd.
* Authors:
* Inki Dae <inki.dae@samsung.com>
* Joonyoung Shim <jy0922.shim@samsung.com>
* Seung-Woo Kim <sw0312.kim@samsung.com>
*
* This program is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License as published by the
* Free Software Foundation; either version 2 of the License, or (at your
* option) any later version.
*/
#include <drm/drm_atomic.h>
#include <drm/drm_atomic_helper.h>
#include <drm/drm_encoder.h>
#include <drm/drm_color_mgmt.h>
#include <drm/drm_crtc_helper.h>
#include <drm/drm_fourcc.h>
#include <drm/drm_ioctl.h>
#include <drm/drm_vblank.h>
#include <drm/drm.h>
#include <dqe_cal.h>
#include "exynos_drm_crtc.h"
#include "exynos_drm_decon.h"
#include "exynos_drm_drv.h"
#include "exynos_drm_plane.h"
enum crtc_active_state {
CRTC_STATE_INACTIVE,
CRTC_STATE_ACTIVE,
CRTC_STATE_SELF_REFRESH,
};
static void exynos_drm_crtc_atomic_enable(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
const enum crtc_active_state active_state = CRTC_STATE_ACTIVE;
if (active_state == exynos_crtc->active_state)
return;
if (exynos_crtc->active_state == CRTC_STATE_INACTIVE)
drm_crtc_vblank_on(crtc);
if (exynos_crtc->ops->enable)
exynos_crtc->ops->enable(exynos_crtc, old_state);
exynos_crtc->active_state = active_state;
}
static void exynos_drm_crtc_atomic_disable(struct drm_crtc *crtc,
struct drm_crtc_state *old_state)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
const enum crtc_active_state active_state =
crtc->state->self_refresh_active ? CRTC_STATE_SELF_REFRESH : CRTC_STATE_INACTIVE;
if (active_state == exynos_crtc->active_state)
return;
if (exynos_crtc->ops->disable)
exynos_crtc->ops->disable(exynos_crtc);
if (crtc->state->event && !crtc->state->active) {
spin_lock_irq(&crtc->dev->event_lock);
drm_crtc_send_vblank_event(crtc, crtc->state->event);
spin_unlock_irq(&crtc->dev->event_lock);
crtc->state->event = NULL;
}
if (active_state == CRTC_STATE_INACTIVE)
drm_crtc_vblank_off(crtc);
exynos_crtc->active_state = active_state;
}
static void exynos_crtc_update_lut(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
struct drm_color_lut *degamma_lut, *gamma_lut;
struct cgc_lut *cgc_lut;
struct decon_device *decon = exynos_crtc->ctx;
struct exynos_drm_crtc_state *exynos_state;
struct exynos_dqe_state *dqe_state;
if (!decon->dqe)
return;
exynos_state = to_exynos_crtc_state(state);
dqe_state = &exynos_state->dqe;
if (exynos_state->cgc_lut) {
cgc_lut = exynos_state->cgc_lut->data;
dqe_state->cgc_lut = cgc_lut;
} else {
dqe_state->cgc_lut = NULL;
}
if (exynos_state->disp_dither)
dqe_state->disp_dither_config = exynos_state->disp_dither->data;
else
dqe_state->disp_dither_config = NULL;
if (exynos_state->cgc_dither)
dqe_state->cgc_dither_config = exynos_state->cgc_dither->data;
else
dqe_state->cgc_dither_config = NULL;
dqe_state->roi = exynos_state->histogram_roi ?
exynos_state->histogram_roi->data : NULL;
dqe_state->weights = exynos_state->histogram_weights ?
exynos_state->histogram_weights->data : NULL;
if (exynos_state->linear_matrix)
dqe_state->linear_matrix = exynos_state->linear_matrix->data;
else
dqe_state->linear_matrix = NULL;
if (exynos_state->gamma_matrix)
dqe_state->gamma_matrix = exynos_state->gamma_matrix->data;
else
dqe_state->gamma_matrix = NULL;
if (state->degamma_lut) {
degamma_lut = state->degamma_lut->data;
dqe_state->degamma_lut = degamma_lut;
} else {
dqe_state->degamma_lut = NULL;
}
if (state->gamma_lut) {
gamma_lut = state->gamma_lut->data;
dqe_state->regamma_lut = gamma_lut;
} else {
dqe_state->regamma_lut = NULL;
}
dqe_state->cgc_gem = exynos_state->cgc_gem;
}
static int exynos_crtc_atomic_check(struct drm_crtc *crtc,
struct drm_crtc_state *crtc_state)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
struct exynos_drm_crtc_state *new_exynos_state =
to_exynos_crtc_state(crtc_state);
const struct drm_crtc_state *old_crtc_state =
drm_atomic_get_old_crtc_state(crtc_state->state, crtc);
struct exynos_drm_crtc_state *old_exynos_state =
to_exynos_crtc_state(old_crtc_state);
struct drm_plane *plane;
const struct drm_plane_state *plane_state;
const struct decon_device *decon = exynos_crtc->ctx;
const struct exynos_dqe *dqe = decon->dqe;
uint32_t max_bpc;
DRM_DEBUG("%s +\n", __func__);
if (!crtc_state->enable)
return 0;
if (crtc_state->color_mgmt_changed)
exynos_crtc_update_lut(crtc, crtc_state);
if (exynos_crtc->ops->atomic_check)
exynos_crtc->ops->atomic_check(exynos_crtc, crtc_state);
if (dqe && (dqe->force_disabled || !new_exynos_state->dqe.enabled) &&
(decon->config.out_bpc == 8)) {
new_exynos_state->in_bpc = 8;
} else if (new_exynos_state->force_bpc == EXYNOS_BPC_MODE_UNSPECIFIED) {
max_bpc = 8; /* initial bpc value */
drm_atomic_crtc_state_for_each_plane_state(plane, plane_state, crtc_state) {
const struct drm_format_info *info;
const struct dpu_fmt *fmt_info;
info = plane_state->fb->format;
fmt_info = dpu_find_fmt_info(info->format);
if (fmt_info->bpc == 10) {
max_bpc = 10;
break;
}
}
new_exynos_state->in_bpc = max_bpc;
} else {
new_exynos_state->in_bpc =
new_exynos_state->force_bpc == EXYNOS_BPC_MODE_10 ?
10 : 8;
}
/* do this only if we have plane to update, this is to avoid skip_update
* always be ignored on the first commit(from continuous splash)
*/
if (crtc_state->plane_mask && (old_exynos_state->in_bpc != new_exynos_state->in_bpc))
crtc_state->color_mgmt_changed = true;
/*
* if the the following conditions are met then skip the update to keep self refresh
* contents on the screen and save some cycles
* 1. Display is in self refresh
* 2. No color mgmt updates
* 3. There are no planes updated
*
* NOTE: may need to handle the case where there is a regular mode set coming out of self
* refresh that requires an update, however most mode set updates require planes to be
* updated too, and/or we may actually want to just update encoder/bridges/connectors only.
*/
if (new_exynos_state->hibernation_exit) {
new_exynos_state->skip_update = true;
crtc_state->no_vblank = true;
} else if (old_crtc_state->self_refresh_active && !crtc_state->color_mgmt_changed &&
!new_exynos_state->planes_updated)
new_exynos_state->skip_update = true;
if (decon->rcd) {
uint32_t rcd_mask = crtc_state->plane_mask & exynos_crtc->rcd_plane_mask;
uint32_t old_rcd_mask = old_crtc_state->plane_mask & exynos_crtc->rcd_plane_mask;
new_exynos_state->dqe.rcd_enabled = false;
crtc_state->color_mgmt_changed |= rcd_mask != old_rcd_mask;
if (rcd_mask) {
drm_atomic_crtc_state_for_each_plane_state(plane, plane_state, crtc_state) {
if (rcd_mask & drm_plane_mask(plane)) {
new_exynos_state->dqe.rcd_enabled = plane_state->visible;
break;
}
}
}
}
DRM_DEBUG("%s -\n", __func__);
return 0;
}
static void exynos_crtc_atomic_begin(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
if (exynos_crtc->ops->atomic_begin)
exynos_crtc->ops->atomic_begin(exynos_crtc);
}
static void exynos_crtc_atomic_flush(struct drm_crtc *crtc,
struct drm_crtc_state *old_crtc_state)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
if (exynos_crtc->ops->atomic_flush)
exynos_crtc->ops->atomic_flush(exynos_crtc, old_crtc_state);
}
static enum drm_mode_status exynos_crtc_mode_valid(struct drm_crtc *crtc,
const struct drm_display_mode *mode)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
if (exynos_crtc->ops->mode_valid)
return exynos_crtc->ops->mode_valid(exynos_crtc, mode);
return MODE_OK;
}
static bool exynos_crtc_mode_fixup(struct drm_crtc *crtc,
const struct drm_display_mode *mode,
struct drm_display_mode *adjusted_mode)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
if (exynos_crtc->ops->mode_fixup)
return exynos_crtc->ops->mode_fixup(exynos_crtc, mode,
adjusted_mode);
return true;
}
static void exynos_crtc_mode_set_nofb(struct drm_crtc *crtc)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
const struct drm_crtc_state *crtc_state = crtc->state;
if (exynos_crtc->ops->mode_set)
return exynos_crtc->ops->mode_set(exynos_crtc,
&crtc_state->mode,
&crtc_state->adjusted_mode);
}
static const struct drm_crtc_helper_funcs exynos_crtc_helper_funcs = {
.mode_valid = exynos_crtc_mode_valid,
.mode_fixup = exynos_crtc_mode_fixup,
.mode_set_nofb = exynos_crtc_mode_set_nofb,
.atomic_check = exynos_crtc_atomic_check,
.atomic_begin = exynos_crtc_atomic_begin,
.atomic_flush = exynos_crtc_atomic_flush,
.atomic_enable = exynos_drm_crtc_atomic_enable,
.atomic_disable = exynos_drm_crtc_atomic_disable,
};
void exynos_crtc_handle_event(struct exynos_drm_crtc *exynos_crtc)
{
struct drm_crtc *crtc = &exynos_crtc->base;
struct drm_pending_vblank_event *event = crtc->state->event;
unsigned long flags;
if (!event)
return;
crtc->state->event = NULL;
WARN_ON(drm_crtc_vblank_get(crtc) != 0);
spin_lock_irqsave(&crtc->dev->event_lock, flags);
drm_crtc_arm_vblank_event(crtc, event);
spin_unlock_irqrestore(&crtc->dev->event_lock, flags);
}
static void exynos_drm_crtc_destroy(struct drm_crtc *crtc)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
drm_crtc_cleanup(crtc);
kfree(exynos_crtc);
}
static int exynos_drm_crtc_enable_vblank(struct drm_crtc *crtc)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
if (exynos_crtc->ops->enable_vblank)
return exynos_crtc->ops->enable_vblank(exynos_crtc);
return 0;
}
static void exynos_drm_crtc_disable_vblank(struct drm_crtc *crtc)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
if (exynos_crtc->ops->disable_vblank)
exynos_crtc->ops->disable_vblank(exynos_crtc);
}
static u32 exynos_drm_crtc_get_vblank_counter(struct drm_crtc *crtc)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
if (exynos_crtc->ops->get_vblank_counter)
return exynos_crtc->ops->get_vblank_counter(exynos_crtc);
return 0;
}
static void exynos_drm_crtc_destroy_state(struct drm_crtc *crtc,
struct drm_crtc_state *state)
{
struct exynos_drm_crtc_state *exynos_crtc_state;
exynos_crtc_state = to_exynos_crtc_state(state);
drm_property_blob_put(exynos_crtc_state->cgc_lut);
drm_property_blob_put(exynos_crtc_state->disp_dither);
drm_property_blob_put(exynos_crtc_state->cgc_dither);
drm_property_blob_put(exynos_crtc_state->linear_matrix);
drm_property_blob_put(exynos_crtc_state->gamma_matrix);
drm_property_blob_put(exynos_crtc_state->histogram_roi);
drm_property_blob_put(exynos_crtc_state->histogram_weights);
drm_property_blob_put(exynos_crtc_state->partial);
if (exynos_crtc_state->cgc_gem)
drm_gem_object_put(exynos_crtc_state->cgc_gem);
__drm_atomic_helper_crtc_destroy_state(state);
kfree(exynos_crtc_state);
}
static void exynos_drm_crtc_reset(struct drm_crtc *crtc)
{
struct exynos_drm_crtc_state *exynos_crtc_state;
if (crtc->state) {
exynos_drm_crtc_destroy_state(crtc, crtc->state);
crtc->state = NULL;
}
exynos_crtc_state = kzalloc(sizeof(*exynos_crtc_state), GFP_KERNEL);
if (exynos_crtc_state) {
exynos_crtc_state->dqe.enabled = true;
__drm_atomic_helper_crtc_reset(crtc, &exynos_crtc_state->base);
} else {
pr_err("failed to allocate exynos crtc state\n");
}
}
static struct drm_crtc_state *
exynos_drm_crtc_duplicate_state(struct drm_crtc *crtc)
{
struct exynos_drm_crtc_state *exynos_crtc_state;
struct exynos_drm_crtc_state *copy;
exynos_crtc_state = to_exynos_crtc_state(crtc->state);
copy = kzalloc(sizeof(*copy), GFP_KERNEL);
if (!copy)
return NULL;
memcpy(copy, exynos_crtc_state, sizeof(*copy));
if (copy->cgc_lut)
drm_property_blob_get(copy->cgc_lut);
if (copy->disp_dither)
drm_property_blob_get(copy->disp_dither);
if (copy->cgc_dither)
drm_property_blob_get(copy->cgc_dither);
if (copy->linear_matrix)
drm_property_blob_get(copy->linear_matrix);
if (copy->gamma_matrix)
drm_property_blob_get(copy->gamma_matrix);
if (copy->histogram_roi)
drm_property_blob_get(copy->histogram_roi);
if (copy->histogram_weights)
drm_property_blob_get(copy->histogram_weights);
if (copy->partial)
drm_property_blob_get(copy->partial);
if (copy->cgc_gem)
drm_gem_object_get(copy->cgc_gem);
__drm_atomic_helper_crtc_duplicate_state(crtc, &copy->base);
copy->seamless_mode_changed = false;
copy->skip_update = false;
copy->planes_updated = false;
copy->hibernation_exit = false;
return &copy->base;
}
static int
exynos_drm_replace_property_blob_from_id(struct drm_device *dev,
struct drm_property_blob **blob,
uint64_t blob_id,
ssize_t expected_size,
ssize_t expected_elem_size,
bool *replaced)
{
struct drm_property_blob *new_blob = NULL;
if (blob_id != 0) {
new_blob = drm_property_lookup_blob(dev, blob_id);
if (new_blob == NULL)
return -EINVAL;
if (expected_size > 0 &&
new_blob->length != expected_size) {
drm_property_blob_put(new_blob);
return -EINVAL;
}
if (expected_elem_size > 0 &&
new_blob->length % expected_elem_size != 0) {
drm_property_blob_put(new_blob);
return -EINVAL;
}
}
*replaced |= drm_property_replace_blob(blob, new_blob);
drm_property_blob_put(new_blob);
return 0;
}
static int exynos_drm_crtc_set_property(struct drm_crtc *crtc,
struct drm_crtc_state *state,
struct drm_property *property,
uint64_t val)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
struct exynos_drm_crtc_state *exynos_crtc_state;
int ret = 0;
bool replaced = false;
exynos_crtc_state = to_exynos_crtc_state(state);
if (property == exynos_crtc->props.color_mode) {
if (val != exynos_crtc_state->color_mode) {
exynos_crtc_state->color_mode = val;
replaced = true;
}
} else if (property == exynos_crtc->props.force_bpc) {
if (val != exynos_crtc_state->force_bpc) {
exynos_crtc_state->force_bpc = val;
replaced = true;
}
} else if (property == exynos_crtc->props.ppc ||
property == exynos_crtc->props.max_disp_freq) {
return 0;
} else if (property == exynos_crtc->props.dqe_enabled) {
if (val != exynos_crtc_state->dqe.enabled) {
exynos_crtc_state->dqe.enabled = val;
replaced = true;
}
} else if (property == exynos_crtc->props.histogram_threshold) {
if (val != exynos_crtc_state->dqe.histogram_threshold) {
exynos_crtc_state->dqe.histogram_threshold = val;
replaced = true;
}
} else if (property == exynos_crtc->props.cgc_lut) {
ret = exynos_drm_replace_property_blob_from_id(state->crtc->dev,
&exynos_crtc_state->cgc_lut, val,
sizeof(struct cgc_lut), -1, &replaced);
} else if (property == exynos_crtc->props.disp_dither) {
ret = exynos_drm_replace_property_blob_from_id(state->crtc->dev,
&exynos_crtc_state->disp_dither, val,
sizeof(struct dither_config), -1, &replaced);
} else if (property == exynos_crtc->props.cgc_dither) {
ret = exynos_drm_replace_property_blob_from_id(state->crtc->dev,
&exynos_crtc_state->cgc_dither, val,
sizeof(struct dither_config), -1, &replaced);
} else if (property == exynos_crtc->props.linear_matrix) {
ret = exynos_drm_replace_property_blob_from_id(state->crtc->dev,
&exynos_crtc_state->linear_matrix, val,
sizeof(struct exynos_matrix), -1, &replaced);
} else if (property == exynos_crtc->props.gamma_matrix) {
ret = exynos_drm_replace_property_blob_from_id(state->crtc->dev,
&exynos_crtc_state->gamma_matrix, val,
sizeof(struct exynos_matrix), -1, &replaced);
} else if (property == exynos_crtc->props.histogram_roi) {
ret = exynos_drm_replace_property_blob_from_id(state->crtc->dev,
&exynos_crtc_state->histogram_roi, val,
sizeof(struct histogram_roi), -1, &replaced);
} else if (property == exynos_crtc->props.histogram_weights) {
ret = exynos_drm_replace_property_blob_from_id(state->crtc->dev,
&exynos_crtc_state->histogram_weights, val,
sizeof(struct histogram_weights), -1, &replaced);
} else if (property == exynos_crtc->props.histogram_pos) {
if (val != exynos_crtc_state->dqe.histogram_pos) {
exynos_crtc_state->dqe.histogram_pos = val;
replaced = true;
}
} else if (property == exynos_crtc->props.partial) {
ret = exynos_drm_replace_property_blob_from_id(state->crtc->dev,
&exynos_crtc_state->partial, val,
sizeof(struct drm_clip_rect), -1, &replaced);
return ret;
} else if (property == exynos_crtc->props.cgc_lut_fd) {
if (exynos_crtc_state->cgc_gem)
drm_gem_object_put(exynos_crtc_state->cgc_gem);
exynos_crtc_state->cgc_gem = (U642I64(val) >= 0) ?
exynos_drm_gem_fd_to_obj(crtc->dev, U642I64(val)) : NULL;
replaced = true;
} else if (property == exynos_crtc->props.expected_present_time) {
exynos_crtc_state->expected_present_time = val;
} else {
return -EINVAL;
}
state->color_mgmt_changed |= replaced;
return ret;
}
static int exynos_drm_crtc_get_property(struct drm_crtc *crtc,
const struct drm_crtc_state *state,
struct drm_property *property,
uint64_t *val)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
struct exynos_drm_crtc_state *exynos_crtc_state;
struct decon_device *decon = exynos_crtc->ctx;
exynos_crtc_state =
to_exynos_crtc_state((struct drm_crtc_state *)state);
if (property == exynos_crtc->props.color_mode)
*val = exynos_crtc_state->color_mode;
else if (property == exynos_crtc->props.ppc)
*val = decon->bts.ppc;
else if (property == exynos_crtc->props.max_disp_freq)
*val = decon->bts.dvfs_max_disp_freq;
else if (property == exynos_crtc->props.force_bpc)
*val = exynos_crtc_state->force_bpc;
else if (property == exynos_crtc->props.dqe_enabled)
*val = exynos_crtc_state->dqe.enabled;
else if (property == exynos_crtc->props.histogram_threshold)
*val = exynos_crtc_state->dqe.histogram_threshold;
else if (property == exynos_crtc->props.cgc_lut)
*val = (exynos_crtc_state->cgc_lut) ?
exynos_crtc_state->cgc_lut->base.id : 0;
else if (property == exynos_crtc->props.disp_dither)
*val = (exynos_crtc_state->disp_dither) ?
exynos_crtc_state->disp_dither->base.id : 0;
else if (property == exynos_crtc->props.cgc_dither)
*val = (exynos_crtc_state->cgc_dither) ?
exynos_crtc_state->cgc_dither->base.id : 0;
else if (property == exynos_crtc->props.linear_matrix)
*val = (exynos_crtc_state->linear_matrix) ?
exynos_crtc_state->linear_matrix->base.id : 0;
else if (property == exynos_crtc->props.gamma_matrix)
*val = (exynos_crtc_state->gamma_matrix) ?
exynos_crtc_state->gamma_matrix->base.id : 0;
else if (property == exynos_crtc->props.histogram_roi)
*val = (exynos_crtc_state->histogram_roi) ?
exynos_crtc_state->histogram_roi->base.id : 0;
else if (property == exynos_crtc->props.histogram_weights)
*val = (exynos_crtc_state->histogram_weights) ?
exynos_crtc_state->histogram_weights->base.id : 0;
else if (property == exynos_crtc->props.histogram_pos)
*val = exynos_crtc_state->dqe.histogram_pos;
else if (property == exynos_crtc->props.partial)
*val = (exynos_crtc_state->partial) ?
exynos_crtc_state->partial->base.id : 0;
else if (property == exynos_crtc->props.cgc_lut_fd)
*val = (exynos_crtc_state->cgc_gem) ?
dma_buf_fd(exynos_crtc_state->cgc_gem->dma_buf, 0) : 0;
else if (property == exynos_crtc->props.expected_present_time)
*val = exynos_crtc_state->expected_present_time;
else
return -EINVAL;
return 0;
}
static void exynos_drm_crtc_print_state(struct drm_printer *p,
const struct drm_crtc_state *state)
{
const struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(state->crtc);
const struct exynos_drm_crtc_state *exynos_crtc_state = to_exynos_crtc_state(state);
const struct decon_device *decon = exynos_crtc->ctx;
const struct decon_config *cfg = &decon->config;
struct exynos_drm_crtc_state *exynos_state;
struct drm_clip_rect *partial_region;
exynos_state = container_of(state, struct exynos_drm_crtc_state, base);
drm_printf(p, "\treserved_win_mask=0x%x\n", exynos_crtc_state->reserved_win_mask);
drm_printf(p, "\tDecon #%u (state:%d)\n", decon->id, decon->state);
drm_printf(p, "\t\ttype=0x%x\n", cfg->out_type);
drm_printf(p, "\t\tsize=%dx%d\n", cfg->image_width, cfg->image_height);
if (cfg->mode.dsi_mode != DSI_MODE_NONE) {
drm_printf(p, "\t\tdsi_mode=%s (%d)\n",
cfg->mode.op_mode == DECON_VIDEO_MODE ? "vid" : "cmd",
cfg->mode.dsi_mode);
if (cfg->mode.op_mode == DECON_COMMAND_MODE)
drm_printf(p, "\t\ttrig_mode=%s ddi=%d\n",
cfg->mode.trig_mode == DECON_HW_TRIG ? "hw" : "sw",
cfg->te_from);
}
drm_printf(p, "\t\tbpc=%d\n", cfg->out_bpc);
if (exynos_state->partial) {
partial_region =
(struct drm_clip_rect *)exynos_state->partial->data;
drm_printf(p, "\t\tpartial region[%d %d %d %d]\n",
partial_region->x1, partial_region->y1,
partial_region->x2 - partial_region->x1,
partial_region->y2 - partial_region->y1);
} else {
drm_printf(p, "\t\tno partial region request\n");
}
}
static int exynos_drm_crtc_late_register(struct drm_crtc *crtc)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
struct decon_device *decon = exynos_crtc->ctx;
return dpu_init_debug(decon);
}
static const struct drm_crtc_funcs exynos_crtc_funcs = {
.set_config = drm_atomic_helper_set_config,
.page_flip = drm_atomic_helper_page_flip,
.destroy = exynos_drm_crtc_destroy,
.reset = exynos_drm_crtc_reset,
.atomic_duplicate_state = exynos_drm_crtc_duplicate_state,
.atomic_destroy_state = exynos_drm_crtc_destroy_state,
.atomic_set_property = exynos_drm_crtc_set_property,
.atomic_get_property = exynos_drm_crtc_get_property,
.atomic_print_state = exynos_drm_crtc_print_state,
.enable_vblank = exynos_drm_crtc_enable_vblank,
.disable_vblank = exynos_drm_crtc_disable_vblank,
.get_vblank_counter = exynos_drm_crtc_get_vblank_counter,
.late_register = exynos_drm_crtc_late_register,
};
static int
exynos_drm_crtc_create_color_mode_property(struct exynos_drm_crtc *exynos_crtc)
{
struct drm_crtc *crtc = &exynos_crtc->base;
struct drm_property *prop;
static const struct drm_prop_enum_list color_mode_list[] = {
{ HAL_COLOR_MODE_NATIVE, "Native" },
{ HAL_COLOR_MODE_STANDARD_BT601_625, "BT601_625" },
{ HAL_COLOR_MODE_STANDARD_BT601_625_UNADJUSTED,
"BT601_625_UNADJUSTED" },
{ HAL_COLOR_MODE_STANDARD_BT601_525, "BT601_525" },
{ HAL_COLOR_MODE_STANDARD_BT601_525_UNADJUSTED,
"BT601_525_UNADJUSTED" },
{ HAL_COLOR_MODE_STANDARD_BT709, "BT709" },
{ HAL_COLOR_MODE_DCI_P3, "DCI-P3" },
{ HAL_COLOR_MODE_SRGB, "sRGB" },
{ HAL_COLOR_MODE_ADOBE_RGB, "Adobe RGB" },
{ HAL_COLOR_MODE_DISPLAY_P3, "Display P3" },
{ HAL_COLOR_MODE_BT2020, "BT2020" },
{ HAL_COLOR_MODE_BT2100_PQ, "BT2100 PQ" },
{ HAL_COLOR_MODE_BT2100_HLG, "BT2100 HLG" },
};
prop = drm_property_create_enum(crtc->dev, 0, "color mode",
color_mode_list, ARRAY_SIZE(color_mode_list));
if (!prop)
return -ENOMEM;
drm_object_attach_property(&crtc->base, prop, HAL_COLOR_MODE_NATIVE);
exynos_crtc->props.color_mode = prop;
return 0;
}
static int
exynos_drm_crtc_create_force_bpc_property(struct exynos_drm_crtc *exynos_crtc)
{
struct drm_crtc *crtc = &exynos_crtc->base;
struct drm_property *prop;
static const struct drm_prop_enum_list bpc_list[] = {
{ EXYNOS_BPC_MODE_UNSPECIFIED, "Unspecified" },
{ EXYNOS_BPC_MODE_8, "8bpc" },
{ EXYNOS_BPC_MODE_10, "10bpc" },
};
prop = drm_property_create_enum(crtc->dev, 0, "force_bpc", bpc_list,
ARRAY_SIZE(bpc_list));
if (!prop)
return -ENOMEM;
drm_object_attach_property(&crtc->base, prop,
EXYNOS_BPC_MODE_UNSPECIFIED);
exynos_crtc->props.force_bpc = prop;
return 0;
}
static int exynos_drm_crtc_create_bool(struct drm_crtc *crtc, const char *name,
struct drm_property **prop)
{
struct drm_property *p;
p = drm_property_create_bool(crtc->dev, 0, name);
if (!p)
return -ENOMEM;
drm_object_attach_property(&crtc->base, p, 0);
*prop = p;
return 0;
}
static int exynos_drm_crtc_create_range(struct drm_crtc *crtc, const char *name,
struct drm_property **prop, uint64_t min, uint64_t max)
{
struct drm_property *p;
p = drm_property_create_range(crtc->dev, 0, name, min, max);
if (!p)
return -ENOMEM;
drm_object_attach_property(&crtc->base, p, 0);
*prop = p;
return 0;
}
static int exynos_drm_crtc_create_signed_range(struct drm_crtc *crtc, const char *name,
struct drm_property **prop, uint64_t min, uint64_t max)
{
struct drm_property *p;
p = drm_property_create_signed_range(crtc->dev, 0, name, min, max);
if (!p)
return -ENOMEM;
drm_object_attach_property(&crtc->base, p, 0);
*prop = p;
return 0;
}
static int exynos_drm_crtc_create_blob(struct drm_crtc *crtc, const char *name,
struct drm_property **prop)
{
struct drm_property *p;
p = drm_property_create(crtc->dev, DRM_MODE_PROP_BLOB, name, 0);
if (!p)
return -ENOMEM;
drm_object_attach_property(&crtc->base, p, 0);
*prop = p;
return 0;
}
static int exynos_drm_crtc_histogram_pos_property(struct exynos_drm_crtc *exynos_crtc)
{
struct drm_crtc *crtc = &exynos_crtc->base;
struct drm_property *prop;
static const struct drm_prop_enum_list histogram_pos_list[] = {
{ POST_DQE, "Post DQE" },
{ PRE_DQE, "Pre DQE" },
};
u32 flags = 0;
if (IS_ENABLED(CONFIG_SOC_GS101))
flags |= DRM_MODE_PROP_IMMUTABLE;
prop = drm_property_create_enum(crtc->dev, flags, "histogram_pos",
histogram_pos_list, ARRAY_SIZE(histogram_pos_list));
if (!prop)
return -ENOMEM;
drm_object_attach_property(&crtc->base, prop, POST_DQE);
exynos_crtc->props.histogram_pos = prop;
return 0;
}
static int exynos_drm_crtc_create_histogram_properties(
struct exynos_drm_crtc *exynos_crtc)
{
struct drm_crtc *crtc = &exynos_crtc->base;
int ret;
ret = exynos_drm_crtc_create_blob(crtc, "histogram_roi",
&exynos_crtc->props.histogram_roi);
if (ret)
return ret;
ret = exynos_drm_crtc_create_blob(crtc, "histogram_weights",
&exynos_crtc->props.histogram_weights);
if (ret)
return ret;
ret = exynos_drm_crtc_create_range(crtc, "histogram_threshold",
&exynos_crtc->props.histogram_threshold,
0, GENMASK(9, 0));
if (ret)
return ret;
ret = exynos_drm_crtc_histogram_pos_property(exynos_crtc);
if (ret)
return ret;
return 0;
}
static int
exynos_drm_crtc_create_partial_property(struct exynos_drm_crtc *exynos_crtc)
{
struct drm_crtc *crtc = &exynos_crtc->base;
struct drm_property *prop;
prop = drm_property_create(crtc->dev, DRM_MODE_PROP_BLOB,
"partial_region", 0);
if (!prop)
return -ENOMEM;
drm_object_attach_property(&crtc->base, prop, 0);
exynos_crtc->props.partial = prop;
return 0;
}
struct exynos_drm_crtc *exynos_drm_crtc_create(struct drm_device *drm_dev,
struct drm_plane *plane,
enum exynos_drm_output_type type,
const struct exynos_drm_crtc_ops *ops,
void *ctx)
{
struct exynos_drm_crtc *exynos_crtc;
struct drm_crtc *crtc;
const struct decon_device *decon = ctx;
int ret;
exynos_crtc = kzalloc(sizeof(*exynos_crtc), GFP_KERNEL);
if (!exynos_crtc)
return ERR_PTR(-ENOMEM);
exynos_crtc->possible_type = type;
exynos_crtc->ops = ops;
exynos_crtc->ctx = ctx;
exynos_crtc->active_state = CRTC_STATE_INACTIVE;
crtc = &exynos_crtc->base;
ret = drm_crtc_init_with_planes(drm_dev, crtc, plane, NULL,
&exynos_crtc_funcs, "exynos-crtc-%u",
decon->id);
if (ret < 0)
goto err_crtc;
drm_crtc_helper_add(crtc, &exynos_crtc_helper_funcs);
ret = exynos_drm_crtc_create_color_mode_property(exynos_crtc);
if (ret)
goto err_crtc;
ret = exynos_drm_crtc_create_force_bpc_property(exynos_crtc);
if (ret)
goto err_crtc;
if (exynos_drm_crtc_create_range(crtc, "ppc", &exynos_crtc->props.ppc,
0, UINT_MAX))
goto err_crtc;
if (exynos_drm_crtc_create_range(crtc, "max_disp_freq",
&exynos_crtc->props.max_disp_freq, 0, UINT_MAX))
goto err_crtc;
if (decon->dqe) {
ret = exynos_drm_crtc_create_blob(crtc, "disp_dither",
&exynos_crtc->props.disp_dither);
if (ret)
goto err_crtc;
ret = exynos_drm_crtc_create_blob(crtc, "cgc_dither",
&exynos_crtc->props.cgc_dither);
if (ret)
goto err_crtc;
drm_crtc_enable_color_mgmt(crtc, DEGAMMA_LUT_SIZE, false,
REGAMMA_LUT_SIZE);
ret = exynos_drm_crtc_create_blob(crtc, "linear_matrix",
&exynos_crtc->props.linear_matrix);
if (ret)
goto err_crtc;
ret = exynos_drm_crtc_create_blob(crtc, "gamma_matrix",
&exynos_crtc->props.gamma_matrix);
if (ret)
goto err_crtc;
ret = exynos_drm_crtc_create_bool(crtc, "dqe_enabled",
&exynos_crtc->props.dqe_enabled);
if (ret)
goto err_crtc;
ret = exynos_drm_crtc_create_histogram_properties(exynos_crtc);
if (ret)
goto err_crtc;
if (decon->cgc_dma) {
if (exynos_drm_crtc_create_signed_range(crtc, "cgc_lut_fd",
&exynos_crtc->props.cgc_lut_fd, INT_MIN, INT_MAX))
goto err_crtc;
} else {
ret = exynos_drm_crtc_create_blob(crtc, "cgc_lut",
&exynos_crtc->props.cgc_lut);
}
if (ret)
goto err_crtc;
}
ret = exynos_drm_crtc_create_partial_property(exynos_crtc);
if (ret)
goto err_crtc;
if (exynos_drm_crtc_create_range(crtc, "expected_present_time",
&exynos_crtc->props.expected_present_time, 0, (uint64_t)(~((uint64_t)0))))
pr_err("create drm property expected_present_time failed\n");
return exynos_crtc;
err_crtc:
plane->funcs->destroy(plane);
kfree(exynos_crtc);
return ERR_PTR(ret);
}
uint32_t exynos_drm_get_possible_crtcs(const struct drm_encoder *encoder,
enum exynos_drm_output_type out_type)
{
const struct drm_crtc *crtc;
uint32_t possible_crtcs = 0;
drm_for_each_crtc(crtc, encoder->dev) {
if (to_exynos_crtc(crtc)->possible_type & out_type)
possible_crtcs |= drm_crtc_mask(crtc);
}
return possible_crtcs;
}
void exynos_drm_crtc_te_handler(struct drm_crtc *crtc)
{
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
if (exynos_crtc->ops->te_handler)
exynos_crtc->ops->te_handler(exynos_crtc);
}
void exynos_crtc_wait_for_flip_done(struct drm_atomic_state *old_state)
{
struct drm_crtc *crtc;
struct drm_crtc_state *old_crtc_state, *new_crtc_state;
int i;
for_each_oldnew_crtc_in_state(old_state, crtc, old_crtc_state, new_crtc_state, i) {
struct exynos_drm_crtc *exynos_crtc = to_exynos_crtc(crtc);
if (exynos_crtc->ops->wait_for_flip_done)
exynos_crtc->ops->wait_for_flip_done(exynos_crtc,
old_crtc_state, new_crtc_state);
}
}