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

2771 lines
74 KiB
C
Raw Permalink 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/clk.h>
#include <linux/completion.h>
#include <linux/delay.h>
#include <linux/iopoll.h>
#include <linux/phy/phy.h>
#include <linux/phy/phy-dp.h>
#include <linux/pm_opp.h>
#include <linux/rational.h>
#include <linux/string_choices.h>
#include <drm/display/drm_dp_helper.h>
#include <drm/drm_device.h>
#include <drm/drm_fixed.h>
#include <drm/drm_print.h>
#include "dp_reg.h"
#include "dp_ctrl.h"
#include "dp_link.h"
#define POLLING_SLEEP_US 1000
#define POLLING_TIMEOUT_US 10000
#define DP_KHZ_TO_HZ 1000
#define IDLE_PATTERN_COMPLETION_TIMEOUT_JIFFIES (30 * HZ / 1000) /* 30 ms */
#define PSR_OPERATION_COMPLETION_TIMEOUT_JIFFIES (300 * HZ / 1000) /* 300 ms */
#define WAIT_FOR_VIDEO_READY_TIMEOUT_JIFFIES (HZ / 2)
#define DP_INTERRUPT_STATUS_ACK_SHIFT 1
#define DP_INTERRUPT_STATUS_MASK_SHIFT 2
#define DP_INTERRUPT_STATUS1 \
(DP_INTR_AUX_XFER_DONE| \
DP_INTR_WRONG_ADDR | DP_INTR_TIMEOUT | \
DP_INTR_NACK_DEFER | DP_INTR_WRONG_DATA_CNT | \
DP_INTR_I2C_NACK | DP_INTR_I2C_DEFER | \
DP_INTR_PLL_UNLOCKED | DP_INTR_AUX_ERROR)
#define DP_INTERRUPT_STATUS1_ACK \
(DP_INTERRUPT_STATUS1 << DP_INTERRUPT_STATUS_ACK_SHIFT)
#define DP_INTERRUPT_STATUS1_MASK \
(DP_INTERRUPT_STATUS1 << DP_INTERRUPT_STATUS_MASK_SHIFT)
#define DP_INTERRUPT_STATUS2 \
(DP_INTR_READY_FOR_VIDEO | DP_INTR_IDLE_PATTERN_SENT | \
DP_INTR_FRAME_END | DP_INTR_CRC_UPDATED)
#define DP_INTERRUPT_STATUS2_ACK \
(DP_INTERRUPT_STATUS2 << DP_INTERRUPT_STATUS_ACK_SHIFT)
#define DP_INTERRUPT_STATUS2_MASK \
(DP_INTERRUPT_STATUS2 << DP_INTERRUPT_STATUS_MASK_SHIFT)
#define DP_INTERRUPT_STATUS4 \
(PSR_UPDATE_INT | PSR_CAPTURE_INT | PSR_EXIT_INT | \
PSR_UPDATE_ERROR_INT | PSR_WAKE_ERROR_INT)
#define DP_INTERRUPT_MASK4 \
(PSR_UPDATE_MASK | PSR_CAPTURE_MASK | PSR_EXIT_MASK | \
PSR_UPDATE_ERROR_MASK | PSR_WAKE_ERROR_MASK)
#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 msm_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 msm_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 msm_dp_ctrl_private {
struct msm_dp_ctrl msm_dp_ctrl;
struct drm_device *drm_dev;
struct device *dev;
struct drm_dp_aux *aux;
struct msm_dp_panel *panel;
struct msm_dp_link *link;
void __iomem *ahb_base;
void __iomem *link_base;
struct phy *phy;
unsigned int num_core_clks;
struct clk_bulk_data *core_clks;
unsigned int num_link_clks;
struct clk_bulk_data *link_clks;
struct clk *pixel_clk;
union phy_configure_opts phy_opts;
struct completion idle_comp;
struct completion psr_op_comp;
struct completion video_comp;
u32 hw_revision;
bool core_clks_on;
bool link_clks_on;
bool stream_clks_on;
};
static inline u32 msm_dp_read_ahb(const struct msm_dp_ctrl_private *ctrl, u32 offset)
{
return readl_relaxed(ctrl->ahb_base + offset);
}
static inline void msm_dp_write_ahb(struct msm_dp_ctrl_private *ctrl,
u32 offset, u32 data)
{
/*
* To make sure phy reg writes happens before any other operation,
* this function uses writel() instread of writel_relaxed()
*/
writel(data, ctrl->ahb_base + offset);
}
static inline u32 msm_dp_read_link(struct msm_dp_ctrl_private *ctrl, u32 offset)
{
return readl_relaxed(ctrl->link_base + offset);
}
static inline void msm_dp_write_link(struct msm_dp_ctrl_private *ctrl,
u32 offset, u32 data)
{
/*
* To make sure link reg writes happens before any other operation,
* this function uses writel() instread of writel_relaxed()
*/
writel(data, ctrl->link_base + offset);
}
static int msm_dp_aux_link_configure(struct drm_dp_aux *aux,
struct msm_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;
}
/*
* NOTE: resetting DP controller will also clear any pending HPD related interrupts
*/
void msm_dp_ctrl_reset(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl =
container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
u32 sw_reset;
sw_reset = msm_dp_read_ahb(ctrl, REG_DP_SW_RESET);
sw_reset |= DP_SW_RESET;
msm_dp_write_ahb(ctrl, REG_DP_SW_RESET, sw_reset);
usleep_range(1000, 1100); /* h/w recommended delay */
sw_reset &= ~DP_SW_RESET;
msm_dp_write_ahb(ctrl, REG_DP_SW_RESET, sw_reset);
if (!ctrl->hw_revision) {
ctrl->hw_revision = msm_dp_read_ahb(ctrl, REG_DP_HW_VERSION);
ctrl->panel->hw_revision = ctrl->hw_revision;
}
}
static u32 msm_dp_ctrl_get_aux_interrupt(struct msm_dp_ctrl_private *ctrl)
{
u32 intr, intr_ack;
intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS);
intr &= ~DP_INTERRUPT_STATUS1_MASK;
intr_ack = (intr & DP_INTERRUPT_STATUS1)
<< DP_INTERRUPT_STATUS_ACK_SHIFT;
msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS,
intr_ack | DP_INTERRUPT_STATUS1_MASK);
return intr;
}
static u32 msm_dp_ctrl_get_interrupt(struct msm_dp_ctrl_private *ctrl)
{
u32 intr, intr_ack;
intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS2);
intr &= ~DP_INTERRUPT_STATUS2_MASK;
intr_ack = (intr & DP_INTERRUPT_STATUS2)
<< DP_INTERRUPT_STATUS_ACK_SHIFT;
msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2,
intr_ack | DP_INTERRUPT_STATUS2_MASK);
return intr;
}
void msm_dp_ctrl_enable_irq(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl =
container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS,
DP_INTERRUPT_STATUS1_MASK);
msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2,
DP_INTERRUPT_STATUS2_MASK);
}
void msm_dp_ctrl_disable_irq(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl =
container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS, 0x00);
msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS2, 0x00);
}
static u32 msm_dp_ctrl_get_psr_interrupt(struct msm_dp_ctrl_private *ctrl)
{
u32 intr, intr_ack;
intr = msm_dp_read_ahb(ctrl, REG_DP_INTR_STATUS4);
intr_ack = (intr & DP_INTERRUPT_STATUS4)
<< DP_INTERRUPT_STATUS_ACK_SHIFT;
msm_dp_write_ahb(ctrl, REG_DP_INTR_STATUS4, intr_ack);
return intr;
}
static void msm_dp_ctrl_config_psr_interrupt(struct msm_dp_ctrl_private *ctrl)
{
msm_dp_write_ahb(ctrl, REG_DP_INTR_MASK4, DP_INTERRUPT_MASK4);
}
static void msm_dp_ctrl_psr_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
{
u32 val;
val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
val |= DP_MAINLINK_CTRL_ENABLE;
msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
}
static void msm_dp_ctrl_psr_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
{
u32 val;
val = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
val &= ~DP_MAINLINK_CTRL_ENABLE;
msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, val);
}
static void msm_dp_ctrl_mainlink_enable(struct msm_dp_ctrl_private *ctrl)
{
u32 mainlink_ctrl;
drm_dbg_dp(ctrl->drm_dev, "enable\n");
mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
mainlink_ctrl &= ~(DP_MAINLINK_CTRL_RESET |
DP_MAINLINK_CTRL_ENABLE);
msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
mainlink_ctrl |= DP_MAINLINK_CTRL_RESET;
msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
mainlink_ctrl &= ~DP_MAINLINK_CTRL_RESET;
msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
mainlink_ctrl |= (DP_MAINLINK_CTRL_ENABLE |
DP_MAINLINK_FB_BOUNDARY_SEL);
msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
}
static void msm_dp_ctrl_mainlink_disable(struct msm_dp_ctrl_private *ctrl)
{
u32 mainlink_ctrl;
drm_dbg_dp(ctrl->drm_dev, "disable\n");
mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
mainlink_ctrl &= ~DP_MAINLINK_CTRL_ENABLE;
msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
}
static void msm_dp_setup_peripheral_flush(struct msm_dp_ctrl_private *ctrl)
{
u32 mainlink_ctrl;
mainlink_ctrl = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
if (ctrl->hw_revision >= DP_HW_VERSION_1_2)
mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_SDE_PERIPH_UPDATE;
else
mainlink_ctrl |= DP_MAINLINK_FLUSH_MODE_UPDATE_SDP;
msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, mainlink_ctrl);
}
static bool msm_dp_ctrl_mainlink_ready(struct msm_dp_ctrl_private *ctrl)
{
u32 data;
int ret;
/* Poll for mainlink ready status */
ret = readl_poll_timeout(ctrl->link_base + REG_DP_MAINLINK_READY,
data, data & DP_MAINLINK_READY_FOR_VIDEO,
POLLING_SLEEP_US, POLLING_TIMEOUT_US);
if (ret < 0) {
DRM_ERROR("mainlink not ready\n");
return false;
}
return true;
}
void msm_dp_ctrl_push_idle(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
reinit_completion(&ctrl->idle_comp);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 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 msm_dp_ctrl_config_ctrl(struct msm_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);
if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
config |= DP_CONFIGURATION_CTRL_RGB_YUV; /* YUV420 */
/* Scrambler reset enable */
if (drm_dp_alternate_scrambler_reset_cap(dpcd))
config |= DP_CONFIGURATION_CTRL_ASSR;
tbd = msm_dp_link_get_test_bits_depth(ctrl->link,
ctrl->panel->msm_dp_mode.bpp);
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;
if (ctrl->panel->psr_cap.version)
config |= DP_CONFIGURATION_CTRL_SEND_VSC;
drm_dbg_dp(ctrl->drm_dev, "DP_CONFIGURATION_CTRL=0x%x\n", config);
msm_dp_write_link(ctrl, REG_DP_CONFIGURATION_CTRL, config);
}
static void msm_dp_ctrl_lane_mapping(struct msm_dp_ctrl_private *ctrl)
{
u32 ln_0 = 0, ln_1 = 1, ln_2 = 2, ln_3 = 3; /* One-to-One mapping */
u32 ln_mapping;
ln_mapping = ln_0 << LANE0_MAPPING_SHIFT;
ln_mapping |= ln_1 << LANE1_MAPPING_SHIFT;
ln_mapping |= ln_2 << LANE2_MAPPING_SHIFT;
ln_mapping |= ln_3 << LANE3_MAPPING_SHIFT;
msm_dp_write_link(ctrl, REG_DP_LOGICAL2PHYSICAL_LANE_MAPPING,
ln_mapping);
}
static void msm_dp_ctrl_configure_source_params(struct msm_dp_ctrl_private *ctrl)
{
u32 colorimetry_cfg, test_bits_depth, misc_val;
msm_dp_ctrl_lane_mapping(ctrl);
msm_dp_setup_peripheral_flush(ctrl);
msm_dp_ctrl_config_ctrl(ctrl);
test_bits_depth = msm_dp_link_get_test_bits_depth(ctrl->link, ctrl->panel->msm_dp_mode.bpp);
colorimetry_cfg = msm_dp_link_get_colorimetry_config(ctrl->link);
misc_val = msm_dp_read_link(ctrl, REG_DP_MISC1_MISC0);
/* clear bpp bits */
misc_val &= ~(0x07 << DP_MISC0_TEST_BITS_DEPTH_SHIFT);
misc_val |= colorimetry_cfg << DP_MISC0_COLORIMETRY_CFG_SHIFT;
misc_val |= test_bits_depth << DP_MISC0_TEST_BITS_DEPTH_SHIFT;
/* Configure clock to synchronous mode */
misc_val |= DP_MISC0_SYNCHRONOUS_CLK;
drm_dbg_dp(ctrl->drm_dev, "misc settings = 0x%x\n", misc_val);
msm_dp_write_link(ctrl, REG_DP_MISC1_MISC0, misc_val);
msm_dp_panel_timing_cfg(ctrl->panel, ctrl->msm_dp_ctrl.wide_bus_en);
}
/*
* 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 msm_dp_panel_update_tu_timings(struct msm_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 msm_dp_ctrl_private *ctrl,
struct msm_dp_tu_calc_input *in,
struct msm_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;
msm_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 msm_dp_ctrl_calc_tu_parameters(struct msm_dp_ctrl_private *ctrl,
struct msm_dp_vc_tu_mapping_table *tu_table)
{
struct msm_dp_tu_calc_input in;
struct drm_display_mode *drm_mode;
drm_mode = &ctrl->panel->msm_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->msm_dp_mode.bpp;
in.pixel_enc = ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420 ? 420 : 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 msm_dp_ctrl_setup_tr_unit(struct msm_dp_ctrl_private *ctrl)
{
u32 msm_dp_tu = 0x0;
u32 valid_boundary = 0x0;
u32 valid_boundary2 = 0x0;
struct msm_dp_vc_tu_mapping_table tu_calc_table;
msm_dp_ctrl_calc_tu_parameters(ctrl, &tu_calc_table);
msm_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",
msm_dp_tu, valid_boundary, valid_boundary2);
msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY, valid_boundary);
msm_dp_write_link(ctrl, REG_DP_TU, msm_dp_tu);
msm_dp_write_link(ctrl, REG_DP_VALID_BOUNDARY_2, valid_boundary2);
}
static int msm_dp_ctrl_wait4video_ready(struct msm_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 msm_dp_ctrl_set_vx_px(struct msm_dp_ctrl_private *ctrl,
u8 v_level, u8 p_level)
{
union phy_configure_opts *phy_opts = &ctrl->phy_opts;
/* TODO: Update for all lanes instead of just first one */
phy_opts->dp.voltage[0] = v_level;
phy_opts->dp.pre[0] = p_level;
phy_opts->dp.set_voltages = 1;
phy_configure(ctrl->phy, phy_opts);
phy_opts->dp.set_voltages = 0;
return 0;
}
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
static int msm_dp_ctrl_update_phy_vx_px(struct msm_dp_ctrl_private *ctrl,
enum drm_dp_phy dp_phy)
{
struct msm_dp_link *link = ctrl->link;
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
int lane, lane_cnt, reg;
int ret = 0;
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 = msm_dp_ctrl_set_vx_px(ctrl,
voltage_swing_level, pre_emphasis_level);
if (ret)
return ret;
if (voltage_swing_level >= DP_TRAIN_LEVEL_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_LEVEL_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);
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
if (dp_phy == DP_PHY_DPRX)
reg = DP_TRAINING_LANE0_SET;
else
reg = DP_TRAINING_LANE0_SET_PHY_REPEATER(dp_phy);
ret = drm_dp_dpcd_write(ctrl->aux, reg, buf, lane_cnt);
if (ret == lane_cnt)
ret = 0;
return ret;
}
static bool msm_dp_ctrl_train_pattern_set(struct msm_dp_ctrl_private *ctrl,
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
u8 pattern, enum drm_dp_phy dp_phy)
{
u8 buf;
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
int reg;
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;
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
if (dp_phy == DP_PHY_DPRX)
reg = DP_TRAINING_PATTERN_SET;
else
reg = DP_TRAINING_PATTERN_SET_PHY_REPEATER(dp_phy);
ret = drm_dp_dpcd_writeb(ctrl->aux, reg, buf);
return ret == 1;
}
static int msm_dp_ctrl_set_pattern_state_bit(struct msm_dp_ctrl_private *ctrl,
u32 state_bit)
{
int bit, ret;
u32 data;
bit = BIT(state_bit - 1);
drm_dbg_dp(ctrl->drm_dev, "hw: bit=%d train=%d\n", bit, state_bit);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, bit);
bit = BIT(state_bit - 1) << DP_MAINLINK_READY_LINK_TRAINING_SHIFT;
/* Poll for mainlink ready status */
ret = readx_poll_timeout(readl, ctrl->link_base + REG_DP_MAINLINK_READY,
data, data & bit,
POLLING_SLEEP_US, POLLING_TIMEOUT_US);
if (ret < 0) {
DRM_ERROR("set state_bit for link_train=%d failed\n", state_bit);
return ret;
}
return 0;
}
static int msm_dp_ctrl_link_train_1(struct msm_dp_ctrl_private *ctrl,
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
int *training_step, enum drm_dp_phy dp_phy)
{
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
int delay_us;
int tries, old_v_level, ret = 0;
u8 link_status[DP_LINK_STATUS_SIZE];
int const maximum_retries = 4;
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
delay_us = drm_dp_read_clock_recovery_delay(ctrl->aux,
ctrl->panel->dpcd, dp_phy, false);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
*training_step = DP_TRAINING_1;
ret = msm_dp_ctrl_set_pattern_state_bit(ctrl, 1);
if (ret)
return ret;
msm_dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_1 |
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
DP_LINK_SCRAMBLING_DISABLE, dp_phy);
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
msm_dp_link_reset_phy_params_vx_px(ctrl->link);
ret = msm_dp_ctrl_update_phy_vx_px(ctrl, dp_phy);
if (ret)
return ret;
tries = 0;
old_v_level = ctrl->link->phy_params.v_level;
for (tries = 0; tries < maximum_retries; tries++) {
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
fsleep(delay_us);
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
ret = drm_dp_dpcd_read_phy_link_status(ctrl->aux, dp_phy, 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_LEVEL_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;
}
msm_dp_link_adjust_levels(ctrl->link, link_status);
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
ret = msm_dp_ctrl_update_phy_vx_px(ctrl, dp_phy);
if (ret)
return ret;
}
DRM_ERROR("max tries reached\n");
return -ETIMEDOUT;
}
static int msm_dp_ctrl_link_rate_down_shift(struct msm_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 msm_dp_ctrl_link_lane_down_shift(struct msm_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;
}
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
static void msm_dp_ctrl_clear_training_pattern(struct msm_dp_ctrl_private *ctrl,
enum drm_dp_phy dp_phy)
{
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
int delay_us;
msm_dp_ctrl_train_pattern_set(ctrl, DP_TRAINING_PATTERN_DISABLE, dp_phy);
delay_us = drm_dp_read_channel_eq_delay(ctrl->aux,
ctrl->panel->dpcd, dp_phy, false);
fsleep(delay_us);
}
static int msm_dp_ctrl_link_train_2(struct msm_dp_ctrl_private *ctrl,
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
int *training_step, enum drm_dp_phy dp_phy)
{
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
int delay_us;
int tries = 0, ret = 0;
u8 pattern;
u32 state_ctrl_bit;
int const maximum_retries = 5;
u8 link_status[DP_LINK_STATUS_SIZE];
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
delay_us = drm_dp_read_channel_eq_delay(ctrl->aux,
ctrl->panel->dpcd, dp_phy, false);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 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 = msm_dp_ctrl_set_pattern_state_bit(ctrl, state_ctrl_bit);
if (ret)
return ret;
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
msm_dp_ctrl_train_pattern_set(ctrl, pattern, dp_phy);
for (tries = 0; tries <= maximum_retries; tries++) {
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
fsleep(delay_us);
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
ret = drm_dp_dpcd_read_phy_link_status(ctrl->aux, dp_phy, link_status);
if (ret)
return ret;
if (drm_dp_channel_eq_ok(link_status,
ctrl->link->link_params.num_lanes)) {
return 0;
}
msm_dp_link_adjust_levels(ctrl->link, link_status);
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
ret = msm_dp_ctrl_update_phy_vx_px(ctrl, dp_phy);
if (ret)
return ret;
}
return -ETIMEDOUT;
}
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
static int msm_dp_ctrl_link_train_1_2(struct msm_dp_ctrl_private *ctrl,
int *training_step, enum drm_dp_phy dp_phy)
{
int ret;
ret = msm_dp_ctrl_link_train_1(ctrl, training_step, dp_phy);
if (ret) {
DRM_ERROR("link training #1 on phy %d failed. ret=%d\n", dp_phy, ret);
return ret;
}
drm_dbg_dp(ctrl->drm_dev, "link training #1 on phy %d successful\n", dp_phy);
ret = msm_dp_ctrl_link_train_2(ctrl, training_step, dp_phy);
if (ret) {
DRM_ERROR("link training #2 on phy %d failed. ret=%d\n", dp_phy, ret);
return ret;
}
drm_dbg_dp(ctrl->drm_dev, "link training #2 on phy %d successful\n", dp_phy);
return 0;
}
static int msm_dp_ctrl_link_train(struct msm_dp_ctrl_private *ctrl,
int *training_step)
{
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
int i;
int ret = 0;
const u8 *dpcd = ctrl->panel->dpcd;
u8 encoding[] = { 0, DP_SET_ANSI_8B10B };
u8 assr;
struct msm_dp_link_info link_info = {0};
msm_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;
msm_dp_aux_link_configure(ctrl->aux, &link_info);
if (drm_dp_max_downspread(dpcd))
encoding[0] |= DP_SPREAD_AMP_0_5;
/* config DOWNSPREAD_CTRL and MAIN_LINK_CHANNEL_CODING_SET */
drm_dp_dpcd_write(ctrl->aux, DP_DOWNSPREAD_CTRL, encoding, 2);
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);
}
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
for (i = ctrl->link->lttpr_count - 1; i >= 0; i--) {
enum drm_dp_phy dp_phy = DP_PHY_LTTPR(i);
ret = msm_dp_ctrl_link_train_1_2(ctrl, training_step, dp_phy);
msm_dp_ctrl_clear_training_pattern(ctrl, dp_phy);
if (ret)
break;
}
if (ret) {
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
DRM_ERROR("link training of LTTPR(s) failed. ret=%d\n", ret);
goto end;
}
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
ret = msm_dp_ctrl_link_train_1_2(ctrl, training_step, DP_PHY_DPRX);
if (ret) {
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
DRM_ERROR("link training on sink failed. ret=%d\n", ret);
goto end;
}
end:
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
return ret;
}
static int msm_dp_ctrl_setup_main_link(struct msm_dp_ctrl_private *ctrl,
int *training_step)
{
int ret = 0;
msm_dp_ctrl_mainlink_enable(ctrl);
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 = msm_dp_ctrl_link_train(ctrl, training_step);
return ret;
}
int msm_dp_ctrl_core_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
int ret = 0;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
if (ctrl->core_clks_on) {
drm_dbg_dp(ctrl->drm_dev, "core clks already enabled\n");
return 0;
}
ret = clk_bulk_prepare_enable(ctrl->num_core_clks, ctrl->core_clks);
if (ret)
return ret;
ctrl->core_clks_on = true;
drm_dbg_dp(ctrl->drm_dev, "enable core clocks \n");
drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n",
str_on_off(ctrl->stream_clks_on),
str_on_off(ctrl->link_clks_on),
str_on_off(ctrl->core_clks_on));
return 0;
}
void msm_dp_ctrl_core_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
clk_bulk_disable_unprepare(ctrl->num_core_clks, ctrl->core_clks);
ctrl->core_clks_on = false;
drm_dbg_dp(ctrl->drm_dev, "disable core clocks \n");
drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n",
str_on_off(ctrl->stream_clks_on),
str_on_off(ctrl->link_clks_on),
str_on_off(ctrl->core_clks_on));
}
static int msm_dp_ctrl_link_clk_enable(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
int ret = 0;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
if (ctrl->link_clks_on) {
drm_dbg_dp(ctrl->drm_dev, "links clks already enabled\n");
return 0;
}
if (!ctrl->core_clks_on) {
drm_dbg_dp(ctrl->drm_dev, "Enable core clks before link clks\n");
msm_dp_ctrl_core_clk_enable(msm_dp_ctrl);
}
ret = clk_bulk_prepare_enable(ctrl->num_link_clks, ctrl->link_clks);
if (ret)
return ret;
ctrl->link_clks_on = true;
drm_dbg_dp(ctrl->drm_dev, "enable link clocks\n");
drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n",
str_on_off(ctrl->stream_clks_on),
str_on_off(ctrl->link_clks_on),
str_on_off(ctrl->core_clks_on));
return 0;
}
static void msm_dp_ctrl_link_clk_disable(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
clk_bulk_disable_unprepare(ctrl->num_link_clks, ctrl->link_clks);
ctrl->link_clks_on = false;
drm_dbg_dp(ctrl->drm_dev, "disabled link clocks\n");
drm_dbg_dp(ctrl->drm_dev, "stream_clks:%s link_clks:%s core_clks:%s\n",
str_on_off(ctrl->stream_clks_on),
str_on_off(ctrl->link_clks_on),
str_on_off(ctrl->core_clks_on));
}
static int msm_dp_ctrl_enable_mainlink_clocks(struct msm_dp_ctrl_private *ctrl)
{
int ret = 0;
struct phy *phy = ctrl->phy;
const u8 *dpcd = ctrl->panel->dpcd;
ctrl->phy_opts.dp.lanes = ctrl->link->link_params.num_lanes;
ctrl->phy_opts.dp.link_rate = ctrl->link->link_params.rate / 100;
ctrl->phy_opts.dp.ssc = drm_dp_max_downspread(dpcd);
phy_configure(phy, &ctrl->phy_opts);
phy_power_on(phy);
dev_pm_opp_set_rate(ctrl->dev, ctrl->link->link_params.rate * 1000);
ret = msm_dp_ctrl_link_clk_enable(&ctrl->msm_dp_ctrl);
if (ret)
DRM_ERROR("Unable to start link clocks. ret=%d\n", ret);
drm_dbg_dp(ctrl->drm_dev, "link rate=%d\n", ctrl->link->link_params.rate);
return ret;
}
static void msm_dp_ctrl_enable_sdp(struct msm_dp_ctrl_private *ctrl)
{
/* trigger sdp */
msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, UPDATE_SDP);
msm_dp_write_link(ctrl, MMSS_DP_SDP_CFG3, 0x0);
}
static void msm_dp_ctrl_psr_enter(struct msm_dp_ctrl_private *ctrl)
{
u32 cmd;
cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
cmd &= ~(PSR_ENTER | PSR_EXIT);
cmd |= PSR_ENTER;
msm_dp_ctrl_enable_sdp(ctrl);
msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
}
static void msm_dp_ctrl_psr_exit(struct msm_dp_ctrl_private *ctrl)
{
u32 cmd;
cmd = msm_dp_read_link(ctrl, REG_PSR_CMD);
cmd &= ~(PSR_ENTER | PSR_EXIT);
cmd |= PSR_EXIT;
msm_dp_ctrl_enable_sdp(ctrl);
msm_dp_write_link(ctrl, REG_PSR_CMD, cmd);
}
void msm_dp_ctrl_config_psr(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl,
struct msm_dp_ctrl_private, msm_dp_ctrl);
u32 cfg;
if (!ctrl->panel->psr_cap.version)
return;
/* enable PSR1 function */
cfg = msm_dp_read_link(ctrl, REG_PSR_CONFIG);
cfg |= PSR1_SUPPORTED;
msm_dp_write_link(ctrl, REG_PSR_CONFIG, cfg);
msm_dp_ctrl_config_psr_interrupt(ctrl);
msm_dp_ctrl_enable_sdp(ctrl);
cfg = DP_PSR_ENABLE;
drm_dp_dpcd_write(ctrl->aux, DP_PSR_EN_CFG, &cfg, 1);
}
void msm_dp_ctrl_set_psr(struct msm_dp_ctrl *msm_dp_ctrl, bool enter)
{
struct msm_dp_ctrl_private *ctrl = container_of(msm_dp_ctrl,
struct msm_dp_ctrl_private, msm_dp_ctrl);
if (!ctrl->panel->psr_cap.version)
return;
/*
* When entering PSR,
* 1. Send PSR enter SDP and wait for the PSR_UPDATE_INT
* 2. Turn off video
* 3. Disable the mainlink
*
* When exiting PSR,
* 1. Enable the mainlink
* 2. Send the PSR exit SDP
*/
if (enter) {
reinit_completion(&ctrl->psr_op_comp);
msm_dp_ctrl_psr_enter(ctrl);
if (!wait_for_completion_timeout(&ctrl->psr_op_comp,
PSR_OPERATION_COMPLETION_TIMEOUT_JIFFIES)) {
DRM_ERROR("PSR_ENTRY timedout\n");
msm_dp_ctrl_psr_exit(ctrl);
return;
}
msm_dp_ctrl_push_idle(msm_dp_ctrl);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
msm_dp_ctrl_psr_mainlink_disable(ctrl);
} else {
msm_dp_ctrl_psr_mainlink_enable(ctrl);
msm_dp_ctrl_psr_exit(ctrl);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
msm_dp_ctrl_wait4video_ready(ctrl);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0);
}
}
static void msm_dp_ctrl_phy_reset(struct msm_dp_ctrl_private *ctrl)
{
msm_dp_write_ahb(ctrl, REG_DP_PHY_CTRL,
DP_PHY_CTRL_SW_RESET | DP_PHY_CTRL_SW_RESET_PLL);
usleep_range(1000, 1100); /* h/w recommended delay */
msm_dp_write_ahb(ctrl, REG_DP_PHY_CTRL, 0x0);
}
void msm_dp_ctrl_phy_init(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
struct phy *phy;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
phy = ctrl->phy;
msm_dp_ctrl_phy_reset(ctrl);
phy_init(phy);
drm_dbg_dp(ctrl->drm_dev, "phy=%p init=%d power_on=%d\n",
phy, phy->init_count, phy->power_count);
}
void msm_dp_ctrl_phy_exit(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
struct phy *phy;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
phy = ctrl->phy;
msm_dp_ctrl_phy_reset(ctrl);
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 int msm_dp_ctrl_reinitialize_mainlink(struct msm_dp_ctrl_private *ctrl)
{
struct phy *phy = ctrl->phy;
int ret = 0;
msm_dp_ctrl_mainlink_disable(ctrl);
ctrl->phy_opts.dp.lanes = ctrl->link->link_params.num_lanes;
phy_configure(phy, &ctrl->phy_opts);
/*
* Disable and re-enable the mainlink clock since the
* link clock might have been adjusted as part of the
* link maintenance.
*/
dev_pm_opp_set_rate(ctrl->dev, 0);
msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
phy_power_off(phy);
/* hw recommended delay before re-enabling clocks */
msleep(20);
ret = msm_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 msm_dp_ctrl_deinitialize_mainlink(struct msm_dp_ctrl_private *ctrl)
{
struct phy *phy;
phy = ctrl->phy;
msm_dp_ctrl_mainlink_disable(ctrl);
msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl);
dev_pm_opp_set_rate(ctrl->dev, 0);
msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
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 10:47:25 -08: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 10:47:25 -08: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 msm_dp_ctrl_link_maintenance(struct msm_dp_ctrl_private *ctrl)
{
int ret = 0;
int training_step = DP_TRAINING_NONE;
msm_dp_ctrl_push_idle(&ctrl->msm_dp_ctrl);
ctrl->link->phy_params.p_level = 0;
ctrl->link->phy_params.v_level = 0;
ret = msm_dp_ctrl_setup_main_link(ctrl, &training_step);
if (ret)
goto end;
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
ret = msm_dp_ctrl_wait4video_ready(ctrl);
end:
return ret;
}
#define SCRAMBLER_RESET_COUNT_VALUE 0xFC
static void msm_dp_ctrl_send_phy_pattern(struct msm_dp_ctrl_private *ctrl,
u32 pattern)
{
u32 value = 0x0;
/* Make sure to clear the current pattern before starting a new one */
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, 0x0);
drm_dbg_dp(ctrl->drm_dev, "pattern: %#x\n", pattern);
switch (pattern) {
case DP_PHY_TEST_PATTERN_D10_2:
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
DP_STATE_CTRL_LINK_TRAINING_PATTERN1);
break;
case DP_PHY_TEST_PATTERN_ERROR_COUNT:
value &= ~(1 << 16);
msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
value);
value |= SCRAMBLER_RESET_COUNT_VALUE;
msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
value);
msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
break;
case DP_PHY_TEST_PATTERN_PRBS7:
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
DP_STATE_CTRL_LINK_PRBS7);
break;
case DP_PHY_TEST_PATTERN_80BIT_CUSTOM:
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
DP_STATE_CTRL_LINK_TEST_CUSTOM_PATTERN);
/* 00111110000011111000001111100000 */
msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG0,
0x3E0F83E0);
/* 00001111100000111110000011111000 */
msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG1,
0x0F83E0F8);
/* 1111100000111110 */
msm_dp_write_link(ctrl, REG_DP_TEST_80BIT_CUSTOM_PATTERN_REG2,
0x0000F83E);
break;
case DP_PHY_TEST_PATTERN_CP2520:
value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
value &= ~DP_MAINLINK_CTRL_SW_BYPASS_SCRAMBLER;
msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
value = DP_HBR2_ERM_PATTERN;
msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
value);
value |= SCRAMBLER_RESET_COUNT_VALUE;
msm_dp_write_link(ctrl, REG_DP_HBR2_COMPLIANCE_SCRAMBLER_RESET,
value);
msm_dp_write_link(ctrl, REG_DP_MAINLINK_LEVELS,
DP_MAINLINK_SAFE_TO_EXIT_LEVEL_2);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
DP_STATE_CTRL_LINK_SYMBOL_ERR_MEASURE);
value = msm_dp_read_link(ctrl, REG_DP_MAINLINK_CTRL);
value |= DP_MAINLINK_CTRL_ENABLE;
msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL, value);
break;
case DP_PHY_TEST_PATTERN_SEL_MASK:
msm_dp_write_link(ctrl, REG_DP_MAINLINK_CTRL,
DP_MAINLINK_CTRL_ENABLE);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL,
DP_STATE_CTRL_LINK_TRAINING_PATTERN4);
break;
default:
drm_dbg_dp(ctrl->drm_dev,
"No valid test pattern requested: %#x\n", pattern);
break;
}
}
static bool msm_dp_ctrl_send_phy_test_pattern(struct msm_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 (msm_dp_ctrl_set_vx_px(ctrl,
ctrl->link->phy_params.v_level,
ctrl->link->phy_params.p_level)) {
DRM_ERROR("Failed to set v/p levels\n");
return false;
}
msm_dp_ctrl_send_phy_pattern(ctrl, pattern_requested);
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
msm_dp_ctrl_update_phy_vx_px(ctrl, DP_PHY_DPRX);
msm_dp_link_send_test_response(ctrl->link);
pattern_sent = msm_dp_read_link(ctrl, REG_DP_MAINLINK_READY);
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;
}
static int msm_dp_ctrl_process_phy_test_request(struct msm_dp_ctrl_private *ctrl)
{
int ret;
unsigned long pixel_rate;
if (!ctrl->link->phy_params.phy_test_pattern_sel) {
drm_dbg_dp(ctrl->drm_dev,
"no test pattern selected by sink\n");
return 0;
}
/*
* 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.
*/
msm_dp_ctrl_off(&ctrl->msm_dp_ctrl);
ret = msm_dp_ctrl_on_link(&ctrl->msm_dp_ctrl);
if (ret) {
DRM_ERROR("failed to enable DP link controller\n");
return ret;
}
pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock;
ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000);
if (ret) {
DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret);
return ret;
}
if (ctrl->stream_clks_on) {
drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n");
} else {
ret = clk_prepare_enable(ctrl->pixel_clk);
if (ret) {
DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret);
return ret;
}
ctrl->stream_clks_on = true;
}
msm_dp_ctrl_send_phy_test_pattern(ctrl);
return 0;
}
void msm_dp_ctrl_handle_sink_request(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
u32 sink_request = 0x0;
if (!msm_dp_ctrl) {
DRM_ERROR("invalid input\n");
return;
}
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_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 (msm_dp_ctrl_process_phy_test_request(ctrl)) {
DRM_ERROR("process phy_test_req failed\n");
return;
}
}
if (sink_request & DP_LINK_STATUS_UPDATED) {
if (msm_dp_ctrl_link_maintenance(ctrl)) {
DRM_ERROR("LM failed: TEST_LINK_TRAINING\n");
return;
}
}
if (sink_request & DP_TEST_LINK_TRAINING) {
msm_dp_link_send_test_response(ctrl->link);
if (msm_dp_ctrl_link_maintenance(ctrl)) {
DRM_ERROR("LM failed: TEST_LINK_TRAINING\n");
return;
}
}
}
static bool msm_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 msm_dp_ctrl_channel_eq_ok(struct msm_dp_ctrl_private *ctrl)
{
u8 link_status[DP_LINK_STATUS_SIZE];
int num_lanes = ctrl->link->link_params.num_lanes;
drm_dp_dpcd_read_link_status(ctrl->aux, link_status);
return drm_dp_channel_eq_ok(link_status, num_lanes);
}
int msm_dp_ctrl_on_link(struct msm_dp_ctrl *msm_dp_ctrl)
{
int rc = 0;
struct msm_dp_ctrl_private *ctrl;
u32 rate;
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;
unsigned long pixel_rate;
if (!msm_dp_ctrl)
return -EINVAL;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
rate = ctrl->panel->link_info.rate;
pixel_rate = ctrl->panel->msm_dp_mode.drm_mode.clock;
msm_dp_ctrl_core_clk_enable(&ctrl->msm_dp_ctrl);
if (ctrl->link->sink_request & DP_TEST_LINK_PHY_TEST_PATTERN) {
drm_dbg_dp(ctrl->drm_dev,
"using phy test link parameters\n");
if (!pixel_rate)
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;
if (ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
pixel_rate >>= 1;
}
drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d, pixel_rate=%lu\n",
ctrl->link->link_params.rate, ctrl->link->link_params.num_lanes,
pixel_rate);
rc = msm_dp_ctrl_enable_mainlink_clocks(ctrl);
if (rc)
return rc;
while (--link_train_max_retries) {
training_step = DP_TRAINING_NONE;
rc = msm_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 (!msm_dp_aux_is_link_connected(ctrl->aux))
break;
drm_dp_dpcd_read_link_status(ctrl->aux, link_status);
rc = msm_dp_ctrl_link_rate_down_shift(ctrl);
if (rc < 0) { /* already in RBR = 1.6G */
if (msm_dp_ctrl_clock_recovery_any_ok(link_status,
ctrl->link->link_params.num_lanes)) {
/*
* some lanes are ready,
* reduce lane number
*/
rc = msm_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 (!msm_dp_aux_is_link_connected(ctrl->aux))
break;
drm_dp_dpcd_read_link_status(ctrl->aux, link_status);
if (!drm_dp_clock_recovery_ok(link_status,
ctrl->link->link_params.num_lanes))
rc = msm_dp_ctrl_link_rate_down_shift(ctrl);
else
rc = msm_dp_ctrl_link_lane_down_shift(ctrl);
if (rc < 0) {
/* end with failure */
break; /* lane == 1 already */
}
/* stop link training before start re training */
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX);
}
rc = msm_dp_ctrl_reinitialize_mainlink(ctrl);
if (rc) {
DRM_ERROR("Failed to reinitialize mainlink. rc=%d\n", rc);
break;
}
}
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
*/
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX);
msm_dp_ctrl_deinitialize_mainlink(ctrl);
rc = -ECONNRESET;
}
return rc;
}
static int msm_dp_ctrl_link_retrain(struct msm_dp_ctrl_private *ctrl)
{
int training_step = DP_TRAINING_NONE;
return msm_dp_ctrl_setup_main_link(ctrl, &training_step);
}
static void msm_dp_ctrl_config_msa(struct msm_dp_ctrl_private *ctrl,
u32 rate, u32 stream_rate_khz,
bool is_ycbcr_420)
{
u32 pixel_m, pixel_n;
u32 mvid, nvid, pixel_div = 0, dispcc_input_rate;
u32 const nvid_fixed = DP_LINK_CONSTANT_N_VALUE;
u32 const link_rate_hbr2 = 540000;
u32 const link_rate_hbr3 = 810000;
unsigned long den, num;
if (rate == link_rate_hbr3)
pixel_div = 6;
else if (rate == 162000 || rate == 270000)
pixel_div = 2;
else if (rate == link_rate_hbr2)
pixel_div = 4;
else
DRM_ERROR("Invalid pixel mux divider\n");
dispcc_input_rate = (rate * 10) / pixel_div;
rational_best_approximation(dispcc_input_rate, stream_rate_khz,
(unsigned long)(1 << 16) - 1,
(unsigned long)(1 << 16) - 1, &den, &num);
den = ~(den - num);
den = den & 0xFFFF;
pixel_m = num;
pixel_n = den;
mvid = (pixel_m & 0xFFFF) * 5;
nvid = (0xFFFF & (~pixel_n)) + (pixel_m & 0xFFFF);
if (nvid < nvid_fixed) {
u32 temp;
temp = (nvid_fixed / nvid) * nvid;
mvid = (nvid_fixed / nvid) * mvid;
nvid = temp;
}
if (is_ycbcr_420)
mvid /= 2;
if (link_rate_hbr2 == rate)
nvid *= 2;
if (link_rate_hbr3 == rate)
nvid *= 3;
drm_dbg_dp(ctrl->drm_dev, "mvid=0x%x, nvid=0x%x\n", mvid, nvid);
msm_dp_write_link(ctrl, REG_DP_SOFTWARE_MVID, mvid);
msm_dp_write_link(ctrl, REG_DP_SOFTWARE_NVID, nvid);
}
int msm_dp_ctrl_on_stream(struct msm_dp_ctrl *msm_dp_ctrl, bool force_link_train)
{
int ret = 0;
bool mainlink_ready = false;
struct msm_dp_ctrl_private *ctrl;
unsigned long pixel_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 13:23:12 -08:00
unsigned long pixel_rate_orig;
if (!msm_dp_ctrl)
return -EINVAL;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
pixel_rate = pixel_rate_orig = ctrl->panel->msm_dp_mode.drm_mode.clock;
if (msm_dp_ctrl->wide_bus_en || ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420)
pixel_rate >>= 1;
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 13:23:12 -08:00
drm_dbg_dp(ctrl->drm_dev, "rate=%d, num_lanes=%d, pixel_rate=%lu\n",
ctrl->link->link_params.rate,
ctrl->link->link_params.num_lanes, pixel_rate);
drm_dbg_dp(ctrl->drm_dev,
"core_clk_on=%d link_clk_on=%d stream_clk_on=%d\n",
ctrl->core_clks_on, ctrl->link_clks_on, ctrl->stream_clks_on);
if (!ctrl->link_clks_on) { /* link clk is off */
ret = msm_dp_ctrl_enable_mainlink_clocks(ctrl);
if (ret) {
DRM_ERROR("Failed to start link clocks. ret=%d\n", ret);
goto end;
}
}
ret = clk_set_rate(ctrl->pixel_clk, pixel_rate * 1000);
if (ret) {
DRM_ERROR("Failed to set pixel clock rate. ret=%d\n", ret);
goto end;
}
if (ctrl->stream_clks_on) {
drm_dbg_dp(ctrl->drm_dev, "pixel clks already enabled\n");
} else {
ret = clk_prepare_enable(ctrl->pixel_clk);
if (ret) {
DRM_ERROR("Failed to start pixel clocks. ret=%d\n", ret);
goto end;
}
ctrl->stream_clks_on = true;
}
if (force_link_train || !msm_dp_ctrl_channel_eq_ok(ctrl))
msm_dp_ctrl_link_retrain(ctrl);
/* stop txing train pattern to end link training */
drm/msm/dp: Introduce link training per-segment for LTTPRs DisplayPort requires per-segment link training when LTTPR are switched to non-transparent mode, starting with LTTPR closest to the source. Only when each segment is trained individually, source can link train to sink. Implement per-segment link traning when LTTPR(s) are detected, to support external docking stations. On higher level, changes are: * Pass phy being trained down to all required helpers * Run CR, EQ link training per phy * Set voltage swing, pre-emphasis levels per phy Since at least some LTTPRs (eg. Parade PS8830) do not correctly report voltage-swing, pre-emphasis level 3 support, always assume level 3 is supported. This is permitted under DP 2.1(a) section 3.6.7.2 stating that LTTPR shall set its transmitter levels as close as possible to those requested by the DPTX, if the DPTX sets the voltage swing or pre-emphasis to a level that the LTTPR does not support. It shall be noted that LTTPR’s level choosing is implementation-specific. This ensures successful link training both when connected directly to the monitor (single LTTPR onboard most X1E laptops) and via the docking station (at least two LTTPRs). Fixes: 72d0af4accd9 ("drm/msm/dp: Add support for LTTPR handling") Tested-by: Jessica Zhang <quic_jesszhan@quicinc.com> # SA8775P Tested-by: Johan Hovold <johan+linaro@kernel.org> Tested-by: Rob Clark <robdclark@gmail.com> Tested-by: Stefan Schmidt <stefan.schmidt@linaro.org> Signed-off-by: Aleksandrs Vinarskis <alex.vinarskis@gmail.com> Reviewed-by: Abel Vesa <abel.vesa@linaro.org> Reviewed-by: Abhinav Kumar <quic_abhinavk@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com> Patchwork: https://patchwork.freedesktop.org/patch/652305/ Link: https://lore.kernel.org/r/20250507230113.14270-5-alex.vinarskis@gmail.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@oss.qualcomm.com>
2025-05-08 00:59:02 +02:00
msm_dp_ctrl_clear_training_pattern(ctrl, DP_PHY_DPRX);
/*
* Set up transfer unit values and set controller state to send
* video.
*/
reinit_completion(&ctrl->video_comp);
msm_dp_ctrl_configure_source_params(ctrl);
msm_dp_ctrl_config_msa(ctrl,
ctrl->link->link_params.rate,
pixel_rate_orig,
ctrl->panel->msm_dp_mode.out_fmt_is_yuv_420);
msm_dp_panel_clear_dsc_dto(ctrl->panel);
msm_dp_ctrl_setup_tr_unit(ctrl);
msm_dp_write_link(ctrl, REG_DP_STATE_CTRL, DP_STATE_CTRL_SEND_VIDEO);
ret = msm_dp_ctrl_wait4video_ready(ctrl);
if (ret)
return ret;
mainlink_ready = msm_dp_ctrl_mainlink_ready(ctrl);
drm_dbg_dp(ctrl->drm_dev,
"mainlink %s\n", mainlink_ready ? "READY" : "NOT READY");
end:
return ret;
}
void msm_dp_ctrl_off_link_stream(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
struct phy *phy;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
phy = ctrl->phy;
msm_dp_panel_disable_vsc_sdp(ctrl->panel);
drm/msm/dp: add VSC SDP support for YUV420 over DP Add support to pack and send the VSC SDP packet for DP. This therefore allows the transmision of format information to the sinks which is needed for YUV420 support over DP. Changes in v5: - Slightly modify use of drm_dp_vsc_sdp_pack() - Remove dp_catalog NULL checks - Modify dp_utils_pack_sdp_header() to more clearly pack the header buffer - Move dp_utils_pack_sdp_header() inside of dp_catalog_panel_send_vsc_sdp to clearly show the relationship between the header buffer and the vsc_sdp struct - Due to the last point, remove the dp_utils_pack_vsc_sdp() function and only call drm_dp_vsc_sdp_pack() in dp_panel_setup_vsc_sdp_yuv_420() Changes in v4: - Remove struct msm_dp_sdp_with_parity - Use dp_utils_pack_sdp_header() to pack the SDP header and parity bytes into a buffer - Use this buffer when writing the VSC SDP data in dp_catalog_panel_send_vsc_sdp() - Write to all of the MMSS_DP_GENERIC0 registers instead of just the ones with non-zero values Changes in v3: - Create a new struct, msm_dp_sdp_with_parity, which holds the packing information for VSC SDP - Use drm_dp_vsc_sdp_pack() to pack the data into the new msm_dp_sdp_with_parity struct instead of specifically packing for YUV420 format - Modify dp_catalog_panel_send_vsc_sdp() to send the VSC SDP data using the new msm_dp_sdp_with_parity struct Changes in v2: - Rename GENERIC0_SDPSIZE macro to GENERIC0_SDPSIZE_VALID - Remove dp_sdp from the dp_catalog struct since this data is being allocated at the point used - Create a new function in dp_utils to pack the VSC SDP data into a buffer - Create a new function that packs the SDP header bytes into a buffer. This function is made generic so that it can be utilized by dp_audio header bytes into a buffer - Create a new function in dp_utils that takes the packed buffer and writes to the DP_GENERIC0_* registers - Split the dp_catalog_panel_config_vsc_sdp() function into two to disable/enable sending VSC SDP packets - Check the DP HW version using the original useage of dp_catalog_hw_revision() and correct the version checking logic - Rename dp_panel_setup_vsc_sdp() to dp_panel_setup_vsc_sdp_yuv_420() to explicitly state that currently VSC SDP is only being set up to support YUV420 modes Signed-off-by: Paloma Arellano <quic_parellan@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> Patchwork: https://patchwork.freedesktop.org/patch/579636/ Link: https://lore.kernel.org/r/20240222194025.25329-14-quic_parellan@quicinc.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2024-02-22 11:39:58 -08:00
/* set dongle to D3 (power off) mode */
msm_dp_link_psm_config(ctrl->link, &ctrl->panel->link_info, true);
msm_dp_ctrl_mainlink_disable(ctrl);
if (ctrl->stream_clks_on) {
clk_disable_unprepare(ctrl->pixel_clk);
ctrl->stream_clks_on = false;
}
dev_pm_opp_set_rate(ctrl->dev, 0);
msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
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);
}
void msm_dp_ctrl_off_link(struct msm_dp_ctrl *msm_dp_ctrl)
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 15:56:22 -07:00
{
struct msm_dp_ctrl_private *ctrl;
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 15:56:22 -07:00
struct phy *phy;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
phy = ctrl->phy;
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 15:56:22 -07:00
msm_dp_ctrl_mainlink_disable(ctrl);
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 15:56:22 -07:00
dev_pm_opp_set_rate(ctrl->dev, 0);
msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
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 15:56:22 -07:00
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);
}
void msm_dp_ctrl_off(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
struct phy *phy;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
phy = ctrl->phy;
msm_dp_panel_disable_vsc_sdp(ctrl->panel);
drm/msm/dp: add VSC SDP support for YUV420 over DP Add support to pack and send the VSC SDP packet for DP. This therefore allows the transmision of format information to the sinks which is needed for YUV420 support over DP. Changes in v5: - Slightly modify use of drm_dp_vsc_sdp_pack() - Remove dp_catalog NULL checks - Modify dp_utils_pack_sdp_header() to more clearly pack the header buffer - Move dp_utils_pack_sdp_header() inside of dp_catalog_panel_send_vsc_sdp to clearly show the relationship between the header buffer and the vsc_sdp struct - Due to the last point, remove the dp_utils_pack_vsc_sdp() function and only call drm_dp_vsc_sdp_pack() in dp_panel_setup_vsc_sdp_yuv_420() Changes in v4: - Remove struct msm_dp_sdp_with_parity - Use dp_utils_pack_sdp_header() to pack the SDP header and parity bytes into a buffer - Use this buffer when writing the VSC SDP data in dp_catalog_panel_send_vsc_sdp() - Write to all of the MMSS_DP_GENERIC0 registers instead of just the ones with non-zero values Changes in v3: - Create a new struct, msm_dp_sdp_with_parity, which holds the packing information for VSC SDP - Use drm_dp_vsc_sdp_pack() to pack the data into the new msm_dp_sdp_with_parity struct instead of specifically packing for YUV420 format - Modify dp_catalog_panel_send_vsc_sdp() to send the VSC SDP data using the new msm_dp_sdp_with_parity struct Changes in v2: - Rename GENERIC0_SDPSIZE macro to GENERIC0_SDPSIZE_VALID - Remove dp_sdp from the dp_catalog struct since this data is being allocated at the point used - Create a new function in dp_utils to pack the VSC SDP data into a buffer - Create a new function that packs the SDP header bytes into a buffer. This function is made generic so that it can be utilized by dp_audio header bytes into a buffer - Create a new function in dp_utils that takes the packed buffer and writes to the DP_GENERIC0_* registers - Split the dp_catalog_panel_config_vsc_sdp() function into two to disable/enable sending VSC SDP packets - Check the DP HW version using the original useage of dp_catalog_hw_revision() and correct the version checking logic - Rename dp_panel_setup_vsc_sdp() to dp_panel_setup_vsc_sdp_yuv_420() to explicitly state that currently VSC SDP is only being set up to support YUV420 modes Signed-off-by: Paloma Arellano <quic_parellan@quicinc.com> Reviewed-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org> Patchwork: https://patchwork.freedesktop.org/patch/579636/ Link: https://lore.kernel.org/r/20240222194025.25329-14-quic_parellan@quicinc.com Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2024-02-22 11:39:58 -08:00
msm_dp_ctrl_mainlink_disable(ctrl);
msm_dp_ctrl_reset(&ctrl->msm_dp_ctrl);
if (ctrl->stream_clks_on) {
clk_disable_unprepare(ctrl->pixel_clk);
ctrl->stream_clks_on = false;
}
dev_pm_opp_set_rate(ctrl->dev, 0);
msm_dp_ctrl_link_clk_disable(&ctrl->msm_dp_ctrl);
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);
}
irqreturn_t msm_dp_ctrl_isr(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
u32 isr;
drm/msm/dp: Return IRQ_NONE for unhandled interrupts If our interrupt handler gets called and we don't really handle the interrupt then we should return IRQ_NONE. The current interrupt handler didn't do this, so let's fix it. NOTE: for some of the cases it's clear that we should return IRQ_NONE and some cases it's clear that we should return IRQ_HANDLED. However, there are a few that fall somewhere in between. Specifically, the documentation for when to return IRQ_NONE vs. IRQ_HANDLED is probably best spelled out in the commit message of commit d9e4ad5badf4 ("Document that IRQ_NONE should be returned when IRQ not actually handled"). That commit makes it clear that we should return IRQ_HANDLED if we've done something to make the interrupt stop happening. The case where it's unclear is, for instance, in dp_aux_isr() after we've read the interrupt using dp_catalog_aux_get_irq() and confirmed that "isr" is non-zero. The function dp_catalog_aux_get_irq() not only reads the interrupts but it also "ack"s all the interrupts that are returned. For an "unknown" interrupt this has a very good chance of actually stopping the interrupt from happening. That would mean we've identified that it's our device and done something to stop them from happening and should return IRQ_HANDLED. Specifically, it should be noted that most interrupts that need "ack"ing are ones that are one-time events and doing an "ack" is enough to clear them. However, since these interrupts are unknown then, by definition, it's unknown if "ack"ing them is truly enough to clear them. It's possible that we also need to remove the original source of the interrupt. In this case, IRQ_NONE would be a better choice. Given that returning an occasional IRQ_NONE isn't the absolute end of the world, however, let's choose that course of action. The IRQ framework will forgive a few IRQ_NONE returns now and again (and it won't even log them, which is why we have to log them ourselves). This means that if we _do_ end hitting an interrupt where "ack"ing isn't enough the kernel will eventually detect the problem and shut our device down. Signed-off-by: Douglas Anderson <dianders@chromium.org> Tested-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Patchwork: https://patchwork.freedesktop.org/patch/520660/ Link: https://lore.kernel.org/r/20230126170745.v2.2.I2d7aec2fadb9c237cd0090a47d6a8ba2054bf0f8@changeid [DB: reformatted commit message to make checkpatch happy] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2023-01-26 17:09:13 -08:00
irqreturn_t ret = IRQ_NONE;
if (!msm_dp_ctrl)
drm/msm/dp: Return IRQ_NONE for unhandled interrupts If our interrupt handler gets called and we don't really handle the interrupt then we should return IRQ_NONE. The current interrupt handler didn't do this, so let's fix it. NOTE: for some of the cases it's clear that we should return IRQ_NONE and some cases it's clear that we should return IRQ_HANDLED. However, there are a few that fall somewhere in between. Specifically, the documentation for when to return IRQ_NONE vs. IRQ_HANDLED is probably best spelled out in the commit message of commit d9e4ad5badf4 ("Document that IRQ_NONE should be returned when IRQ not actually handled"). That commit makes it clear that we should return IRQ_HANDLED if we've done something to make the interrupt stop happening. The case where it's unclear is, for instance, in dp_aux_isr() after we've read the interrupt using dp_catalog_aux_get_irq() and confirmed that "isr" is non-zero. The function dp_catalog_aux_get_irq() not only reads the interrupts but it also "ack"s all the interrupts that are returned. For an "unknown" interrupt this has a very good chance of actually stopping the interrupt from happening. That would mean we've identified that it's our device and done something to stop them from happening and should return IRQ_HANDLED. Specifically, it should be noted that most interrupts that need "ack"ing are ones that are one-time events and doing an "ack" is enough to clear them. However, since these interrupts are unknown then, by definition, it's unknown if "ack"ing them is truly enough to clear them. It's possible that we also need to remove the original source of the interrupt. In this case, IRQ_NONE would be a better choice. Given that returning an occasional IRQ_NONE isn't the absolute end of the world, however, let's choose that course of action. The IRQ framework will forgive a few IRQ_NONE returns now and again (and it won't even log them, which is why we have to log them ourselves). This means that if we _do_ end hitting an interrupt where "ack"ing isn't enough the kernel will eventually detect the problem and shut our device down. Signed-off-by: Douglas Anderson <dianders@chromium.org> Tested-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Patchwork: https://patchwork.freedesktop.org/patch/520660/ Link: https://lore.kernel.org/r/20230126170745.v2.2.I2d7aec2fadb9c237cd0090a47d6a8ba2054bf0f8@changeid [DB: reformatted commit message to make checkpatch happy] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2023-01-26 17:09:13 -08:00
return IRQ_NONE;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
if (ctrl->panel->psr_cap.version) {
isr = msm_dp_ctrl_get_psr_interrupt(ctrl);
if (isr)
complete(&ctrl->psr_op_comp);
if (isr & PSR_EXIT_INT)
drm_dbg_dp(ctrl->drm_dev, "PSR exit done\n");
if (isr & PSR_UPDATE_INT)
drm_dbg_dp(ctrl->drm_dev, "PSR frame update done\n");
if (isr & PSR_CAPTURE_INT)
drm_dbg_dp(ctrl->drm_dev, "PSR frame capture done\n");
}
isr = msm_dp_ctrl_get_interrupt(ctrl);
drm/msm/dp: Return IRQ_NONE for unhandled interrupts If our interrupt handler gets called and we don't really handle the interrupt then we should return IRQ_NONE. The current interrupt handler didn't do this, so let's fix it. NOTE: for some of the cases it's clear that we should return IRQ_NONE and some cases it's clear that we should return IRQ_HANDLED. However, there are a few that fall somewhere in between. Specifically, the documentation for when to return IRQ_NONE vs. IRQ_HANDLED is probably best spelled out in the commit message of commit d9e4ad5badf4 ("Document that IRQ_NONE should be returned when IRQ not actually handled"). That commit makes it clear that we should return IRQ_HANDLED if we've done something to make the interrupt stop happening. The case where it's unclear is, for instance, in dp_aux_isr() after we've read the interrupt using dp_catalog_aux_get_irq() and confirmed that "isr" is non-zero. The function dp_catalog_aux_get_irq() not only reads the interrupts but it also "ack"s all the interrupts that are returned. For an "unknown" interrupt this has a very good chance of actually stopping the interrupt from happening. That would mean we've identified that it's our device and done something to stop them from happening and should return IRQ_HANDLED. Specifically, it should be noted that most interrupts that need "ack"ing are ones that are one-time events and doing an "ack" is enough to clear them. However, since these interrupts are unknown then, by definition, it's unknown if "ack"ing them is truly enough to clear them. It's possible that we also need to remove the original source of the interrupt. In this case, IRQ_NONE would be a better choice. Given that returning an occasional IRQ_NONE isn't the absolute end of the world, however, let's choose that course of action. The IRQ framework will forgive a few IRQ_NONE returns now and again (and it won't even log them, which is why we have to log them ourselves). This means that if we _do_ end hitting an interrupt where "ack"ing isn't enough the kernel will eventually detect the problem and shut our device down. Signed-off-by: Douglas Anderson <dianders@chromium.org> Tested-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Patchwork: https://patchwork.freedesktop.org/patch/520660/ Link: https://lore.kernel.org/r/20230126170745.v2.2.I2d7aec2fadb9c237cd0090a47d6a8ba2054bf0f8@changeid [DB: reformatted commit message to make checkpatch happy] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2023-01-26 17:09:13 -08:00
if (isr & DP_CTRL_INTR_READY_FOR_VIDEO) {
drm_dbg_dp(ctrl->drm_dev, "dp_video_ready\n");
complete(&ctrl->video_comp);
drm/msm/dp: Return IRQ_NONE for unhandled interrupts If our interrupt handler gets called and we don't really handle the interrupt then we should return IRQ_NONE. The current interrupt handler didn't do this, so let's fix it. NOTE: for some of the cases it's clear that we should return IRQ_NONE and some cases it's clear that we should return IRQ_HANDLED. However, there are a few that fall somewhere in between. Specifically, the documentation for when to return IRQ_NONE vs. IRQ_HANDLED is probably best spelled out in the commit message of commit d9e4ad5badf4 ("Document that IRQ_NONE should be returned when IRQ not actually handled"). That commit makes it clear that we should return IRQ_HANDLED if we've done something to make the interrupt stop happening. The case where it's unclear is, for instance, in dp_aux_isr() after we've read the interrupt using dp_catalog_aux_get_irq() and confirmed that "isr" is non-zero. The function dp_catalog_aux_get_irq() not only reads the interrupts but it also "ack"s all the interrupts that are returned. For an "unknown" interrupt this has a very good chance of actually stopping the interrupt from happening. That would mean we've identified that it's our device and done something to stop them from happening and should return IRQ_HANDLED. Specifically, it should be noted that most interrupts that need "ack"ing are ones that are one-time events and doing an "ack" is enough to clear them. However, since these interrupts are unknown then, by definition, it's unknown if "ack"ing them is truly enough to clear them. It's possible that we also need to remove the original source of the interrupt. In this case, IRQ_NONE would be a better choice. Given that returning an occasional IRQ_NONE isn't the absolute end of the world, however, let's choose that course of action. The IRQ framework will forgive a few IRQ_NONE returns now and again (and it won't even log them, which is why we have to log them ourselves). This means that if we _do_ end hitting an interrupt where "ack"ing isn't enough the kernel will eventually detect the problem and shut our device down. Signed-off-by: Douglas Anderson <dianders@chromium.org> Tested-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Patchwork: https://patchwork.freedesktop.org/patch/520660/ Link: https://lore.kernel.org/r/20230126170745.v2.2.I2d7aec2fadb9c237cd0090a47d6a8ba2054bf0f8@changeid [DB: reformatted commit message to make checkpatch happy] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2023-01-26 17:09:13 -08:00
ret = IRQ_HANDLED;
}
if (isr & DP_CTRL_INTR_IDLE_PATTERN_SENT) {
drm_dbg_dp(ctrl->drm_dev, "idle_patterns_sent\n");
complete(&ctrl->idle_comp);
drm/msm/dp: Return IRQ_NONE for unhandled interrupts If our interrupt handler gets called and we don't really handle the interrupt then we should return IRQ_NONE. The current interrupt handler didn't do this, so let's fix it. NOTE: for some of the cases it's clear that we should return IRQ_NONE and some cases it's clear that we should return IRQ_HANDLED. However, there are a few that fall somewhere in between. Specifically, the documentation for when to return IRQ_NONE vs. IRQ_HANDLED is probably best spelled out in the commit message of commit d9e4ad5badf4 ("Document that IRQ_NONE should be returned when IRQ not actually handled"). That commit makes it clear that we should return IRQ_HANDLED if we've done something to make the interrupt stop happening. The case where it's unclear is, for instance, in dp_aux_isr() after we've read the interrupt using dp_catalog_aux_get_irq() and confirmed that "isr" is non-zero. The function dp_catalog_aux_get_irq() not only reads the interrupts but it also "ack"s all the interrupts that are returned. For an "unknown" interrupt this has a very good chance of actually stopping the interrupt from happening. That would mean we've identified that it's our device and done something to stop them from happening and should return IRQ_HANDLED. Specifically, it should be noted that most interrupts that need "ack"ing are ones that are one-time events and doing an "ack" is enough to clear them. However, since these interrupts are unknown then, by definition, it's unknown if "ack"ing them is truly enough to clear them. It's possible that we also need to remove the original source of the interrupt. In this case, IRQ_NONE would be a better choice. Given that returning an occasional IRQ_NONE isn't the absolute end of the world, however, let's choose that course of action. The IRQ framework will forgive a few IRQ_NONE returns now and again (and it won't even log them, which is why we have to log them ourselves). This means that if we _do_ end hitting an interrupt where "ack"ing isn't enough the kernel will eventually detect the problem and shut our device down. Signed-off-by: Douglas Anderson <dianders@chromium.org> Tested-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Patchwork: https://patchwork.freedesktop.org/patch/520660/ Link: https://lore.kernel.org/r/20230126170745.v2.2.I2d7aec2fadb9c237cd0090a47d6a8ba2054bf0f8@changeid [DB: reformatted commit message to make checkpatch happy] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2023-01-26 17:09:13 -08:00
ret = IRQ_HANDLED;
}
drm/msm/dp: Return IRQ_NONE for unhandled interrupts If our interrupt handler gets called and we don't really handle the interrupt then we should return IRQ_NONE. The current interrupt handler didn't do this, so let's fix it. NOTE: for some of the cases it's clear that we should return IRQ_NONE and some cases it's clear that we should return IRQ_HANDLED. However, there are a few that fall somewhere in between. Specifically, the documentation for when to return IRQ_NONE vs. IRQ_HANDLED is probably best spelled out in the commit message of commit d9e4ad5badf4 ("Document that IRQ_NONE should be returned when IRQ not actually handled"). That commit makes it clear that we should return IRQ_HANDLED if we've done something to make the interrupt stop happening. The case where it's unclear is, for instance, in dp_aux_isr() after we've read the interrupt using dp_catalog_aux_get_irq() and confirmed that "isr" is non-zero. The function dp_catalog_aux_get_irq() not only reads the interrupts but it also "ack"s all the interrupts that are returned. For an "unknown" interrupt this has a very good chance of actually stopping the interrupt from happening. That would mean we've identified that it's our device and done something to stop them from happening and should return IRQ_HANDLED. Specifically, it should be noted that most interrupts that need "ack"ing are ones that are one-time events and doing an "ack" is enough to clear them. However, since these interrupts are unknown then, by definition, it's unknown if "ack"ing them is truly enough to clear them. It's possible that we also need to remove the original source of the interrupt. In this case, IRQ_NONE would be a better choice. Given that returning an occasional IRQ_NONE isn't the absolute end of the world, however, let's choose that course of action. The IRQ framework will forgive a few IRQ_NONE returns now and again (and it won't even log them, which is why we have to log them ourselves). This means that if we _do_ end hitting an interrupt where "ack"ing isn't enough the kernel will eventually detect the problem and shut our device down. Signed-off-by: Douglas Anderson <dianders@chromium.org> Tested-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Patchwork: https://patchwork.freedesktop.org/patch/520660/ Link: https://lore.kernel.org/r/20230126170745.v2.2.I2d7aec2fadb9c237cd0090a47d6a8ba2054bf0f8@changeid [DB: reformatted commit message to make checkpatch happy] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2023-01-26 17:09:13 -08:00
/* DP aux isr */
isr = msm_dp_ctrl_get_aux_interrupt(ctrl);
if (isr)
ret |= msm_dp_aux_isr(ctrl->aux, isr);
drm/msm/dp: Return IRQ_NONE for unhandled interrupts If our interrupt handler gets called and we don't really handle the interrupt then we should return IRQ_NONE. The current interrupt handler didn't do this, so let's fix it. NOTE: for some of the cases it's clear that we should return IRQ_NONE and some cases it's clear that we should return IRQ_HANDLED. However, there are a few that fall somewhere in between. Specifically, the documentation for when to return IRQ_NONE vs. IRQ_HANDLED is probably best spelled out in the commit message of commit d9e4ad5badf4 ("Document that IRQ_NONE should be returned when IRQ not actually handled"). That commit makes it clear that we should return IRQ_HANDLED if we've done something to make the interrupt stop happening. The case where it's unclear is, for instance, in dp_aux_isr() after we've read the interrupt using dp_catalog_aux_get_irq() and confirmed that "isr" is non-zero. The function dp_catalog_aux_get_irq() not only reads the interrupts but it also "ack"s all the interrupts that are returned. For an "unknown" interrupt this has a very good chance of actually stopping the interrupt from happening. That would mean we've identified that it's our device and done something to stop them from happening and should return IRQ_HANDLED. Specifically, it should be noted that most interrupts that need "ack"ing are ones that are one-time events and doing an "ack" is enough to clear them. However, since these interrupts are unknown then, by definition, it's unknown if "ack"ing them is truly enough to clear them. It's possible that we also need to remove the original source of the interrupt. In this case, IRQ_NONE would be a better choice. Given that returning an occasional IRQ_NONE isn't the absolute end of the world, however, let's choose that course of action. The IRQ framework will forgive a few IRQ_NONE returns now and again (and it won't even log them, which is why we have to log them ourselves). This means that if we _do_ end hitting an interrupt where "ack"ing isn't enough the kernel will eventually detect the problem and shut our device down. Signed-off-by: Douglas Anderson <dianders@chromium.org> Tested-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Reviewed-by: Kuogee Hsieh <quic_khsieh@quicinc.com> Patchwork: https://patchwork.freedesktop.org/patch/520660/ Link: https://lore.kernel.org/r/20230126170745.v2.2.I2d7aec2fadb9c237cd0090a47d6a8ba2054bf0f8@changeid [DB: reformatted commit message to make checkpatch happy] Signed-off-by: Dmitry Baryshkov <dmitry.baryshkov@linaro.org>
2023-01-26 17:09:13 -08:00
return ret;
}
static const char *core_clks[] = {
"core_iface",
"core_aux",
};
static const char *ctrl_clks[] = {
"ctrl_link",
"ctrl_link_iface",
};
static int msm_dp_ctrl_clk_init(struct msm_dp_ctrl *msm_dp_ctrl)
{
struct msm_dp_ctrl_private *ctrl;
struct device *dev;
int i, rc;
ctrl = container_of(msm_dp_ctrl, struct msm_dp_ctrl_private, msm_dp_ctrl);
dev = ctrl->dev;
ctrl->num_core_clks = ARRAY_SIZE(core_clks);
ctrl->core_clks = devm_kcalloc(dev, ctrl->num_core_clks, sizeof(*ctrl->core_clks), GFP_KERNEL);
if (!ctrl->core_clks)
return -ENOMEM;
for (i = 0; i < ctrl->num_core_clks; i++)
ctrl->core_clks[i].id = core_clks[i];
rc = devm_clk_bulk_get(dev, ctrl->num_core_clks, ctrl->core_clks);
if (rc)
return rc;
ctrl->num_link_clks = ARRAY_SIZE(ctrl_clks);
ctrl->link_clks = devm_kcalloc(dev, ctrl->num_link_clks, sizeof(*ctrl->link_clks), GFP_KERNEL);
if (!ctrl->link_clks)
return -ENOMEM;
for (i = 0; i < ctrl->num_link_clks; i++)
ctrl->link_clks[i].id = ctrl_clks[i];
rc = devm_clk_bulk_get(dev, ctrl->num_link_clks, ctrl->link_clks);
if (rc)
return rc;
ctrl->pixel_clk = devm_clk_get(dev, "stream_pixel");
if (IS_ERR(ctrl->pixel_clk))
return PTR_ERR(ctrl->pixel_clk);
return 0;
}
struct msm_dp_ctrl *msm_dp_ctrl_get(struct device *dev, struct msm_dp_link *link,
struct msm_dp_panel *panel, struct drm_dp_aux *aux,
struct phy *phy,
void __iomem *ahb_base,
void __iomem *link_base)
{
struct msm_dp_ctrl_private *ctrl;
int ret;
if (!dev || !panel || !aux || !link) {
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 msm_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->psr_op_comp);
init_completion(&ctrl->video_comp);
/* in parameters */
ctrl->panel = panel;
ctrl->aux = aux;
ctrl->link = link;
ctrl->dev = dev;
ctrl->phy = phy;
ctrl->ahb_base = ahb_base;
ctrl->link_base = link_base;
ret = msm_dp_ctrl_clk_init(&ctrl->msm_dp_ctrl);
if (ret) {
dev_err(dev, "failed to init clocks\n");
return ERR_PTR(ret);
}
return &ctrl->msm_dp_ctrl;
}