Files
android_kernel_samsung_sm86…/msm/sde/sde_encoder_phys_wb.c
Mahadevan d8db76b3ff disp: msm: sde: clear wb mode and cached cwb encoder mask
The issue scenario is as follows
1. A CWB commit has run and it has disabled. Composer kill is
   done.
2. If Composer starts again or another client has open DRM
   the previous cwb state is intact.
3. When userspace is trying to query wb modes, primary modes
   which are attached to wb as part of cwb commit is exposed.

This leads to commit failures if userspace is trying to trigger wb
on the same CRTC of what primary has run cwb before. This change
properly clears wb mode and the cached encoder mask to avoid commit
failures.

Change-Id: I4ca8bd2b52a980630b7fb1319bf67b718ebb2ac2
Signed-off-by: Mahadevan <quic_mahap@quicinc.com>
2024-01-17 20:35:46 +05:30

2903 行
94 KiB
C

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2021-2024 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 <linux/debugfs.h>
#include <drm/sde_drm.h>
#include "sde_encoder_phys.h"
#include "sde_formats.h"
#include "sde_hw_top.h"
#include "sde_hw_interrupts.h"
#include "sde_core_irq.h"
#include "sde_wb.h"
#include "sde_vbif.h"
#include "sde_crtc.h"
#include "sde_hw_dnsc_blur.h"
#include "sde_trace.h"
#define to_sde_encoder_phys_wb(x) \
container_of(x, struct sde_encoder_phys_wb, base)
#define WBID(wb_enc) \
((wb_enc && wb_enc->wb_dev) ? wb_enc->wb_dev->wb_idx - WB_0 : -1)
#define TO_S15D16(_x_) ((_x_) << 7)
#define SDE_WB_MAX_LINEWIDTH(fmt, wb_cfg) \
((SDE_FORMAT_IS_UBWC(fmt) || SDE_FORMAT_IS_YUV(fmt)) ? wb_cfg->sblk->maxlinewidth : \
wb_cfg->sblk->maxlinewidth_linear)
/* a5x mini-tile width and height */
#define MINI_TILE_W 4
#define MINI_TILE_H 4
#define SDE_WB_ROT_MAX_SRCW 4096
#define SDE_WB_ROT_MAX_SRCH 4096
static const u32 cwb_irq_tbl[PINGPONG_MAX] = {SDE_NONE, INTR_IDX_PP1_OVFL,
INTR_IDX_PP2_OVFL, INTR_IDX_PP3_OVFL, INTR_IDX_PP4_OVFL,
INTR_IDX_PP5_OVFL, SDE_NONE, SDE_NONE};
static const u32 dcwb_irq_tbl[PINGPONG_MAX] = {SDE_NONE, SDE_NONE,
SDE_NONE, SDE_NONE, SDE_NONE, SDE_NONE,
INTR_IDX_PP_CWB_OVFL, SDE_NONE, INTR_IDX_PP_CWB2_OVFL, SDE_NONE};
/**
* sde_rgb2yuv_601l - rgb to yuv color space conversion matrix
*
*/
static struct sde_csc_cfg sde_encoder_phys_wb_rgb2yuv_601l = {
{
TO_S15D16(0x0083), TO_S15D16(0x0102), TO_S15D16(0x0032),
TO_S15D16(0x1fb5), TO_S15D16(0x1f6c), TO_S15D16(0x00e1),
TO_S15D16(0x00e1), TO_S15D16(0x1f45), TO_S15D16(0x1fdc)
},
{ 0x00, 0x00, 0x00 },
{ 0x0040, 0x0200, 0x0200 },
{ 0x000, 0x3ff, 0x000, 0x3ff, 0x000, 0x3ff },
{ 0x040, 0x3ac, 0x040, 0x3c0, 0x040, 0x3c0 },
};
/**
* sde_encoder_phys_wb_is_master - report wb always as master encoder
*/
static bool sde_encoder_phys_wb_is_master(struct sde_encoder_phys *phys_enc)
{
return true;
}
/**
* sde_encoder_phys_wb_get_intr_type - get interrupt type based on block mode
* @hw_wb: Pointer to h/w writeback driver
*/
static enum sde_intr_type sde_encoder_phys_wb_get_intr_type(
struct sde_hw_wb *hw_wb)
{
return (hw_wb->caps->features & BIT(SDE_WB_BLOCK_MODE)) ?
SDE_IRQ_TYPE_WB_ROT_COMP : SDE_IRQ_TYPE_WB_WFD_COMP;
}
/**
* sde_encoder_phys_wb_set_ot_limit - set OT limit for writeback interface
* @phys_enc: Pointer to physical encoder
*/
static void sde_encoder_phys_wb_set_ot_limit(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
struct drm_connector_state *conn_state;
struct sde_vbif_set_ot_params ot_params;
enum sde_wb_usage_type usage_type;
conn_state = phys_enc->connector->state;
usage_type = sde_connector_get_property(conn_state, CONNECTOR_PROP_WB_USAGE_TYPE);
memset(&ot_params, 0, sizeof(ot_params));
ot_params.xin_id = hw_wb->caps->xin_id;
ot_params.num = hw_wb->idx - WB_0;
ot_params.width = wb_enc->wb_roi.w;
ot_params.height = wb_enc->wb_roi.h;
ot_params.is_wfd = (usage_type == WB_USAGE_WFD);
ot_params.frame_rate = drm_mode_vrefresh(&phys_enc->cached_mode);
ot_params.vbif_idx = hw_wb->caps->vbif_idx;
ot_params.clk_ctrl = hw_wb->caps->clk_ctrl;
ot_params.rd = false;
sde_vbif_set_ot_limit(phys_enc->sde_kms, &ot_params);
}
/**
* sde_encoder_phys_wb_set_qos_remap - set QoS remapper for writeback
* @phys_enc: Pointer to physical encoder
*/
static void sde_encoder_phys_wb_set_qos_remap(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc;
struct sde_hw_wb *hw_wb;
struct drm_crtc *crtc;
struct drm_connector_state *conn_state;
struct sde_vbif_set_qos_params qos_params;
enum sde_wb_usage_type usage_type;
if (!phys_enc || !phys_enc->parent || !phys_enc->parent->crtc) {
SDE_ERROR("invalid arguments\n");
return;
}
wb_enc = to_sde_encoder_phys_wb(phys_enc);
if (!wb_enc->crtc) {
SDE_ERROR("[enc:%d, wb:%d] invalid crtc\n", DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
crtc = wb_enc->crtc;
conn_state = phys_enc->connector->state;
usage_type = sde_connector_get_property(conn_state, CONNECTOR_PROP_WB_USAGE_TYPE);
if (!wb_enc->hw_wb || !wb_enc->hw_wb->caps) {
SDE_ERROR("[enc:%d wb:%d] invalid WB HW\n", DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
hw_wb = wb_enc->hw_wb;
memset(&qos_params, 0, sizeof(qos_params));
qos_params.vbif_idx = hw_wb->caps->vbif_idx;
qos_params.xin_id = hw_wb->caps->xin_id;
qos_params.clk_ctrl = hw_wb->caps->clk_ctrl;
qos_params.num = hw_wb->idx - WB_0;
if (phys_enc->in_clone_mode)
qos_params.client_type = VBIF_CWB_CLIENT;
else if (usage_type == WB_USAGE_OFFLINE_WB)
qos_params.client_type = VBIF_OFFLINE_WB_CLIENT;
else if (usage_type == WB_USAGE_ROT)
qos_params.client_type = VBIF_WB_ROT_CLIENT;
else
qos_params.client_type = VBIF_NRT_CLIENT;
SDE_DEBUG("[enc:%d wb:%d] qos_remap - wb:%d vbif:%d xin:%d clone:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), qos_params.num,
qos_params.vbif_idx, qos_params.xin_id, qos_params.client_type);
sde_vbif_set_qos_remap(phys_enc->sde_kms, &qos_params);
}
/**
* sde_encoder_phys_wb_set_qos - set QoS/danger/safe LUTs for writeback
* @phys_enc: Pointer to physical encoder
*/
static void sde_encoder_phys_wb_set_qos(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc;
struct sde_hw_wb *hw_wb;
struct drm_connector_state *conn_state;
struct sde_hw_wb_qos_cfg qos_cfg = {0};
struct sde_perf_cfg *perf;
u32 fps_index = 0, lut_index, creq_index, ds_index, frame_rate, qos_count;
enum sde_wb_usage_type usage_type;
if (!phys_enc || !phys_enc->sde_kms || !phys_enc->sde_kms->catalog) {
SDE_ERROR("invalid parameter(s)\n");
return;
}
wb_enc = to_sde_encoder_phys_wb(phys_enc);
if (!wb_enc->hw_wb) {
SDE_ERROR("[enc:%d wb:%d] invalid WB HW\n", DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
conn_state = phys_enc->connector->state;
usage_type = sde_connector_get_property(conn_state, CONNECTOR_PROP_WB_USAGE_TYPE);
perf = &phys_enc->sde_kms->catalog->perf;
frame_rate = drm_mode_vrefresh(&phys_enc->cached_mode);
hw_wb = wb_enc->hw_wb;
qos_count = perf->qos_refresh_count;
while ((fps_index < qos_count) && perf->qos_refresh_rate) {
if ((frame_rate <= perf->qos_refresh_rate[fps_index]) ||
(fps_index == qos_count - 1))
break;
fps_index++;
}
qos_cfg.danger_safe_en = true;
if (usage_type == WB_USAGE_ROT) {
qos_cfg.qos_mode = SDE_WB_QOS_MODE_DYNAMIC;
qos_cfg.bytes_per_clk = sde_connector_get_property(conn_state,
CONNECTOR_PROP_WB_ROT_BYTES_PER_CLK);
}
if (phys_enc->in_clone_mode)
lut_index = (SDE_FORMAT_IS_TILE(wb_enc->wb_fmt)
|| SDE_FORMAT_IS_UBWC(wb_enc->wb_fmt)) ?
SDE_QOS_LUT_USAGE_CWB_TILE : SDE_QOS_LUT_USAGE_CWB;
else if (usage_type == WB_USAGE_ROT)
lut_index = SDE_QOS_LUT_USAGE_WB_ROT;
else
lut_index = (usage_type == WB_USAGE_OFFLINE_WB) ?
SDE_QOS_LUT_USAGE_OFFLINE_WB : SDE_QOS_LUT_USAGE_NRT;
creq_index = lut_index * SDE_CREQ_LUT_TYPE_MAX;
creq_index += (fps_index * SDE_QOS_LUT_USAGE_MAX * SDE_CREQ_LUT_TYPE_MAX);
qos_cfg.creq_lut = perf->creq_lut[creq_index];
ds_index = lut_index * SDE_DANGER_SAFE_LUT_TYPE_MAX;
ds_index += (fps_index * SDE_QOS_LUT_USAGE_MAX * SDE_DANGER_SAFE_LUT_TYPE_MAX);
qos_cfg.danger_lut = perf->danger_lut[ds_index];
qos_cfg.safe_lut = (u32) perf->safe_lut[ds_index];
SDE_DEBUG("[enc:%d wb:%d] fps:%d mode:%d type:%d luts[0x%x,0x%x 0x%llx]\n",
DRMID(phys_enc->parent), WBID(wb_enc), frame_rate, phys_enc->in_clone_mode,
usage_type, qos_cfg.danger_lut, qos_cfg.safe_lut, qos_cfg.creq_lut);
if (hw_wb->ops.setup_qos_lut)
hw_wb->ops.setup_qos_lut(hw_wb, &qos_cfg);
}
/**
* sde_encoder_phys_setup_cdm - setup chroma down block
* @phys_enc: Pointer to physical encoder
* @fb: Pointer to output framebuffer
* @format: Output format
*/
void sde_encoder_phys_setup_cdm(struct sde_encoder_phys *phys_enc, struct drm_framebuffer *fb,
const struct sde_format *format, struct sde_rect *wb_roi)
{
struct sde_hw_cdm *hw_cdm;
struct sde_hw_cdm_cfg *cdm_cfg;
struct sde_hw_pingpong *hw_pp;
struct sde_encoder_phys_wb *wb_enc;
int ret;
if (!phys_enc || !format)
return;
wb_enc = to_sde_encoder_phys_wb(phys_enc);
cdm_cfg = &phys_enc->cdm_cfg;
hw_pp = phys_enc->hw_pp;
hw_cdm = phys_enc->hw_cdm;
if (!hw_cdm)
return;
if (!SDE_FORMAT_IS_YUV(format)) {
SDE_DEBUG("[enc:%d wb:%d] cdm_disable fmt:%x\n", DRMID(phys_enc->parent),
WBID(wb_enc), format->base.pixel_format);
if (hw_cdm && hw_cdm->ops.disable)
hw_cdm->ops.disable(hw_cdm);
return;
}
memset(cdm_cfg, 0, sizeof(struct sde_hw_cdm_cfg));
if (!wb_roi)
return;
cdm_cfg->output_width = wb_roi->w;
cdm_cfg->output_height = wb_roi->h;
cdm_cfg->output_fmt = format;
cdm_cfg->output_type = CDM_CDWN_OUTPUT_WB;
cdm_cfg->output_bit_depth = SDE_FORMAT_IS_DX(format) ?
CDM_CDWN_OUTPUT_10BIT : CDM_CDWN_OUTPUT_8BIT;
/* enable 10 bit logic */
switch (cdm_cfg->output_fmt->chroma_sample) {
case SDE_CHROMA_RGB:
cdm_cfg->h_cdwn_type = CDM_CDWN_DISABLE;
cdm_cfg->v_cdwn_type = CDM_CDWN_DISABLE;
break;
case SDE_CHROMA_H2V1:
cdm_cfg->h_cdwn_type = CDM_CDWN_COSITE;
cdm_cfg->v_cdwn_type = CDM_CDWN_DISABLE;
break;
case SDE_CHROMA_420:
cdm_cfg->h_cdwn_type = CDM_CDWN_COSITE;
cdm_cfg->v_cdwn_type = CDM_CDWN_OFFSITE;
break;
case SDE_CHROMA_H1V2:
default:
SDE_ERROR("[enc:%d wb:%d] unsupported chroma sampling type\n",
DRMID(phys_enc->parent), WBID(wb_enc));
cdm_cfg->h_cdwn_type = CDM_CDWN_DISABLE;
cdm_cfg->v_cdwn_type = CDM_CDWN_DISABLE;
break;
}
SDE_DEBUG("[enc:%d wb:%d] cdm_enable:%d,%d,%X,%d,%d,%d,%d]\n",
DRMID(phys_enc->parent), WBID(wb_enc), cdm_cfg->output_width,
cdm_cfg->output_height, cdm_cfg->output_fmt->base.pixel_format,
cdm_cfg->output_type, cdm_cfg->output_bit_depth,
cdm_cfg->h_cdwn_type, cdm_cfg->v_cdwn_type);
if (hw_cdm && hw_cdm->ops.setup_csc_data) {
ret = hw_cdm->ops.setup_csc_data(hw_cdm, &sde_encoder_phys_wb_rgb2yuv_601l);
if (ret < 0) {
SDE_ERROR("[enc:%d wb:%d] failed to setup CSC; ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), ret);
return;
}
}
if (hw_cdm && hw_cdm->ops.setup_cdwn) {
ret = hw_cdm->ops.setup_cdwn(hw_cdm, cdm_cfg);
if (ret < 0) {
SDE_ERROR("[enc:%d wb:%d] failed to setup CDWN; ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), ret);
return;
}
}
if (hw_cdm && hw_pp && hw_cdm->ops.enable) {
cdm_cfg->pp_id = hw_pp->idx;
ret = hw_cdm->ops.enable(hw_cdm, cdm_cfg);
if (ret < 0) {
SDE_ERROR("[enc:%d wb:%d] failed to enable CDM; ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), ret);
return;
}
}
}
static void _sde_enc_phys_wb_get_out_resolution(struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state, u32 *out_width, u32 *out_height)
{
struct sde_crtc_state *cstate = to_sde_crtc_state(crtc_state);
const struct drm_display_mode *mode = &crtc_state->mode;
struct sde_io_res ds_res = {0, }, dnsc_blur_res = {0, };
u32 ds_tap_pt = sde_crtc_get_property(cstate, CRTC_PROP_CAPTURE_OUTPUT);
enum sde_wb_rot_type rotation_type;
sde_crtc_get_ds_io_res(crtc_state, &ds_res);
sde_connector_get_dnsc_blur_io_res(conn_state, &dnsc_blur_res);
rotation_type = sde_connector_get_property(conn_state, CONNECTOR_PROP_WB_ROT_TYPE);
if (dnsc_blur_res.enabled) {
*out_width = dnsc_blur_res.dst_w;
*out_height = dnsc_blur_res.dst_h;
} else if (ds_res.enabled) {
if (ds_tap_pt == CAPTURE_DSPP_OUT) {
*out_width = ds_res.dst_w;
*out_height = ds_res.dst_h;
} else if (ds_tap_pt == CAPTURE_MIXER_OUT) {
*out_width = ds_res.src_w;
*out_height = ds_res.src_h;
} else {
*out_width = mode->hdisplay;
*out_height = mode->vdisplay;
}
} else {
*out_width = mode->hdisplay;
*out_height = mode->vdisplay;
}
if (rotation_type != WB_ROT_NONE)
swap(*out_width, *out_height);
}
static void _sde_encoder_phys_wb_setup_cdp(struct sde_encoder_phys *phys_enc,
struct sde_hw_wb_cfg *wb_cfg)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
struct sde_hw_wb_cdp_cfg *cdp_cfg = &wb_enc->cdp_cfg;
u32 cdp_index;
if (!hw_wb->ops.setup_cdp)
return;
memset(cdp_cfg, 0, sizeof(struct sde_hw_wb_cdp_cfg));
cdp_index = phys_enc->in_clone_mode ? SDE_PERF_CDP_USAGE_RT : SDE_PERF_CDP_USAGE_NRT;
cdp_cfg->enable = phys_enc->sde_kms->catalog->perf.cdp_cfg[cdp_index].wr_enable;
cdp_cfg->ubwc_meta_enable = SDE_FORMAT_IS_UBWC(wb_cfg->dest.format);
cdp_cfg->tile_amortize_enable = SDE_FORMAT_IS_UBWC(wb_cfg->dest.format) ||
SDE_FORMAT_IS_TILE(wb_cfg->dest.format);
cdp_cfg->preload_ahead = SDE_WB_CDP_PRELOAD_AHEAD_64;
hw_wb->ops.setup_cdp(hw_wb, cdp_cfg);
}
static void _sde_encoder_phys_wb_setup_roi(struct sde_encoder_phys *phys_enc,
struct sde_hw_wb_cfg *wb_cfg, u32 out_width, u32 out_height)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
struct drm_crtc_state *crtc_state = wb_enc->crtc->state;
struct sde_crtc_state *cstate = to_sde_crtc_state(crtc_state);
struct sde_rect pu_roi = {0,};
if (!hw_wb->ops.setup_roi)
return;
if (hw_wb->ops.setup_crop && phys_enc->in_clone_mode) {
wb_cfg->crop.x = wb_cfg->roi.x;
wb_cfg->crop.y = wb_cfg->roi.y;
if (cstate->user_roi_list.num_rects) {
sde_kms_rect_merge_rectangles(&cstate->user_roi_list, &pu_roi);
if ((wb_cfg->roi.w != pu_roi.w) || (wb_cfg->roi.h != pu_roi.h)) {
/* offset cropping region to PU region */
wb_cfg->crop.x = wb_cfg->crop.x - pu_roi.x;
wb_cfg->crop.y = wb_cfg->crop.y - pu_roi.y;
hw_wb->ops.setup_crop(hw_wb, wb_cfg, true);
} else {
hw_wb->ops.setup_crop(hw_wb, wb_cfg, false);
}
} else if ((wb_cfg->roi.w != out_width) || (wb_cfg->roi.h != out_height)) {
hw_wb->ops.setup_crop(hw_wb, wb_cfg, true);
} else {
hw_wb->ops.setup_crop(hw_wb, wb_cfg, false);
}
/* If output buffer is less than source size, align roi at top left corner */
if (wb_cfg->dest.width < out_width || wb_cfg->dest.height < out_height) {
wb_cfg->roi.x = 0;
wb_cfg->roi.y = 0;
}
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc), wb_cfg->crop.x, wb_cfg->crop.y,
pu_roi.x, pu_roi.y, pu_roi.w, pu_roi.h);
}
hw_wb->ops.setup_roi(hw_wb, wb_cfg);
}
static void _sde_encoder_phys_wb_setup_out_cfg(struct sde_encoder_phys *phys_enc,
struct sde_hw_wb_cfg *wb_cfg)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
SDE_DEBUG("[enc:%d wb:%d] [fb_offset:%8.8x,%8.8x,%8.8x,%8.8x], fb_sec:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), wb_cfg->dest.plane_addr[0],
wb_cfg->dest.plane_addr[1], wb_cfg->dest.plane_addr[2],
wb_cfg->dest.plane_addr[3], wb_cfg->is_secure);
SDE_DEBUG("[fb_stride:%8.8x,%8.8x,%8.8x,%8.8x]\n", wb_cfg->dest.plane_pitch[0],
wb_cfg->dest.plane_pitch[1], wb_cfg->dest.plane_pitch[2],
wb_cfg->dest.plane_pitch[3]);
if (hw_wb->ops.setup_outformat)
hw_wb->ops.setup_outformat(hw_wb, wb_cfg);
if (hw_wb->ops.setup_outaddress) {
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc),
wb_cfg->dest.width, wb_cfg->dest.height,
wb_cfg->dest.plane_addr[0], wb_cfg->dest.plane_size[0],
wb_cfg->dest.plane_addr[1], wb_cfg->dest.plane_size[1],
wb_cfg->dest.plane_addr[2], wb_cfg->dest.plane_size[2],
wb_cfg->dest.plane_addr[3], wb_cfg->dest.plane_size[3],
wb_cfg->roi.x, wb_cfg->roi.y, wb_cfg->roi.w, wb_cfg->roi.h);
hw_wb->ops.setup_outaddress(hw_wb, wb_cfg);
}
}
/**
* sde_encoder_phys_wb_setup_fb - setup output framebuffer
* @phys_enc: Pointer to physical encoder
* @fb: Pointer to output framebuffer
* @wb_roi: Pointer to output region of interest
*/
static void sde_encoder_phys_wb_setup_fb(struct sde_encoder_phys *phys_enc,
struct drm_framebuffer *fb, struct sde_rect *wb_roi, u32 out_width, u32 out_height)
{
struct sde_encoder_phys_wb *wb_enc;
struct sde_hw_wb *hw_wb;
struct sde_hw_wb_cfg *wb_cfg;
const struct msm_format *format;
enum sde_wb_rot_type rotation_type;
struct msm_gem_address_space *aspace;
u32 fb_mode;
int ret;
if (!phys_enc || !phys_enc->sde_kms || !phys_enc->sde_kms->catalog ||
!phys_enc->connector) {
SDE_ERROR("invalid encoder\n");
return;
}
wb_enc = to_sde_encoder_phys_wb(phys_enc);
hw_wb = wb_enc->hw_wb;
wb_cfg = &wb_enc->wb_cfg;
memset(wb_cfg, 0, sizeof(struct sde_hw_wb_cfg));
wb_cfg->intf_mode = phys_enc->intf_mode;
fb_mode = sde_connector_get_property(phys_enc->connector->state,
CONNECTOR_PROP_FB_TRANSLATION_MODE);
if (phys_enc->enable_state == SDE_ENC_DISABLING)
wb_cfg->is_secure = false;
else
wb_cfg->is_secure = (fb_mode == SDE_DRM_FB_SEC) ? true : false;
aspace = (wb_cfg->is_secure) ? wb_enc->aspace[SDE_IOMMU_DOMAIN_SECURE] :
wb_enc->aspace[SDE_IOMMU_DOMAIN_UNSECURE];
ret = msm_framebuffer_prepare(fb, aspace);
if (ret) {
SDE_ERROR("[enc:%d wb:%d] prep fb failed; fb_sec:%d, ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), wb_cfg->is_secure, ret);
return;
}
/* cache framebuffer for cleanup in writeback done */
wb_enc->wb_fb = fb;
wb_enc->wb_aspace = aspace;
drm_framebuffer_get(fb);
format = msm_framebuffer_format(fb);
if (!format) {
SDE_DEBUG("[enc:%d wb:%d] invalid fb fmt\n", DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
rotation_type = sde_connector_get_property(phys_enc->connector->state,
CONNECTOR_PROP_WB_ROT_TYPE);
wb_cfg->rotate_90 = (rotation_type != WB_ROT_NONE);
SDE_DEBUG("[enc:%d wb:%d] conn:%d rotation_type:%d format %4.4s and modifier 0x%llX\n",
DRMID(phys_enc->parent), WBID(wb_enc), DRMID(phys_enc->connector),
rotation_type, (char *)&format->pixel_format, fb->modifier);
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc), rotation_type, out_width, out_height,
fb->width, fb->height);
wb_cfg->dest.format = sde_get_sde_format_ext(format->pixel_format, fb->modifier);
if (!wb_cfg->dest.format) {
/* this error should be detected during atomic_check */
SDE_ERROR("[enc:%d wb:%d] failed to get format:%x\n",
DRMID(phys_enc->parent), WBID(wb_enc), format->pixel_format);
return;
}
wb_cfg->roi = *wb_roi;
ret = sde_format_populate_layout(aspace, fb, &wb_cfg->dest);
if (ret) {
SDE_DEBUG("[enc:%d wb:%d] failed to populate layout; ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), ret);
return;
}
wb_cfg->dest.width = fb->width;
wb_cfg->dest.height = fb->height;
wb_cfg->dest.num_planes = wb_cfg->dest.format->num_planes;
if ((wb_cfg->dest.format->fetch_planes == SDE_PLANE_PLANAR) &&
(wb_cfg->dest.format->element[0] == C1_B_Cb))
swap(wb_cfg->dest.plane_addr[1], wb_cfg->dest.plane_addr[2]);
_sde_encoder_phys_wb_setup_roi(phys_enc, wb_cfg, out_width, out_height);
_sde_encoder_phys_wb_setup_cdp(phys_enc, wb_cfg);
_sde_encoder_phys_wb_setup_out_cfg(phys_enc, wb_cfg);
}
static void _sde_encoder_phys_wb_setup_cwb(struct sde_encoder_phys *phys_enc, bool enable)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
struct sde_hw_wb_cfg *wb_cfg = &wb_enc->wb_cfg;
struct sde_hw_ctl *hw_ctl = phys_enc->hw_ctl;
struct sde_crtc *crtc = to_sde_crtc(wb_enc->crtc);
struct sde_hw_pingpong *hw_pp = phys_enc->hw_pp;
struct sde_hw_dnsc_blur *hw_dnsc_blur = phys_enc->hw_dnsc_blur;
bool need_merge = (crtc->num_mixers > 1);
enum sde_dcwb;
int i = 0;
const int num_wb = 1;
if (!phys_enc->in_clone_mode) {
SDE_DEBUG("[enc:%d wb:%d] not in CWB mode. early return\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
if (!hw_pp || !hw_ctl || !hw_wb || hw_pp->idx >= PINGPONG_MAX) {
SDE_ERROR("[enc:%d wb:%d] invalid hw resources - return\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
if (crtc->num_mixers > MAX_CWB_PER_CTL_V1) {
SDE_ERROR("[enc:%d wb:%d] %d LM %d CWB case not supported\n",
DRMID(phys_enc->parent), WBID(wb_enc),
crtc->num_mixers, MAX_CWB_PER_CTL_V1);
return;
}
hw_ctl = crtc->mixers[0].hw_ctl;
if (hw_ctl && hw_ctl->ops.setup_intf_cfg_v1 &&
(test_bit(SDE_WB_CWB_CTRL, &hw_wb->caps->features) ||
test_bit(SDE_WB_DCWB_CTRL, &hw_wb->caps->features))) {
struct sde_hw_intf_cfg_v1 intf_cfg = { 0, };
intf_cfg.wb_count = num_wb;
intf_cfg.wb[0] = hw_wb->idx;
for (i = 0; i < crtc->num_mixers; i++) {
if (test_bit(SDE_WB_DCWB_CTRL, &hw_wb->caps->features))
intf_cfg.cwb[intf_cfg.cwb_count++] =
(enum sde_cwb)(hw_pp->dcwb_idx + i);
else
intf_cfg.cwb[intf_cfg.cwb_count++] = (enum sde_cwb)(hw_pp->idx + i);
}
if (hw_pp->merge_3d && (intf_cfg.merge_3d_count <
MAX_MERGE_3D_PER_CTL_V1) && need_merge)
intf_cfg.merge_3d[intf_cfg.merge_3d_count++] = hw_pp->merge_3d->idx;
if (hw_dnsc_blur)
intf_cfg.dnsc_blur[intf_cfg.dnsc_blur_count++] = hw_dnsc_blur->idx;
if (hw_pp->ops.setup_3d_mode)
hw_pp->ops.setup_3d_mode(hw_pp, (enable && need_merge) ?
BLEND_3D_H_ROW_INT : 0);
if ((hw_wb->ops.bind_pingpong_blk) &&
test_bit(SDE_WB_CWB_CTRL, &hw_wb->caps->features))
hw_wb->ops.bind_pingpong_blk(hw_wb, enable, hw_pp->idx);
if ((hw_wb->ops.bind_dcwb_pp_blk) &&
test_bit(SDE_WB_DCWB_CTRL, &hw_wb->caps->features))
hw_wb->ops.bind_dcwb_pp_blk(hw_wb, enable, hw_pp->idx);
if (hw_wb->ops.setup_crop && !enable)
hw_wb->ops.setup_crop(hw_wb, wb_cfg, false);
if (phys_enc->hw_dnsc_blur &&
phys_enc->hw_dnsc_blur->ops.setup_dnsc_blur && !enable)
phys_enc->hw_dnsc_blur->ops.setup_dnsc_blur(
phys_enc->hw_dnsc_blur, NULL, 0);
if (hw_ctl->ops.update_intf_cfg) {
hw_ctl->ops.update_intf_cfg(hw_ctl, &intf_cfg, enable);
SDE_DEBUG("[enc:%d wb:%d] in CWB/DCWB mode on CTL_%d PP-%d merge3d:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc),
hw_ctl->idx - CTL_0, hw_pp->idx - PINGPONG_0,
hw_pp->merge_3d ? hw_pp->merge_3d->idx - MERGE_3D_0 : -1);
}
} else {
struct sde_hw_intf_cfg *intf_cfg = &phys_enc->intf_cfg;
memset(intf_cfg, 0, sizeof(struct sde_hw_intf_cfg));
intf_cfg->intf = SDE_NONE;
intf_cfg->wb = hw_wb->idx;
if (hw_ctl && hw_ctl->ops.update_wb_cfg) {
hw_ctl->ops.update_wb_cfg(hw_ctl, intf_cfg, enable);
SDE_DEBUG("[enc:%d wb:%d] in CWB/DCWB mode adding WB for CTL_%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), hw_ctl->idx - CTL_0);
}
}
}
static void _sde_encoder_phys_wb_setup_ctl(struct sde_encoder_phys *phys_enc,
const struct sde_format *format)
{
struct sde_encoder_phys_wb *wb_enc;
struct sde_hw_wb *hw_wb;
struct sde_hw_cdm *hw_cdm;
struct sde_hw_dnsc_blur *hw_dnsc_blur;
struct sde_hw_ctl *ctl;
const int num_wb = 1;
if (!phys_enc) {
SDE_ERROR("invalid encoder\n");
return;
}
wb_enc = to_sde_encoder_phys_wb(phys_enc);
if (phys_enc->in_clone_mode) {
SDE_DEBUG("[enc:%d wb:%d] in CWB mode. early return\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
hw_wb = wb_enc->hw_wb;
hw_cdm = phys_enc->hw_cdm;
hw_dnsc_blur = phys_enc->hw_dnsc_blur;
ctl = phys_enc->hw_ctl;
if (test_bit(SDE_CTL_ACTIVE_CFG, &ctl->caps->features) &&
(phys_enc->hw_ctl && phys_enc->hw_ctl->ops.setup_intf_cfg_v1)) {
struct sde_hw_intf_cfg_v1 *intf_cfg_v1 = &phys_enc->intf_cfg_v1;
struct sde_hw_pingpong *hw_pp = phys_enc->hw_pp;
enum sde_3d_blend_mode mode_3d;
memset(intf_cfg_v1, 0, sizeof(struct sde_hw_intf_cfg_v1));
mode_3d = sde_encoder_helper_get_3d_blend_mode(phys_enc);
intf_cfg_v1->intf_count = SDE_NONE;
intf_cfg_v1->wb_count = num_wb;
intf_cfg_v1->wb[0] = hw_wb->idx;
if (SDE_FORMAT_IS_YUV(format)) {
intf_cfg_v1->cdm_count = num_wb;
intf_cfg_v1->cdm[0] = hw_cdm->idx;
}
if (hw_dnsc_blur) {
intf_cfg_v1->dnsc_blur_count = num_wb;
intf_cfg_v1->dnsc_blur[0] = hw_dnsc_blur->idx;
}
if (mode_3d && hw_pp && hw_pp->merge_3d &&
intf_cfg_v1->merge_3d_count < MAX_MERGE_3D_PER_CTL_V1)
intf_cfg_v1->merge_3d[intf_cfg_v1->merge_3d_count++] = hw_pp->merge_3d->idx;
if (hw_pp && hw_pp->ops.setup_3d_mode)
hw_pp->ops.setup_3d_mode(hw_pp, mode_3d);
/* setup which pp blk will connect to this wb */
if (hw_pp && hw_wb->ops.bind_pingpong_blk)
hw_wb->ops.bind_pingpong_blk(hw_wb, true, hw_pp->idx);
phys_enc->hw_ctl->ops.setup_intf_cfg_v1(phys_enc->hw_ctl, intf_cfg_v1);
} else if (phys_enc->hw_ctl && phys_enc->hw_ctl->ops.setup_intf_cfg) {
struct sde_hw_intf_cfg *intf_cfg = &phys_enc->intf_cfg;
memset(intf_cfg, 0, sizeof(struct sde_hw_intf_cfg));
intf_cfg->intf = SDE_NONE;
intf_cfg->wb = hw_wb->idx;
intf_cfg->mode_3d = sde_encoder_helper_get_3d_blend_mode(phys_enc);
phys_enc->hw_ctl->ops.setup_intf_cfg(phys_enc->hw_ctl, intf_cfg);
}
}
static void _sde_enc_phys_wb_detect_cwb(struct sde_encoder_phys *phys_enc,
struct drm_crtc_state *crtc_state)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_crtc_state *cstate = to_sde_crtc_state(crtc_state);
const struct sde_wb_cfg *wb_cfg = wb_enc->hw_wb->caps;
u32 encoder_mask = 0;
/* Check if WB has CWB support */
if ((wb_cfg->features & BIT(SDE_WB_HAS_CWB)) || (wb_cfg->features & BIT(SDE_WB_HAS_DCWB))) {
encoder_mask = crtc_state->encoder_mask;
encoder_mask &= ~drm_encoder_mask(phys_enc->parent);
}
cstate->cwb_enc_mask = encoder_mask ? drm_encoder_mask(phys_enc->parent) : 0;
SDE_DEBUG("[enc:%d wb:%d] detect CWB - status:%d, phys state:%d in_clone_mode:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), cstate->cwb_enc_mask,
phys_enc->enable_state, phys_enc->in_clone_mode);
}
static int _sde_enc_phys_wb_validate_dnsc_blur_filter(
struct sde_dnsc_blur_filter_info *filter_info, u32 src, u32 dst)
{
u32 dnsc_ratio;
if (!src || !dst || (src < dst)) {
SDE_ERROR("invalid dnsc_blur src:%u, dst:%u\n", src, dst);
return -EINVAL;
}
dnsc_ratio = DIV_ROUND_UP(src, dst);
if ((src < filter_info->src_min) || (src > filter_info->src_max)
|| (dst < filter_info->dst_min) || (dst > filter_info->dst_max)) {
SDE_ERROR(
"invalid dnsc_blur size, fil:%d, src/dst:%u/%u, [min/max-src:%u/%u, dst:%u/%u]\n",
filter_info->filter, src, dst, filter_info->src_min,
filter_info->src_max, filter_info->dst_min, filter_info->dst_max);
return -EINVAL;
} else if ((dnsc_ratio < filter_info->min_ratio)
|| (dnsc_ratio > filter_info->max_ratio)) {
SDE_ERROR(
"invalid dnsc_blur ratio, fil:%d, src/dst:%u/%u, ratio:%u, ratio-min/max:%u/%u\n",
filter_info->filter, src, dst, dnsc_ratio,
filter_info->min_ratio, filter_info->max_ratio);
return -EINVAL;
}
return 0;
}
static int _sde_enc_phys_wb_validate_dnsc_blur_filters(struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct sde_connector_state *sde_conn_state = to_sde_connector_state(conn_state);
struct sde_dnsc_blur_filter_info *filter_info;
struct sde_drm_dnsc_blur_cfg *cfg;
struct sde_kms *sde_kms;
int ret = 0, i, j;
sde_kms = sde_connector_get_kms(conn_state->connector);
if (!sde_kms) {
SDE_ERROR("invalid kms\n");
return -EINVAL;
}
for (i = 0; i < sde_conn_state->dnsc_blur_count; i++) {
cfg = &sde_conn_state->dnsc_blur_cfg[i];
for (j = 0; j < sde_kms->catalog->dnsc_blur_filter_count; j++) {
filter_info = &sde_kms->catalog->dnsc_blur_filters[i];
if (cfg->flags_h == filter_info->filter) {
ret = _sde_enc_phys_wb_validate_dnsc_blur_filter(filter_info,
cfg->src_width, cfg->dst_width);
if (ret)
break;
}
if (cfg->flags_v == filter_info->filter) {
ret = _sde_enc_phys_wb_validate_dnsc_blur_filter(filter_info,
cfg->src_height, cfg->dst_height);
if (ret)
break;
}
}
}
return ret;
}
static int _sde_enc_phys_wb_validate_dnsc_blur_ds(struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state, const struct sde_format *fmt,
struct sde_rect *wb_roi)
{
struct sde_crtc_state *cstate = to_sde_crtc_state(crtc_state);
const struct drm_display_mode *mode = &crtc_state->mode;
struct sde_io_res ds_res = {0, }, dnsc_blur_res = {0, };
enum sde_wb_rot_type rotation_type;
u32 ds_tap_pt = sde_crtc_get_property(cstate, CRTC_PROP_CAPTURE_OUTPUT);
sde_crtc_get_ds_io_res(crtc_state, &ds_res);
sde_connector_get_dnsc_blur_io_res(conn_state, &dnsc_blur_res);
rotation_type = sde_connector_get_property(conn_state, CONNECTOR_PROP_WB_ROT_TYPE);
/* wb_roi should match with mode w/h if none of these features are enabled */
if ((rotation_type == WB_ROT_NONE) &&
(!ds_res.enabled && !dnsc_blur_res.enabled && !cstate->cwb_enc_mask)
&& ((wb_roi->w && (wb_roi->w != mode->hdisplay))
|| (wb_roi->h && (wb_roi->h != mode->vdisplay)))) {
SDE_ERROR("invalid wb-roi {%u,%u,%u,%u} mode:%ux%u\n",
wb_roi->x, wb_roi->y, wb_roi->w, wb_roi->h,
mode->hdisplay, mode->vdisplay);
return -EINVAL;
}
if (!dnsc_blur_res.enabled)
return 0;
if (!dnsc_blur_res.src_w || !dnsc_blur_res.src_h
|| !dnsc_blur_res.dst_w || !dnsc_blur_res.dst_h
|| (dnsc_blur_res.src_w < dnsc_blur_res.dst_w)
|| (dnsc_blur_res.src_h < dnsc_blur_res.dst_h)) {
SDE_ERROR("invalid dnsc_blur cfg src:%ux%u dst:%ux%u\n",
dnsc_blur_res.src_w, dnsc_blur_res.src_h,
dnsc_blur_res.dst_w, dnsc_blur_res.dst_h);
return -EINVAL;
} else if (ds_res.enabled && (ds_tap_pt == CAPTURE_DSPP_OUT)
&& ((ds_res.dst_w != dnsc_blur_res.src_w)
|| (ds_res.dst_h != dnsc_blur_res.src_h))) {
SDE_ERROR("invalid DSPP OUT cfg: ds dst:%ux%u dnsc_blur src:%ux%u\n",
ds_res.dst_w, ds_res.dst_h,
dnsc_blur_res.src_w, dnsc_blur_res.src_h);
return -EINVAL;
} else if (ds_res.enabled && (ds_tap_pt == CAPTURE_MIXER_OUT)
&& ((ds_res.src_w != dnsc_blur_res.src_w)
|| (ds_res.src_h != dnsc_blur_res.src_h))) {
SDE_ERROR("invalid MIXER OUT cfg: ds src:%ux%u dnsc_blur src:%ux%u\n",
ds_res.dst_w, ds_res.dst_h,
dnsc_blur_res.src_w, dnsc_blur_res.src_h);
return -EINVAL;
} else if (cstate->user_roi_list.num_rects) {
SDE_ERROR("PU with dnsc_blur not supported\n");
return -EINVAL;
} else if (SDE_FORMAT_IS_YUV(fmt)) {
SDE_ERROR("YUV output not supported with dnsc_blur\n");
return -EINVAL;
} else if ((rotation_type != WB_ROT_NONE) &&
((wb_roi->w && (wb_roi->w != dnsc_blur_res.dst_h)) ||
(wb_roi->h && (wb_roi->h != dnsc_blur_res.dst_w)))) {
SDE_ERROR("invalid WB ROI for dnsc and rotate, roi:{%d,%d,%d,%d}, dnsc dst:%ux%u\n",
wb_roi->x, wb_roi->y, wb_roi->w, wb_roi->h,
dnsc_blur_res.dst_w, dnsc_blur_res.dst_h);
return -EINVAL;
} else if ((rotation_type == WB_ROT_NONE) &&
((wb_roi->w && (wb_roi->w != dnsc_blur_res.dst_w)) ||
(wb_roi->h && (wb_roi->h != dnsc_blur_res.dst_h)))) {
SDE_ERROR("invalid WB ROI with dnsc_blur, roi:{%d,%d,%d,%d}, dnsc_blur dst:%ux%u\n",
wb_roi->x, wb_roi->y, wb_roi->w, wb_roi->h,
dnsc_blur_res.dst_w, dnsc_blur_res.dst_h);
return -EINVAL;
}
return _sde_enc_phys_wb_validate_dnsc_blur_filters(crtc_state, conn_state);
}
static int _sde_enc_phys_wb_validate_cwb(struct sde_encoder_phys *phys_enc,
struct drm_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct drm_framebuffer *fb;
struct sde_crtc_state *cstate = to_sde_crtc_state(crtc_state);
struct sde_rect wb_roi = {0,}, pu_roi = {0,};
u32 out_width = 0, out_height = 0;
const struct sde_format *fmt;
int num_lm, prog_line, ret = 0;
fb = sde_wb_connector_state_get_output_fb(conn_state);
if (!fb) {
SDE_DEBUG("no output framebuffer\n");
return 0;
}
num_lm = sde_crtc_get_num_datapath(crtc_state->crtc, conn_state->connector, crtc_state);
if (num_lm > MAX_CWB_PER_CTL_V1) {
SDE_ERROR("%d LM %d CWB case not supported\n", num_lm, MAX_CWB_PER_CTL_V1);
return -EINVAL;
}
fmt = sde_get_sde_format_ext(fb->format->format, fb->modifier);
if (!fmt) {
SDE_ERROR("unsupported output pixel format:%x\n", fb->format->format);
return -EINVAL;
}
ret = sde_wb_connector_state_get_output_roi(conn_state, &wb_roi);
if (ret) {
SDE_ERROR("failed to get roi %d\n", ret);
return ret;
}
if (!wb_roi.w || !wb_roi.h) {
SDE_ERROR("cwb roi is not set wxh:%dx%d\n", wb_roi.w, wb_roi.h);
return -EINVAL;
}
prog_line = sde_connector_get_property(conn_state, CONNECTOR_PROP_EARLY_FENCE_LINE);
if (prog_line) {
SDE_ERROR("early fence not supported with CWB, prog_line:%d\n", prog_line);
return -EINVAL;
}
/*
* 1) No DS case: same restrictions for LM & DSSPP tap point
* a) wb-roi should be inside FB
* b) mode resolution & wb-roi should be same
* 2) With DS case: restrictions would change based on tap point
* 2.1) LM Tap Point:
* a) wb-roi should be inside FB
* b) wb-roi should be same as crtc-LM bounds
* 2.2) DSPP Tap point: same as No DS case
* a) wb-roi should be inside FB
* b) mode resolution & wb-roi should be same
* 3) With DNSC_BLUR case:
* a) wb-roi should be inside FB
* b) mode resolution and wb-roi should be same
* 4) Partial Update case: additional stride check
* a) cwb roi should be inside PU region or FB
* b) cropping is only allowed for fully sampled data
* c) add check for stride and QOS setting by 256B
*/
_sde_enc_phys_wb_get_out_resolution(crtc_state, conn_state, &out_width, &out_height);
if (SDE_FORMAT_IS_YUV(fmt) && ((wb_roi.w != out_width) || (wb_roi.h != out_height))) {
SDE_ERROR("invalid wb roi[%dx%d] out[%dx%d] fmt:%x\n",
wb_roi.w, wb_roi.h, out_width, out_height, fmt->base.pixel_format);
return -EINVAL;
}
if ((wb_roi.w > out_width) || (wb_roi.h > out_height)) {
SDE_ERROR("invalid wb roi[%dx%d] out[%dx%d]\n",
wb_roi.w, wb_roi.h, out_width, out_height);
return -EINVAL;
}
/*
* If output size is equal to input size ensure wb_roi with x and y offset
* will be within buffer. If output size is smaller, only width and height are taken
* into consideration as output region will begin at top left corner
*/
if ((fb->width == out_width && fb->height == out_height) &&
(((wb_roi.x + wb_roi.w) > fb->width)
|| ((wb_roi.y + wb_roi.h) > fb->height))) {
SDE_ERROR("invalid wb roi[%d,%d,%d,%d] fb[%dx%d] out[%dx%d]\n",
wb_roi.x, wb_roi.y, wb_roi.w, wb_roi.h, fb->width, fb->height,
out_width, out_height);
return -EINVAL;
} else if ((fb->width < out_width || fb->height < out_height) &&
((wb_roi.w > fb->width || wb_roi.h > fb->height))) {
SDE_ERROR("invalid wb roi[%d,%d,%d,%d] fb[%dx%d] out[%dx%d]\n",
wb_roi.x, wb_roi.y, wb_roi.w, wb_roi.h, fb->width, fb->height,
out_width, out_height);
return -EINVAL;
}
/* validate wb roi against pu rect */
if (cstate->user_roi_list.num_rects) {
sde_kms_rect_merge_rectangles(&cstate->user_roi_list, &pu_roi);
if (wb_roi.w > pu_roi.w || wb_roi.h > pu_roi.h) {
SDE_ERROR("invalid wb roi with pu [%dx%d vs %dx%d]\n",
wb_roi.w, wb_roi.h, pu_roi.w, pu_roi.h);
return -EINVAL;
}
}
return ret;
}
static int _sde_encoder_phys_wb_validate_rotation(struct sde_encoder_phys *phys_enc,
struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state)
{
enum sde_wb_rot_type rotation_type;
int ret = 0;
u32 src_w, src_h;
u32 bytes_per_clk;
struct sde_rect wb_src, wb_roi = {0,};
struct sde_io_res dnsc_res = {0,};
const struct sde_rect *crtc_roi = NULL;
struct drm_display_mode *mode;
enum sde_wb_usage_type usage_type;
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
rotation_type = sde_connector_get_property(conn_state, CONNECTOR_PROP_WB_ROT_TYPE);
if (rotation_type == WB_ROT_NONE)
return ret;
usage_type = sde_connector_get_property(conn_state, CONNECTOR_PROP_WB_USAGE_TYPE);
if (usage_type != WB_USAGE_ROT) {
SDE_ERROR("[enc:%d wb:%d] invalid WB usage_ype:%d for rotation_type:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), usage_type, rotation_type);
return -EINVAL;
}
bytes_per_clk = sde_connector_get_property(conn_state, CONNECTOR_PROP_WB_ROT_BYTES_PER_CLK);
if (!bytes_per_clk) {
SDE_ERROR("[enc:%d wb:%d] WB output bytes per XO clock is must for rotation\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return -EINVAL;
}
ret = sde_wb_connector_state_get_output_roi(conn_state, &wb_roi);
if (ret) {
SDE_ERROR("[enc:%d wb:%d] failed to get WB output roi, ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), ret);
return ret;
}
sde_crtc_get_crtc_roi(crtc_state, &crtc_roi);
if (!crtc_roi) {
SDE_ERROR("[enc:%d wb:%d] could not get crtc roi\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return -EINVAL;
} else if (!sde_kms_rect_is_null(crtc_roi)) {
SDE_ERROR("[enc:%d wb:%d] not supporting pu scenario on wb\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return -EINVAL;
}
mode = &crtc_state->mode;
sde_crtc_get_resolution(crtc_state->crtc, crtc_state, mode, &src_w, &src_h);
if (!src_w || !src_h) {
SDE_ERROR("[enc:%d wb:%d] invalid wb input dimensions src_w:%d src_h:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), src_w, src_h);
return -EINVAL;
}
sde_connector_get_dnsc_blur_io_res(conn_state, &dnsc_res);
wb_src.w = dnsc_res.enabled ? dnsc_res.dst_w : src_w;
wb_src.h = dnsc_res.enabled ? dnsc_res.dst_h : src_h;
SDE_DEBUG("[enc:%d wb:%d] wb_src=[%dx%d] dnsc_dst=[%dx%d] wb_roi=[%dx%d]\n",
DRMID(phys_enc->parent), WBID(wb_enc), wb_src.w, wb_src.h,
dnsc_res.dst_w, dnsc_res.dst_h, wb_roi.w, wb_roi.h);
if (((wb_src.w != wb_roi.h) || (wb_src.h != wb_roi.w))) {
SDE_ERROR("[enc:%d wb:%d] invalid dimension for rotation src:%dx%d vs out:%dx%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), wb_src.w, wb_src.h,
wb_roi.w, wb_roi.h);
return -EINVAL;
} else if ((wb_roi.x % MINI_TILE_W) || (wb_roi.y % MINI_TILE_H)) {
SDE_ERROR("[enc:%d wb:%d] unaligned x,y offsets for rotation:%d x:%d y:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), rotation_type,
wb_roi.x, wb_roi.y);
return -EINVAL;
} else if ((rotation_type == WB_ROT_JOB1) && (wb_roi.h % MINI_TILE_H)) {
SDE_ERROR("[enc:%d wb:%d] job1 rotation height:%d is not tile aligned\n",
DRMID(phys_enc->parent), WBID(wb_enc), wb_roi.h);
return -EINVAL;
} else if (wb_src.w > SDE_WB_ROT_MAX_SRCW || wb_src.h > SDE_WB_ROT_MAX_SRCH) {
SDE_ERROR("[enc:%d wb:%d] rotate limit exceeded srcw:[%d vs %d], srch:[%d vs %d]\n",
DRMID(phys_enc->parent), WBID(wb_enc), wb_src.w, SDE_WB_ROT_MAX_SRCW,
wb_src.h, SDE_WB_ROT_MAX_SRCH);
return -EINVAL;
}
return ret;
}
static int _sde_encoder_phys_wb_validate_output_fmt(struct sde_encoder_phys *phys_enc,
struct drm_framebuffer *fb, enum sde_wb_rot_type rotation_type)
{
int ret = 0;
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
const struct sde_format *fmt;
const struct sde_format_extended *format_list;
struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
const struct sde_wb_cfg *wb_cfg = hw_wb->caps;
struct sde_kms *sde_kms = phys_enc->sde_kms;
fmt = sde_get_sde_format_ext(fb->format->format, fb->modifier);
if (!fmt) {
SDE_ERROR("[enc:%d wb:%d] invalid output pixel format:0x%x mod:0x%x\n",
DRMID(phys_enc->parent), WBID(wb_enc),
fb->format->format, fb->modifier);
return -EINVAL;
}
/* find if sde format is listed as supported format on WB */
format_list = (rotation_type != WB_ROT_NONE) ?
wb_cfg->rot_format_list : wb_cfg->format_list;
ret = sde_format_validate_fmt(&sde_kms->base, fmt, format_list);
if (ret) {
SDE_ERROR("[enc:%d wb:%d] unsupported format for wb rotate:%d fmt:0x%x mod:0x%x\n",
DRMID(phys_enc->parent), WBID(wb_enc), rotation_type,
fb->format->format, fb->modifier);
return ret;
} else if (fmt->chroma_sample == SDE_CHROMA_H2V1 || fmt->chroma_sample == SDE_CHROMA_H1V2) {
SDE_ERROR("[enc:%d wb:%d] invalid chroma sample type in output format:%x\n",
DRMID(phys_enc->parent), WBID(wb_enc), fmt->base.pixel_format);
return -EINVAL;
} else if (SDE_FORMAT_IS_UBWC(fmt) && !(wb_cfg->features & BIT(SDE_WB_UBWC))) {
SDE_ERROR("[enc:%d wb:%d] invalid output format:%x\n",
DRMID(phys_enc->parent), WBID(wb_enc), fmt->base.pixel_format);
return -EINVAL;
}
return ret;
}
/**
* sde_encoder_phys_wb_atomic_check - verify and fixup given atomic states
* @phys_enc: Pointer to physical encoder
* @crtc_state: Pointer to CRTC atomic state
* @conn_state: Pointer to connector atomic state
*/
static int sde_encoder_phys_wb_atomic_check(struct sde_encoder_phys *phys_enc,
struct drm_crtc_state *crtc_state, struct drm_connector_state *conn_state)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_crtc_state *cstate = to_sde_crtc_state(crtc_state);
struct sde_connector_state *sde_conn_state;
struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
const struct sde_wb_cfg *wb_cfg = hw_wb->caps;
struct drm_framebuffer *fb;
const struct sde_format *fmt;
struct sde_rect wb_roi;
u32 out_width = 0, out_height = 0;
const struct drm_display_mode *mode = &crtc_state->mode;
int rc;
bool clone_mode_curr = false;
enum sde_wb_rot_type rotation_type;
SDE_DEBUG("[enc:%d wb:%d] atomic_check:\"%s\",%d,%d]\n", DRMID(phys_enc->parent),
WBID(wb_enc), mode->name, mode->hdisplay, mode->vdisplay);
if (!conn_state || !conn_state->connector) {
SDE_ERROR("[enc:%d wb:%d] invalid connector state\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return -EINVAL;
} else if (conn_state->connector->status != connector_status_connected) {
SDE_ERROR("[enc:%d wb:%d] connector not connected; ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), conn_state->connector->status);
return -EINVAL;
}
sde_conn_state = to_sde_connector_state(conn_state);
clone_mode_curr = phys_enc->in_clone_mode;
_sde_enc_phys_wb_detect_cwb(phys_enc, crtc_state);
if (clone_mode_curr && !cstate->cwb_enc_mask) {
SDE_ERROR("[enc:%d wb:%d] WB commit before CWB disable\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return -EINVAL;
}
memset(&wb_roi, 0, sizeof(struct sde_rect));
rc = sde_wb_connector_state_get_output_roi(conn_state, &wb_roi);
if (rc) {
SDE_ERROR("[enc:%d wb:%d] failed to get roi; ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), rc);
return rc;
}
/* bypass check if commit with no framebuffer */
fb = sde_wb_connector_state_get_output_fb(conn_state);
if (!fb) {
SDE_ERROR("[enc:%d wb:%d] no out fb\n", DRMID(phys_enc->parent), WBID(wb_enc));
return -EINVAL;
}
rotation_type = sde_connector_get_property(conn_state, CONNECTOR_PROP_WB_ROT_TYPE);
fmt = sde_get_sde_format_ext(fb->format->format, fb->modifier);
if (!fmt) {
SDE_ERROR("[enc:%d wb:%d] invalid output pixel format:0x%x mod:0x%x\n",
DRMID(phys_enc->parent), WBID(wb_enc),
fb->format->format, fb->modifier);
return -EINVAL;
}
SDE_DEBUG("[enc:%d wb:%d] fb_id:%u, wxh:%ux%u, fb_fmt:%x,%llx, roi:{%d,%d,%d,%d}, rot:%u\n",
DRMID(phys_enc->parent), WBID(wb_enc), fb->base.id, fb->width, fb->height,
fb->format->format, fb->modifier, wb_roi.x, wb_roi.y, wb_roi.w, wb_roi.h,
rotation_type);
rc = _sde_encoder_phys_wb_validate_output_fmt(phys_enc, fb, rotation_type);
if (rc) {
SDE_ERROR("[enc:%d wb:%d] output fmt failed fb:%u fmt:0x%x mod:0x%x rot:%d",
DRMID(phys_enc->parent), WBID(wb_enc), fb->base.id,
fb->format->format, fb->modifier, rotation_type);
return rc;
}
if (SDE_FORMAT_IS_YUV(fmt) != !!phys_enc->hw_cdm)
crtc_state->mode_changed = true;
rc = _sde_enc_phys_wb_validate_dnsc_blur_ds(crtc_state, conn_state, fmt, &wb_roi);
if (rc) {
SDE_ERROR("[enc:%d wb:%d] failed dnsc_blur/ds validation; ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), rc);
return rc;
}
/* if in clone mode, return after cwb validation */
if (cstate->cwb_enc_mask) {
rc = _sde_enc_phys_wb_validate_cwb(phys_enc, crtc_state, conn_state);
if (rc)
SDE_ERROR("[enc:%d wb:%d] failed in cwb validation %d\n",
DRMID(phys_enc->parent), WBID(wb_enc), rc);
return rc;
}
if (rotation_type != WB_ROT_NONE) {
rc = _sde_encoder_phys_wb_validate_rotation(phys_enc, crtc_state, conn_state);
if (rc) {
SDE_ERROR("[enc:%d wb:%d] failed in WB rotation validation %d\n",
DRMID(phys_enc->parent), WBID(wb_enc), rc);
return rc;
}
}
_sde_enc_phys_wb_get_out_resolution(crtc_state, conn_state, &out_width, &out_height);
if (!wb_roi.w || !wb_roi.h) {
wb_roi.x = 0;
wb_roi.y = 0;
wb_roi.w = out_width;
wb_roi.h = out_height;
}
if ((wb_roi.x + wb_roi.w > fb->width) || (wb_roi.w > out_width)) {
SDE_ERROR("[enc:%d wb:%d] invalid roi x:%d, w:%d, fb_w:%d, mode_w:%d, out_w:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), wb_roi.x, wb_roi.w,
fb->width, mode->hdisplay, out_width);
return -EINVAL;
} else if ((wb_roi.y + wb_roi.h > fb->height) || (wb_roi.h > out_height)) {
SDE_ERROR("[enc:%d wb:%d] invalid roi y:%d, h:%d, fb_h:%d, mode_h%d, out_h:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), wb_roi.y, wb_roi.h,
fb->height, mode->vdisplay, out_height);
return -EINVAL;
} else if ((rotation_type == WB_ROT_NONE) && ((out_width > mode->hdisplay) || (out_height > mode->vdisplay))) {
SDE_ERROR("[enc:%d wb:%d] invalid o w/h o_w:%d, mode_w:%d, o_h:%d, mode_h:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), out_width, mode->hdisplay,
out_height, mode->vdisplay);
return -EINVAL;
} else if (wb_roi.w > SDE_WB_MAX_LINEWIDTH(fmt, wb_cfg)) {
SDE_ERROR("[enc:%d wb:%d] invalid roi ubwc:%d, w:%d, maxlinewidth:%u\n",
DRMID(phys_enc->parent), WBID(wb_enc), SDE_FORMAT_IS_UBWC(fmt),
wb_roi.w, SDE_WB_MAX_LINEWIDTH(fmt, wb_cfg));
return -EINVAL;
}
return rc;
}
static void _sde_encoder_phys_wb_setup_sys_cache(struct sde_encoder_phys *phys_enc,
struct drm_framebuffer *fb)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_wb_device *wb_dev = wb_enc->wb_dev;
struct drm_connector_state *state = wb_dev->connector->state;
struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
struct sde_crtc *sde_crtc = to_sde_crtc(wb_enc->crtc);
struct sde_sc_cfg *sc_cfg;
struct sde_hw_wb_sc_cfg *cfg = &wb_enc->sc_cfg;
u32 cache_enable, cache_flag, cache_rd_type, cache_wr_type;
int i;
if (!fb) {
SDE_ERROR("invalid fb on wb %d\n", WBID(wb_enc));
return;
}
if (!hw_wb || !hw_wb->ops.setup_sys_cache) {
SDE_DEBUG("unsupported ops: setup_sys_cache WB %d\n", WBID(wb_enc));
return;
}
/*
* - use LLCC_DISP/LLCC_DISP_1 for cwb static display
* - use LLCC_DISP_WB for 2-pass composition using offline-wb
*/
if (phys_enc->in_clone_mode) {
/* toggle system cache SCID between consecutive CWB writes */
if (test_bit(SDE_SYS_CACHE_DISP_1, hw_wb->catalog->sde_sys_cache_type_map)
&& cfg->type == SDE_SYS_CACHE_DISP &&
!test_bit(SDE_FEATURE_SYS_CACHE_STALING,
hw_wb->catalog->features)) {
cache_wr_type = SDE_SYS_CACHE_DISP_1;
cache_rd_type = SDE_SYS_CACHE_DISP_1;
} else {
cache_wr_type = SDE_SYS_CACHE_DISP;
cache_rd_type = SDE_SYS_CACHE_DISP;
sde_core_perf_llcc_stale_frame(&sde_crtc->base, cache_wr_type);
}
} else {
cache_rd_type = SDE_SYS_CACHE_DISP_WB;
cache_wr_type = SDE_SYS_CACHE_DISP_WB;
}
sc_cfg = &hw_wb->catalog->sc_cfg[cache_wr_type];
if (!test_bit(cache_wr_type, hw_wb->catalog->sde_sys_cache_type_map)) {
SDE_DEBUG("sys cache type %d not enabled\n", cache_wr_type);
return;
}
cache_enable = sde_connector_get_property(state, CONNECTOR_PROP_CACHE_STATE);
if (!cfg->wr_en && !cache_enable)
return;
cfg->wr_en = cache_enable;
cfg->flags = SYS_CACHE_EN_FLAG | SYS_CACHE_SCID;
if (cache_enable) {
cfg->wr_scid = sc_cfg->llcc_scid;
cfg->type = cache_wr_type;
cache_flag = MSM_FB_CACHE_WRITE_EN;
} else {
cfg->wr_scid = 0x0;
cfg->type = SDE_SYS_CACHE_NONE;
cache_flag = MSM_FB_CACHE_NONE;
cache_rd_type = SDE_SYS_CACHE_NONE;
cache_wr_type = SDE_SYS_CACHE_NONE;
}
msm_framebuffer_set_cache_hint(fb, cache_flag, cache_rd_type, cache_wr_type);
/*
* avoid llcc_active reset for crtc while in clone mode as it will reset it for
* primary display as well
*/
if (cache_enable) {
sde_crtc->new_perf.llcc_active[cache_wr_type] = true;
sde_crtc->new_perf.llcc_active[cache_rd_type] = true;
sde_core_perf_crtc_update_llcc(wb_enc->crtc);
} else if (!phys_enc->in_clone_mode) {
for (i = 0; i < SDE_SYS_CACHE_MAX; i++)
sde_crtc->new_perf.llcc_active[i] = false;
sde_core_perf_crtc_update_llcc(wb_enc->crtc);
}
hw_wb->ops.setup_sys_cache(hw_wb, cfg);
SDE_EVT32(WBID(wb_enc), cfg->wr_scid, cfg->flags, cfg->type, cache_enable,
phys_enc->in_clone_mode, cache_flag, cache_rd_type,
cache_wr_type, fb->base.id);
}
static void _sde_encoder_phys_wb_update_cwb_flush_helper(
struct sde_encoder_phys *phys_enc, bool enable)
{
struct sde_connector *c_conn = NULL;
struct sde_connector_state *c_state = NULL;
struct sde_hw_wb *hw_wb;
struct sde_hw_ctl *hw_ctl;
struct sde_hw_pingpong *hw_pp;
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_crtc_state *crtc_state;
struct sde_crtc *crtc;
int i = 0;
int cwb_capture_mode = 0;
bool need_merge = false;
bool dspp_out = false;
enum sde_cwb cwb_idx = 0;
enum sde_cwb src_pp_idx = 0;
enum sde_dcwb dcwb_idx = 0;
size_t dither_sz = 0;
void *dither_cfg = NULL;
/* In CWB mode, program actual source master sde_hw_ctl from crtc */
crtc = to_sde_crtc(wb_enc->crtc);
hw_ctl = crtc->mixers[0].hw_ctl;
hw_pp = phys_enc->hw_pp;
hw_wb = wb_enc->hw_wb;
if (!hw_ctl || !hw_wb || !hw_pp) {
SDE_ERROR("[enc:%d wb:%d] HW resource not available for CWB\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
crtc_state = to_sde_crtc_state(wb_enc->crtc->state);
cwb_capture_mode = sde_crtc_get_property(crtc_state, CRTC_PROP_CAPTURE_OUTPUT);
need_merge = (crtc->num_mixers > 1) ? true : false;
dspp_out = (cwb_capture_mode == CAPTURE_DSPP_OUT);
cwb_idx = (enum sde_cwb)hw_pp->idx;
src_pp_idx = (enum sde_cwb)crtc->mixers[0].hw_lm->idx;
if (test_bit(SDE_WB_CWB_DITHER_CTRL, &hw_wb->caps->features)) {
if (cwb_capture_mode) {
c_conn = to_sde_connector(phys_enc->connector);
c_state = to_sde_connector_state(phys_enc->connector->state);
dither_cfg = msm_property_get_blob(&c_conn->property_info,
&c_state->property_state, &dither_sz,
CONNECTOR_PROP_PP_CWB_DITHER);
SDE_DEBUG("Read cwb dither setting from blob %pK\n", dither_cfg);
} else {
/* disable case: tap is lm */
dither_cfg = NULL;
}
}
for (i = 0; i < crtc->num_mixers; i++) {
src_pp_idx = (enum sde_cwb) (src_pp_idx + i);
if (test_bit(SDE_WB_DCWB_CTRL, &hw_wb->caps->features)) {
dcwb_idx = hw_pp->dcwb_idx + i;
if ((test_bit(SDE_WB_CWB_DITHER_CTRL, &hw_wb->caps->features)) &&
hw_wb->ops.program_cwb_dither_ctrl){
hw_wb->ops.program_cwb_dither_ctrl(hw_wb,
dcwb_idx, dither_cfg, dither_sz, enable);
}
if (hw_wb->ops.program_dcwb_ctrl)
hw_wb->ops.program_dcwb_ctrl(hw_wb, dcwb_idx,
src_pp_idx, cwb_capture_mode, enable);
if (hw_ctl->ops.update_bitmask)
hw_ctl->ops.update_bitmask(hw_ctl,
SDE_HW_FLUSH_CWB, dcwb_idx, 1);
} else if (test_bit(SDE_WB_CWB_CTRL, &hw_wb->caps->features)) {
cwb_idx = (enum sde_cwb) (hw_pp->idx + i);
if (hw_wb->ops.program_cwb_ctrl)
hw_wb->ops.program_cwb_ctrl(hw_wb, cwb_idx,
src_pp_idx, dspp_out, enable);
if (hw_ctl->ops.update_bitmask)
hw_ctl->ops.update_bitmask(hw_ctl,
SDE_HW_FLUSH_CWB, cwb_idx, 1);
}
}
if (need_merge && hw_ctl->ops.update_bitmask && hw_pp && hw_pp->merge_3d)
hw_ctl->ops.update_bitmask(hw_ctl, SDE_HW_FLUSH_MERGE_3D,
hw_pp->merge_3d->idx, 1);
}
static void _sde_encoder_phys_wb_update_cwb_flush(struct sde_encoder_phys *phys_enc, bool enable)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_hw_wb *hw_wb;
struct sde_hw_ctl *hw_ctl;
struct sde_hw_cdm *hw_cdm;
struct sde_hw_pingpong *hw_pp;
struct sde_hw_dnsc_blur *hw_dnsc_blur;
struct sde_crtc *crtc;
struct sde_crtc_state *crtc_state;
int cwb_capture_mode = 0;
enum sde_cwb cwb_idx = 0;
enum sde_dcwb dcwb_idx = 0;
enum sde_cwb src_pp_idx = 0;
bool dspp_out = false, need_merge = false;
if (!phys_enc->in_clone_mode) {
SDE_DEBUG("enc:%d, wb:%d - not in CWB mode. early return\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
crtc = to_sde_crtc(wb_enc->crtc);
crtc_state = to_sde_crtc_state(wb_enc->crtc->state);
cwb_capture_mode = sde_crtc_get_property(crtc_state,
CRTC_PROP_CAPTURE_OUTPUT);
hw_pp = phys_enc->hw_pp;
hw_wb = wb_enc->hw_wb;
hw_cdm = phys_enc->hw_cdm;
hw_dnsc_blur = phys_enc->hw_dnsc_blur;
/* In CWB mode, program actual source master sde_hw_ctl from crtc */
hw_ctl = crtc->mixers[0].hw_ctl;
if (!hw_ctl || !hw_wb || !hw_pp) {
SDE_ERROR("[enc:%d wb:%d] HW resource not available for CWB\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
/* treating LM idx of primary display ctl path as source ping-pong idx*/
src_pp_idx = (enum sde_cwb)crtc->mixers[0].hw_lm->idx;
cwb_idx = (enum sde_cwb)hw_pp->idx;
dspp_out = (cwb_capture_mode == CAPTURE_DSPP_OUT);
need_merge = (crtc->num_mixers > 1) ? true : false;
if (test_bit(SDE_WB_DCWB_CTRL, &hw_wb->caps->features)) {
dcwb_idx = hw_pp->dcwb_idx;
if ((dcwb_idx + crtc->num_mixers) > DCWB_MAX) {
SDE_ERROR("[enc:%d, wb:%d] invalid DCWB config; dcwb=%d, num_lm=%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), dcwb_idx, crtc->num_mixers);
return;
}
} else {
if (src_pp_idx > CWB_0 || ((cwb_idx + crtc->num_mixers) > CWB_MAX)) {
SDE_ERROR("[enc:%d wb:%d] invalid CWB onfig; pp_idx:%d, cwb:%d, num_lm%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), src_pp_idx,
dcwb_idx, crtc->num_mixers);
return;
}
}
if (hw_ctl->ops.update_bitmask)
hw_ctl->ops.update_bitmask(hw_ctl, SDE_HW_FLUSH_WB, hw_wb->idx, 1);
if (hw_ctl->ops.update_bitmask && hw_cdm)
hw_ctl->ops.update_bitmask(hw_ctl, SDE_HW_FLUSH_CDM, hw_cdm->idx, 1);
if (hw_ctl->ops.update_dnsc_blur_bitmask && hw_dnsc_blur)
hw_ctl->ops.update_dnsc_blur_bitmask(hw_ctl, hw_dnsc_blur->idx, 1);
if (test_bit(SDE_WB_CWB_CTRL, &hw_wb->caps->features) ||
test_bit(SDE_WB_DCWB_CTRL, &hw_wb->caps->features)) {
_sde_encoder_phys_wb_update_cwb_flush_helper(phys_enc, enable);
} else {
phys_enc->hw_mdptop->ops.set_cwb_ppb_cntl(phys_enc->hw_mdptop,
need_merge, dspp_out);
}
}
/**
* _sde_encoder_phys_wb_update_flush - flush hardware update
* @phys_enc: Pointer to physical encoder
*/
static void _sde_encoder_phys_wb_update_flush(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc;
struct sde_hw_wb *hw_wb;
struct sde_hw_ctl *hw_ctl;
struct sde_hw_cdm *hw_cdm;
struct sde_hw_pingpong *hw_pp;
struct sde_hw_dnsc_blur *hw_dnsc_blur;
struct sde_ctl_flush_cfg pending_flush = {0,};
if (!phys_enc)
return;
wb_enc = to_sde_encoder_phys_wb(phys_enc);
hw_wb = wb_enc->hw_wb;
hw_cdm = phys_enc->hw_cdm;
hw_pp = phys_enc->hw_pp;
hw_ctl = phys_enc->hw_ctl;
hw_dnsc_blur = phys_enc->hw_dnsc_blur;
SDE_DEBUG("[enc:%d wb:%d]\n", DRMID(phys_enc->parent), WBID(wb_enc));
if (phys_enc->in_clone_mode) {
SDE_DEBUG("[enc:%d wb:%d] in CWB mode. early return\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
if (!hw_ctl) {
SDE_DEBUG("[enc:%d wb:%d] invalid ctl\n", DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
if (hw_ctl->ops.update_bitmask)
hw_ctl->ops.update_bitmask(hw_ctl, SDE_HW_FLUSH_WB, hw_wb->idx, 1);
if (hw_ctl->ops.update_bitmask && hw_cdm)
hw_ctl->ops.update_bitmask(hw_ctl, SDE_HW_FLUSH_CDM, hw_cdm->idx, 1);
if (hw_ctl->ops.update_bitmask && hw_pp && hw_pp->merge_3d)
hw_ctl->ops.update_bitmask(hw_ctl, SDE_HW_FLUSH_MERGE_3D, hw_pp->merge_3d->idx, 1);
if (hw_ctl->ops.update_dnsc_blur_bitmask && hw_dnsc_blur)
hw_ctl->ops.update_dnsc_blur_bitmask(hw_ctl, hw_dnsc_blur->idx, 1);
if (hw_ctl->ops.get_pending_flush)
hw_ctl->ops.get_pending_flush(hw_ctl, &pending_flush);
SDE_DEBUG("[enc:%d wb:%d] Pending flush mask for CTL_%d is 0x%x\n",
DRMID(phys_enc->parent), WBID(wb_enc),
hw_ctl->idx - CTL_0, pending_flush.pending_flush_mask);
}
static void _sde_encoder_phys_wb_setup_dnsc_blur(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_wb_device *wb_dev = wb_enc->wb_dev;
struct sde_kms *sde_kms = phys_enc->sde_kms;
struct sde_hw_dnsc_blur *hw_dnsc_blur = phys_enc->hw_dnsc_blur;
struct sde_hw_pingpong *hw_pp = phys_enc->hw_pp;
struct sde_connector *sde_conn;
struct sde_connector_state *sde_conn_state;
struct sde_drm_dnsc_blur_cfg *cfg;
int i;
bool enable;
if (!sde_kms->catalog->dnsc_blur_count || !hw_pp)
return;
sde_conn = to_sde_connector(wb_dev->connector);
sde_conn_state = to_sde_connector_state(wb_dev->connector->state);
if (sde_conn_state->dnsc_blur_count
&& (!hw_dnsc_blur || !hw_dnsc_blur->ops.setup_dnsc_blur)) {
SDE_ERROR("[enc:%d wb:%d] invalid config - dnsc_blur block not reserved\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
/* swap between 0 & 1 lut idx on each config change for gaussian lut */
sde_conn_state->dnsc_blur_lut = 1 - sde_conn_state->dnsc_blur_lut;
/*
* disable dnsc_blur case - safe to update the opmode as dynamic switching of
* dnsc_blur hw block between WBs are not supported currently.
*/
if (hw_dnsc_blur && !sde_conn_state->dnsc_blur_count) {
hw_dnsc_blur->ops.setup_dnsc_blur(hw_dnsc_blur, NULL, 0);
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc), SDE_EVTLOG_FUNC_CASE1);
return;
}
for (i = 0; i < sde_conn_state->dnsc_blur_count; i++) {
cfg = &sde_conn_state->dnsc_blur_cfg[i];
enable = (cfg->flags & DNSC_BLUR_EN);
hw_dnsc_blur->ops.setup_dnsc_blur(hw_dnsc_blur, cfg, sde_conn_state->dnsc_blur_lut);
if (hw_dnsc_blur->ops.setup_dither)
hw_dnsc_blur->ops.setup_dither(hw_dnsc_blur, cfg);
if (hw_dnsc_blur->ops.bind_pingpong_blk)
hw_dnsc_blur->ops.bind_pingpong_blk(hw_dnsc_blur, enable, hw_pp->idx,
phys_enc->in_clone_mode);
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc), sde_conn_state->dnsc_blur_count,
cfg->flags, cfg->flags_h, cfg->flags_v, cfg->src_width,
cfg->src_height, cfg->dst_width, cfg->dst_height,
sde_conn_state->dnsc_blur_lut);
}
}
static void _sde_encoder_phys_wb_setup_prog_line(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_wb_device *wb_dev = wb_enc->wb_dev;
struct drm_connector_state *state = wb_dev->connector->state;
struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
u32 prog_line;
if (phys_enc->in_clone_mode || !hw_wb->ops.set_prog_line_count)
return;
prog_line = sde_connector_get_property(state, CONNECTOR_PROP_EARLY_FENCE_LINE);
if (wb_enc->prog_line != prog_line) {
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc), wb_enc->prog_line, prog_line);
wb_enc->prog_line = prog_line;
hw_wb->ops.set_prog_line_count(hw_wb, prog_line);
}
}
/**
* sde_encoder_phys_wb_setup - setup writeback encoder
* @phys_enc: Pointer to physical encoder
*/
static void sde_encoder_phys_wb_setup(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct drm_display_mode mode = phys_enc->cached_mode;
struct drm_connector_state *conn_state = phys_enc->connector->state;
struct drm_crtc_state *crtc_state = wb_enc->crtc->state;
struct drm_framebuffer *fb;
struct sde_rect *wb_roi = &wb_enc->wb_roi;
u32 out_width = 0, out_height = 0;
SDE_DEBUG("[enc:%d wb:%d] mode_set:\"%s\",%d,%d]\n", DRMID(phys_enc->parent),
WBID(wb_enc), mode.name, mode.hdisplay, mode.vdisplay);
memset(wb_roi, 0, sizeof(struct sde_rect));
/* clear writeback framebuffer - will be updated in setup_fb */
wb_enc->wb_fb = NULL;
wb_enc->wb_aspace = NULL;
if (phys_enc->enable_state == SDE_ENC_DISABLING) {
fb = wb_enc->fb_disable;
wb_roi->w = 0;
wb_roi->h = 0;
} else {
fb = sde_wb_get_output_fb(wb_enc->wb_dev);
sde_wb_get_output_roi(wb_enc->wb_dev, wb_roi);
}
if (!fb) {
SDE_DEBUG("[enc:%d wb:%d] no out fb\n", DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
SDE_DEBUG("[fb_id:%u][fb:%u,%u]\n", fb->base.id, fb->width, fb->height);
_sde_enc_phys_wb_get_out_resolution(crtc_state, conn_state, &out_width, &out_height);
if (wb_roi->w == 0 || wb_roi->h == 0) {
wb_roi->x = 0;
wb_roi->y = 0;
wb_roi->w = out_width;
wb_roi->h = out_height;
}
wb_enc->wb_fmt = sde_get_sde_format_ext(fb->format->format,
fb->modifier);
if (!wb_enc->wb_fmt) {
SDE_ERROR("[enc:%d wb:%d] unsupported output pixel format:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), fb->format->format);
return;
}
SDE_DEBUG("[enc:%d enc:%d] fb_id:%u, wxh:%ux%u, fb_fmt:%x,%llx, roi:{%d,%d,%d,%d}\n",
DRMID(phys_enc->parent), WBID(wb_enc), fb->base.id, fb->width, fb->height,
fb->format->format, fb->modifier, wb_roi->x, wb_roi->y, wb_roi->w, wb_roi->h);
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc), wb_roi->x, wb_roi->y, wb_roi->w, wb_roi->h,
out_width, out_height, fb->width, fb->height, mode.hdisplay, mode.vdisplay);
sde_encoder_phys_wb_set_ot_limit(phys_enc);
sde_encoder_phys_wb_set_qos_remap(phys_enc);
sde_encoder_phys_wb_set_qos(phys_enc);
sde_encoder_phys_setup_cdm(phys_enc, fb, wb_enc->wb_fmt, wb_roi);
sde_encoder_phys_wb_setup_fb(phys_enc, fb, wb_roi, out_width, out_height);
_sde_encoder_phys_wb_setup_ctl(phys_enc, wb_enc->wb_fmt);
_sde_encoder_phys_wb_setup_sys_cache(phys_enc, fb);
_sde_encoder_phys_wb_setup_cwb(phys_enc, true);
_sde_encoder_phys_wb_setup_prog_line(phys_enc);
_sde_encoder_phys_wb_setup_dnsc_blur(phys_enc);
}
static void sde_encoder_phys_wb_ctl_start_irq(void *arg, int irq_idx)
{
struct sde_encoder_phys_wb *wb_enc = arg;
struct sde_encoder_phys *phys_enc;
struct sde_hw_wb *hw_wb;
u32 line_cnt = 0;
if (!wb_enc)
return;
SDE_ATRACE_BEGIN("ctl_start_irq");
phys_enc = &wb_enc->base;
if (atomic_add_unless(&phys_enc->pending_ctl_start_cnt, -1, 0))
wake_up_all(&phys_enc->pending_kickoff_wq);
hw_wb = wb_enc->hw_wb;
if (hw_wb->ops.get_line_count)
line_cnt = hw_wb->ops.get_line_count(hw_wb);
SDE_ATRACE_END("ctl_start_irq");
SDE_EVT32_IRQ(DRMID(phys_enc->parent), WBID(wb_enc), line_cnt);
}
static void _sde_encoder_phys_wb_frame_done_helper(void *arg, bool frame_error)
{
struct sde_encoder_phys_wb *wb_enc = arg;
struct sde_encoder_phys *phys_enc = &wb_enc->base;
u32 event = frame_error ? SDE_ENCODER_FRAME_EVENT_ERROR : 0;
u32 ubwc_error = 0;
unsigned long flags;
/* don't notify upper layer for internal commit */
if (phys_enc->enable_state == SDE_ENC_DISABLING && !phys_enc->in_clone_mode)
goto end;
spin_lock_irqsave(phys_enc->enc_spinlock, flags);
if (phys_enc->parent_ops.handle_frame_done &&
atomic_add_unless(&phys_enc->pending_kickoff_cnt, -1, 0)) {
event |= SDE_ENCODER_FRAME_EVENT_DONE;
/*
* signal retire-fence during wb-done
* - when prog_line is not configured
* - when prog_line is configured and line-ptr-irq is missed
*/
if (!wb_enc->prog_line || (wb_enc->prog_line &&
(atomic_read(&phys_enc->pending_kickoff_cnt) <
atomic_read(&phys_enc->pending_retire_fence_cnt)))) {
atomic_add_unless(&phys_enc->pending_retire_fence_cnt, -1, 0);
event |= SDE_ENCODER_FRAME_EVENT_SIGNAL_RETIRE_FENCE;
}
if (phys_enc->in_clone_mode)
event |= SDE_ENCODER_FRAME_EVENT_CWB_DONE
| SDE_ENCODER_FRAME_EVENT_SIGNAL_RETIRE_FENCE;
else
event |= SDE_ENCODER_FRAME_EVENT_SIGNAL_RELEASE_FENCE;
}
spin_unlock_irqrestore(phys_enc->enc_spinlock, flags);
if (event & SDE_ENCODER_FRAME_EVENT_DONE)
phys_enc->parent_ops.handle_frame_done(phys_enc->parent, phys_enc, event);
if (!phys_enc->in_clone_mode && phys_enc->parent_ops.handle_vblank_virt)
phys_enc->parent_ops.handle_vblank_virt(phys_enc->parent, phys_enc);
end:
if (frame_error && wb_enc->hw_wb->ops.get_ubwc_error
&& wb_enc->hw_wb->ops.clear_ubwc_error) {
wb_enc->hw_wb->ops.get_ubwc_error(wb_enc->hw_wb);
wb_enc->hw_wb->ops.clear_ubwc_error(wb_enc->hw_wb);
}
SDE_EVT32_IRQ(DRMID(phys_enc->parent), WBID(wb_enc), phys_enc->in_clone_mode,
phys_enc->enable_state, event, atomic_read(&phys_enc->pending_kickoff_cnt),
atomic_read(&phys_enc->pending_retire_fence_cnt),
ubwc_error, frame_error);
wake_up_all(&phys_enc->pending_kickoff_wq);
}
/**
* sde_encoder_phys_wb_done_irq - Pingpong overflow interrupt handler for CWB
* @arg: Pointer to writeback encoder
* @irq_idx: interrupt index
*/
static void sde_encoder_phys_cwb_ovflow(void *arg, int irq_idx)
{
_sde_encoder_phys_wb_frame_done_helper(arg, true);
}
/**
* sde_encoder_phys_wb_done_irq - writeback interrupt handler
* @arg: Pointer to writeback encoder
* @irq_idx: interrupt index
*/
static void sde_encoder_phys_wb_done_irq(void *arg, int irq_idx)
{
SDE_ATRACE_BEGIN("wb_done_irq");
_sde_encoder_phys_wb_frame_done_helper(arg, false);
SDE_ATRACE_END("wb_done_irq");
}
static void sde_encoder_phys_wb_lineptr_irq(void *arg, int irq_idx)
{
struct sde_encoder_phys_wb *wb_enc = arg;
struct sde_encoder_phys *phys_enc;
struct sde_hw_wb *hw_wb;
u32 event = 0, line_cnt = 0;
if (!wb_enc || !wb_enc->prog_line)
return;
SDE_ATRACE_BEGIN("wb_lineptr_irq");
phys_enc = &wb_enc->base;
if (phys_enc->parent_ops.handle_frame_done &&
atomic_add_unless(&phys_enc->pending_retire_fence_cnt, -1, 0)) {
event = SDE_ENCODER_FRAME_EVENT_SIGNAL_RETIRE_FENCE;
phys_enc->parent_ops.handle_frame_done(phys_enc->parent, phys_enc, event);
}
hw_wb = wb_enc->hw_wb;
if (hw_wb->ops.get_line_count)
line_cnt = hw_wb->ops.get_line_count(hw_wb);
SDE_ATRACE_END("wb_lineptr_irq");
SDE_EVT32_IRQ(DRMID(phys_enc->parent), WBID(wb_enc), event, wb_enc->prog_line, line_cnt);
}
/**
* sde_encoder_phys_wb_irq_ctrl - irq control of WB
* @phys: Pointer to physical encoder
* @enable: indicates enable or disable interrupts
*/
static void sde_encoder_phys_wb_irq_ctrl(struct sde_encoder_phys *phys, bool enable)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys);
const struct sde_wb_cfg *wb_cfg;
int index = 0, pp = 0;
u32 max_num_of_irqs = 0;
const u32 *irq_table = NULL;
if (!wb_enc)
return;
pp = phys->hw_pp->idx - PINGPONG_0;
if ((pp + CRTC_DUAL_MIXERS_ONLY) >= PINGPONG_MAX) {
SDE_ERROR("[enc:%d wb:%d] invalid pp:%d\n", DRMID(phys->parent), WBID(wb_enc), pp);
return;
}
/*
* For Dedicated CWB, only one overflow IRQ is used for
* both the PP_CWB blks. Make sure only one IRQ is registered
* when D-CWB is enabled.
*/
wb_cfg = wb_enc->hw_wb->caps;
if (wb_cfg->features & BIT(SDE_WB_HAS_DCWB)) {
max_num_of_irqs = 1;
irq_table = dcwb_irq_tbl;
} else {
max_num_of_irqs = CRTC_DUAL_MIXERS_ONLY;
irq_table = cwb_irq_tbl;
}
if (enable && atomic_inc_return(&phys->wbirq_refcount) == 1) {
sde_encoder_helper_register_irq(phys, INTR_IDX_WB_DONE);
sde_encoder_helper_register_irq(phys, INTR_IDX_CTL_START);
if (test_bit(SDE_WB_PROG_LINE, &wb_cfg->features))
sde_encoder_helper_register_irq(phys, INTR_IDX_WB_LINEPTR);
for (index = 0; index < max_num_of_irqs; index++)
if (irq_table[index + pp] != SDE_NONE)
sde_encoder_helper_register_irq(phys, irq_table[index + pp]);
} else if (!enable && atomic_dec_return(&phys->wbirq_refcount) == 0) {
sde_encoder_helper_unregister_irq(phys, INTR_IDX_WB_DONE);
sde_encoder_helper_unregister_irq(phys, INTR_IDX_CTL_START);
if (test_bit(SDE_WB_PROG_LINE, &wb_cfg->features))
sde_encoder_helper_unregister_irq(phys, INTR_IDX_WB_LINEPTR);
for (index = 0; index < max_num_of_irqs; index++)
if (irq_table[index + pp] != SDE_NONE)
sde_encoder_helper_unregister_irq(phys, irq_table[index + pp]);
}
}
/**
* sde_encoder_phys_wb_mode_set - set display mode
* @phys_enc: Pointer to physical encoder
* @mode: Pointer to requested display mode
* @adj_mode: Pointer to adjusted display mode
*/
static void sde_encoder_phys_wb_mode_set(
struct sde_encoder_phys *phys_enc,
struct drm_display_mode *mode,
struct drm_display_mode *adj_mode, bool *reinit_mixers)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_rm *rm = &phys_enc->sde_kms->rm;
struct sde_rm_hw_iter iter;
int i, instance;
struct sde_encoder_irq *irq;
phys_enc->cached_mode = *adj_mode;
instance = phys_enc->split_role == ENC_ROLE_SLAVE ? 1 : 0;
SDE_DEBUG("[enc:%d wb:%d] mode_set_cache:\"%s\",%d,%d\n", DRMID(phys_enc->parent),
WBID(wb_enc), mode->name, mode->hdisplay, mode->vdisplay);
phys_enc->hw_ctl = NULL;
phys_enc->hw_cdm = NULL;
phys_enc->hw_dnsc_blur = NULL;
/* Retrieve previously allocated HW Resources. CTL shouldn't fail */
sde_rm_init_hw_iter(&iter, phys_enc->parent->base.id, SDE_HW_BLK_CTL);
for (i = 0; i <= instance; i++) {
sde_rm_get_hw(rm, &iter);
if (i == instance) {
if (phys_enc->hw_ctl && phys_enc->hw_ctl != to_sde_hw_ctl(iter.hw)) {
*reinit_mixers = true;
SDE_EVT32(phys_enc->hw_ctl->idx, to_sde_hw_ctl(iter.hw)->idx);
}
phys_enc->hw_ctl = to_sde_hw_ctl(iter.hw);
}
}
if (IS_ERR_OR_NULL(phys_enc->hw_ctl)) {
SDE_ERROR("[enc:%d, wb:%d] failed init ctl: %ld\n", DRMID(phys_enc->parent),
WBID(wb_enc), (!phys_enc->hw_ctl) ? -EINVAL : PTR_ERR(phys_enc->hw_ctl));
phys_enc->hw_ctl = NULL;
return;
}
/* CDM is optional */
sde_rm_init_hw_iter(&iter, phys_enc->parent->base.id, SDE_HW_BLK_CDM);
for (i = 0; i <= instance; i++) {
sde_rm_get_hw(rm, &iter);
if (i == instance)
phys_enc->hw_cdm = to_sde_hw_cdm(iter.hw);
}
if (IS_ERR(phys_enc->hw_cdm)) {
SDE_ERROR("[enc:%d wb:%d] CDM required but not allocated:%ld\n",
DRMID(phys_enc->parent), WBID(wb_enc), PTR_ERR(phys_enc->hw_cdm));
phys_enc->hw_cdm = NULL;
}
/* Downscale Blur is optional */
sde_rm_init_hw_iter(&iter, phys_enc->parent->base.id, SDE_HW_BLK_DNSC_BLUR);
for (i = 0; i <= instance; i++) {
sde_rm_get_hw(rm, &iter);
if (i == instance)
phys_enc->hw_dnsc_blur = to_sde_hw_dnsc_blur(iter.hw);
}
if (IS_ERR(phys_enc->hw_dnsc_blur)) {
SDE_ERROR("[enc:%d wb:%d] Downscale Blur required but not allocated:%ld\n",
DRMID(phys_enc->parent), WBID(wb_enc), PTR_ERR(phys_enc->hw_dnsc_blur));
phys_enc->hw_dnsc_blur = NULL;
}
phys_enc->kickoff_timeout_ms =
sde_encoder_helper_get_kickoff_timeout_ms(phys_enc->parent);
/* set ctl idx for ctl-start-irq */
irq = &phys_enc->irq[INTR_IDX_CTL_START];
irq->hw_idx = phys_enc->hw_ctl->idx;
}
static bool _sde_encoder_phys_wb_is_idle(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
struct sde_vbif_get_xin_status_params xin_status = {0};
xin_status.vbif_idx = hw_wb->caps->vbif_idx;
xin_status.xin_id = hw_wb->caps->xin_id;
xin_status.clk_ctrl = hw_wb->caps->clk_ctrl;
return sde_vbif_get_xin_status(phys_enc->sde_kms, &xin_status);
}
static void _sde_encoder_phys_wb_reset_state(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_encoder_virt *sde_enc = to_sde_encoder_virt(phys_enc->parent);
struct sde_wb_device *wb_dev = wb_enc->wb_dev;
struct sde_crtc *sde_crtc;
phys_enc->enable_state = SDE_ENC_DISABLED;
/* cleanup any pending buffer */
if (wb_enc->wb_fb && wb_enc->wb_aspace) {
msm_framebuffer_cleanup(wb_enc->wb_fb, wb_enc->wb_aspace);
drm_framebuffer_put(wb_enc->wb_fb);
wb_enc->wb_fb = NULL;
wb_enc->wb_aspace = NULL;
}
sde_crtc = to_sde_crtc(sde_enc->crtc);
if (sde_crtc)
sde_crtc->cached_encoder_mask &= ~drm_encoder_mask(phys_enc->parent);
wb_enc->crtc = NULL;
phys_enc->hw_cdm = NULL;
phys_enc->hw_ctl = NULL;
phys_enc->in_clone_mode = false;
kfree(wb_dev->modes);
wb_dev->modes = NULL;
wb_dev->count_modes = 0;
atomic_set(&phys_enc->pending_kickoff_cnt, 0);
atomic_set(&phys_enc->pending_retire_fence_cnt, 0);
atomic_set(&phys_enc->pending_ctl_start_cnt, 0);
}
static int _sde_encoder_phys_wb_wait_for_idle(struct sde_encoder_phys *phys_enc, bool force_wait)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_encoder_wait_info wait_info = {0};
int rc = 0;
bool is_idle;
/* Return EWOULDBLOCK since we know the wait isn't necessary */
if (phys_enc->enable_state == SDE_ENC_DISABLED) {
SDE_ERROR("enc:%d, wb:%d - encoder already disabled\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return -EWOULDBLOCK;
}
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc), phys_enc->in_clone_mode,
atomic_read(&phys_enc->pending_kickoff_cnt), force_wait);
if (!force_wait && phys_enc->in_clone_mode
&& (atomic_read(&phys_enc->pending_kickoff_cnt) <= 1))
return 0;
/*
* signal completion if commit with no framebuffer
* handle frame-done when WB HW is idle
*/
is_idle = _sde_encoder_phys_wb_is_idle(phys_enc);
if (!wb_enc->wb_fb || is_idle) {
SDE_EVT32((phys_enc->parent), WBID(wb_enc), !wb_enc->wb_fb, is_idle);
goto frame_done;
}
if (atomic_read(&phys_enc->pending_kickoff_cnt) > 1)
wait_info.count_check = 1;
wait_info.wq = &phys_enc->pending_kickoff_wq;
wait_info.atomic_cnt = &phys_enc->pending_kickoff_cnt;
wait_info.timeout_ms = max_t(u32, wb_enc->wbdone_timeout, phys_enc->kickoff_timeout_ms);
rc = sde_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_WB_DONE, &wait_info);
if (rc == -ETIMEDOUT) {
/* handle frame-done when WB HW is idle */
if (_sde_encoder_phys_wb_is_idle(phys_enc))
rc = 0;
SDE_ERROR("caller:%pS [enc:%d, wb:%d] clone_mode:%d kickoff timed out\n",
__builtin_return_address(0), DRMID(phys_enc->parent), WBID(wb_enc),
phys_enc->in_clone_mode);
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc),
atomic_read(&phys_enc->pending_kickoff_cnt), SDE_EVTLOG_ERROR);
goto frame_done;
}
return 0;
frame_done:
_sde_encoder_phys_wb_frame_done_helper(wb_enc, rc ? true : false);
return rc;
}
static int _sde_encoder_phys_wb_wait_for_ctl_start(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_encoder_wait_info wait_info = {0};
struct sde_hw_ctl *hw_ctl = phys_enc->hw_ctl;
int rc = 0;
if (!atomic_read(&phys_enc->pending_ctl_start_cnt))
return 0;
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc), phys_enc->in_clone_mode,
atomic_read(&phys_enc->pending_kickoff_cnt),
atomic_read(&phys_enc->pending_retire_fence_cnt),
atomic_read(&phys_enc->pending_ctl_start_cnt));
wait_info.wq = &phys_enc->pending_kickoff_wq;
wait_info.atomic_cnt = &phys_enc->pending_ctl_start_cnt;
wait_info.timeout_ms = max_t(u32, wb_enc->wbdone_timeout, phys_enc->kickoff_timeout_ms);
rc = sde_encoder_helper_wait_for_irq(phys_enc, INTR_IDX_CTL_START, &wait_info);
/*
* if hwfencing enabled, try again to wait for up to the extended timeout time in
* increments as long as fence has not been signaled.
*/
if (rc == -ETIMEDOUT && phys_enc->sde_kms->catalog->hw_fence_rev && hw_ctl)
rc = sde_encoder_helper_hw_fence_extended_wait(phys_enc, hw_ctl,
&wait_info, INTR_IDX_CTL_START);
if (rc == -ETIMEDOUT) {
atomic_add_unless(&phys_enc->pending_ctl_start_cnt, -1, 0);
/* if we timeout after the extended wait, reset mixers and do sw override */
if (phys_enc->sde_kms->catalog->hw_fence_rev)
sde_encoder_helper_hw_fence_sw_override(phys_enc, hw_ctl);
SDE_ERROR("[enc:%d wb:%d] ctl_start timed out\n",
DRMID(phys_enc->parent), WBID(wb_enc));
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc), SDE_EVTLOG_ERROR);
}
return rc;
}
/**
* sde_encoder_phys_wb_wait_for_commit_done - wait until request is committed
* @phys_enc: Pointer to physical encoder
*/
static int sde_encoder_phys_wb_wait_for_commit_done(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
int rc, pending_cnt, i;
bool is_idle;
/* CWB - wait for previous frame completion */
if (phys_enc->in_clone_mode) {
rc = _sde_encoder_phys_wb_wait_for_idle(phys_enc, false);
goto end;
}
/*
* WB - wait for ctl-start-irq by default and additionally for
* wb-done-irq during timeout or serialize frame-trigger
*/
rc = _sde_encoder_phys_wb_wait_for_ctl_start(phys_enc);
pending_cnt = atomic_read(&phys_enc->pending_kickoff_cnt);
is_idle = _sde_encoder_phys_wb_is_idle(phys_enc);
if (rc || (pending_cnt > 1) || (pending_cnt && is_idle)
|| (!rc && (phys_enc->frame_trigger_mode == FRAME_DONE_WAIT_SERIALIZE))) {
for (i = 0; i < pending_cnt; i++)
rc |= _sde_encoder_phys_wb_wait_for_idle(phys_enc, true);
if (rc) {
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc),
phys_enc->frame_trigger_mode,
atomic_read(&phys_enc->pending_kickoff_cnt), is_idle, rc);
SDE_ERROR("[enc:%d, wb:%d] failed wait_for_idle; ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), rc);
}
}
end:
/* cleanup any pending previous buffer */
if (wb_enc->old_fb && wb_enc->old_aspace) {
msm_framebuffer_cleanup(wb_enc->old_fb, wb_enc->old_aspace);
drm_framebuffer_put(wb_enc->old_fb);
wb_enc->old_fb = NULL;
wb_enc->old_aspace = NULL;
}
return rc;
}
static int sde_encoder_phys_wb_wait_for_tx_complete(struct sde_encoder_phys *phys_enc)
{
int rc = 0;
unsigned long flags;
if (atomic_read(&phys_enc->pending_kickoff_cnt))
rc = _sde_encoder_phys_wb_wait_for_idle(phys_enc, true);
if ((phys_enc->enable_state == SDE_ENC_DISABLING) && phys_enc->in_clone_mode) {
spin_lock_irqsave(phys_enc->enc_spinlock, flags);
_sde_encoder_phys_wb_reset_state(phys_enc);
spin_unlock_irqrestore(phys_enc->enc_spinlock, flags);
sde_encoder_phys_wb_irq_ctrl(phys_enc, false);
}
return rc;
}
/**
* sde_encoder_phys_wb_prepare_for_kickoff - pre-kickoff processing
* @phys_enc: Pointer to physical encoder
* @params: kickoff parameters
* Returns: Zero on success
*/
static int sde_encoder_phys_wb_prepare_for_kickoff(struct sde_encoder_phys *phys_enc,
struct sde_encoder_kickoff_params *params)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
int ret = 0;
phys_enc->frame_trigger_mode = params ?
params->frame_trigger_mode : FRAME_DONE_WAIT_DEFAULT;
if (!phys_enc->in_clone_mode && (phys_enc->frame_trigger_mode == FRAME_DONE_WAIT_DEFAULT)
&& (atomic_read(&phys_enc->pending_kickoff_cnt))) {
ret = _sde_encoder_phys_wb_wait_for_idle(phys_enc, true);
if (ret)
atomic_set(&phys_enc->pending_kickoff_cnt, 0);
}
/* cache the framebuffer/aspace for cleanup later */
wb_enc->old_fb = wb_enc->wb_fb;
wb_enc->old_aspace = wb_enc->wb_aspace;
/* set OT limit & enable traffic shaper */
sde_encoder_phys_wb_setup(phys_enc);
_sde_encoder_phys_wb_update_flush(phys_enc);
_sde_encoder_phys_wb_update_cwb_flush(phys_enc, true);
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc), phys_enc->in_clone_mode,
phys_enc->frame_trigger_mode, ret);
return ret;
}
/**
* sde_encoder_phys_wb_trigger_flush - trigger flush processing
* @phys_enc: Pointer to physical encoder
*/
static void sde_encoder_phys_wb_trigger_flush(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
if (!phys_enc || !wb_enc->hw_wb) {
SDE_ERROR("invalid encoder\n");
return;
}
/*
* Bail out iff in CWB mode. In case of CWB, primary control-path
* which is actually driving would trigger the flush
*/
if (phys_enc->in_clone_mode) {
SDE_DEBUG("[enc:%d wb:%d] in CWB mode. early return\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
SDE_DEBUG("[enc:%d wb:%d]\n", DRMID(phys_enc->parent), WBID(wb_enc));
/* clear pending flush if commit with no framebuffer */
if (!wb_enc->wb_fb) {
SDE_DEBUG("[enc:%d wb:%d] no out FB\n", DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
sde_encoder_helper_trigger_flush(phys_enc);
}
/**
* _sde_encoder_phys_wb_init_internal_fb - create fb for internal commit
* @wb_enc: Pointer to writeback encoder
* @pixel_format: DRM pixel format
* @width: Desired fb width
* @height: Desired fb height
* @pitch: Desired fb pitch
*/
static int _sde_encoder_phys_wb_init_internal_fb(struct sde_encoder_phys_wb *wb_enc,
uint32_t pixel_format, uint32_t width, uint32_t height, uint32_t pitch)
{
struct drm_device *dev;
struct drm_framebuffer *fb;
struct drm_mode_fb_cmd2 mode_cmd;
uint32_t size;
int nplanes, i, ret;
struct msm_gem_address_space *aspace;
const struct drm_format_info *info;
struct sde_encoder_phys *phys_enc;
if (!wb_enc || !wb_enc->base.parent || !wb_enc->base.sde_kms) {
SDE_ERROR("invalid params\n");
return -EINVAL;
}
phys_enc = &wb_enc->base;
aspace = wb_enc->base.sde_kms->aspace[SDE_IOMMU_DOMAIN_UNSECURE];
if (!aspace) {
SDE_ERROR("[enc:%d wb:%d] invalid aspace\n", DRMID(phys_enc->parent), WBID(wb_enc));
return -EINVAL;
}
dev = wb_enc->base.sde_kms->dev;
if (!dev) {
SDE_ERROR("[enc:%d wb:%d] invalid dev\n", DRMID(phys_enc->parent), WBID(wb_enc));
return -EINVAL;
}
memset(&mode_cmd, 0, sizeof(mode_cmd));
mode_cmd.pixel_format = pixel_format;
mode_cmd.width = width;
mode_cmd.height = height;
mode_cmd.pitches[0] = pitch;
size = sde_format_get_framebuffer_size(pixel_format, mode_cmd.width, mode_cmd.height,
mode_cmd.pitches, 0);
if (!size) {
SDE_DEBUG("[enc:%d wb:%d] invalid fbsize\n", DRMID(phys_enc->parent), WBID(wb_enc));
return -EINVAL;
}
/* allocate gem tracking object */
info = drm_get_format_info(dev, &mode_cmd);
nplanes = info->num_planes;
if (nplanes >= SDE_MAX_PLANES) {
SDE_ERROR("[enc:%d wb:%d] requested format has too many planes:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), nplanes);
return -EINVAL;
}
wb_enc->bo_disable[0] = msm_gem_new(dev, size, MSM_BO_SCANOUT | MSM_BO_WC);
if (IS_ERR_OR_NULL(wb_enc->bo_disable[0])) {
ret = PTR_ERR(wb_enc->bo_disable[0]);
wb_enc->bo_disable[0] = NULL;
SDE_ERROR("[enc:%d wb:%d] failed to create bo; ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), ret);
return ret;
}
for (i = 0; i < nplanes; ++i) {
wb_enc->bo_disable[i] = wb_enc->bo_disable[0];
mode_cmd.pitches[i] = width * info->cpp[i];
}
fb = msm_framebuffer_init(dev, &mode_cmd, wb_enc->bo_disable);
if (IS_ERR_OR_NULL(fb)) {
ret = PTR_ERR(fb);
drm_gem_object_put(wb_enc->bo_disable[0]);
wb_enc->bo_disable[0] = NULL;
SDE_ERROR("[enc:%d wb:%d] failed to init fb; ret:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), ret);
return ret;
}
/* prepare the backing buffer now so that it's available later */
ret = msm_framebuffer_prepare(fb, aspace);
if (!ret)
wb_enc->fb_disable = fb;
return ret;
}
/**
* _sde_encoder_phys_wb_destroy_internal_fb - deconstruct internal fb
* @wb_enc: Pointer to writeback encoder
*/
static void _sde_encoder_phys_wb_destroy_internal_fb(
struct sde_encoder_phys_wb *wb_enc)
{
if (!wb_enc)
return;
if (wb_enc->fb_disable) {
drm_framebuffer_unregister_private(wb_enc->fb_disable);
drm_framebuffer_remove(wb_enc->fb_disable);
wb_enc->fb_disable = NULL;
}
if (wb_enc->bo_disable[0]) {
drm_gem_object_put(wb_enc->bo_disable[0]);
wb_enc->bo_disable[0] = NULL;
}
}
/**
* sde_encoder_phys_wb_enable - enable writeback encoder
* @phys_enc: Pointer to physical encoder
*/
static void sde_encoder_phys_wb_enable(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct drm_device *dev;
struct drm_connector *connector;
SDE_DEBUG("[enc:%d wb:%d]\n", DRMID(phys_enc->parent), WBID(wb_enc));
if (!wb_enc->base.parent || !wb_enc->base.parent->dev) {
SDE_ERROR("[enc:%d, wb:%d] invalid dev\n", DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
dev = wb_enc->base.parent->dev;
/* find associated writeback connector */
connector = phys_enc->connector;
if (!connector || connector->encoder != phys_enc->parent) {
SDE_ERROR("[enc:%d, wb:%d] failed to find writeback connector\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
wb_enc->wb_dev = sde_wb_connector_get_wb(connector);
phys_enc->enable_state = SDE_ENC_ENABLED;
/*
* cache the crtc in wb_enc on enable for duration of use case
* for correctly servicing asynchronous irq events and timers
*/
wb_enc->crtc = phys_enc->parent->crtc;
}
/**
* sde_encoder_phys_wb_disable - disable writeback encoder
* @phys_enc: Pointer to physical encoder
*/
static void sde_encoder_phys_wb_disable(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
struct sde_hw_wb *hw_wb = wb_enc->hw_wb;
struct sde_crtc *sde_crtc = to_sde_crtc(wb_enc->crtc);
struct sde_hw_wb_sc_cfg cfg = { 0 };
int i;
if (phys_enc->enable_state == SDE_ENC_DISABLED) {
SDE_ERROR("[enc:%d wb:%d] encoder is already disabled\n",
DRMID(phys_enc->parent), WBID(wb_enc));
return;
}
SDE_DEBUG("[enc:%d, wb:%d] clone_mode:%d, kickoff_cnt:%u\n",
DRMID(phys_enc->parent), WBID(wb_enc), phys_enc->in_clone_mode,
atomic_read(&phys_enc->pending_kickoff_cnt));
if (!phys_enc->hw_ctl || !phys_enc->parent ||
!phys_enc->sde_kms || !wb_enc->fb_disable) {
SDE_DEBUG("[enc:%d wb:%d] invalid hw; skipping extra commit\n",
DRMID(phys_enc->parent), WBID(wb_enc));
goto exit;
}
/* reset system cache properties */
if (wb_enc->sc_cfg.wr_en) {
if (hw_wb->ops.setup_sys_cache)
hw_wb->ops.setup_sys_cache(hw_wb, &cfg);
/*
* avoid llcc_active reset for crtc while in clone mode as it will reset it for
* primary display as well
*/
if (!phys_enc->in_clone_mode) {
for (i = 0; i < SDE_SYS_CACHE_MAX; i++)
sde_crtc->new_perf.llcc_active[i] = 0;
sde_core_perf_crtc_update_llcc(wb_enc->crtc);
}
}
if (phys_enc->in_clone_mode) {
_sde_encoder_phys_wb_setup_cwb(phys_enc, false);
_sde_encoder_phys_wb_update_cwb_flush(phys_enc, false);
phys_enc->enable_state = SDE_ENC_DISABLING;
if (wb_enc->crtc->state->active) {
sde_encoder_phys_wb_irq_ctrl(phys_enc, true);
return;
}
if (phys_enc->connector)
sde_connector_commit_reset(phys_enc->connector, ktime_get());
goto exit;
}
/* reset h/w before final flush */
if (phys_enc->hw_ctl->ops.clear_pending_flush)
phys_enc->hw_ctl->ops.clear_pending_flush(phys_enc->hw_ctl);
/*
* New CTL reset sequence from 5.0 MDP onwards.
* If has_3d_merge_reset is not set, legacy reset
* sequence is executed.
*/
if (test_bit(SDE_FEATURE_3D_MERGE_RESET, hw_wb->catalog->features)) {
sde_encoder_helper_phys_disable(phys_enc, wb_enc);
goto exit;
}
if (sde_encoder_helper_reset_mixers(phys_enc, NULL))
goto exit;
phys_enc->enable_state = SDE_ENC_DISABLING;
sde_encoder_phys_wb_prepare_for_kickoff(phys_enc, NULL);
sde_encoder_phys_wb_irq_ctrl(phys_enc, true);
if (phys_enc->hw_ctl->ops.trigger_flush)
phys_enc->hw_ctl->ops.trigger_flush(phys_enc->hw_ctl);
sde_encoder_helper_trigger_start(phys_enc);
_sde_encoder_phys_wb_wait_for_idle(phys_enc, true);
sde_encoder_phys_wb_irq_ctrl(phys_enc, false);
exit:
SDE_EVT32(DRMID(phys_enc->parent), WBID(wb_enc), phys_enc->in_clone_mode);
_sde_encoder_phys_wb_reset_state(phys_enc);
}
/**
* sde_encoder_phys_wb_get_hw_resources - get hardware resources
* @phys_enc: Pointer to physical encoder
* @hw_res: Pointer to encoder resources
*/
static void sde_encoder_phys_wb_get_hw_resources(struct sde_encoder_phys *phys_enc,
struct sde_encoder_hw_resources *hw_res, struct drm_connector_state *conn_state)
{
struct sde_encoder_phys_wb *wb_enc;
struct sde_hw_wb *hw_wb;
struct drm_framebuffer *fb;
const struct sde_format *fmt = NULL;
if (!phys_enc) {
SDE_ERROR("invalid encoder\n");
return;
}
wb_enc = to_sde_encoder_phys_wb(phys_enc);
fb = sde_wb_connector_state_get_output_fb(conn_state);
if (fb) {
fmt = sde_get_sde_format_ext(fb->format->format, fb->modifier);
if (!fmt) {
SDE_ERROR("[enc:%d wb:%d] unsupported output pixel format:%d\n",
DRMID(phys_enc->parent), WBID(wb_enc), fb->format->format);
return;
}
}
hw_wb = wb_enc->hw_wb;
hw_res->wbs[hw_wb->idx - WB_0] = phys_enc->intf_mode;
hw_res->needs_cdm = fmt ? SDE_FORMAT_IS_YUV(fmt) : false;
SDE_DEBUG("[enc:%d wb:%d] intf_mode:%d needs_cdm:%d\n", DRMID(phys_enc->parent),
WBID(wb_enc), hw_res->wbs[hw_wb->idx - WB_0], hw_res->needs_cdm);
}
#if IS_ENABLED(CONFIG_DEBUG_FS)
/**
* sde_encoder_phys_wb_init_debugfs - initialize writeback encoder debugfs
* @phys_enc: Pointer to physical encoder
* @debugfs_root: Pointer to virtual encoder's debugfs_root dir
*/
static int sde_encoder_phys_wb_init_debugfs(
struct sde_encoder_phys *phys_enc, struct dentry *debugfs_root)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
if (!phys_enc || !wb_enc->hw_wb || !debugfs_root)
return -EINVAL;
debugfs_create_u32("wbdone_timeout", 0600, debugfs_root, &wb_enc->wbdone_timeout);
return 0;
}
#else
static int sde_encoder_phys_wb_init_debugfs(
struct sde_encoder_phys *phys_enc, struct dentry *debugfs_root)
{
return 0;
}
#endif /* CONFIG_DEBUG_FS */
static int sde_encoder_phys_wb_late_register(struct sde_encoder_phys *phys_enc,
struct dentry *debugfs_root)
{
return sde_encoder_phys_wb_init_debugfs(phys_enc, debugfs_root);
}
/**
* sde_encoder_phys_wb_destroy - destroy writeback encoder
* @phys_enc: Pointer to physical encoder
*/
static void sde_encoder_phys_wb_destroy(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc;
if (!phys_enc)
return;
wb_enc = to_sde_encoder_phys_wb(phys_enc);
SDE_DEBUG("[enc:%d wb:%d]\n", DRMID(phys_enc->parent), WBID(wb_enc));
_sde_encoder_phys_wb_destroy_internal_fb(wb_enc);
kfree(wb_enc);
}
void sde_encoder_phys_wb_add_enc_to_minidump(struct sde_encoder_phys *phys_enc)
{
struct sde_encoder_phys_wb *wb_enc = to_sde_encoder_phys_wb(phys_enc);
sde_mini_dump_add_va_region("sde_enc_phys_wb", sizeof(*wb_enc), wb_enc);
}
/**
* sde_encoder_phys_wb_init_ops - initialize writeback operations
* @ops: Pointer to encoder operation table
*/
static void sde_encoder_phys_wb_init_ops(struct sde_encoder_phys_ops *ops)
{
ops->late_register = sde_encoder_phys_wb_late_register;
ops->is_master = sde_encoder_phys_wb_is_master;
ops->mode_set = sde_encoder_phys_wb_mode_set;
ops->enable = sde_encoder_phys_wb_enable;
ops->disable = sde_encoder_phys_wb_disable;
ops->destroy = sde_encoder_phys_wb_destroy;
ops->atomic_check = sde_encoder_phys_wb_atomic_check;
ops->get_hw_resources = sde_encoder_phys_wb_get_hw_resources;
ops->wait_for_commit_done = sde_encoder_phys_wb_wait_for_commit_done;
ops->wait_for_tx_complete = sde_encoder_phys_wb_wait_for_tx_complete;
ops->prepare_for_kickoff = sde_encoder_phys_wb_prepare_for_kickoff;
ops->trigger_flush = sde_encoder_phys_wb_trigger_flush;
ops->trigger_start = sde_encoder_helper_trigger_start;
ops->hw_reset = sde_encoder_helper_hw_reset;
ops->irq_control = sde_encoder_phys_wb_irq_ctrl;
ops->add_to_minidump = sde_encoder_phys_wb_add_enc_to_minidump;
}
/**
* sde_encoder_phys_wb_init - initialize writeback encoder
* @init: Pointer to init info structure with initialization params
*/
struct sde_encoder_phys *sde_encoder_phys_wb_init(struct sde_enc_phys_init_params *p)
{
struct sde_encoder_phys *phys_enc;
struct sde_encoder_phys_wb *wb_enc;
const struct sde_wb_cfg *wb_cfg;
struct sde_hw_mdp *hw_mdp;
struct sde_encoder_irq *irq;
int ret = 0, i;
SDE_DEBUG("\n");
if (!p || !p->parent) {
SDE_ERROR("invalid params\n");
ret = -EINVAL;
goto fail_alloc;
}
wb_enc = kzalloc(sizeof(*wb_enc), GFP_KERNEL);
if (!wb_enc) {
SDE_ERROR("failed to allocate wb enc\n");
ret = -ENOMEM;
goto fail_alloc;
}
phys_enc = &wb_enc->base;
phys_enc->kickoff_timeout_ms = DEFAULT_KICKOFF_TIMEOUT_MS;
if (p->sde_kms->vbif[VBIF_NRT]) {
wb_enc->aspace[SDE_IOMMU_DOMAIN_UNSECURE] =
p->sde_kms->aspace[MSM_SMMU_DOMAIN_NRT_UNSECURE];
wb_enc->aspace[SDE_IOMMU_DOMAIN_SECURE] =
p->sde_kms->aspace[MSM_SMMU_DOMAIN_NRT_SECURE];
} else {
wb_enc->aspace[SDE_IOMMU_DOMAIN_UNSECURE] =
p->sde_kms->aspace[MSM_SMMU_DOMAIN_UNSECURE];
wb_enc->aspace[SDE_IOMMU_DOMAIN_SECURE] =
p->sde_kms->aspace[MSM_SMMU_DOMAIN_SECURE];
}
hw_mdp = sde_rm_get_mdp(&p->sde_kms->rm);
if (IS_ERR_OR_NULL(hw_mdp)) {
ret = PTR_ERR(hw_mdp);
SDE_ERROR("failed to init hw_top: %d\n", ret);
goto fail_mdp_init;
}
phys_enc->hw_mdptop = hw_mdp;
/**
* hw_wb resource permanently assigned to this encoder
* Other resources allocated at atomic commit time by use case
*/
if (p->wb_idx != SDE_NONE) {
struct sde_rm_hw_iter iter;
sde_rm_init_hw_iter(&iter, 0, SDE_HW_BLK_WB);
while (sde_rm_get_hw(&p->sde_kms->rm, &iter)) {
struct sde_hw_wb *hw_wb = to_sde_hw_wb(iter.hw);
if (hw_wb->idx == p->wb_idx) {
wb_enc->hw_wb = hw_wb;
break;
}
}
if (!wb_enc->hw_wb) {
ret = -EINVAL;
SDE_ERROR("failed to init hw_wb%d\n", p->wb_idx - WB_0);
goto fail_wb_init;
}
} else {
ret = -EINVAL;
SDE_ERROR("invalid wb_idx\n");
goto fail_wb_check;
}
sde_encoder_phys_wb_init_ops(&phys_enc->ops);
phys_enc->parent = p->parent;
phys_enc->parent_ops = p->parent_ops;
phys_enc->sde_kms = p->sde_kms;
phys_enc->split_role = p->split_role;
phys_enc->intf_mode = INTF_MODE_WB_LINE;
phys_enc->intf_idx = p->intf_idx;
phys_enc->enc_spinlock = p->enc_spinlock;
atomic_set(&phys_enc->pending_retire_fence_cnt, 0);
atomic_set(&phys_enc->pending_kickoff_cnt, 0);
atomic_set(&phys_enc->pending_ctl_start_cnt, 0);
init_waitqueue_head(&phys_enc->pending_kickoff_wq);
wb_cfg = wb_enc->hw_wb->caps;
for (i = 0; i < INTR_IDX_MAX; i++) {
irq = &phys_enc->irq[i];
INIT_LIST_HEAD(&irq->cb.list);
irq->irq_idx = -EINVAL;
irq->hw_idx = -EINVAL;
irq->cb.arg = wb_enc;
}
irq = &phys_enc->irq[INTR_IDX_WB_DONE];
irq->name = "wb_done";
irq->hw_idx = wb_enc->hw_wb->idx;
irq->intr_type = sde_encoder_phys_wb_get_intr_type(wb_enc->hw_wb);
irq->intr_idx = INTR_IDX_WB_DONE;
irq->cb.func = sde_encoder_phys_wb_done_irq;
irq = &phys_enc->irq[INTR_IDX_CTL_START];
irq->name = "ctl_start";
irq->intr_type = SDE_IRQ_TYPE_CTL_START;
irq->intr_idx = INTR_IDX_CTL_START;
irq->cb.func = sde_encoder_phys_wb_ctl_start_irq;
irq = &phys_enc->irq[INTR_IDX_WB_LINEPTR];
irq->name = "lineptr_irq";
irq->hw_idx = wb_enc->hw_wb->idx;
irq->intr_type = SDE_IRQ_TYPE_WB_PROG_LINE;
irq->intr_idx = INTR_IDX_WB_LINEPTR;
irq->cb.func = sde_encoder_phys_wb_lineptr_irq;
if (wb_cfg && (wb_cfg->features & BIT(SDE_WB_HAS_DCWB))) {
if (test_bit(SDE_HW_HAS_DUAL_DCWB, &wb_cfg->features)) {
irq = &phys_enc->irq[INTR_IDX_PP_CWB2_OVFL];
irq->name = "pp_cwb2_overflow";
irq->hw_idx = PINGPONG_CWB_2;
irq->intr_type = SDE_IRQ_TYPE_CWB_OVERFLOW;
irq->intr_idx = INTR_IDX_PP_CWB2_OVFL;
irq->cb.func = sde_encoder_phys_cwb_ovflow;
}
irq = &phys_enc->irq[INTR_IDX_PP_CWB_OVFL];
irq->name = "pp_cwb0_overflow";
irq->hw_idx = PINGPONG_CWB_0;
irq->intr_type = SDE_IRQ_TYPE_CWB_OVERFLOW;
irq->intr_idx = INTR_IDX_PP_CWB_OVFL;
irq->cb.func = sde_encoder_phys_cwb_ovflow;
} else {
irq = &phys_enc->irq[INTR_IDX_PP1_OVFL];
irq->name = "pp1_overflow";
irq->hw_idx = CWB_1;
irq->intr_type = SDE_IRQ_TYPE_CWB_OVERFLOW;
irq->intr_idx = INTR_IDX_PP1_OVFL;
irq->cb.func = sde_encoder_phys_cwb_ovflow;
irq = &phys_enc->irq[INTR_IDX_PP2_OVFL];
irq->name = "pp2_overflow";
irq->hw_idx = CWB_2;
irq->intr_type = SDE_IRQ_TYPE_CWB_OVERFLOW;
irq->intr_idx = INTR_IDX_PP2_OVFL;
irq->cb.func = sde_encoder_phys_cwb_ovflow;
irq = &phys_enc->irq[INTR_IDX_PP3_OVFL];
irq->name = "pp3_overflow";
irq->hw_idx = CWB_3;
irq->intr_type = SDE_IRQ_TYPE_CWB_OVERFLOW;
irq->intr_idx = INTR_IDX_PP3_OVFL;
irq->cb.func = sde_encoder_phys_cwb_ovflow;
irq = &phys_enc->irq[INTR_IDX_PP4_OVFL];
irq->name = "pp4_overflow";
irq->hw_idx = CWB_4;
irq->intr_type = SDE_IRQ_TYPE_CWB_OVERFLOW;
irq->intr_idx = INTR_IDX_PP4_OVFL;
irq->cb.func = sde_encoder_phys_cwb_ovflow;
irq = &phys_enc->irq[INTR_IDX_PP5_OVFL];
irq->name = "pp5_overflow";
irq->hw_idx = CWB_5;
irq->intr_type = SDE_IRQ_TYPE_CWB_OVERFLOW;
irq->intr_idx = INTR_IDX_PP5_OVFL;
irq->cb.func = sde_encoder_phys_cwb_ovflow;
}
/* create internal buffer for disable logic */
if (_sde_encoder_phys_wb_init_internal_fb(wb_enc, DRM_FORMAT_RGB888, 2, 1, 6)) {
SDE_ERROR("[enc:%d, wb:%d] failed to init internal fb\n",
DRMID(phys_enc->parent), WBID(wb_enc));
goto fail_wb_init;
}
SDE_DEBUG("[enc:%d wb:%d] Created wb_phys\n", DRMID(phys_enc->parent), WBID(wb_enc));
return phys_enc;
fail_wb_init:
fail_wb_check:
fail_mdp_init:
kfree(wb_enc);
fail_alloc:
return ERR_PTR(ret);
}