linux/drivers/gpu/drm/msm/dp/dp_ctrl.c

2081 lines
54 KiB
C
Raw Normal View History

// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright (c) 2012-2020, The Linux Foundation. All rights reserved.
*/
#define pr_fmt(fmt) "[drm-dp] %s: " fmt, __func__
#include <linux/types.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/phy/phy.h>
#include <linux/phy/phy-dp.h>
#include <linux/pm_opp.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/drm_fixed.h>
#include <drm/drm_print.h>
#include "dp_reg.h"
#include "dp_ctrl.h"
#include "dp_link.h"
#define DP_KHZ_TO_HZ 1000
#define IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES (30 * HZ / 1000) /* 30 ms */
#define WAIT_FOR_VIDEO_READY_TIMEOUT_JIFFIES (HZ / 2)
#define DP_CTRL_INTR_READY_FOR_VIDEO BIT(0)
#define DP_CTRL_INTR_IDLE_PATTERN_SENT BIT(3)
#define MR_LINK_TRAINING1 0x8
#define MR_LINK_SYMBOL_ERM 0x80
#define MR_LINK_PRBS7 0x100
#define MR_LINK_CUSTOM80 0x200
#define MR_LINK_TRAINING4 0x40
enum {
DP_TRAINING_NONE,
DP_TRAINING_1,
DP_TRAINING_2,
};
struct dp_tu_calc_input {
u64 lclk; /* 162, 270, 540 and 810 */
u64 pclk_khz; /* in KHz */
u64 hactive; /* active h-width */
u64 hporch; /* bp + fp + pulse */
int nlanes; /* no.of.lanes */
int bpp; /* bits */
int pixel_enc; /* 444, 420, 422 */
int dsc_en; /* dsc on/off */
int async_en; /* async mode */
int fec_en; /* fec */
int compress_ratio; /* 2:1 = 200, 3:1 = 300, 3.75:1 = 375 */
int num_of_dsc_slices; /* number of slices per line */
};
struct dp_vc_tu_mapping_table {
u32 vic;
u8 lanes;
u8 lrate; /* DP_LINK_RATE -> 162(6), 270(10), 540(20), 810 (30) */
u8 bpp;
u8 valid_boundary_link;
u16 delay_start_link;
bool boundary_moderation_en;
u8 valid_lower_boundary_link;
u8 upper_boundary_count;
u8 lower_boundary_count;
u8 tu_size_minus1;
};
struct dp_ctrl_private {
struct dp_ctrl dp_ctrl;
struct drm_device *drm_dev;
struct device *dev;
struct drm_dp_aux *aux;
struct dp_panel *panel;
struct dp_link *link;
struct dp_power *power;
struct dp_parser *parser;
struct dp_catalog *catalog;
struct completion idle_comp;
struct completion video_comp;
};
static int dp_aux_link_configure(struct drm_dp_aux *aux,
struct dp_link_info *link)
{
u8 values[2];
int err;
values[0] = drm_dp_link_rate_to_bw_code(link->rate);
values[1] = link->num_lanes;
if (link->capabilities & DP_LINK_CAP_ENHANCED_FRAMING)
values[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN;
err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, values, sizeof(values));
if (err < 0)
return err;
return 0;
}
void dp_ctrl_push_idle(struct dp_ctrl *dp_ctrl)
{
struct dp_ctrl_private *ctrl;
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
reinit_completion(&ctrl->idle_comp);
dp_catalog_ctrl_state_ctrl(ctrl->catalog, DP_STATE_CTRL_PUSH_IDLE);
if (!wait_for_completion_timeout(&ctrl->idle_comp,
IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES))
pr_warn("PUSH_IDLE pattern timedout\n");
drm_dbg_dp(ctrl->drm_dev, "mainlink off\n");
}
static void dp_ctrl_config_ctrl(struct dp_ctrl_private *ctrl)
{
u32 config = 0, tbd;
const u8 *dpcd = ctrl->panel->dpcd;
/* Default-> LSCLK DIV: 1/4 LCLK */
config |= (2 << DP_CONFIGURATION_CTRL_LSCLK_DIV_SHIFT);
/* Scrambler reset enable */
if (drm_dp_alternate_scrambler_reset_cap(dpcd))
config |= DP_CONFIGURATION_CTRL_ASSR;
tbd = dp_link_get_test_bits_depth(ctrl->link,
ctrl->panel->dp_mode.bpp);
if (tbd == DP_TEST_BIT_DEPTH_UNKNOWN) {
pr_debug("BIT_DEPTH not set. Configure default\n");
tbd = DP_TEST_BIT_DEPTH_8;
}
config |= tbd << DP_CONFIGURATION_CTRL_BPC_SHIFT;
/* Num of Lanes */
config |= ((ctrl->link->link_params.num_lanes - 1)
<< DP_CONFIGURATION_CTRL_NUM_OF_LANES_SHIFT);
if (drm_dp_enhanced_frame_cap(dpcd))
config |= DP_CONFIGURATION_CTRL_ENHANCED_FRAMING;
config |= DP_CONFIGURATION_CTRL_P_INTERLACED; /* progressive video */
/* sync clock & static Mvid */
config |= DP_CONFIGURATION_CTRL_STATIC_DYNAMIC_CN;
config |= DP_CONFIGURATION_CTRL_SYNC_ASYNC_CLK;
dp_catalog_ctrl_config_ctrl(ctrl->catalog, config);
}
static void dp_ctrl_configure_source_params(struct dp_ctrl_private *ctrl)
{
u32 cc, tb;
dp_catalog_ctrl_lane_mapping(ctrl->catalog);
dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, true);
dp_ctrl_config_ctrl(ctrl);
tb = dp_link_get_test_bits_depth(ctrl->link,
ctrl->panel->dp_mode.bpp);
cc = dp_link_get_colorimetry_config(ctrl->link);
dp_catalog_ctrl_config_misc(ctrl->catalog, cc, tb);
dp_panel_timing_cfg(ctrl->panel);
}
/*
* The structure and few functions present below are IP/Hardware
* specific implementation. Most of the implementation will not
* have coding comments
*/
struct tu_algo_data {
s64 lclk_fp;
s64 pclk_fp;
s64 lwidth;
s64 lwidth_fp;
s64 hbp_relative_to_pclk;
s64 hbp_relative_to_pclk_fp;
int nlanes;
int bpp;
int pixelEnc;
int dsc_en;
int async_en;
int bpc;
uint delay_start_link_extra_pixclk;
int extra_buffer_margin;
s64 ratio_fp;
s64 original_ratio_fp;
s64 err_fp;
s64 n_err_fp;
s64 n_n_err_fp;
int tu_size;
int tu_size_desired;
int tu_size_minus1;
int valid_boundary_link;
s64 resulting_valid_fp;
s64 total_valid_fp;
s64 effective_valid_fp;
s64 effective_valid_recorded_fp;
int n_tus;
int n_tus_per_lane;
int paired_tus;
int remainder_tus;
int remainder_tus_upper;
int remainder_tus_lower;
int extra_bytes;
int filler_size;
int delay_start_link;
int extra_pclk_cycles;
int extra_pclk_cycles_in_link_clk;
s64 ratio_by_tu_fp;
s64 average_valid2_fp;
int new_valid_boundary_link;
int remainder_symbols_exist;
int n_symbols;
s64 n_remainder_symbols_per_lane_fp;
s64 last_partial_tu_fp;
s64 TU_ratio_err_fp;
int n_tus_incl_last_incomplete_tu;
int extra_pclk_cycles_tmp;
int extra_pclk_cycles_in_link_clk_tmp;
int extra_required_bytes_new_tmp;
int filler_size_tmp;
int lower_filler_size_tmp;
int delay_start_link_tmp;
bool boundary_moderation_en;
int boundary_mod_lower_err;
int upper_boundary_count;
int lower_boundary_count;
int i_upper_boundary_count;
int i_lower_boundary_count;
int valid_lower_boundary_link;
int even_distribution_BF;
int even_distribution_legacy;
int even_distribution;
int min_hblank_violated;
s64 delay_start_time_fp;
s64 hbp_time_fp;
s64 hactive_time_fp;
s64 diff_abs_fp;
s64 ratio;
};
static int _tu_param_compare(s64 a, s64 b)
{
u32 a_sign;
u32 b_sign;
s64 a_temp, b_temp, minus_1;
if (a == b)
return 0;
minus_1 = drm_fixp_from_fraction(-1, 1);
a_sign = (a >> 32) & 0x80000000 ? 1 : 0;
b_sign = (b >> 32) & 0x80000000 ? 1 : 0;
if (a_sign > b_sign)
return 2;
else if (b_sign > a_sign)
return 1;
if (!a_sign && !b_sign) { /* positive */
if (a > b)
return 1;
else
return 2;
} else { /* negative */
a_temp = drm_fixp_mul(a, minus_1);
b_temp = drm_fixp_mul(b, minus_1);
if (a_temp > b_temp)
return 2;
else
return 1;
}
}
static void dp_panel_update_tu_timings(struct dp_tu_calc_input *in,
struct tu_algo_data *tu)
{
int nlanes = in->nlanes;
int dsc_num_slices = in->num_of_dsc_slices;
int dsc_num_bytes = 0;
int numerator;
s64 pclk_dsc_fp;
s64 dwidth_dsc_fp;
s64 hbp_dsc_fp;
int tot_num_eoc_symbols = 0;
int tot_num_hor_bytes = 0;
int tot_num_dummy_bytes = 0;
int dwidth_dsc_bytes = 0;
int eoc_bytes = 0;
s64 temp1_fp, temp2_fp, temp3_fp;
tu->lclk_fp = drm_fixp_from_fraction(in->lclk, 1);
tu->pclk_fp = drm_fixp_from_fraction(in->pclk_khz, 1000);
tu->lwidth = in->hactive;
tu->hbp_relative_to_pclk = in->hporch;
tu->nlanes = in->nlanes;
tu->bpp = in->bpp;
tu->pixelEnc = in->pixel_enc;
tu->dsc_en = in->dsc_en;
tu->async_en = in->async_en;
tu->lwidth_fp = drm_fixp_from_fraction(in->hactive, 1);
tu->hbp_relative_to_pclk_fp = drm_fixp_from_fraction(in->hporch, 1);
if (tu->pixelEnc == 420) {
temp1_fp = drm_fixp_from_fraction(2, 1);
tu->pclk_fp = drm_fixp_div(tu->pclk_fp, temp1_fp);
tu->lwidth_fp = drm_fixp_div(tu->lwidth_fp, temp1_fp);
tu->hbp_relative_to_pclk_fp =
drm_fixp_div(tu->hbp_relative_to_pclk_fp, 2);
}
if (tu->pixelEnc == 422) {
switch (tu->bpp) {
case 24:
tu->bpp = 16;
tu->bpc = 8;
break;
case 30:
tu->bpp = 20;
tu->bpc = 10;
break;
default:
tu->bpp = 16;
tu->bpc = 8;
break;
}
} else {
tu->bpc = tu->bpp/3;
}
if (!in->dsc_en)
goto fec_check;
temp1_fp = drm_fixp_from_fraction(in->compress_ratio, 100);
temp2_fp = drm_fixp_from_fraction(in->bpp, 1);
temp3_fp = drm_fixp_div(temp2_fp, temp1_fp);
temp2_fp = drm_fixp_mul(tu->lwidth_fp, temp3_fp);
temp1_fp = drm_fixp_from_fraction(8, 1);
temp3_fp = drm_fixp_div(temp2_fp, temp1_fp);
numerator = drm_fixp2int(temp3_fp);
dsc_num_bytes = numerator / dsc_num_slices;
eoc_bytes = dsc_num_bytes % nlanes;
tot_num_eoc_symbols = nlanes * dsc_num_slices;
tot_num_hor_bytes = dsc_num_bytes * dsc_num_slices;
tot_num_dummy_bytes = (nlanes - eoc_bytes) * dsc_num_slices;
if (dsc_num_bytes == 0)
pr_info("incorrect no of bytes per slice=%d\n", dsc_num_bytes);
dwidth_dsc_bytes = (tot_num_hor_bytes +
tot_num_eoc_symbols +
(eoc_bytes == 0 ? 0 : tot_num_dummy_bytes));
dwidth_dsc_fp = drm_fixp_from_fraction(dwidth_dsc_bytes, 3);
temp2_fp = drm_fixp_mul(tu->pclk_fp, dwidth_dsc_fp);
temp1_fp = drm_fixp_div(temp2_fp, tu->lwidth_fp);
pclk_dsc_fp = temp1_fp;
temp1_fp = drm_fixp_div(pclk_dsc_fp, tu->pclk_fp);
temp2_fp = drm_fixp_mul(tu->hbp_relative_to_pclk_fp, temp1_fp);
hbp_dsc_fp = temp2_fp;
/* output */
tu->pclk_fp = pclk_dsc_fp;
tu->lwidth_fp = dwidth_dsc_fp;
tu->hbp_relative_to_pclk_fp = hbp_dsc_fp;
fec_check:
if (in->fec_en) {
temp1_fp = drm_fixp_from_fraction(976, 1000); /* 0.976 */
tu->lclk_fp = drm_fixp_mul(tu->lclk_fp, temp1_fp);
}
}
static void _tu_valid_boundary_calc(struct tu_algo_data *tu)
{
s64 temp1_fp, temp2_fp, temp, temp1, temp2;
int compare_result_1, compare_result_2, compare_result_3;
temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1);
temp2_fp = drm_fixp_mul(tu->ratio_fp, temp1_fp);
tu->new_valid_boundary_link = drm_fixp2int_ceil(temp2_fp);
temp = (tu->i_upper_boundary_count *
tu->new_valid_boundary_link +
tu->i_lower_boundary_count *
(tu->new_valid_boundary_link-1));
tu->average_valid2_fp = drm_fixp_from_fraction(temp,
(tu->i_upper_boundary_count +
tu->i_lower_boundary_count));
temp1_fp = drm_fixp_from_fraction(tu->bpp, 8);
temp2_fp = tu->lwidth_fp;
temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp);
temp2_fp = drm_fixp_div(temp1_fp, tu->average_valid2_fp);
tu->n_tus = drm_fixp2int(temp2_fp);
if ((temp2_fp & 0xFFFFFFFF) > 0xFFFFF000)
tu->n_tus += 1;
temp1_fp = drm_fixp_from_fraction(tu->n_tus, 1);
temp2_fp = drm_fixp_mul(temp1_fp, tu->average_valid2_fp);
temp1_fp = drm_fixp_from_fraction(tu->n_symbols, 1);
temp2_fp = temp1_fp - temp2_fp;
temp1_fp = drm_fixp_from_fraction(tu->nlanes, 1);
temp2_fp = drm_fixp_div(temp2_fp, temp1_fp);
tu->n_remainder_symbols_per_lane_fp = temp2_fp;
temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1);
tu->last_partial_tu_fp =
drm_fixp_div(tu->n_remainder_symbols_per_lane_fp,
temp1_fp);
if (tu->n_remainder_symbols_per_lane_fp != 0)
tu->remainder_symbols_exist = 1;
else
tu->remainder_symbols_exist = 0;
temp1_fp = drm_fixp_from_fraction(tu->n_tus, tu->nlanes);
tu->n_tus_per_lane = drm_fixp2int(temp1_fp);
tu->paired_tus = (int)((tu->n_tus_per_lane) /
(tu->i_upper_boundary_count +
tu->i_lower_boundary_count));
tu->remainder_tus = tu->n_tus_per_lane - tu->paired_tus *
(tu->i_upper_boundary_count +
tu->i_lower_boundary_count);
if ((tu->remainder_tus - tu->i_upper_boundary_count) > 0) {
tu->remainder_tus_upper = tu->i_upper_boundary_count;
tu->remainder_tus_lower = tu->remainder_tus -
tu->i_upper_boundary_count;
} else {
tu->remainder_tus_upper = tu->remainder_tus;
tu->remainder_tus_lower = 0;
}
temp = tu->paired_tus * (tu->i_upper_boundary_count *
tu->new_valid_boundary_link +
tu->i_lower_boundary_count *
(tu->new_valid_boundary_link - 1)) +
(tu->remainder_tus_upper *
tu->new_valid_boundary_link) +
(tu->remainder_tus_lower *
(tu->new_valid_boundary_link - 1));
tu->total_valid_fp = drm_fixp_from_fraction(temp, 1);
if (tu->remainder_symbols_exist) {
temp1_fp = tu->total_valid_fp +
tu->n_remainder_symbols_per_lane_fp;
temp2_fp = drm_fixp_from_fraction(tu->n_tus_per_lane, 1);
temp2_fp = temp2_fp + tu->last_partial_tu_fp;
temp1_fp = drm_fixp_div(temp1_fp, temp2_fp);
} else {
temp2_fp = drm_fixp_from_fraction(tu->n_tus_per_lane, 1);
temp1_fp = drm_fixp_div(tu->total_valid_fp, temp2_fp);
}
tu->effective_valid_fp = temp1_fp;
temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1);
temp2_fp = drm_fixp_mul(tu->ratio_fp, temp1_fp);
tu->n_n_err_fp = tu->effective_valid_fp - temp2_fp;
temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1);
temp2_fp = drm_fixp_mul(tu->ratio_fp, temp1_fp);
tu->n_err_fp = tu->average_valid2_fp - temp2_fp;
tu->even_distribution = tu->n_tus % tu->nlanes == 0 ? 1 : 0;
temp1_fp = drm_fixp_from_fraction(tu->bpp, 8);
temp2_fp = tu->lwidth_fp;
temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp);
temp2_fp = drm_fixp_div(temp1_fp, tu->average_valid2_fp);
if (temp2_fp)
tu->n_tus_incl_last_incomplete_tu = drm_fixp2int_ceil(temp2_fp);
else
tu->n_tus_incl_last_incomplete_tu = 0;
temp1 = 0;
temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1);
temp2_fp = drm_fixp_mul(tu->original_ratio_fp, temp1_fp);
temp1_fp = tu->average_valid2_fp - temp2_fp;
temp2_fp = drm_fixp_from_fraction(tu->n_tus_incl_last_incomplete_tu, 1);
temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp);
if (temp1_fp)
temp1 = drm_fixp2int_ceil(temp1_fp);
temp = tu->i_upper_boundary_count * tu->nlanes;
temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1);
temp2_fp = drm_fixp_mul(tu->original_ratio_fp, temp1_fp);
temp1_fp = drm_fixp_from_fraction(tu->new_valid_boundary_link, 1);
temp2_fp = temp1_fp - temp2_fp;
temp1_fp = drm_fixp_from_fraction(temp, 1);
temp2_fp = drm_fixp_mul(temp1_fp, temp2_fp);
if (temp2_fp)
temp2 = drm_fixp2int_ceil(temp2_fp);
else
temp2 = 0;
tu->extra_required_bytes_new_tmp = (int)(temp1 + temp2);
temp1_fp = drm_fixp_from_fraction(8, tu->bpp);
temp2_fp = drm_fixp_from_fraction(
tu->extra_required_bytes_new_tmp, 1);
temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp);
if (temp1_fp)
tu->extra_pclk_cycles_tmp = drm_fixp2int_ceil(temp1_fp);
else
tu->extra_pclk_cycles_tmp = 0;
temp1_fp = drm_fixp_from_fraction(tu->extra_pclk_cycles_tmp, 1);
temp2_fp = drm_fixp_div(tu->lclk_fp, tu->pclk_fp);
temp1_fp = drm_fixp_mul(temp1_fp, temp2_fp);
if (temp1_fp)
tu->extra_pclk_cycles_in_link_clk_tmp =
drm_fixp2int_ceil(temp1_fp);
else
tu->extra_pclk_cycles_in_link_clk_tmp = 0;
tu->filler_size_tmp = tu->tu_size - tu->new_valid_boundary_link;
tu->lower_filler_size_tmp = tu->filler_size_tmp + 1;
tu->delay_start_link_tmp = tu->extra_pclk_cycles_in_link_clk_tmp +
tu->lower_filler_size_tmp +
tu->extra_buffer_margin;
temp1_fp = drm_fixp_from_fraction(tu->delay_start_link_tmp, 1);
tu->delay_start_time_fp = drm_fixp_div(temp1_fp, tu->lclk_fp);
compare_result_1 = _tu_param_compare(tu->n_n_err_fp, tu->diff_abs_fp);
if (compare_result_1 == 2)
compare_result_1 = 1;
else
compare_result_1 = 0;
compare_result_2 = _tu_param_compare(tu->n_n_err_fp, tu->err_fp);
if (compare_result_2 == 2)
compare_result_2 = 1;
else
compare_result_2 = 0;
compare_result_3 = _tu_param_compare(tu->hbp_time_fp,
tu->delay_start_time_fp);
if (compare_result_3 == 2)
compare_result_3 = 0;
else
compare_result_3 = 1;
if (((tu->even_distribution == 1) ||
((tu->even_distribution_BF == 0) &&
(tu->even_distribution_legacy == 0))) &&
tu->n_err_fp >= 0 && tu->n_n_err_fp >= 0 &&
compare_result_2 &&
(compare_result_1 || (tu->min_hblank_violated == 1)) &&
(tu->new_valid_boundary_link - 1) > 0 &&
compare_result_3 &&
(tu->delay_start_link_tmp <= 1023)) {
tu->upper_boundary_count = tu->i_upper_boundary_count;
tu->lower_boundary_count = tu->i_lower_boundary_count;
tu->err_fp = tu->n_n_err_fp;
tu->boundary_moderation_en = true;
tu->tu_size_desired = tu->tu_size;
tu->valid_boundary_link = tu->new_valid_boundary_link;
tu->effective_valid_recorded_fp = tu->effective_valid_fp;
tu->even_distribution_BF = 1;
tu->delay_start_link = tu->delay_start_link_tmp;
} else if (tu->boundary_mod_lower_err == 0) {
compare_result_1 = _tu_param_compare(tu->n_n_err_fp,
tu->diff_abs_fp);
if (compare_result_1 == 2)
tu->boundary_mod_lower_err = 1;
}
}
static void _dp_ctrl_calc_tu(struct dp_ctrl_private *ctrl,
struct dp_tu_calc_input *in,
struct dp_vc_tu_mapping_table *tu_table)
{
struct tu_algo_data *tu;
int compare_result_1, compare_result_2;
u64 temp = 0;
s64 temp_fp = 0, temp1_fp = 0, temp2_fp = 0;
s64 LCLK_FAST_SKEW_fp = drm_fixp_from_fraction(6, 10000); /* 0.0006 */
s64 const_p49_fp = drm_fixp_from_fraction(49, 100); /* 0.49 */
s64 const_p56_fp = drm_fixp_from_fraction(56, 100); /* 0.56 */
s64 RATIO_SCALE_fp = drm_fixp_from_fraction(1001, 1000);
u8 DP_BRUTE_FORCE = 1;
s64 BRUTE_FORCE_THRESHOLD_fp = drm_fixp_from_fraction(1, 10); /* 0.1 */
uint EXTRA_PIXCLK_CYCLE_DELAY = 4;
uint HBLANK_MARGIN = 4;
tu = kzalloc(sizeof(*tu), GFP_KERNEL);
if (!tu)
return;
dp_panel_update_tu_timings(in, tu);
tu->err_fp = drm_fixp_from_fraction(1000, 1); /* 1000 */
temp1_fp = drm_fixp_from_fraction(4, 1);
temp2_fp = drm_fixp_mul(temp1_fp, tu->lclk_fp);
temp_fp = drm_fixp_div(temp2_fp, tu->pclk_fp);
tu->extra_buffer_margin = drm_fixp2int_ceil(temp_fp);
temp1_fp = drm_fixp_from_fraction(tu->bpp, 8);
temp2_fp = drm_fixp_mul(tu->pclk_fp, temp1_fp);
temp1_fp = drm_fixp_from_fraction(tu->nlanes, 1);
temp2_fp = drm_fixp_div(temp2_fp, temp1_fp);
tu->ratio_fp = drm_fixp_div(temp2_fp, tu->lclk_fp);
tu->original_ratio_fp = tu->ratio_fp;
tu->boundary_moderation_en = false;
tu->upper_boundary_count = 0;
tu->lower_boundary_count = 0;
tu->i_upper_boundary_count = 0;
tu->i_lower_boundary_count = 0;
tu->valid_lower_boundary_link = 0;
tu->even_distribution_BF = 0;
tu->even_distribution_legacy = 0;
tu->even_distribution = 0;
tu->delay_start_time_fp = 0;
tu->err_fp = drm_fixp_from_fraction(1000, 1);
tu->n_err_fp = 0;
tu->n_n_err_fp = 0;
tu->ratio = drm_fixp2int(tu->ratio_fp);
temp1_fp = drm_fixp_from_fraction(tu->nlanes, 1);
div64_u64_rem(tu->lwidth_fp, temp1_fp, &temp2_fp);
if (temp2_fp != 0 &&
!tu->ratio && tu->dsc_en == 0) {
tu->ratio_fp = drm_fixp_mul(tu->ratio_fp, RATIO_SCALE_fp);
tu->ratio = drm_fixp2int(tu->ratio_fp);
if (tu->ratio)
tu->ratio_fp = drm_fixp_from_fraction(1, 1);
}
if (tu->ratio > 1)
tu->ratio = 1;
if (tu->ratio == 1)
goto tu_size_calc;
compare_result_1 = _tu_param_compare(tu->ratio_fp, const_p49_fp);
if (!compare_result_1 || compare_result_1 == 1)
compare_result_1 = 1;
else
compare_result_1 = 0;
compare_result_2 = _tu_param_compare(tu->ratio_fp, const_p56_fp);
if (!compare_result_2 || compare_result_2 == 2)
compare_result_2 = 1;
else
compare_result_2 = 0;
if (tu->dsc_en && compare_result_1 && compare_result_2) {
HBLANK_MARGIN += 4;
drm_dbg_dp(ctrl->drm_dev,
"increase HBLANK_MARGIN to %d\n", HBLANK_MARGIN);
}
tu_size_calc:
for (tu->tu_size = 32; tu->tu_size <= 64; tu->tu_size++) {
temp1_fp = drm_fixp_from_fraction(tu->tu_size, 1);
temp2_fp = drm_fixp_mul(tu->ratio_fp, temp1_fp);
temp = drm_fixp2int_ceil(temp2_fp);
temp1_fp = drm_fixp_from_fraction(temp, 1);
tu->n_err_fp = temp1_fp - temp2_fp;
if (tu->n_err_fp < tu->err_fp) {
tu->err_fp = tu->n_err_fp;
tu->tu_size_desired = tu->tu_size;
}
}
tu->tu_size_minus1 = tu->tu_size_desired - 1;
temp1_fp = drm_fixp_from_fraction(tu->tu_size_desired, 1);
temp2_fp = drm_fixp_mul(tu->ratio_fp, temp1_fp);
tu->valid_boundary_link = drm_fixp2int_ceil(temp2_fp);
temp1_fp = drm_fixp_from_fraction(tu->bpp, 8);
temp2_fp = tu->lwidth_fp;
temp2_fp = drm_fixp_mul(temp2_fp, temp1_fp);
temp1_fp = drm_fixp_from_fraction(tu->valid_boundary_link, 1);
temp2_fp = drm_fixp_div(temp2_fp, temp1_fp);
tu->n_tus = drm_fixp2int(temp2_fp);
if ((temp2_fp & 0xFFFFFFFF) > 0xFFFFF000)
tu->n_tus += 1;
tu->even_distribution_legacy = tu->n_tus % tu->nlanes == 0 ? 1 : 0;
drm_dbg_dp(ctrl->drm_dev,
"n_sym = %d, num_of_tus = %d\n",
tu->valid_boundary_link, tu->n_tus);
temp1_fp = drm_fixp_from_fraction(tu->tu_size_desired, 1);
temp2_fp = drm_fixp_mul(tu->original_ratio_fp, temp1_fp);
temp1_fp = drm_fixp_from_fraction(tu->valid_boundary_link, 1);
temp2_fp = temp1_fp - temp2_fp;
temp1_fp = drm_fixp_from_fraction(tu->n_tus + 1, 1);
temp2_fp = drm_fixp_mul(temp1_fp, temp2_fp);
temp = drm_fixp2int(temp2_fp);
if (temp && temp2_fp)
tu->extra_bytes = drm_fixp2int_ceil(temp2_fp);
else
tu->extra_bytes = 0;
temp1_fp = drm_fixp_from_fraction(tu->extra_bytes, 1);
temp2_fp = drm_fixp_from_fraction(8, tu->bpp);
temp1_fp = drm_fixp_mul(temp1_fp, temp2_fp);
if (temp && temp1_fp)
tu->extra_pclk_cycles = drm_fixp2int_ceil(temp1_fp);
else
tu->extra_pclk_cycles = drm_fixp2int(temp1_fp);
temp1_fp = drm_fixp_div(tu->lclk_fp, tu->pclk_fp);
temp2_fp = drm_fixp_from_fraction(tu->extra_pclk_cycles, 1);
temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp);
if (temp1_fp)
tu->extra_pclk_cycles_in_link_clk = drm_fixp2int_ceil(temp1_fp);
else
tu->extra_pclk_cycles_in_link_clk = drm_fixp2int(temp1_fp);
tu->filler_size = tu->tu_size_desired - tu->valid_boundary_link;
temp1_fp = drm_fixp_from_fraction(tu->tu_size_desired, 1);
tu->ratio_by_tu_fp = drm_fixp_mul(tu->ratio_fp, temp1_fp);
tu->delay_start_link = tu->extra_pclk_cycles_in_link_clk +
tu->filler_size + tu->extra_buffer_margin;
tu->resulting_valid_fp =
drm_fixp_from_fraction(tu->valid_boundary_link, 1);
temp1_fp = drm_fixp_from_fraction(tu->tu_size_desired, 1);
temp2_fp = drm_fixp_div(tu->resulting_valid_fp, temp1_fp);
tu->TU_ratio_err_fp = temp2_fp - tu->original_ratio_fp;
temp1_fp = drm_fixp_from_fraction(HBLANK_MARGIN, 1);
temp1_fp = tu->hbp_relative_to_pclk_fp - temp1_fp;
tu->hbp_time_fp = drm_fixp_div(temp1_fp, tu->pclk_fp);
temp1_fp = drm_fixp_from_fraction(tu->delay_start_link, 1);
tu->delay_start_time_fp = drm_fixp_div(temp1_fp, tu->lclk_fp);
compare_result_1 = _tu_param_compare(tu->hbp_time_fp,
tu->delay_start_time_fp);
if (compare_result_1 == 2) /* if (hbp_time_fp < delay_start_time_fp) */
tu->min_hblank_violated = 1;
tu->hactive_time_fp = drm_fixp_div(tu->lwidth_fp, tu->pclk_fp);
compare_result_2 = _tu_param_compare(tu->hactive_time_fp,
tu->delay_start_time_fp);
if (compare_result_2 == 2)
tu->min_hblank_violated = 1;
tu->delay_start_time_fp = 0;
/* brute force */
tu->delay_start_link_extra_pixclk = EXTRA_PIXCLK_CYCLE_DELAY;
tu->diff_abs_fp = tu->resulting_valid_fp - tu->ratio_by_tu_fp;
temp = drm_fixp2int(tu->diff_abs_fp);
if (!temp && tu->diff_abs_fp <= 0xffff)
tu->diff_abs_fp = 0;
/* if(diff_abs < 0) diff_abs *= -1 */
if (tu->diff_abs_fp < 0)
tu->diff_abs_fp = drm_fixp_mul(tu->diff_abs_fp, -1);
tu->boundary_mod_lower_err = 0;
if ((tu->diff_abs_fp != 0 &&
((tu->diff_abs_fp > BRUTE_FORCE_THRESHOLD_fp) ||
(tu->even_distribution_legacy == 0) ||
(DP_BRUTE_FORCE == 1))) ||
(tu->min_hblank_violated == 1)) {
do {
tu->err_fp = drm_fixp_from_fraction(1000, 1);
temp1_fp = drm_fixp_div(tu->lclk_fp, tu->pclk_fp);
temp2_fp = drm_fixp_from_fraction(
tu->delay_start_link_extra_pixclk, 1);
temp1_fp = drm_fixp_mul(temp2_fp, temp1_fp);
if (temp1_fp)
tu->extra_buffer_margin =
drm_fixp2int_ceil(temp1_fp);
else
tu->extra_buffer_margin = 0;
temp1_fp = drm_fixp_from_fraction(tu->bpp, 8);
temp1_fp = drm_fixp_mul(tu->lwidth_fp, temp1_fp);
if (temp1_fp)
tu->n_symbols = drm_fixp2int_ceil(temp1_fp);
else
tu->n_symbols = 0;
for (tu->tu_size = 32; tu->tu_size <= 64; tu->tu_size++) {
for (tu->i_upper_boundary_count = 1;
tu->i_upper_boundary_count <= 15;
tu->i_upper_boundary_count++) {
for (tu->i_lower_boundary_count = 1;
tu->i_lower_boundary_count <= 15;
tu->i_lower_boundary_count++) {
_tu_valid_boundary_calc(tu);
}
}
}
tu->delay_start_link_extra_pixclk--;
} while (tu->boundary_moderation_en != true &&
tu->boundary_mod_lower_err == 1 &&
tu->delay_start_link_extra_pixclk != 0);
if (tu->boundary_moderation_en == true) {
temp1_fp = drm_fixp_from_fraction(
(tu->upper_boundary_count *
tu->valid_boundary_link +
tu->lower_boundary_count *
(tu->valid_boundary_link - 1)), 1);
temp2_fp = drm_fixp_from_fraction(
(tu->upper_boundary_count +
tu->lower_boundary_count), 1);
tu->resulting_valid_fp =
drm_fixp_div(temp1_fp, temp2_fp);
temp1_fp = drm_fixp_from_fraction(
tu->tu_size_desired, 1);
tu->ratio_by_tu_fp =
drm_fixp_mul(tu->original_ratio_fp, temp1_fp);
tu->valid_lower_boundary_link =
tu->valid_boundary_link - 1;
temp1_fp = drm_fixp_from_fraction(tu->bpp, 8);
temp1_fp = drm_fixp_mul(tu->lwidth_fp, temp1_fp);
temp2_fp = drm_fixp_div(temp1_fp,
tu->resulting_valid_fp);
tu->n_tus = drm_fixp2int(temp2_fp);
tu->tu_size_minus1 = tu->tu_size_desired - 1;
tu->even_distribution_BF = 1;
temp1_fp =
drm_fixp_from_fraction(tu->tu_size_desired, 1);
temp2_fp =
drm_fixp_div(tu->resulting_valid_fp, temp1_fp);
tu->TU_ratio_err_fp = temp2_fp - tu->original_ratio_fp;
}
}
temp2_fp = drm_fixp_mul(LCLK_FAST_SKEW_fp, tu->lwidth_fp);
if (temp2_fp)
temp = drm_fixp2int_ceil(temp2_fp);
else
temp = 0;
temp1_fp = drm_fixp_from_fraction(tu->nlanes, 1);
temp2_fp = drm_fixp_mul(tu->original_ratio_fp, temp1_fp);
temp1_fp = drm_fixp_from_fraction(tu->bpp, 8);
temp2_fp = drm_fixp_div(temp1_fp, temp2_fp);
temp1_fp = drm_fixp_from_fraction(temp, 1);
temp2_fp = drm_fixp_mul(temp1_fp, temp2_fp);
temp = drm_fixp2int(temp2_fp);
if (tu->async_en)
tu->delay_start_link += (int)temp;
temp1_fp = drm_fixp_from_fraction(tu->delay_start_link, 1);
tu->delay_start_time_fp = drm_fixp_div(temp1_fp, tu->lclk_fp);
/* OUTPUTS */
tu_table->valid_boundary_link = tu->valid_boundary_link;
tu_table->delay_start_link = tu->delay_start_link;
tu_table->boundary_moderation_en = tu->boundary_moderation_en;
tu_table->valid_lower_boundary_link = tu->valid_lower_boundary_link;
tu_table->upper_boundary_count = tu->upper_boundary_count;
tu_table->lower_boundary_count = tu->lower_boundary_count;
tu_table->tu_size_minus1 = tu->tu_size_minus1;
drm_dbg_dp(ctrl->drm_dev, "TU: valid_boundary_link: %d\n",
tu_table->valid_boundary_link);
drm_dbg_dp(ctrl->drm_dev, "TU: delay_start_link: %d\n",
tu_table->delay_start_link);
drm_dbg_dp(ctrl->drm_dev, "TU: boundary_moderation_en: %d\n",
tu_table->boundary_moderation_en);
drm_dbg_dp(ctrl->drm_dev, "TU: valid_lower_boundary_link: %d\n",
tu_table->valid_lower_boundary_link);
drm_dbg_dp(ctrl->drm_dev, "TU: upper_boundary_count: %d\n",
tu_table->upper_boundary_count);
drm_dbg_dp(ctrl->drm_dev, "TU: lower_boundary_count: %d\n",
tu_table->lower_boundary_count);
drm_dbg_dp(ctrl->drm_dev, "TU: tu_size_minus1: %d\n",
tu_table->tu_size_minus1);
kfree(tu);
}
static void dp_ctrl_calc_tu_parameters(struct dp_ctrl_private *ctrl,
struct dp_vc_tu_mapping_table *tu_table)
{
struct dp_tu_calc_input in;
struct drm_display_mode *drm_mode;
drm_mode = &ctrl->panel->dp_mode.drm_mode;
in.lclk = ctrl->link->link_params.rate / 1000;
in.pclk_khz = drm_mode->clock;
in.hactive = drm_mode->hdisplay;
in.hporch = drm_mode->htotal - drm_mode->hdisplay;
in.nlanes = ctrl->link->link_params.num_lanes;
in.bpp = ctrl->panel->dp_mode.bpp;
in.pixel_enc = 444;
in.dsc_en = 0;
in.async_en = 0;
in.fec_en = 0;
in.num_of_dsc_slices = 0;
in.compress_ratio = 100;
_dp_ctrl_calc_tu(ctrl, &in, tu_table);
}
static void dp_ctrl_setup_tr_unit(struct dp_ctrl_private *ctrl)
{
u32 dp_tu = 0x0;
u32 valid_boundary = 0x0;
u32 valid_boundary2 = 0x0;
struct dp_vc_tu_mapping_table tu_calc_table;
dp_ctrl_calc_tu_parameters(ctrl, &tu_calc_table);
dp_tu |= tu_calc_table.tu_size_minus1;
valid_boundary |= tu_calc_table.valid_boundary_link;
valid_boundary |= (tu_calc_table.delay_start_link << 16);
valid_boundary2 |= (tu_calc_table.valid_lower_boundary_link << 1);
valid_boundary2 |= (tu_calc_table.upper_boundary_count << 16);
valid_boundary2 |= (tu_calc_table.lower_boundary_count << 20);
if (tu_calc_table.boundary_moderation_en)
valid_boundary2 |= BIT(0);
pr_debug("dp_tu=0x%x, valid_boundary=0x%x, valid_boundary2=0x%x\n",
dp_tu, valid_boundary, valid_boundary2);
dp_catalog_ctrl_update_transfer_unit(ctrl->catalog,
dp_tu, valid_boundary, valid_boundary2);
}
static int dp_ctrl_wait4video_ready(struct dp_ctrl_private *ctrl)
{
int ret = 0;
if (!wait_for_completion_timeout(&ctrl->video_comp,
WAIT_FOR_VIDEO_READY_TIMEOUT_JIFFIES)) {
DRM_ERROR("wait4video timedout\n");
ret = -ETIMEDOUT;
}
return ret;
}
static int dp_ctrl_update_vx_px(struct dp_ctrl_private *ctrl)
{
struct dp_link *link = ctrl->link;
int ret = 0, lane, lane_cnt;
u8 buf[4];
u32 max_level_reached = 0;
u32 voltage_swing_level = link->phy_params.v_level;
u32 pre_emphasis_level = link->phy_params.p_level;
drm_dbg_dp(ctrl->drm_dev,
"voltage level: %d emphasis level: %d\n",
voltage_swing_level, pre_emphasis_level);
ret = dp_catalog_ctrl_update_vx_px(ctrl->catalog,
voltage_swing_level, pre_emphasis_level);
if (ret)
return ret;
if (voltage_swing_level >= DP_TRAIN_VOLTAGE_SWING_MAX) {
drm_dbg_dp(ctrl->drm_dev,
"max. voltage swing level reached %d\n",
voltage_swing_level);
max_level_reached |= DP_TRAIN_MAX_SWING_REACHED;
}
if (pre_emphasis_level >= DP_TRAIN_PRE_EMPHASIS_MAX) {
drm_dbg_dp(ctrl->drm_dev,
"max. pre-emphasis level reached %d\n",
pre_emphasis_level);
max_level_reached |= DP_TRAIN_MAX_PRE_EMPHASIS_REACHED;
}
pre_emphasis_level <<= DP_TRAIN_PRE_EMPHASIS_SHIFT;
lane_cnt = ctrl->link->link_params.num_lanes;
for (lane = 0; lane < lane_cnt; lane++)
buf[lane] = voltage_swing_level | pre_emphasis_level
| max_level_reached;
drm_dbg_dp(ctrl->drm_dev, "sink: p|v=0x%x\n",
voltage_swing_level | pre_emphasis_level);
ret = drm_dp_dpcd_write(ctrl->aux, DP_TRAINING_LANE0_SET,
buf, lane_cnt);
if (ret == lane_cnt)
ret = 0;
return ret;
}
static bool dp_ctrl_train_pattern_set(struct dp_ctrl_private *ctrl,
u8 pattern)
{
u8 buf;
int ret = 0;
drm_dbg_dp(ctrl->drm_dev, "sink: pattern=%x\n", pattern);
buf = pattern;
if (pattern && pattern != DP_TRAINING_PATTERN_4)
buf |= DP_LINK_SCRAMBLING_DISABLE;
ret = drm_dp_dpcd_writeb(ctrl->aux, DP_TRAINING_PATTERN_SET, buf);
return ret == 1;
}
static int dp_ctrl_read_link_status(struct dp_ctrl_private *ctrl,
u8 *link_status)
{
int ret = 0, len;
len = drm_dp_dpcd_read_link_status(ctrl->aux, link_status);
if (len != DP_LINK_STATUS_SIZE) {
DRM_ERROR("DP link status read failed, err: %d\n", len);
ret = -EINVAL;
}
return ret;
}
static int dp_ctrl_link_train_1(struct dp_ctrl_private *ctrl,
int *training_step)
{
int tries, old_v_level, ret = 0;
u8 link_status[DP_LINK_STATUS_SIZE];
int const maximum_retries = 4;
dp_catalog_ctrl_state_ctrl(ctrl->catalog, 0);
*training_step = DP_TRAINING_1;
ret = dp_catalog_ctrl_set_pattern_state_bit(ctrl->catalog, 1);
if (ret)
return ret;
dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_1 |
DP_LINK_SCRAMBLING_DISABLE);
ret = dp_ctrl_update_vx_px(ctrl);
if (ret)
return ret;
tries = 0;
old_v_level = ctrl->link->phy_params.v_level;
for (tries = 0; tries < maximum_retries; tries++) {
drm_dp_link_train_clock_recovery_delay(ctrl->aux, ctrl->panel->dpcd);
ret = dp_ctrl_read_link_status(ctrl, link_status);
if (ret)
return ret;
if (drm_dp_clock_recovery_ok(link_status,
ctrl->link->link_params.num_lanes)) {
return 0;
}
if (ctrl->link->phy_params.v_level >=
DP_TRAIN_VOLTAGE_SWING_MAX) {
DRM_ERROR_RATELIMITED("max v_level reached\n");
return -EAGAIN;
}
if (old_v_level != ctrl->link->phy_params.v_level) {
tries = 0;
old_v_level = ctrl->link->phy_params.v_level;
}
dp_link_adjust_levels(ctrl->link, link_status);
ret = dp_ctrl_update_vx_px(ctrl);
if (ret)
return ret;
}
DRM_ERROR("max tries reached\n");
return -ETIMEDOUT;
}
static int dp_ctrl_link_rate_down_shift(struct dp_ctrl_private *ctrl)
{
int ret = 0;
switch (ctrl->link->link_params.rate) {
case 810000:
ctrl->link->link_params.rate = 540000;
break;
case 540000:
ctrl->link->link_params.rate = 270000;
break;
case 270000:
ctrl->link->link_params.rate = 162000;
break;
case 162000:
default:
ret = -EINVAL;
break;
}
if (!ret) {
drm_dbg_dp(ctrl->drm_dev, "new rate=0x%x\n",
ctrl->link->link_params.rate);
}
return ret;
}
static int dp_ctrl_link_lane_down_shift(struct dp_ctrl_private *ctrl)
{
if (ctrl->link->link_params.num_lanes == 1)
return -1;
ctrl->link->link_params.num_lanes /= 2;
ctrl->link->link_params.rate = ctrl->panel->link_info.rate;
ctrl->link->phy_params.p_level = 0;
ctrl->link->phy_params.v_level = 0;
return 0;
}
static void dp_ctrl_clear_training_pattern(struct dp_ctrl_private *ctrl)
{
dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_DISABLE);
drm_dp_link_train_channel_eq_delay(ctrl->aux, ctrl->panel->dpcd);
}
static int dp_ctrl_link_train_2(struct dp_ctrl_private *ctrl,
int *training_step)
{
int tries = 0, ret = 0;
u8 pattern;
u32 state_ctrl_bit;
int const maximum_retries = 5;
u8 link_status[DP_LINK_STATUS_SIZE];
dp_catalog_ctrl_state_ctrl(ctrl->catalog, 0);
*training_step = DP_TRAINING_2;
if (drm_dp_tps4_supported(ctrl->panel->dpcd)) {
pattern = DP_TRAINING_PATTERN_4;
state_ctrl_bit = 4;
} else if (drm_dp_tps3_supported(ctrl->panel->dpcd)) {
pattern = DP_TRAINING_PATTERN_3;
state_ctrl_bit = 3;
} else {
pattern = DP_TRAINING_PATTERN_2;
state_ctrl_bit = 2;
}
ret = dp_catalog_ctrl_set_pattern_state_bit(ctrl->catalog, state_ctrl_bit);
if (ret)
return ret;
dp_ctrl_train_pattern_set(ctrl, pattern | DP_RECOVERED_CLOCK_OUT_EN);
for (tries = 0; tries <= maximum_retries; tries++) {
drm_dp_link_train_channel_eq_delay(ctrl->aux, ctrl->panel->dpcd);
ret = dp_ctrl_read_link_status(ctrl, link_status);
if (ret)
return ret;
if (drm_dp_channel_eq_ok(link_status,
ctrl->link->link_params.num_lanes)) {
return 0;
}
dp_link_adjust_levels(ctrl->link, link_status);
ret = dp_ctrl_update_vx_px(ctrl);
if (ret)
return ret;
}
return -ETIMEDOUT;
}
static int dp_ctrl_reinitialize_mainlink(struct dp_ctrl_private *ctrl);
static int dp_ctrl_link_train(struct dp_ctrl_private *ctrl,
int *training_step)
{
int ret = 0;
const u8 *dpcd = ctrl->panel->dpcd;
u8 encoding = DP_SET_ANSI_8B10B;
u8 ssc;
u8 assr;
struct dp_link_info link_info = {0};
dp_ctrl_config_ctrl(ctrl);
link_info.num_lanes = ctrl->link->link_params.num_lanes;
link_info.rate = ctrl->link->link_params.rate;
link_info.capabilities = DP_LINK_CAP_ENHANCED_FRAMING;
dp_aux_link_configure(ctrl->aux, &link_info);
if (drm_dp_max_downspread(dpcd)) {
ssc = DP_SPREAD_AMP_0_5;
drm_dp_dpcd_write(ctrl->aux, DP_DOWNSPREAD_CTRL, &ssc, 1);
}
drm_dp_dpcd_write(ctrl->aux, DP_MAIN_LINK_CHANNEL_CODING_SET,
&encoding, 1);
if (drm_dp_alternate_scrambler_reset_cap(dpcd)) {
assr = DP_ALTERNATE_SCRAMBLER_RESET_ENABLE;
drm_dp_dpcd_write(ctrl->aux, DP_EDP_CONFIGURATION_SET,
&assr, 1);
}
ret = dp_ctrl_link_train_1(ctrl, training_step);
if (ret) {
DRM_ERROR("link training #1 failed. ret=%d\n", ret);
goto end;
}
/* print success info as this is a result of user initiated action */
drm_dbg_dp(ctrl->drm_dev, "link training #1 successful\n");
ret = dp_ctrl_link_train_2(ctrl, training_step);
if (ret) {
DRM_ERROR("link training #2 failed. ret=%d\n", ret);
goto end;
}
/* print success info as this is a result of user initiated action */
drm_dbg_dp(ctrl->drm_dev, "link training #2 successful\n");
end:
dp_catalog_ctrl_state_ctrl(ctrl->catalog, 0);
return ret;
}
static int dp_ctrl_setup_main_link(struct dp_ctrl_private *ctrl,
int *training_step)
{
int ret = 0;
dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, true);
if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN)
return ret;
/*
* As part of previous calls, DP controller state might have
* transitioned to PUSH_IDLE. In order to start transmitting
* a link training pattern, we have to first do soft reset.
*/
ret = dp_ctrl_link_train(ctrl, training_step);
return ret;
}
static void dp_ctrl_set_clock_rate(struct dp_ctrl_private *ctrl,
enum dp_pm_type module, char *name, unsigned long rate)
{
u32 num = ctrl->parser->mp[module].num_clk;
struct dss_clk *cfg = ctrl->parser->mp[module].clk_config;
while (num && strcmp(cfg->clk_name, name)) {
num--;
cfg++;
}
drm_dbg_dp(ctrl->drm_dev, "setting rate=%lu on clk=%s\n",
rate, name);
if (num)
cfg->rate = rate;
else
DRM_ERROR("%s clock doesn't exit to set rate %lu\n",
name, rate);
}
static int dp_ctrl_enable_mainlink_clocks(struct dp_ctrl_private *ctrl)
{
int ret = 0;
struct dp_io *dp_io = &ctrl->parser->io;
struct phy *phy = dp_io->phy;
struct phy_configure_opts_dp *opts_dp = &dp_io->phy_opts.dp;
const u8 *dpcd = ctrl->panel->dpcd;
opts_dp->lanes = ctrl->link->link_params.num_lanes;
opts_dp->link_rate = ctrl->link->link_params.rate / 100;
opts_dp->ssc = drm_dp_max_downspread(dpcd);
dp_ctrl_set_clock_rate(ctrl, DP_CTRL_PM, "ctrl_link",
ctrl->link->link_params.rate * 1000);
phy_configure(phy, &dp_io->phy_opts);
phy_power_on(phy);
ret = dp_power_clk_enable(ctrl->power, DP_CTRL_PM, true);
if (ret)
DRM_ERROR("Unable to start link clocks. ret=%d\n", ret);
drm_dbg_dp(ctrl->drm_dev, "link rate=%d pixel_clk=%d\n",
ctrl->link->link_params.rate, ctrl->dp_ctrl.pixel_rate);
return ret;
}
static int dp_ctrl_enable_stream_clocks(struct dp_ctrl_private *ctrl)
{
int ret = 0;
dp_ctrl_set_clock_rate(ctrl, DP_STREAM_PM, "stream_pixel",
ctrl->dp_ctrl.pixel_rate * 1000);
ret = dp_power_clk_enable(ctrl->power, DP_STREAM_PM, true);
if (ret)
DRM_ERROR("Unabled to start pixel clocks. ret=%d\n", ret);
drm_dbg_dp(ctrl->drm_dev, "link rate=%d pixel_clk=%d\n",
ctrl->link->link_params.rate, ctrl->dp_ctrl.pixel_rate);
return ret;
}
drm/msm/dp: do not initialize phy until plugin interrupt received Current DP drivers have regulators, clocks, irq and phy are grouped together within a function and executed not in a symmetric manner. This increase difficulty of code maintenance and limited code scalability. This patch divides the driver life cycle of operation into four states, resume (including booting up), dongle plugin, dongle unplugged and suspend. Regulators, core clocks and irq are grouped together and enabled at resume (or booting up) so that the DP controller is armed and ready to receive HPD plugin interrupts. HPD plugin interrupt is generated when a dongle plugs into DUT (device under test). Once HPD plugin interrupt is received, DP controller will initialize phy so that dpcd read/write will function and following link training can be proceeded successfully. DP phy will be disabled after main link is teared down at end of unplugged HPD interrupt handle triggered by dongle unplugged out of DUT. Finally regulators, code clocks and irq are disabled at corresponding suspension. Changes in V2: -- removed unnecessary dp_ctrl NULL check -- removed unnecessary phy init_count and power_count DRM_DEBUG_DP logs -- remove flip parameter out of dp_ctrl_irq_enable() -- add fixes tag Changes in V3: -- call dp_display_host_phy_init() instead of dp_ctrl_phy_init() at dp_display_host_init() for eDP Changes in V4: -- rewording commit text to match this commit changes Changes in V5: -- rebase on top of msm-next branch Changes in V6: -- delete flip variable Changes in V7: -- dp_ctrl_irq_enable/disabe() merged into dp_ctrl_reset_irq_ctrl() Changes in V8: -- add more detail comment regrading dp phy at dp_display_host_init() Changes in V9: -- remove set phy_initialized to false when -ECONNRESET detected Changes in v10: -- group into one series Changes in v11: -- drop drm/msm/dp: dp_link_parse_sink_count() return immediately if aux read Changes in v12: -- move dp_display_host_phy_exit() after dp_display_host_deinit() Changes in v13: -- do not execute phy_init until plugged_in interrupt for edp, same as DP. Changes in v14: -- remove redundant dp->core_initialized = false form dp_pm_suspend. Changes in v15: -- remove core_initialized flag check at both host_init and host_deinit Changes in v16: -- remove dp_display_host_phy_exit core_initialized=false at dp_pm_suspend Changes in v17: -- remove core_initialized checking before execute attention_cb() Changes in v18: -- remove core_initialized checking at dp_pm_suspend Fixes: 8ede2ecc3e5e ("drm/msm/dp: Add DP compliance tests on Snapdragon Chipsets") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Link: https://lore.kernel.org/r/1642531648-8448-2-git-send-email-quic_khsieh@quicinc.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2022-01-18 18:47:25 +00:00
void dp_ctrl_reset_irq_ctrl(struct dp_ctrl *dp_ctrl, bool enable)
{
struct dp_ctrl_private *ctrl;
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
dp_catalog_ctrl_reset(ctrl->catalog);
drm/msm/dp: Always clear mask bits to disable interrupts at dp_ctrl_reset_irq_ctrl() dp_catalog_ctrl_reset() will software reset DP controller. But it will not reset programmable registers to default value. DP driver still have to clear mask bits to interrupt status registers to disable interrupts after software reset of controller. At current implementation, dp_ctrl_reset_irq_ctrl() will software reset dp controller but did not call dp_catalog_ctrl_enable_irq(false) to clear hpd related interrupt mask bits to disable hpd related interrupts due to it mistakenly think hpd related interrupt mask bits will be cleared by software reset of dp controller automatically. This mistake may cause system to crash during suspending procedure due to unexpected irq fired and trigger event thread to access dp controller registers with controller clocks are disabled. This patch fixes system crash during suspending problem by removing "enable" flag condition checking at dp_ctrl_reset_irq_ctrl() so that hpd related interrupt mask bits are cleared to prevent unexpected from happening. Changes in v2: -- add more details commit text Changes in v3: -- add synchrons_irq() -- add atomic_t suspended Changes in v4: -- correct Fixes's commit ID -- remove synchrons_irq() Changes in v5: -- revise commit text Changes in v6: -- add event_lock to protect "suspended" Changes in v7: -- delete "suspended" flag Fixes: 989ebe7bc446 ("drm/msm/dp: do not initialize phy until plugin interrupt received") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Patchwork: https://patchwork.freedesktop.org/patch/486591/ Link: https://lore.kernel.org/r/1652804494-19650-1-git-send-email-quic_khsieh@quicinc.com Signed-off-by: Abhinav Kumar <quic_abhinavk@quicinc.com>
2022-05-17 16:21:34 +00:00
/*
* all dp controller programmable registers will not
* be reset to default value after DP_SW_RESET
* therefore interrupt mask bits have to be updated
* to enable/disable interrupts
*/
dp_catalog_ctrl_enable_irq(ctrl->catalog, enable);
drm/msm/dp: do not initialize phy until plugin interrupt received Current DP drivers have regulators, clocks, irq and phy are grouped together within a function and executed not in a symmetric manner. This increase difficulty of code maintenance and limited code scalability. This patch divides the driver life cycle of operation into four states, resume (including booting up), dongle plugin, dongle unplugged and suspend. Regulators, core clocks and irq are grouped together and enabled at resume (or booting up) so that the DP controller is armed and ready to receive HPD plugin interrupts. HPD plugin interrupt is generated when a dongle plugs into DUT (device under test). Once HPD plugin interrupt is received, DP controller will initialize phy so that dpcd read/write will function and following link training can be proceeded successfully. DP phy will be disabled after main link is teared down at end of unplugged HPD interrupt handle triggered by dongle unplugged out of DUT. Finally regulators, code clocks and irq are disabled at corresponding suspension. Changes in V2: -- removed unnecessary dp_ctrl NULL check -- removed unnecessary phy init_count and power_count DRM_DEBUG_DP logs -- remove flip parameter out of dp_ctrl_irq_enable() -- add fixes tag Changes in V3: -- call dp_display_host_phy_init() instead of dp_ctrl_phy_init() at dp_display_host_init() for eDP Changes in V4: -- rewording commit text to match this commit changes Changes in V5: -- rebase on top of msm-next branch Changes in V6: -- delete flip variable Changes in V7: -- dp_ctrl_irq_enable/disabe() merged into dp_ctrl_reset_irq_ctrl() Changes in V8: -- add more detail comment regrading dp phy at dp_display_host_init() Changes in V9: -- remove set phy_initialized to false when -ECONNRESET detected Changes in v10: -- group into one series Changes in v11: -- drop drm/msm/dp: dp_link_parse_sink_count() return immediately if aux read Changes in v12: -- move dp_display_host_phy_exit() after dp_display_host_deinit() Changes in v13: -- do not execute phy_init until plugged_in interrupt for edp, same as DP. Changes in v14: -- remove redundant dp->core_initialized = false form dp_pm_suspend. Changes in v15: -- remove core_initialized flag check at both host_init and host_deinit Changes in v16: -- remove dp_display_host_phy_exit core_initialized=false at dp_pm_suspend Changes in v17: -- remove core_initialized checking before execute attention_cb() Changes in v18: -- remove core_initialized checking at dp_pm_suspend Fixes: 8ede2ecc3e5e ("drm/msm/dp: Add DP compliance tests on Snapdragon Chipsets") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Link: https://lore.kernel.org/r/1642531648-8448-2-git-send-email-quic_khsieh@quicinc.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2022-01-18 18:47:25 +00:00
}
void dp_ctrl_phy_init(struct dp_ctrl *dp_ctrl)
{
struct dp_ctrl_private *ctrl;
struct dp_io *dp_io;
struct phy *phy;
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
dp_io = &ctrl->parser->io;
phy = dp_io->phy;
dp_catalog_ctrl_phy_reset(ctrl->catalog);
phy_init(phy);
drm_dbg_dp(ctrl->drm_dev, "phy=%p init=%d power_on=%d\n",
phy, phy->init_count, phy->power_count);
}
drm/msm/dp: do not initialize phy until plugin interrupt received Current DP drivers have regulators, clocks, irq and phy are grouped together within a function and executed not in a symmetric manner. This increase difficulty of code maintenance and limited code scalability. This patch divides the driver life cycle of operation into four states, resume (including booting up), dongle plugin, dongle unplugged and suspend. Regulators, core clocks and irq are grouped together and enabled at resume (or booting up) so that the DP controller is armed and ready to receive HPD plugin interrupts. HPD plugin interrupt is generated when a dongle plugs into DUT (device under test). Once HPD plugin interrupt is received, DP controller will initialize phy so that dpcd read/write will function and following link training can be proceeded successfully. DP phy will be disabled after main link is teared down at end of unplugged HPD interrupt handle triggered by dongle unplugged out of DUT. Finally regulators, code clocks and irq are disabled at corresponding suspension. Changes in V2: -- removed unnecessary dp_ctrl NULL check -- removed unnecessary phy init_count and power_count DRM_DEBUG_DP logs -- remove flip parameter out of dp_ctrl_irq_enable() -- add fixes tag Changes in V3: -- call dp_display_host_phy_init() instead of dp_ctrl_phy_init() at dp_display_host_init() for eDP Changes in V4: -- rewording commit text to match this commit changes Changes in V5: -- rebase on top of msm-next branch Changes in V6: -- delete flip variable Changes in V7: -- dp_ctrl_irq_enable/disabe() merged into dp_ctrl_reset_irq_ctrl() Changes in V8: -- add more detail comment regrading dp phy at dp_display_host_init() Changes in V9: -- remove set phy_initialized to false when -ECONNRESET detected Changes in v10: -- group into one series Changes in v11: -- drop drm/msm/dp: dp_link_parse_sink_count() return immediately if aux read Changes in v12: -- move dp_display_host_phy_exit() after dp_display_host_deinit() Changes in v13: -- do not execute phy_init until plugged_in interrupt for edp, same as DP. Changes in v14: -- remove redundant dp->core_initialized = false form dp_pm_suspend. Changes in v15: -- remove core_initialized flag check at both host_init and host_deinit Changes in v16: -- remove dp_display_host_phy_exit core_initialized=false at dp_pm_suspend Changes in v17: -- remove core_initialized checking before execute attention_cb() Changes in v18: -- remove core_initialized checking at dp_pm_suspend Fixes: 8ede2ecc3e5e ("drm/msm/dp: Add DP compliance tests on Snapdragon Chipsets") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Link: https://lore.kernel.org/r/1642531648-8448-2-git-send-email-quic_khsieh@quicinc.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2022-01-18 18:47:25 +00:00
void dp_ctrl_phy_exit(struct dp_ctrl *dp_ctrl)
{
struct dp_ctrl_private *ctrl;
struct dp_io *dp_io;
struct phy *phy;
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
dp_io = &ctrl->parser->io;
phy = dp_io->phy;
drm/msm/dp: do not initialize phy until plugin interrupt received Current DP drivers have regulators, clocks, irq and phy are grouped together within a function and executed not in a symmetric manner. This increase difficulty of code maintenance and limited code scalability. This patch divides the driver life cycle of operation into four states, resume (including booting up), dongle plugin, dongle unplugged and suspend. Regulators, core clocks and irq are grouped together and enabled at resume (or booting up) so that the DP controller is armed and ready to receive HPD plugin interrupts. HPD plugin interrupt is generated when a dongle plugs into DUT (device under test). Once HPD plugin interrupt is received, DP controller will initialize phy so that dpcd read/write will function and following link training can be proceeded successfully. DP phy will be disabled after main link is teared down at end of unplugged HPD interrupt handle triggered by dongle unplugged out of DUT. Finally regulators, code clocks and irq are disabled at corresponding suspension. Changes in V2: -- removed unnecessary dp_ctrl NULL check -- removed unnecessary phy init_count and power_count DRM_DEBUG_DP logs -- remove flip parameter out of dp_ctrl_irq_enable() -- add fixes tag Changes in V3: -- call dp_display_host_phy_init() instead of dp_ctrl_phy_init() at dp_display_host_init() for eDP Changes in V4: -- rewording commit text to match this commit changes Changes in V5: -- rebase on top of msm-next branch Changes in V6: -- delete flip variable Changes in V7: -- dp_ctrl_irq_enable/disabe() merged into dp_ctrl_reset_irq_ctrl() Changes in V8: -- add more detail comment regrading dp phy at dp_display_host_init() Changes in V9: -- remove set phy_initialized to false when -ECONNRESET detected Changes in v10: -- group into one series Changes in v11: -- drop drm/msm/dp: dp_link_parse_sink_count() return immediately if aux read Changes in v12: -- move dp_display_host_phy_exit() after dp_display_host_deinit() Changes in v13: -- do not execute phy_init until plugged_in interrupt for edp, same as DP. Changes in v14: -- remove redundant dp->core_initialized = false form dp_pm_suspend. Changes in v15: -- remove core_initialized flag check at both host_init and host_deinit Changes in v16: -- remove dp_display_host_phy_exit core_initialized=false at dp_pm_suspend Changes in v17: -- remove core_initialized checking before execute attention_cb() Changes in v18: -- remove core_initialized checking at dp_pm_suspend Fixes: 8ede2ecc3e5e ("drm/msm/dp: Add DP compliance tests on Snapdragon Chipsets") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Link: https://lore.kernel.org/r/1642531648-8448-2-git-send-email-quic_khsieh@quicinc.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2022-01-18 18:47:25 +00:00
dp_catalog_ctrl_phy_reset(ctrl->catalog);
phy_exit(phy);
drm_dbg_dp(ctrl->drm_dev, "phy=%p init=%d power_on=%d\n",
phy, phy->init_count, phy->power_count);
}
static bool dp_ctrl_use_fixed_nvid(struct dp_ctrl_private *ctrl)
{
const u8 *dpcd = ctrl->panel->dpcd;
/*
* For better interop experience, used a fixed NVID=0x8000
* whenever connected to a VGA dongle downstream.
*/
if (drm_dp_is_branch(dpcd))
return (drm_dp_has_quirk(&ctrl->panel->desc,
DP_DPCD_QUIRK_CONSTANT_N));
return false;
}
static int dp_ctrl_reinitialize_mainlink(struct dp_ctrl_private *ctrl)
{
int ret = 0;
struct dp_io *dp_io = &ctrl->parser->io;
struct phy *phy = dp_io->phy;
struct phy_configure_opts_dp *opts_dp = &dp_io->phy_opts.dp;
dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, false);
opts_dp->lanes = ctrl->link->link_params.num_lanes;
phy_configure(phy, &dp_io->phy_opts);
/*
* Disable and re-enable the mainlink clock since the
* link clock might have been adjusted as part of the
* link maintenance.
*/
ret = dp_power_clk_enable(ctrl->power, DP_CTRL_PM, false);
if (ret) {
DRM_ERROR("Failed to disable clocks. ret=%d\n", ret);
return ret;
}
phy_power_off(phy);
/* hw recommended delay before re-enabling clocks */
msleep(20);
ret = dp_ctrl_enable_mainlink_clocks(ctrl);
if (ret) {
DRM_ERROR("Failed to enable mainlink clks. ret=%d\n", ret);
return ret;
}
return ret;
}
static int dp_ctrl_deinitialize_mainlink(struct dp_ctrl_private *ctrl)
{
struct dp_io *dp_io;
struct phy *phy;
int ret;
dp_io = &ctrl->parser->io;
phy = dp_io->phy;
dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, false);
dp_catalog_ctrl_reset(ctrl->catalog);
ret = dp_power_clk_enable(ctrl->power, DP_CTRL_PM, false);
if (ret) {
DRM_ERROR("Failed to disable link clocks. ret=%d\n", ret);
}
phy_power_off(phy);
drm/msm/dp: do not initialize phy until plugin interrupt received Current DP drivers have regulators, clocks, irq and phy are grouped together within a function and executed not in a symmetric manner. This increase difficulty of code maintenance and limited code scalability. This patch divides the driver life cycle of operation into four states, resume (including booting up), dongle plugin, dongle unplugged and suspend. Regulators, core clocks and irq are grouped together and enabled at resume (or booting up) so that the DP controller is armed and ready to receive HPD plugin interrupts. HPD plugin interrupt is generated when a dongle plugs into DUT (device under test). Once HPD plugin interrupt is received, DP controller will initialize phy so that dpcd read/write will function and following link training can be proceeded successfully. DP phy will be disabled after main link is teared down at end of unplugged HPD interrupt handle triggered by dongle unplugged out of DUT. Finally regulators, code clocks and irq are disabled at corresponding suspension. Changes in V2: -- removed unnecessary dp_ctrl NULL check -- removed unnecessary phy init_count and power_count DRM_DEBUG_DP logs -- remove flip parameter out of dp_ctrl_irq_enable() -- add fixes tag Changes in V3: -- call dp_display_host_phy_init() instead of dp_ctrl_phy_init() at dp_display_host_init() for eDP Changes in V4: -- rewording commit text to match this commit changes Changes in V5: -- rebase on top of msm-next branch Changes in V6: -- delete flip variable Changes in V7: -- dp_ctrl_irq_enable/disabe() merged into dp_ctrl_reset_irq_ctrl() Changes in V8: -- add more detail comment regrading dp phy at dp_display_host_init() Changes in V9: -- remove set phy_initialized to false when -ECONNRESET detected Changes in v10: -- group into one series Changes in v11: -- drop drm/msm/dp: dp_link_parse_sink_count() return immediately if aux read Changes in v12: -- move dp_display_host_phy_exit() after dp_display_host_deinit() Changes in v13: -- do not execute phy_init until plugged_in interrupt for edp, same as DP. Changes in v14: -- remove redundant dp->core_initialized = false form dp_pm_suspend. Changes in v15: -- remove core_initialized flag check at both host_init and host_deinit Changes in v16: -- remove dp_display_host_phy_exit core_initialized=false at dp_pm_suspend Changes in v17: -- remove core_initialized checking before execute attention_cb() Changes in v18: -- remove core_initialized checking at dp_pm_suspend Fixes: 8ede2ecc3e5e ("drm/msm/dp: Add DP compliance tests on Snapdragon Chipsets") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Link: https://lore.kernel.org/r/1642531648-8448-2-git-send-email-quic_khsieh@quicinc.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2022-01-18 18:47:25 +00:00
/* aux channel down, reinit phy */
phy_exit(phy);
drm/msm/dp: do not initialize phy until plugin interrupt received Current DP drivers have regulators, clocks, irq and phy are grouped together within a function and executed not in a symmetric manner. This increase difficulty of code maintenance and limited code scalability. This patch divides the driver life cycle of operation into four states, resume (including booting up), dongle plugin, dongle unplugged and suspend. Regulators, core clocks and irq are grouped together and enabled at resume (or booting up) so that the DP controller is armed and ready to receive HPD plugin interrupts. HPD plugin interrupt is generated when a dongle plugs into DUT (device under test). Once HPD plugin interrupt is received, DP controller will initialize phy so that dpcd read/write will function and following link training can be proceeded successfully. DP phy will be disabled after main link is teared down at end of unplugged HPD interrupt handle triggered by dongle unplugged out of DUT. Finally regulators, code clocks and irq are disabled at corresponding suspension. Changes in V2: -- removed unnecessary dp_ctrl NULL check -- removed unnecessary phy init_count and power_count DRM_DEBUG_DP logs -- remove flip parameter out of dp_ctrl_irq_enable() -- add fixes tag Changes in V3: -- call dp_display_host_phy_init() instead of dp_ctrl_phy_init() at dp_display_host_init() for eDP Changes in V4: -- rewording commit text to match this commit changes Changes in V5: -- rebase on top of msm-next branch Changes in V6: -- delete flip variable Changes in V7: -- dp_ctrl_irq_enable/disabe() merged into dp_ctrl_reset_irq_ctrl() Changes in V8: -- add more detail comment regrading dp phy at dp_display_host_init() Changes in V9: -- remove set phy_initialized to false when -ECONNRESET detected Changes in v10: -- group into one series Changes in v11: -- drop drm/msm/dp: dp_link_parse_sink_count() return immediately if aux read Changes in v12: -- move dp_display_host_phy_exit() after dp_display_host_deinit() Changes in v13: -- do not execute phy_init until plugged_in interrupt for edp, same as DP. Changes in v14: -- remove redundant dp->core_initialized = false form dp_pm_suspend. Changes in v15: -- remove core_initialized flag check at both host_init and host_deinit Changes in v16: -- remove dp_display_host_phy_exit core_initialized=false at dp_pm_suspend Changes in v17: -- remove core_initialized checking before execute attention_cb() Changes in v18: -- remove core_initialized checking at dp_pm_suspend Fixes: 8ede2ecc3e5e ("drm/msm/dp: Add DP compliance tests on Snapdragon Chipsets") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Link: https://lore.kernel.org/r/1642531648-8448-2-git-send-email-quic_khsieh@quicinc.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2022-01-18 18:47:25 +00:00
phy_init(phy);
drm_dbg_dp(ctrl->drm_dev, "phy=%p init=%d power_on=%d\n",
phy, phy->init_count, phy->power_count);
return 0;
}
static int dp_ctrl_link_maintenance(struct dp_ctrl_private *ctrl)
{
int ret = 0;
int training_step = DP_TRAINING_NONE;
dp_ctrl_push_idle(&ctrl->dp_ctrl);
ctrl->link->phy_params.p_level = 0;
ctrl->link->phy_params.v_level = 0;
ctrl->dp_ctrl.pixel_rate = ctrl->panel->dp_mode.drm_mode.clock;
ret = dp_ctrl_setup_main_link(ctrl, &training_step);
if (ret)
goto end;
dp_ctrl_clear_training_pattern(ctrl);
dp_catalog_ctrl_state_ctrl(ctrl->catalog, DP_STATE_CTRL_SEND_VIDEO);
ret = dp_ctrl_wait4video_ready(ctrl);
end:
return ret;
}
drm/msm/dp: force link training for display resolution change Display resolution change is implemented through drm modeset. Older modeset (resolution) has to be disabled first before newer modeset (resolution) can be enabled. Display disable will turn off both pixel clock and main link clock so that main link have to be re-trained during display enable to have new video stream flow again. At current implementation, display enable function manually kicks up irq_hpd_handle which will read panel link status and start link training if link status is not in sync state. However, there is rare case that a particular panel links status keep staying in sync for some period of time after main link had been shut down previously at display disabled. In this case, main link retraining will not be executed by irq_hdp_handle(). Hence video stream of newer display resolution will fail to be transmitted to panel due to main link is not in sync between host and panel. This patch will bypass irq_hpd_handle() in favor of directly call dp_ctrl_on_stream() to always perform link training in regardless of main link status. So that no unexpected exception resolution change failure cases will happen. Also this implementation are more efficient than manual kicking off irq_hpd_handle function. Changes in v2: -- set force_link_train flag on DP only (is_edp == false) Changes in v3: -- revise commit text -- add Fixes tag Changes in v4: -- revise commit text Changes in v5: -- fix spelling at commit text Changes in v6: -- split dp_ctrl_on_stream() for phy test case -- revise commit text for modeset Changes in v7: -- drop 0 assignment at local variable (ret = 0) Changes in v8: -- add patch to remove pixel_rate from dp_ctrl Changes in v9: -- forward declare dp_ctrl_on_stream_phy_test_report() Fixes: 62671d2ef24b ("drm/msm/dp: fixes wrong connection state caused by failure of link train") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Patchwork: https://patchwork.freedesktop.org/patch/489895/ Link: https://lore.kernel.org/r/1655411200-7255-1-git-send-email-quic_khsieh@quicinc.com Signed-off-by: Rob Clark <robdclark@chromium.org>
2022-06-16 20:26:40 +00:00
static int dp_ctrl_on_stream_phy_test_report(struct dp_ctrl *dp_ctrl);
static int dp_ctrl_process_phy_test_request(struct dp_ctrl_private *ctrl)
{
int ret = 0;
if (!ctrl->link->phy_params.phy_test_pattern_sel) {
drm_dbg_dp(ctrl->drm_dev,
"no test pattern selected by sink\n");
return ret;
}
/*
* The global reset will need DP link related clocks to be
* running. Add the global reset just before disabling the
* link clocks and core clocks.
*/
ret = dp_ctrl_off(&ctrl->dp_ctrl);
if (ret) {
DRM_ERROR("failed to disable DP controller\n");
return ret;
}
ret = dp_ctrl_on_link(&ctrl->dp_ctrl);
if (!ret)
drm/msm/dp: force link training for display resolution change Display resolution change is implemented through drm modeset. Older modeset (resolution) has to be disabled first before newer modeset (resolution) can be enabled. Display disable will turn off both pixel clock and main link clock so that main link have to be re-trained during display enable to have new video stream flow again. At current implementation, display enable function manually kicks up irq_hpd_handle which will read panel link status and start link training if link status is not in sync state. However, there is rare case that a particular panel links status keep staying in sync for some period of time after main link had been shut down previously at display disabled. In this case, main link retraining will not be executed by irq_hdp_handle(). Hence video stream of newer display resolution will fail to be transmitted to panel due to main link is not in sync between host and panel. This patch will bypass irq_hpd_handle() in favor of directly call dp_ctrl_on_stream() to always perform link training in regardless of main link status. So that no unexpected exception resolution change failure cases will happen. Also this implementation are more efficient than manual kicking off irq_hpd_handle function. Changes in v2: -- set force_link_train flag on DP only (is_edp == false) Changes in v3: -- revise commit text -- add Fixes tag Changes in v4: -- revise commit text Changes in v5: -- fix spelling at commit text Changes in v6: -- split dp_ctrl_on_stream() for phy test case -- revise commit text for modeset Changes in v7: -- drop 0 assignment at local variable (ret = 0) Changes in v8: -- add patch to remove pixel_rate from dp_ctrl Changes in v9: -- forward declare dp_ctrl_on_stream_phy_test_report() Fixes: 62671d2ef24b ("drm/msm/dp: fixes wrong connection state caused by failure of link train") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Patchwork: https://patchwork.freedesktop.org/patch/489895/ Link: https://lore.kernel.org/r/1655411200-7255-1-git-send-email-quic_khsieh@quicinc.com Signed-off-by: Rob Clark <robdclark@chromium.org>
2022-06-16 20:26:40 +00:00
ret = dp_ctrl_on_stream_phy_test_report(&ctrl->dp_ctrl);
else
DRM_ERROR("failed to enable DP link controller\n");
return ret;
}
static bool dp_ctrl_send_phy_test_pattern(struct dp_ctrl_private *ctrl)
{
bool success = false;
u32 pattern_sent = 0x0;
u32 pattern_requested = ctrl->link->phy_params.phy_test_pattern_sel;
drm_dbg_dp(ctrl->drm_dev, "request: 0x%x\n", pattern_requested);
if (dp_catalog_ctrl_update_vx_px(ctrl->catalog,
ctrl->link->phy_params.v_level,
ctrl->link->phy_params.p_level)) {
DRM_ERROR("Failed to set v/p levels\n");
return false;
}
dp_catalog_ctrl_send_phy_pattern(ctrl->catalog, pattern_requested);
dp_ctrl_update_vx_px(ctrl);
dp_link_send_test_response(ctrl->link);
pattern_sent = dp_catalog_ctrl_read_phy_pattern(ctrl->catalog);
switch (pattern_sent) {
case MR_LINK_TRAINING1:
success = (pattern_requested ==
DP_PHY_TEST_PATTERN_D10_2);
break;
case MR_LINK_SYMBOL_ERM:
success = ((pattern_requested ==
DP_PHY_TEST_PATTERN_ERROR_COUNT) ||
(pattern_requested ==
DP_PHY_TEST_PATTERN_CP2520));
break;
case MR_LINK_PRBS7:
success = (pattern_requested ==
DP_PHY_TEST_PATTERN_PRBS7);
break;
case MR_LINK_CUSTOM80:
success = (pattern_requested ==
DP_PHY_TEST_PATTERN_80BIT_CUSTOM);
break;
case MR_LINK_TRAINING4:
success = (pattern_requested ==
DP_PHY_TEST_PATTERN_SEL_MASK);
break;
default:
success = false;
}
drm_dbg_dp(ctrl->drm_dev, "%s: test->0x%x\n",
success ? "success" : "failed", pattern_requested);
return success;
}
void dp_ctrl_handle_sink_request(struct dp_ctrl *dp_ctrl)
{
struct dp_ctrl_private *ctrl;
u32 sink_request = 0x0;
if (!dp_ctrl) {
DRM_ERROR("invalid input\n");
return;
}
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
sink_request = ctrl->link->sink_request;
if (sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) {
drm_dbg_dp(ctrl->drm_dev, "PHY_TEST_PATTERN request\n");
if (dp_ctrl_process_phy_test_request(ctrl)) {
DRM_ERROR("process phy_test_req failed\n");
return;
}
}
if (sink_request & DP_LINK_STATUS_UPDATED) {
if (dp_ctrl_link_maintenance(ctrl)) {
DRM_ERROR("LM failed: TEST_LINK_TRAINING\n");
return;
}
}
if (sink_request & DP_TEST_LINK_TRAINING) {
dp_link_send_test_response(ctrl->link);
if (dp_ctrl_link_maintenance(ctrl)) {
DRM_ERROR("LM failed: TEST_LINK_TRAINING\n");
return;
}
}
}
static bool dp_ctrl_clock_recovery_any_ok(
const u8 link_status[DP_LINK_STATUS_SIZE],
int lane_count)
{
int reduced_cnt;
if (lane_count <= 1)
return false;
/*
* only interested in the lane number after reduced
* lane_count = 4, then only interested in 2 lanes
* lane_count = 2, then only interested in 1 lane
*/
reduced_cnt = lane_count >> 1;
return drm_dp_clock_recovery_ok(link_status, reduced_cnt);
}
static bool dp_ctrl_channel_eq_ok(struct dp_ctrl_private *ctrl)
{
u8 link_status[DP_LINK_STATUS_SIZE];
int num_lanes = ctrl->link->link_params.num_lanes;
dp_ctrl_read_link_status(ctrl, link_status);
return drm_dp_channel_eq_ok(link_status, num_lanes);
}
int dp_ctrl_on_link(struct dp_ctrl *dp_ctrl)
{
int rc = 0;
struct dp_ctrl_private *ctrl;
u32 rate = 0;
int link_train_max_retries = 5;
u32 const phy_cts_pixel_clk_khz = 148500;
u8 link_status[DP_LINK_STATUS_SIZE];
unsigned int training_step;
if (!dp_ctrl)
return -EINVAL;
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
rate = ctrl->panel->link_info.rate;
dp_power_clk_enable(ctrl->power, DP_CORE_PM, true);
if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) {
drm_dbg_dp(ctrl->drm_dev,
"using phy test link parameters\n");
if (!ctrl->panel->dp_mode.drm_mode.clock)
ctrl->dp_ctrl.pixel_rate = phy_cts_pixel_clk_khz;
} else {
ctrl->link->link_params.rate = rate;
ctrl->link->link_params.num_lanes =
ctrl->panel->link_info.num_lanes;
ctrl->dp_ctrl.pixel_rate = ctrl->panel->dp_mode.drm_mode.clock;
}
drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d, pixel_rate=%d\n",
ctrl->link->link_params.rate, ctrl->link->link_params.num_lanes,
ctrl->dp_ctrl.pixel_rate);
rc = dp_ctrl_enable_mainlink_clocks(ctrl);
if (rc)
return rc;
while (--link_train_max_retries) {
rc = dp_ctrl_reinitialize_mainlink(ctrl);
if (rc) {
DRM_ERROR("Failed to reinitialize mainlink. rc=%d\n",
rc);
break;
}
training_step = DP_TRAINING_NONE;
rc = dp_ctrl_setup_main_link(ctrl, &training_step);
if (rc == 0) {
/* training completed successfully */
break;
} else if (training_step == DP_TRAINING_1) {
/* link train_1 failed */
if (!dp_catalog_link_is_connected(ctrl->catalog))
break;
dp_ctrl_read_link_status(ctrl, link_status);
rc = dp_ctrl_link_rate_down_shift(ctrl);
if (rc < 0) { /* already in RBR = 1.6G */
if (dp_ctrl_clock_recovery_any_ok(link_status,
ctrl->link->link_params.num_lanes)) {
/*
* some lanes are ready,
* reduce lane number
*/
rc = dp_ctrl_link_lane_down_shift(ctrl);
if (rc < 0) { /* lane == 1 already */
/* end with failure */
break;
}
} else {
/* end with failure */
break; /* lane == 1 already */
}
}
} else if (training_step == DP_TRAINING_2) {
/* link train_2 failed */
if (!dp_catalog_link_is_connected(ctrl->catalog))
break;
dp_ctrl_read_link_status(ctrl, link_status);
if (!drm_dp_clock_recovery_ok(link_status,
ctrl->link->link_params.num_lanes))
rc = dp_ctrl_link_rate_down_shift(ctrl);
else
rc = dp_ctrl_link_lane_down_shift(ctrl);
if (rc < 0) {
/* end with failure */
break; /* lane == 1 already */
}
/* stop link training before start re training */
dp_ctrl_clear_training_pattern(ctrl);
}
}
if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN)
return rc;
if (rc == 0) { /* link train successfully */
/*
* do not stop train pattern here
* stop link training at on_stream
* to pass compliance test
*/
} else {
/*
* link training failed
* end txing train pattern here
*/
dp_ctrl_clear_training_pattern(ctrl);
dp_ctrl_deinitialize_mainlink(ctrl);
rc = -ECONNRESET;
}
return rc;
}
static int dp_ctrl_link_retrain(struct dp_ctrl_private *ctrl)
{
int training_step = DP_TRAINING_NONE;
return dp_ctrl_setup_main_link(ctrl, &training_step);
}
drm/msm/dp: force link training for display resolution change Display resolution change is implemented through drm modeset. Older modeset (resolution) has to be disabled first before newer modeset (resolution) can be enabled. Display disable will turn off both pixel clock and main link clock so that main link have to be re-trained during display enable to have new video stream flow again. At current implementation, display enable function manually kicks up irq_hpd_handle which will read panel link status and start link training if link status is not in sync state. However, there is rare case that a particular panel links status keep staying in sync for some period of time after main link had been shut down previously at display disabled. In this case, main link retraining will not be executed by irq_hdp_handle(). Hence video stream of newer display resolution will fail to be transmitted to panel due to main link is not in sync between host and panel. This patch will bypass irq_hpd_handle() in favor of directly call dp_ctrl_on_stream() to always perform link training in regardless of main link status. So that no unexpected exception resolution change failure cases will happen. Also this implementation are more efficient than manual kicking off irq_hpd_handle function. Changes in v2: -- set force_link_train flag on DP only (is_edp == false) Changes in v3: -- revise commit text -- add Fixes tag Changes in v4: -- revise commit text Changes in v5: -- fix spelling at commit text Changes in v6: -- split dp_ctrl_on_stream() for phy test case -- revise commit text for modeset Changes in v7: -- drop 0 assignment at local variable (ret = 0) Changes in v8: -- add patch to remove pixel_rate from dp_ctrl Changes in v9: -- forward declare dp_ctrl_on_stream_phy_test_report() Fixes: 62671d2ef24b ("drm/msm/dp: fixes wrong connection state caused by failure of link train") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Patchwork: https://patchwork.freedesktop.org/patch/489895/ Link: https://lore.kernel.org/r/1655411200-7255-1-git-send-email-quic_khsieh@quicinc.com Signed-off-by: Rob Clark <robdclark@chromium.org>
2022-06-16 20:26:40 +00:00
static int dp_ctrl_on_stream_phy_test_report(struct dp_ctrl *dp_ctrl)
{
int ret;
struct dp_ctrl_private *ctrl;
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
ctrl->dp_ctrl.pixel_rate = ctrl->panel->dp_mode.drm_mode.clock;
ret = dp_ctrl_enable_stream_clocks(ctrl);
if (ret) {
DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret);
return ret;
}
dp_ctrl_send_phy_test_pattern(ctrl);
return 0;
}
int dp_ctrl_on_stream(struct dp_ctrl *dp_ctrl, bool force_link_train)
{
int ret = 0;
bool mainlink_ready = false;
struct dp_ctrl_private *ctrl;
drm/msm/dp: enable widebus feature for display port Widebus feature will transmit two pixel data per pixel clock to interface. This feature now is required to be enabled to easy migrant to higher resolution applications in future. However since some legacy chipsets does not support this feature, this feature is enabled by setting wide_bus_en flag to true within msm_dp_desc struct. changes in v2: -- remove compression related code from timing -- remove op_info from struct msm_drm_private -- remove unnecessary wide_bus_en variables -- pass wide_bus_en into timing configuration by struct msm_dp Changes in v3: -- split patch into 3 patches -- enable widebus feature base on chip hardware revision Changes in v5: -- DP_INTF_CONFIG_DATABUS_WIDEN Changes in v6: -- static inline bool msm_dp_wide_bus_enable() in msm_drv.h Changes in v7: -- add Tested-by Changes in v9: -- add wide_bus_en to msm_dp_desc Changes in v10: -- add wide_bus_en boolean to dp_catalog struc to avoid passing it as parameter Changes in v11: -- add const to dp_catalog_hw_revision() -- add const to msm_dp_wide_bus_available() Changes in v12: -- dp_catalog_hw_revision(const struct dp_catalog *dp_catalog) -- msm_dp_wide_bus_available(const struct msm_dp *dp_display) Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reported-by: kernel test robot <lkp@intel.com> Tested-by: Bjorn Andersson <bjorn.andersson@linaro.org> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> Patchwork: https://patchwork.freedesktop.org/patch/476283/ Link: https://lore.kernel.org/r/1645824192-29670-5-git-send-email-quic_khsieh@quicinc.com [DB: fixed the compilation] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2022-02-25 21:23:12 +00:00
unsigned long pixel_rate_orig;
if (!dp_ctrl)
return -EINVAL;
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
ctrl->dp_ctrl.pixel_rate = ctrl->panel->dp_mode.drm_mode.clock;
drm/msm/dp: enable widebus feature for display port Widebus feature will transmit two pixel data per pixel clock to interface. This feature now is required to be enabled to easy migrant to higher resolution applications in future. However since some legacy chipsets does not support this feature, this feature is enabled by setting wide_bus_en flag to true within msm_dp_desc struct. changes in v2: -- remove compression related code from timing -- remove op_info from struct msm_drm_private -- remove unnecessary wide_bus_en variables -- pass wide_bus_en into timing configuration by struct msm_dp Changes in v3: -- split patch into 3 patches -- enable widebus feature base on chip hardware revision Changes in v5: -- DP_INTF_CONFIG_DATABUS_WIDEN Changes in v6: -- static inline bool msm_dp_wide_bus_enable() in msm_drv.h Changes in v7: -- add Tested-by Changes in v9: -- add wide_bus_en to msm_dp_desc Changes in v10: -- add wide_bus_en boolean to dp_catalog struc to avoid passing it as parameter Changes in v11: -- add const to dp_catalog_hw_revision() -- add const to msm_dp_wide_bus_available() Changes in v12: -- dp_catalog_hw_revision(const struct dp_catalog *dp_catalog) -- msm_dp_wide_bus_available(const struct msm_dp *dp_display) Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reported-by: kernel test robot <lkp@intel.com> Tested-by: Bjorn Andersson <bjorn.andersson@linaro.org> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> Patchwork: https://patchwork.freedesktop.org/patch/476283/ Link: https://lore.kernel.org/r/1645824192-29670-5-git-send-email-quic_khsieh@quicinc.com [DB: fixed the compilation] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2022-02-25 21:23:12 +00:00
pixel_rate_orig = ctrl->dp_ctrl.pixel_rate;
if (dp_ctrl->wide_bus_en)
ctrl->dp_ctrl.pixel_rate >>= 1;
drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d, pixel_rate=%d\n",
ctrl->link->link_params.rate,
ctrl->link->link_params.num_lanes, ctrl->dp_ctrl.pixel_rate);
if (!dp_power_clk_status(ctrl->power, DP_CTRL_PM)) { /* link clk is off */
ret = dp_ctrl_enable_mainlink_clocks(ctrl);
if (ret) {
DRM_ERROR("Failed to start link clocks. ret=%d\n", ret);
goto end;
}
}
ret = dp_ctrl_enable_stream_clocks(ctrl);
if (ret) {
DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret);
goto end;
}
drm/msm/dp: force link training for display resolution change Display resolution change is implemented through drm modeset. Older modeset (resolution) has to be disabled first before newer modeset (resolution) can be enabled. Display disable will turn off both pixel clock and main link clock so that main link have to be re-trained during display enable to have new video stream flow again. At current implementation, display enable function manually kicks up irq_hpd_handle which will read panel link status and start link training if link status is not in sync state. However, there is rare case that a particular panel links status keep staying in sync for some period of time after main link had been shut down previously at display disabled. In this case, main link retraining will not be executed by irq_hdp_handle(). Hence video stream of newer display resolution will fail to be transmitted to panel due to main link is not in sync between host and panel. This patch will bypass irq_hpd_handle() in favor of directly call dp_ctrl_on_stream() to always perform link training in regardless of main link status. So that no unexpected exception resolution change failure cases will happen. Also this implementation are more efficient than manual kicking off irq_hpd_handle function. Changes in v2: -- set force_link_train flag on DP only (is_edp == false) Changes in v3: -- revise commit text -- add Fixes tag Changes in v4: -- revise commit text Changes in v5: -- fix spelling at commit text Changes in v6: -- split dp_ctrl_on_stream() for phy test case -- revise commit text for modeset Changes in v7: -- drop 0 assignment at local variable (ret = 0) Changes in v8: -- add patch to remove pixel_rate from dp_ctrl Changes in v9: -- forward declare dp_ctrl_on_stream_phy_test_report() Fixes: 62671d2ef24b ("drm/msm/dp: fixes wrong connection state caused by failure of link train") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Patchwork: https://patchwork.freedesktop.org/patch/489895/ Link: https://lore.kernel.org/r/1655411200-7255-1-git-send-email-quic_khsieh@quicinc.com Signed-off-by: Rob Clark <robdclark@chromium.org>
2022-06-16 20:26:40 +00:00
if (force_link_train || !dp_ctrl_channel_eq_ok(ctrl))
dp_ctrl_link_retrain(ctrl);
/* stop txing train pattern to end link training */
dp_ctrl_clear_training_pattern(ctrl);
/*
* Set up transfer unit values and set controller state to send
* video.
*/
reinit_completion(&ctrl->video_comp);
dp_ctrl_configure_source_params(ctrl);
dp_catalog_ctrl_config_msa(ctrl->catalog,
ctrl->link->link_params.rate,
drm/msm/dp: enable widebus feature for display port Widebus feature will transmit two pixel data per pixel clock to interface. This feature now is required to be enabled to easy migrant to higher resolution applications in future. However since some legacy chipsets does not support this feature, this feature is enabled by setting wide_bus_en flag to true within msm_dp_desc struct. changes in v2: -- remove compression related code from timing -- remove op_info from struct msm_drm_private -- remove unnecessary wide_bus_en variables -- pass wide_bus_en into timing configuration by struct msm_dp Changes in v3: -- split patch into 3 patches -- enable widebus feature base on chip hardware revision Changes in v5: -- DP_INTF_CONFIG_DATABUS_WIDEN Changes in v6: -- static inline bool msm_dp_wide_bus_enable() in msm_drv.h Changes in v7: -- add Tested-by Changes in v9: -- add wide_bus_en to msm_dp_desc Changes in v10: -- add wide_bus_en boolean to dp_catalog struc to avoid passing it as parameter Changes in v11: -- add const to dp_catalog_hw_revision() -- add const to msm_dp_wide_bus_available() Changes in v12: -- dp_catalog_hw_revision(const struct dp_catalog *dp_catalog) -- msm_dp_wide_bus_available(const struct msm_dp *dp_display) Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reported-by: kernel test robot <lkp@intel.com> Tested-by: Bjorn Andersson <bjorn.andersson@linaro.org> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> Patchwork: https://patchwork.freedesktop.org/patch/476283/ Link: https://lore.kernel.org/r/1645824192-29670-5-git-send-email-quic_khsieh@quicinc.com [DB: fixed the compilation] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2022-02-25 21:23:12 +00:00
pixel_rate_orig, dp_ctrl_use_fixed_nvid(ctrl));
dp_ctrl_setup_tr_unit(ctrl);
dp_catalog_ctrl_state_ctrl(ctrl->catalog, DP_STATE_CTRL_SEND_VIDEO);
ret = dp_ctrl_wait4video_ready(ctrl);
if (ret)
return ret;
mainlink_ready = dp_catalog_ctrl_mainlink_ready(ctrl->catalog);
drm_dbg_dp(ctrl->drm_dev,
"mainlink %s\n", mainlink_ready ? "READY" : "NOT READY");
end:
return ret;
}
int dp_ctrl_off_link_stream(struct dp_ctrl *dp_ctrl)
{
struct dp_ctrl_private *ctrl;
struct dp_io *dp_io;
struct phy *phy;
int ret;
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
dp_io = &ctrl->parser->io;
phy = dp_io->phy;
/* set dongle to D3 (power off) mode */
dp_link_psm_config(ctrl->link, &ctrl->panel->link_info, true);
dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, false);
if (dp_power_clk_status(ctrl->power, DP_STREAM_PM)) {
ret = dp_power_clk_enable(ctrl->power, DP_STREAM_PM, false);
if (ret) {
DRM_ERROR("Failed to disable pclk. ret=%d\n", ret);
return ret;
}
}
ret = dp_power_clk_enable(ctrl->power, DP_CTRL_PM, false);
if (ret) {
DRM_ERROR("Failed to disable link clocks. ret=%d\n", ret);
return ret;
}
phy_power_off(phy);
/* aux channel down, reinit phy */
phy_exit(phy);
phy_init(phy);
drm_dbg_dp(ctrl->drm_dev, "phy=%p init=%d power_on=%d\n",
phy, phy->init_count, phy->power_count);
return ret;
}
drm/msm/dp: tear down main link at unplug handle immediately Two stages are required to setup up main link to be ready to transmit video stream. Stage 1: dp_hpd_plug_handle() perform link training to set up main link stage 2: user space framework (msm_dp_display_enable()) to enable pixel clock and transfer main link to video ready state. At current implementation, when dongle unplugged dp_hdp_unplug_handle() has to wait until stage 2 completed before it can send link down uevent to user space framework to disable pixel clock followed by tearing down main link. This introduce unnecessary latency if dongle unplugged happen after stage 1 and before stage 2. It also has possibility leave main link stay at ready state after dongle unplugged if framework does not response to link down uevent notification. This will prevent next dongle plug in from working. This scenario could possibly happen when dongle unplug while system in the middle of suspending. This patch allow unplug handle to tear down main link and notify framework link down immediately if dongle unplugged happen after stage 1 and before stage 2. With this approach, dp driver is much more resilient to any different scenarios. Also redundant both dp_connect_pending_timeout() and dp_disconnect_pending_timeout() are removed to reduce logic complexity. Changes in V2: -- return -EINVAL at msm_dp_display_enable() if not in correct state -- replace ST_CONNECT_PENDING with ST_MAINLINK_READY Fixes: 8ede2ecc3e5e ("drm/msm/dp: Add DP compliance tests on Snapdragon Chipsets") Signed-off-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Stephen Boyd <swboyd@chromium.org> Patchwork: https://patchwork.freedesktop.org/patch/483391/ Link: https://lore.kernel.org/r/1650927382-22461-1-git-send-email-quic_khsieh@quicinc.com [DB: fixed return values due to conversion to function merge] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2022-04-25 22:56:22 +00:00
int dp_ctrl_off_link(struct dp_ctrl *dp_ctrl)
{
struct dp_ctrl_private *ctrl;
struct dp_io *dp_io;
struct phy *phy;
int ret;
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
dp_io = &ctrl->parser->io;
phy = dp_io->phy;
dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, false);
ret = dp_power_clk_enable(ctrl->power, DP_CTRL_PM, false);
if (ret) {
DRM_ERROR("Failed to disable link clocks. ret=%d\n", ret);
}
DRM_DEBUG_DP("Before, phy=%p init_count=%d power_on=%d\n",
phy, phy->init_count, phy->power_count);
phy_power_off(phy);
DRM_DEBUG_DP("After, phy=%p init_count=%d power_on=%d\n",
phy, phy->init_count, phy->power_count);
return ret;
}
int dp_ctrl_off(struct dp_ctrl *dp_ctrl)
{
struct dp_ctrl_private *ctrl;
struct dp_io *dp_io;
struct phy *phy;
int ret = 0;
if (!dp_ctrl)
return -EINVAL;
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
dp_io = &ctrl->parser->io;
phy = dp_io->phy;
dp_catalog_ctrl_mainlink_ctrl(ctrl->catalog, false);
dp_catalog_ctrl_reset(ctrl->catalog);
ret = dp_power_clk_enable(ctrl->power, DP_STREAM_PM, false);
if (ret)
DRM_ERROR("Failed to disable pixel clocks. ret=%d\n", ret);
ret = dp_power_clk_enable(ctrl->power, DP_CTRL_PM, false);
if (ret) {
DRM_ERROR("Failed to disable link clocks. ret=%d\n", ret);
}
phy_power_off(phy);
drm_dbg_dp(ctrl->drm_dev, "phy=%p init=%d power_on=%d\n",
phy, phy->init_count, phy->power_count);
return ret;
}
void dp_ctrl_isr(struct dp_ctrl *dp_ctrl)
{
struct dp_ctrl_private *ctrl;
u32 isr;
if (!dp_ctrl)
return;
ctrl = container_of(dp_ctrl, struct dp_ctrl_private, dp_ctrl);
isr = dp_catalog_ctrl_get_interrupt(ctrl->catalog);
if (isr & DP_CTRL_INTR_READY_FOR_VIDEO) {
drm_dbg_dp(ctrl->drm_dev, "dp_video_ready\n");
complete(&ctrl->video_comp);
}
if (isr & DP_CTRL_INTR_IDLE_PATTERN_SENT) {
drm_dbg_dp(ctrl->drm_dev, "idle_patterns_sent\n");
complete(&ctrl->idle_comp);
}
}
struct dp_ctrl *dp_ctrl_get(struct device *dev, struct dp_link *link,
struct dp_panel *panel, struct drm_dp_aux *aux,
struct dp_power *power, struct dp_catalog *catalog,
struct dp_parser *parser)
{
struct dp_ctrl_private *ctrl;
int ret;
if (!dev || !panel || !aux ||
!link || !catalog) {
DRM_ERROR("invalid input\n");
return ERR_PTR(-EINVAL);
}
ctrl = devm_kzalloc(dev, sizeof(*ctrl), GFP_KERNEL);
if (!ctrl) {
DRM_ERROR("Mem allocation failure\n");
return ERR_PTR(-ENOMEM);
}
ret = devm_pm_opp_set_clkname(dev, "ctrl_link");
if (ret) {
dev_err(dev, "invalid DP OPP table in device tree\n");
/* caller do PTR_ERR(opp_table) */
return (struct dp_ctrl *)ERR_PTR(ret);
}
/* OPP table is optional */
ret = devm_pm_opp_of_add_table(dev);
if (ret)
dev_err(dev, "failed to add DP OPP table\n");
init_completion(&ctrl->idle_comp);
init_completion(&ctrl->video_comp);
/* in parameters */
ctrl->parser = parser;
ctrl->panel = panel;
ctrl->power = power;
ctrl->aux = aux;
ctrl->link = link;
ctrl->catalog = catalog;
ctrl->dev = dev;
return &ctrl->dp_ctrl;
}