
Expose the number of cdm blocks available through the connector capabilities. Add CDM to the topology_control table, so usermode can use the property to reserve the CDM block during modeset. Additionally, fix a error code return during CDM block reservation failure in sde resource manager. Change-Id: Ib42ca4e8614076a8e5df77d8abc77a9e73674390 Signed-off-by: Veera Sundaram Sankaran <quic_veeras@quicinc.com>
1067 righe
26 KiB
C
1067 righe
26 KiB
C
// SPDX-License-Identifier: GPL-2.0-only
|
|
/*
|
|
* Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
|
|
* Copyright (c) 2015-2021, The Linux Foundation. All rights reserved.
|
|
*/
|
|
|
|
#define pr_fmt(fmt) "[drm:%s:%d] " fmt, __func__, __LINE__
|
|
|
|
#include <drm/sde_drm.h>
|
|
#include <drm/drm_probe_helper.h>
|
|
|
|
#include "msm_kms.h"
|
|
#include "sde_kms.h"
|
|
#include "sde_wb.h"
|
|
#include "sde_formats.h"
|
|
|
|
/* maximum display mode resolution if not available from catalog */
|
|
#define SDE_WB_MODE_MAX_WIDTH 5120
|
|
#define SDE_WB_MODE_MAX_HEIGHT 5120
|
|
|
|
static const struct drm_display_mode sde_custom_wb_modes[] = {
|
|
/* 5120x2160@60Hz */
|
|
{ DRM_MODE("5120x2160", DRM_MODE_TYPE_DRIVER, 693264, 5120, 5128,
|
|
5160, 5200, 0, 2160, 2208, 2216, 2222, 0,
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) },
|
|
|
|
{ DRM_MODE("2160x5120", DRM_MODE_TYPE_DRIVER, 693264, 2160, 2208,
|
|
2216, 2222, 0, 5120, 5128, 5160, 5200, 0,
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) },
|
|
|
|
{ DRM_MODE("5120x2560", DRM_MODE_TYPE_DRIVER, 818064, 5120, 5128,
|
|
5160, 5200, 0, 2560, 2608, 2616, 2622, 0,
|
|
DRM_MODE_FLAG_PHSYNC | DRM_MODE_FLAG_NVSYNC) },
|
|
};
|
|
|
|
/* Serialization lock for sde_wb_list */
|
|
static DEFINE_MUTEX(sde_wb_list_lock);
|
|
|
|
/* List of all writeback devices installed */
|
|
static LIST_HEAD(sde_wb_list);
|
|
|
|
/**
|
|
* sde_wb_is_format_valid - check if given format/modifier is supported
|
|
* @wb_dev: Pointer to writeback device
|
|
* @pixel_format: Fourcc pixel format
|
|
* @format_modifier: Format modifier
|
|
* Returns: true if valid; false otherwise
|
|
*/
|
|
static int sde_wb_is_format_valid(struct sde_wb_device *wb_dev,
|
|
u32 pixel_format, u64 format_modifier)
|
|
{
|
|
const struct sde_format_extended *fmts = wb_dev->wb_cfg->format_list;
|
|
int i;
|
|
|
|
if (!fmts)
|
|
return false;
|
|
|
|
for (i = 0; fmts[i].fourcc_format; i++)
|
|
if ((fmts[i].modifier == format_modifier) &&
|
|
(fmts[i].fourcc_format == pixel_format))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
enum drm_connector_status
|
|
sde_wb_connector_detect(struct drm_connector *connector,
|
|
bool force,
|
|
void *display)
|
|
{
|
|
enum drm_connector_status rc = connector_status_unknown;
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
if (display)
|
|
rc = ((struct sde_wb_device *)display)->detect_status;
|
|
|
|
return rc;
|
|
}
|
|
|
|
static int sde_wb_connector_add_custom_modes(struct drm_connector *connector,
|
|
u32 hdisplay, u32 vdisplay)
|
|
{
|
|
int i, num_modes = 0;
|
|
struct drm_display_mode *mode;
|
|
struct drm_device *dev = connector->dev;
|
|
|
|
if (!hdisplay || !vdisplay)
|
|
return 0;
|
|
|
|
if (hdisplay > SDE_WB_MODE_MAX_WIDTH)
|
|
hdisplay = SDE_WB_MODE_MAX_WIDTH;
|
|
if (vdisplay > SDE_WB_MODE_MAX_HEIGHT)
|
|
vdisplay = SDE_WB_MODE_MAX_HEIGHT;
|
|
|
|
for (i = 0; i < ARRAY_SIZE(sde_custom_wb_modes); i++) {
|
|
const struct drm_display_mode *ptr = &sde_custom_wb_modes[i];
|
|
|
|
if (ptr->hdisplay > hdisplay || ptr->vdisplay > vdisplay)
|
|
continue;
|
|
|
|
mode = drm_mode_duplicate(dev, ptr);
|
|
if (mode) {
|
|
drm_mode_probed_add(connector, mode);
|
|
num_modes++;
|
|
}
|
|
}
|
|
|
|
return num_modes;
|
|
}
|
|
|
|
int sde_wb_connector_get_modes(struct drm_connector *connector, void *display,
|
|
const struct msm_resource_caps_info *avail_res)
|
|
{
|
|
struct sde_wb_device *wb_dev;
|
|
int num_modes = 0;
|
|
|
|
if (!connector || !display)
|
|
return 0;
|
|
|
|
wb_dev = display;
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
mutex_lock(&wb_dev->wb_lock);
|
|
if (wb_dev->count_modes && wb_dev->modes) {
|
|
struct drm_display_mode *mode;
|
|
int i, ret;
|
|
|
|
for (i = 0; i < wb_dev->count_modes; i++) {
|
|
mode = drm_mode_create(connector->dev);
|
|
if (!mode) {
|
|
SDE_ERROR("failed to create mode\n");
|
|
break;
|
|
}
|
|
ret = drm_mode_convert_umode(wb_dev->drm_dev, mode,
|
|
&wb_dev->modes[i]);
|
|
if (ret) {
|
|
SDE_ERROR("failed to convert mode %d\n", ret);
|
|
break;
|
|
}
|
|
|
|
drm_mode_probed_add(connector, mode);
|
|
num_modes++;
|
|
}
|
|
} else {
|
|
u32 max_width = SDE_WB_MODE_MAX_WIDTH;
|
|
|
|
if (wb_dev->wb_cfg && wb_dev->wb_cfg->sblk)
|
|
max_width = max(wb_dev->wb_cfg->sblk->maxlinewidth,
|
|
wb_dev->wb_cfg->sblk->maxlinewidth_linear);
|
|
|
|
num_modes = drm_add_modes_noedid(connector, max_width,
|
|
SDE_WB_MODE_MAX_HEIGHT);
|
|
|
|
num_modes += sde_wb_connector_add_custom_modes(connector, max_width,
|
|
SDE_WB_MODE_MAX_HEIGHT);
|
|
}
|
|
mutex_unlock(&wb_dev->wb_lock);
|
|
return num_modes;
|
|
}
|
|
|
|
struct drm_framebuffer *
|
|
sde_wb_connector_state_get_output_fb(struct drm_connector_state *state)
|
|
{
|
|
if (!state || !state->connector ||
|
|
(state->connector->connector_type !=
|
|
DRM_MODE_CONNECTOR_VIRTUAL)) {
|
|
SDE_ERROR("invalid params\n");
|
|
return NULL;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
return sde_connector_get_out_fb(state);
|
|
}
|
|
|
|
int sde_wb_connector_state_get_output_roi(struct drm_connector_state *state,
|
|
struct sde_rect *roi)
|
|
{
|
|
if (!state || !roi || !state->connector ||
|
|
(state->connector->connector_type !=
|
|
DRM_MODE_CONNECTOR_VIRTUAL)) {
|
|
SDE_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
roi->x = sde_connector_get_property(state, CONNECTOR_PROP_DST_X);
|
|
roi->y = sde_connector_get_property(state, CONNECTOR_PROP_DST_Y);
|
|
roi->w = sde_connector_get_property(state, CONNECTOR_PROP_DST_W);
|
|
roi->h = sde_connector_get_property(state, CONNECTOR_PROP_DST_H);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* sde_wb_connector_set_modes - set writeback modes and connection status
|
|
* @wb_dev: Pointer to write back device
|
|
* @count_modes: Count of modes
|
|
* @modes: Pointer to writeback mode requested
|
|
* @connected: Connection status requested
|
|
* Returns: 0 if success; error code otherwise
|
|
*/
|
|
static
|
|
int sde_wb_connector_set_modes(struct sde_wb_device *wb_dev,
|
|
u32 count_modes, struct drm_mode_modeinfo __user *modes,
|
|
bool connected)
|
|
{
|
|
struct drm_mode_modeinfo *modeinfo = NULL;
|
|
int ret = 0;
|
|
int i;
|
|
|
|
if (!wb_dev || !wb_dev->connector ||
|
|
(wb_dev->connector->connector_type !=
|
|
DRM_MODE_CONNECTOR_VIRTUAL)) {
|
|
SDE_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
if (connected) {
|
|
SDE_DEBUG("connect\n");
|
|
|
|
if (!count_modes || !modes) {
|
|
SDE_ERROR("invalid count_modes :%u and modes :%d\n",
|
|
count_modes, !modes);
|
|
return -EINVAL;
|
|
}
|
|
|
|
modeinfo = kcalloc(count_modes,
|
|
sizeof(struct drm_mode_modeinfo),
|
|
GFP_KERNEL);
|
|
if (!modeinfo) {
|
|
SDE_ERROR("invalid params\n");
|
|
ret = -ENOMEM;
|
|
goto error;
|
|
}
|
|
|
|
if (copy_from_user(modeinfo, modes,
|
|
count_modes *
|
|
sizeof(struct drm_mode_modeinfo))) {
|
|
SDE_ERROR("failed to copy modes\n");
|
|
kfree(modeinfo);
|
|
ret = -EFAULT;
|
|
goto error;
|
|
}
|
|
|
|
for (i = 0; i < count_modes; i++) {
|
|
struct drm_display_mode dispmode;
|
|
|
|
memset(&dispmode, 0, sizeof(dispmode));
|
|
ret = drm_mode_convert_umode(wb_dev->drm_dev,
|
|
&dispmode, &modeinfo[i]);
|
|
if (ret) {
|
|
SDE_ERROR(
|
|
"failed to convert mode %d:\"%s\" %d %d %d %d %d %d %d %d %d %d 0x%x 0x%x status:%d rc:%d\n",
|
|
i,
|
|
modeinfo[i].name,
|
|
modeinfo[i].vrefresh,
|
|
modeinfo[i].clock,
|
|
modeinfo[i].hdisplay,
|
|
modeinfo[i].hsync_start,
|
|
modeinfo[i].hsync_end,
|
|
modeinfo[i].htotal,
|
|
modeinfo[i].vdisplay,
|
|
modeinfo[i].vsync_start,
|
|
modeinfo[i].vsync_end,
|
|
modeinfo[i].vtotal,
|
|
modeinfo[i].type,
|
|
modeinfo[i].flags,
|
|
dispmode.status,
|
|
ret);
|
|
kfree(modeinfo);
|
|
goto error;
|
|
}
|
|
}
|
|
|
|
if (wb_dev->modes) {
|
|
wb_dev->count_modes = 0;
|
|
|
|
kfree(wb_dev->modes);
|
|
wb_dev->modes = NULL;
|
|
}
|
|
|
|
wb_dev->count_modes = count_modes;
|
|
wb_dev->modes = modeinfo;
|
|
wb_dev->detect_status = connector_status_connected;
|
|
} else {
|
|
SDE_DEBUG("disconnect\n");
|
|
|
|
if (wb_dev->modes) {
|
|
wb_dev->count_modes = 0;
|
|
|
|
kfree(wb_dev->modes);
|
|
wb_dev->modes = NULL;
|
|
}
|
|
|
|
wb_dev->detect_status = connector_status_disconnected;
|
|
}
|
|
|
|
error:
|
|
return ret;
|
|
}
|
|
|
|
static void _sde_wb_connector_clear_dnsc_blur(struct drm_connector_state *state)
|
|
{
|
|
struct sde_connector_state *cstate = to_sde_connector_state(state);
|
|
int i;
|
|
|
|
for (i = 0; i < cstate->dnsc_blur_count; i++)
|
|
memset(&cstate->dnsc_blur_cfg[i], 0, sizeof(struct sde_drm_dnsc_blur_cfg));
|
|
cstate->dnsc_blur_count = 0;
|
|
}
|
|
|
|
static int _sde_wb_connector_set_dnsc_blur(struct sde_wb_device *wb_dev,
|
|
struct drm_connector_state *state, void __user *usr_ptr)
|
|
{
|
|
struct sde_connector_state *cstate = to_sde_connector_state(state);
|
|
struct sde_kms *sde_kms = sde_connector_get_kms(wb_dev->connector);
|
|
struct sde_drm_dnsc_blur_cfg *dnsc_blur_cfg = &cstate->dnsc_blur_cfg[0];
|
|
u32 copy_count;
|
|
int ret = 0, i;
|
|
|
|
if (!sde_kms || !sde_kms->catalog)
|
|
return -EINVAL;
|
|
|
|
if (!usr_ptr)
|
|
goto disable;
|
|
|
|
/* copy only the first block */
|
|
if (copy_from_user(dnsc_blur_cfg, usr_ptr, sizeof(struct sde_drm_dnsc_blur_cfg))) {
|
|
SDE_ERROR("failed to copy dnsc_blur block 0 data\n");
|
|
ret = -EINVAL;
|
|
goto disable;
|
|
}
|
|
|
|
if (dnsc_blur_cfg->num_blocks > sde_kms->catalog->dnsc_blur_count) {
|
|
SDE_ERROR("invalid number of dnsc_blur blocks:%d\n", dnsc_blur_cfg->num_blocks);
|
|
ret = -EINVAL;
|
|
goto disable;
|
|
}
|
|
|
|
/* no further data required */
|
|
if (dnsc_blur_cfg->num_blocks <= 1)
|
|
goto end;
|
|
|
|
dnsc_blur_cfg++;
|
|
usr_ptr += sizeof(struct sde_drm_dnsc_blur_cfg);
|
|
copy_count = dnsc_blur_cfg->num_blocks - 1;
|
|
|
|
/* copy rest of the blocks */
|
|
if ((dnsc_blur_cfg->flags & DNSC_BLUR_INDEPENDENT_BLK_CFG)) {
|
|
if (copy_from_user(dnsc_blur_cfg, usr_ptr,
|
|
copy_count * sizeof(struct sde_drm_dnsc_blur_cfg))) {
|
|
SDE_ERROR("failed to copy dnsc_blur data\n");
|
|
ret = -EINVAL;
|
|
goto disable;
|
|
}
|
|
|
|
/* duplicate rest of the blocks */
|
|
} else if (dnsc_blur_cfg->flags & DNSC_BLUR_MIRROR_BLK_CFG) {
|
|
for (i = 0; i < copy_count; i++) {
|
|
memcpy(dnsc_blur_cfg, &cstate->dnsc_blur_cfg[0],
|
|
sizeof(struct sde_drm_dnsc_blur_cfg));
|
|
dnsc_blur_cfg++;
|
|
}
|
|
}
|
|
|
|
end:
|
|
cstate->dnsc_blur_count = dnsc_blur_cfg->num_blocks;
|
|
return 0;
|
|
|
|
disable:
|
|
_sde_wb_connector_clear_dnsc_blur(state);
|
|
return ret;
|
|
}
|
|
|
|
static int _sde_wb_connector_set_out_fb(struct sde_wb_device *wb_dev,
|
|
struct drm_connector_state *state)
|
|
{
|
|
struct drm_framebuffer *out_fb;
|
|
const struct sde_format *sde_format;
|
|
int rc = 0;
|
|
|
|
out_fb = sde_connector_get_out_fb(state);
|
|
if (!out_fb)
|
|
goto end;
|
|
|
|
sde_format = sde_get_sde_format_ext(out_fb->format->format, out_fb->modifier);
|
|
if (!sde_format) {
|
|
SDE_ERROR("failed to get sde format\n");
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
if (!sde_wb_is_format_valid(wb_dev, out_fb->format->format, out_fb->modifier)) {
|
|
SDE_ERROR("unsupported writeback format 0x%x/0x%llx\n",
|
|
out_fb->format->format, out_fb->modifier);
|
|
rc = -EINVAL;
|
|
goto end;
|
|
}
|
|
|
|
end:
|
|
return rc;
|
|
}
|
|
|
|
int sde_wb_connector_set_property(struct drm_connector *connector,
|
|
struct drm_connector_state *state, int idx, uint64_t value, void *display)
|
|
{
|
|
struct sde_wb_device *wb_dev = display;
|
|
int rc = 0;
|
|
|
|
if (!connector || !state || !wb_dev) {
|
|
SDE_ERROR("invalid argument(s)\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
switch (idx) {
|
|
case CONNECTOR_PROP_OUT_FB:
|
|
rc = _sde_wb_connector_set_out_fb(wb_dev, state);
|
|
break;
|
|
case CONNECTOR_PROP_DNSC_BLUR:
|
|
rc = _sde_wb_connector_set_dnsc_blur(wb_dev, state,
|
|
(void __user *)(uintptr_t)value);
|
|
break;
|
|
default:
|
|
/* nothing to do */
|
|
break;
|
|
}
|
|
|
|
return rc;
|
|
}
|
|
|
|
int sde_wb_get_info(struct drm_connector *connector,
|
|
struct msm_display_info *info, void *display)
|
|
{
|
|
struct sde_wb_device *wb_dev = display;
|
|
u32 max_width = SDE_WB_MODE_MAX_WIDTH;
|
|
|
|
if (!info || !wb_dev) {
|
|
pr_err("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
if (wb_dev->wb_cfg && wb_dev->wb_cfg->sblk)
|
|
max_width = max(wb_dev->wb_cfg->sblk->maxlinewidth,
|
|
wb_dev->wb_cfg->sblk->maxlinewidth_linear);
|
|
|
|
memset(info, 0, sizeof(struct msm_display_info));
|
|
info->intf_type = DRM_MODE_CONNECTOR_VIRTUAL;
|
|
info->num_of_h_tiles = 1;
|
|
info->h_tile_instance[0] = sde_wb_get_index(display);
|
|
info->is_connected = true;
|
|
info->capabilities = MSM_DISPLAY_CAP_HOT_PLUG | MSM_DISPLAY_CAP_EDID;
|
|
info->max_width = max_width;
|
|
info->max_height = SDE_WB_MODE_MAX_HEIGHT;
|
|
return 0;
|
|
}
|
|
|
|
int sde_wb_get_mode_info(struct drm_connector *connector,
|
|
const struct drm_display_mode *drm_mode,
|
|
struct msm_sub_mode *sub_mode,
|
|
struct msm_mode_info *mode_info,
|
|
void *display, const struct msm_resource_caps_info *avail_res)
|
|
{
|
|
const u32 dual_lm = 2;
|
|
const u32 single_lm = 1;
|
|
const u32 single_intf = 1;
|
|
const u32 no_enc = 0;
|
|
struct msm_display_topology *topology;
|
|
struct sde_wb_device *wb_dev = display;
|
|
u16 hdisplay;
|
|
int i;
|
|
|
|
if (!drm_mode || !mode_info || !avail_res ||
|
|
!avail_res->max_mixer_width || !display) {
|
|
pr_err("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
hdisplay = drm_mode->hdisplay;
|
|
|
|
/* find maximum display width to support */
|
|
for (i = 0; i < wb_dev->count_modes; i++)
|
|
hdisplay = max(hdisplay, wb_dev->modes[i].hdisplay);
|
|
|
|
topology = &mode_info->topology;
|
|
topology->num_lm = (avail_res->max_mixer_width <= hdisplay) ?
|
|
dual_lm : single_lm;
|
|
topology->num_enc = no_enc;
|
|
topology->num_intf = single_intf;
|
|
|
|
mode_info->comp_info.comp_type = MSM_DISPLAY_COMPRESSION_NONE;
|
|
mode_info->wide_bus_en = false;
|
|
mode_info->comp_info.comp_ratio = MSM_DISPLAY_COMPRESSION_RATIO_NONE;
|
|
|
|
return 0;
|
|
}
|
|
|
|
int sde_wb_connector_set_info_blob(struct drm_connector *connector,
|
|
void *info, void *display, struct msm_mode_info *mode_info)
|
|
{
|
|
struct sde_wb_device *wb_dev = display;
|
|
const struct sde_format_extended *format_list;
|
|
struct sde_kms *sde_kms;
|
|
struct sde_mdss_cfg *catalog;
|
|
int i;
|
|
|
|
if (!connector || !info || !display || !wb_dev->wb_cfg) {
|
|
SDE_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
sde_kms = sde_connector_get_kms(connector);
|
|
if (!sde_kms)
|
|
return -EINVAL;
|
|
catalog = sde_kms->catalog;
|
|
|
|
format_list = wb_dev->wb_cfg->format_list;
|
|
|
|
/* Populate info buffer */
|
|
if (format_list) {
|
|
sde_kms_info_start(info, "pixel_formats");
|
|
while (format_list->fourcc_format) {
|
|
sde_kms_info_append_format(info, format_list->fourcc_format,
|
|
format_list->modifier);
|
|
++format_list;
|
|
}
|
|
sde_kms_info_stop(info);
|
|
}
|
|
|
|
sde_kms_info_add_keyint(info, "wb_intf_index", wb_dev->wb_idx - WB_0);
|
|
sde_kms_info_add_keyint(info, "maxlinewidth", wb_dev->wb_cfg->sblk->maxlinewidth);
|
|
sde_kms_info_add_keyint(info, "maxlinewidth_linear",
|
|
wb_dev->wb_cfg->sblk->maxlinewidth_linear);
|
|
|
|
sde_kms_info_start(info, "features");
|
|
if (wb_dev->wb_cfg && (wb_dev->wb_cfg->features & BIT(SDE_WB_UBWC)))
|
|
sde_kms_info_append(info, "wb_ubwc");
|
|
sde_kms_info_stop(info);
|
|
|
|
sde_kms_info_add_keyint(info, "has_cwb_dither", test_bit(SDE_FEATURE_CWB_DITHER,
|
|
catalog->features));
|
|
|
|
if (catalog->cdm_count)
|
|
sde_kms_info_add_keyint(info, "cdm_count", catalog->cdm_count);
|
|
|
|
if (catalog->dnsc_blur_count && catalog->dnsc_blur_filters) {
|
|
sde_kms_info_add_keyint(info, "dnsc_blur_count", catalog->dnsc_blur_count);
|
|
|
|
sde_kms_info_start(info, "dnsc_blur_info");
|
|
for (i = 0; i < catalog->dnsc_blur_filter_count; i++)
|
|
sde_kms_info_append_dnsc_blur_filter_info(info,
|
|
&catalog->dnsc_blur_filters[i]);
|
|
sde_kms_info_stop(info);
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
static void _sde_wb_connector_install_dither_property(struct sde_wb_device *wb_dev)
|
|
{
|
|
struct sde_connector *c_conn = to_sde_connector(wb_dev->connector);
|
|
struct sde_kms *sde_kms = sde_connector_get_kms(wb_dev->connector);
|
|
struct sde_mdss_cfg *catalog;
|
|
char prop_name[DRM_PROP_NAME_LEN];
|
|
u32 version = 0;
|
|
|
|
if (!sde_kms || !sde_kms->catalog)
|
|
return;
|
|
catalog = sde_kms->catalog;
|
|
|
|
if (!test_bit(SDE_FEATURE_CWB_DITHER, catalog->features))
|
|
return;
|
|
|
|
version = SDE_COLOR_PROCESS_MAJOR(catalog->pingpong[0].sblk->dither.version);
|
|
snprintf(prop_name, ARRAY_SIZE(prop_name), "%s%d", "SDE_PP_CWB_DITHER_V", version);
|
|
switch (version) {
|
|
case 2:
|
|
msm_property_install_blob(&c_conn->property_info, prop_name,
|
|
DRM_MODE_PROP_BLOB, CONNECTOR_PROP_PP_CWB_DITHER);
|
|
break;
|
|
default:
|
|
SDE_ERROR("unsupported cwb dither version %d\n", version);
|
|
return;
|
|
}
|
|
}
|
|
|
|
int sde_wb_connector_post_init(struct drm_connector *connector, void *display)
|
|
{
|
|
struct sde_connector *c_conn;
|
|
struct sde_wb_device *wb_dev = display;
|
|
struct msm_drm_private *priv;
|
|
struct sde_kms *sde_kms;
|
|
struct sde_mdss_cfg *catalog;
|
|
static const struct drm_prop_enum_list e_fb_translation_mode[] = {
|
|
{SDE_DRM_FB_NON_SEC, "non_sec"},
|
|
{SDE_DRM_FB_SEC, "sec"},
|
|
};
|
|
static const struct drm_prop_enum_list e_cache_state[] = {
|
|
{CACHE_STATE_DISABLED, "cache_state_disabled"},
|
|
{CACHE_STATE_ENABLED, "cache_state_enabled"},
|
|
};
|
|
|
|
static const struct drm_prop_enum_list e_wb_usage_type[] = {
|
|
{WB_USAGE_WFD, "wb_usage_wfd"},
|
|
{WB_USAGE_CWB, "wb_usage_cwb"},
|
|
{WB_USAGE_OFFLINE_WB, "wb_usage_offline_wb"},
|
|
};
|
|
|
|
if (!connector || !display || !wb_dev->wb_cfg || !wb_dev->drm_dev->dev_private) {
|
|
SDE_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
priv = wb_dev->drm_dev->dev_private;
|
|
sde_kms = to_sde_kms(priv->kms);
|
|
if (!sde_kms || !sde_kms->catalog) {
|
|
SDE_ERROR("invalid sde_kms\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
catalog = sde_kms->catalog;
|
|
c_conn = to_sde_connector(connector);
|
|
wb_dev->connector = connector;
|
|
wb_dev->detect_status = connector_status_connected;
|
|
|
|
if (test_bit(SDE_SYS_CACHE_DISP, catalog->sde_sys_cache_type_map)
|
|
|| test_bit(SDE_SYS_CACHE_DISP_WB, catalog->sde_sys_cache_type_map))
|
|
msm_property_install_enum(&c_conn->property_info, "cache_state",
|
|
0x0, 0, e_cache_state, ARRAY_SIZE(e_cache_state),
|
|
0, CONNECTOR_PROP_CACHE_STATE);
|
|
|
|
/*
|
|
* Add extra connector properties
|
|
*/
|
|
msm_property_install_range(&c_conn->property_info, "FB_ID",
|
|
0x0, 0, ~0, 0, CONNECTOR_PROP_OUT_FB);
|
|
msm_property_install_range(&c_conn->property_info, "DST_X",
|
|
0x0, 0, UINT_MAX, 0, CONNECTOR_PROP_DST_X);
|
|
msm_property_install_range(&c_conn->property_info, "DST_Y",
|
|
0x0, 0, UINT_MAX, 0, CONNECTOR_PROP_DST_Y);
|
|
msm_property_install_range(&c_conn->property_info, "DST_W",
|
|
0x0, 0, UINT_MAX, 0, CONNECTOR_PROP_DST_W);
|
|
msm_property_install_range(&c_conn->property_info, "DST_H",
|
|
0x0, 0, UINT_MAX, 0, CONNECTOR_PROP_DST_H);
|
|
msm_property_install_enum(&c_conn->property_info,
|
|
"fb_translation_mode",
|
|
0x0,
|
|
0, e_fb_translation_mode,
|
|
ARRAY_SIZE(e_fb_translation_mode), 0,
|
|
CONNECTOR_PROP_FB_TRANSLATION_MODE);
|
|
|
|
if (wb_dev->wb_cfg->features & BIT(SDE_WB_PROG_LINE))
|
|
msm_property_install_range(&c_conn->property_info, "early_fence_line",
|
|
0x0, 0, UINT_MAX, 0, CONNECTOR_PROP_EARLY_FENCE_LINE);
|
|
|
|
if (catalog->dnsc_blur_count && catalog->dnsc_blur_filters)
|
|
msm_property_install_range(&c_conn->property_info, "dnsc_blur",
|
|
0x0, 0, ~0, 0, CONNECTOR_PROP_DNSC_BLUR);
|
|
|
|
msm_property_install_enum(&c_conn->property_info, "wb_usage_type",
|
|
0x0, 0, e_wb_usage_type, ARRAY_SIZE(e_wb_usage_type),
|
|
0, CONNECTOR_PROP_WB_USAGE_TYPE);
|
|
|
|
_sde_wb_connector_install_dither_property(wb_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
struct drm_framebuffer *sde_wb_get_output_fb(struct sde_wb_device *wb_dev)
|
|
{
|
|
struct drm_framebuffer *fb;
|
|
|
|
if (!wb_dev || !wb_dev->connector) {
|
|
SDE_ERROR("invalid params\n");
|
|
return NULL;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
mutex_lock(&wb_dev->wb_lock);
|
|
fb = sde_wb_connector_state_get_output_fb(wb_dev->connector->state);
|
|
mutex_unlock(&wb_dev->wb_lock);
|
|
|
|
return fb;
|
|
}
|
|
|
|
int sde_wb_get_output_roi(struct sde_wb_device *wb_dev, struct sde_rect *roi)
|
|
{
|
|
int rc;
|
|
|
|
if (!wb_dev || !wb_dev->connector || !roi) {
|
|
SDE_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
mutex_lock(&wb_dev->wb_lock);
|
|
rc = sde_wb_connector_state_get_output_roi(
|
|
wb_dev->connector->state, roi);
|
|
mutex_unlock(&wb_dev->wb_lock);
|
|
|
|
return rc;
|
|
}
|
|
|
|
u32 sde_wb_get_num_of_displays(void)
|
|
{
|
|
u32 count = 0;
|
|
struct sde_wb_device *wb_dev;
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
mutex_lock(&sde_wb_list_lock);
|
|
list_for_each_entry(wb_dev, &sde_wb_list, wb_list) {
|
|
count++;
|
|
}
|
|
mutex_unlock(&sde_wb_list_lock);
|
|
|
|
return count;
|
|
}
|
|
|
|
int wb_display_get_displays(void **display_array, u32 max_display_count)
|
|
{
|
|
struct sde_wb_device *curr;
|
|
int i = 0;
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
if (!display_array || !max_display_count) {
|
|
if (!display_array)
|
|
SDE_ERROR("invalid param\n");
|
|
return 0;
|
|
}
|
|
|
|
mutex_lock(&sde_wb_list_lock);
|
|
list_for_each_entry(curr, &sde_wb_list, wb_list) {
|
|
if (i >= max_display_count)
|
|
break;
|
|
display_array[i++] = curr;
|
|
}
|
|
mutex_unlock(&sde_wb_list_lock);
|
|
|
|
return i;
|
|
}
|
|
|
|
int sde_wb_config(struct drm_device *drm_dev, void *data,
|
|
struct drm_file *file_priv)
|
|
{
|
|
struct sde_drm_wb_cfg *config = data;
|
|
struct msm_drm_private *priv;
|
|
struct sde_wb_device *wb_dev = NULL;
|
|
struct sde_wb_device *curr;
|
|
struct drm_connector *connector;
|
|
uint32_t flags;
|
|
uint32_t connector_id;
|
|
uint32_t count_modes;
|
|
uint64_t modes;
|
|
int rc;
|
|
|
|
if (!drm_dev || !data) {
|
|
SDE_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
flags = config->flags;
|
|
connector_id = config->connector_id;
|
|
count_modes = config->count_modes;
|
|
modes = config->modes;
|
|
|
|
priv = drm_dev->dev_private;
|
|
|
|
connector = drm_connector_lookup(drm_dev, file_priv, connector_id);
|
|
if (!connector) {
|
|
SDE_ERROR("failed to find connector\n");
|
|
rc = -ENOENT;
|
|
goto fail;
|
|
}
|
|
|
|
mutex_lock(&sde_wb_list_lock);
|
|
list_for_each_entry(curr, &sde_wb_list, wb_list) {
|
|
if (curr->connector == connector) {
|
|
wb_dev = curr;
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&sde_wb_list_lock);
|
|
|
|
if (!wb_dev) {
|
|
SDE_ERROR("failed to find wb device\n");
|
|
rc = -ENOENT;
|
|
goto fail;
|
|
}
|
|
|
|
mutex_lock(&wb_dev->wb_lock);
|
|
|
|
rc = sde_wb_connector_set_modes(wb_dev, count_modes,
|
|
(struct drm_mode_modeinfo __user *) (uintptr_t) modes,
|
|
(flags & SDE_DRM_WB_CFG_FLAGS_CONNECTED) ? true : false);
|
|
|
|
mutex_unlock(&wb_dev->wb_lock);
|
|
drm_helper_hpd_irq_event(drm_dev);
|
|
fail:
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* _sde_wb_dev_init - perform device initialization
|
|
* @wb_dev: Pointer to writeback device
|
|
*/
|
|
static int _sde_wb_dev_init(struct sde_wb_device *wb_dev)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!wb_dev) {
|
|
SDE_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* _sde_wb_dev_deinit - perform device de-initialization
|
|
* @wb_dev: Pointer to writeback device
|
|
*/
|
|
static int _sde_wb_dev_deinit(struct sde_wb_device *wb_dev)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!wb_dev) {
|
|
SDE_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* sde_wb_bind - bind writeback device with controlling device
|
|
* @dev: Pointer to base of platform device
|
|
* @master: Pointer to container of drm device
|
|
* @data: Pointer to private data
|
|
* Returns: Zero on success
|
|
*/
|
|
static int sde_wb_bind(struct device *dev, struct device *master, void *data)
|
|
{
|
|
struct sde_wb_device *wb_dev;
|
|
|
|
if (!dev || !master) {
|
|
SDE_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
wb_dev = platform_get_drvdata(to_platform_device(dev));
|
|
if (!wb_dev) {
|
|
SDE_ERROR("invalid wb device\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
mutex_lock(&wb_dev->wb_lock);
|
|
wb_dev->drm_dev = dev_get_drvdata(master);
|
|
mutex_unlock(&wb_dev->wb_lock);
|
|
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* sde_wb_unbind - unbind writeback from controlling device
|
|
* @dev: Pointer to base of platform device
|
|
* @master: Pointer to container of drm device
|
|
* @data: Pointer to private data
|
|
*/
|
|
static void sde_wb_unbind(struct device *dev,
|
|
struct device *master, void *data)
|
|
{
|
|
struct sde_wb_device *wb_dev;
|
|
|
|
if (!dev) {
|
|
SDE_ERROR("invalid params\n");
|
|
return;
|
|
}
|
|
|
|
wb_dev = platform_get_drvdata(to_platform_device(dev));
|
|
if (!wb_dev) {
|
|
SDE_ERROR("invalid wb device\n");
|
|
return;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
mutex_lock(&wb_dev->wb_lock);
|
|
wb_dev->drm_dev = NULL;
|
|
mutex_unlock(&wb_dev->wb_lock);
|
|
}
|
|
|
|
static const struct component_ops sde_wb_comp_ops = {
|
|
.bind = sde_wb_bind,
|
|
.unbind = sde_wb_unbind,
|
|
};
|
|
|
|
/**
|
|
* sde_wb_drm_init - perform DRM initialization
|
|
* @wb_dev: Pointer to writeback device
|
|
* @encoder: Pointer to associated encoder
|
|
*/
|
|
int sde_wb_drm_init(struct sde_wb_device *wb_dev, struct drm_encoder *encoder)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!wb_dev || !wb_dev->drm_dev || !encoder) {
|
|
SDE_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
mutex_lock(&wb_dev->wb_lock);
|
|
|
|
if (wb_dev->drm_dev->dev_private) {
|
|
struct msm_drm_private *priv = wb_dev->drm_dev->dev_private;
|
|
struct sde_kms *sde_kms = to_sde_kms(priv->kms);
|
|
|
|
if (wb_dev->index < sde_kms->catalog->wb_count) {
|
|
wb_dev->wb_idx = sde_kms->catalog->wb[wb_dev->index].id;
|
|
wb_dev->wb_cfg = &sde_kms->catalog->wb[wb_dev->index];
|
|
}
|
|
}
|
|
|
|
wb_dev->drm_dev = encoder->dev;
|
|
wb_dev->encoder = encoder;
|
|
mutex_unlock(&wb_dev->wb_lock);
|
|
return rc;
|
|
}
|
|
|
|
int sde_wb_drm_deinit(struct sde_wb_device *wb_dev)
|
|
{
|
|
int rc = 0;
|
|
|
|
if (!wb_dev) {
|
|
SDE_ERROR("invalid params\n");
|
|
return -EINVAL;
|
|
}
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
return rc;
|
|
}
|
|
|
|
/**
|
|
* sde_wb_probe - load writeback module
|
|
* @pdev: Pointer to platform device
|
|
*/
|
|
static int sde_wb_probe(struct platform_device *pdev)
|
|
{
|
|
struct sde_wb_device *wb_dev;
|
|
int ret;
|
|
|
|
wb_dev = devm_kzalloc(&pdev->dev, sizeof(*wb_dev), GFP_KERNEL);
|
|
if (!wb_dev)
|
|
return -ENOMEM;
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
ret = of_property_read_u32(pdev->dev.of_node, "cell-index",
|
|
&wb_dev->index);
|
|
if (ret) {
|
|
SDE_DEBUG("cell index not set, default to 0\n");
|
|
wb_dev->index = 0;
|
|
}
|
|
|
|
wb_dev->name = of_get_property(pdev->dev.of_node, "label", NULL);
|
|
if (!wb_dev->name) {
|
|
SDE_DEBUG("label not set, default to unknown\n");
|
|
wb_dev->name = "unknown";
|
|
}
|
|
|
|
wb_dev->wb_idx = SDE_NONE;
|
|
|
|
mutex_init(&wb_dev->wb_lock);
|
|
platform_set_drvdata(pdev, wb_dev);
|
|
|
|
mutex_lock(&sde_wb_list_lock);
|
|
list_add_tail(&wb_dev->wb_list, &sde_wb_list);
|
|
mutex_unlock(&sde_wb_list_lock);
|
|
|
|
if (!_sde_wb_dev_init(wb_dev)) {
|
|
ret = component_add(&pdev->dev, &sde_wb_comp_ops);
|
|
if (ret)
|
|
pr_err("component add failed\n");
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/**
|
|
* sde_wb_remove - unload writeback module
|
|
* @pdev: Pointer to platform device
|
|
*/
|
|
static int sde_wb_remove(struct platform_device *pdev)
|
|
{
|
|
struct sde_wb_device *wb_dev;
|
|
struct sde_wb_device *curr, *next;
|
|
|
|
wb_dev = platform_get_drvdata(pdev);
|
|
if (!wb_dev)
|
|
return 0;
|
|
|
|
SDE_DEBUG("\n");
|
|
|
|
(void)_sde_wb_dev_deinit(wb_dev);
|
|
|
|
mutex_lock(&sde_wb_list_lock);
|
|
list_for_each_entry_safe(curr, next, &sde_wb_list, wb_list) {
|
|
if (curr == wb_dev) {
|
|
list_del(&wb_dev->wb_list);
|
|
break;
|
|
}
|
|
}
|
|
mutex_unlock(&sde_wb_list_lock);
|
|
|
|
kfree(wb_dev->modes);
|
|
mutex_destroy(&wb_dev->wb_lock);
|
|
|
|
platform_set_drvdata(pdev, NULL);
|
|
devm_kfree(&pdev->dev, wb_dev);
|
|
|
|
return 0;
|
|
}
|
|
|
|
static const struct of_device_id dt_match[] = {
|
|
{ .compatible = "qcom,wb-display"},
|
|
{}
|
|
};
|
|
|
|
static struct platform_driver sde_wb_driver = {
|
|
.probe = sde_wb_probe,
|
|
.remove = sde_wb_remove,
|
|
.driver = {
|
|
.name = "sde_wb",
|
|
.of_match_table = dt_match,
|
|
.suppress_bind_attrs = true,
|
|
},
|
|
};
|
|
|
|
void __init sde_wb_register(void)
|
|
{
|
|
platform_driver_register(&sde_wb_driver);
|
|
}
|
|
|
|
void __exit sde_wb_unregister(void)
|
|
{
|
|
platform_driver_unregister(&sde_wb_driver);
|
|
}
|