162306a36Sopenharmony_ci// SPDX-License-Identifier: MIT 262306a36Sopenharmony_ci/* 362306a36Sopenharmony_ci * Copyright (C) 2013-2019 NVIDIA Corporation 462306a36Sopenharmony_ci * Copyright (C) 2015 Rob Clark 562306a36Sopenharmony_ci */ 662306a36Sopenharmony_ci 762306a36Sopenharmony_ci#include <drm/display/drm_dp_helper.h> 862306a36Sopenharmony_ci#include <drm/drm_crtc.h> 962306a36Sopenharmony_ci#include <drm/drm_print.h> 1062306a36Sopenharmony_ci 1162306a36Sopenharmony_ci#include "dp.h" 1262306a36Sopenharmony_ci 1362306a36Sopenharmony_cistatic const u8 drm_dp_edp_revisions[] = { 0x11, 0x12, 0x13, 0x14 }; 1462306a36Sopenharmony_ci 1562306a36Sopenharmony_cistatic void drm_dp_link_caps_reset(struct drm_dp_link_caps *caps) 1662306a36Sopenharmony_ci{ 1762306a36Sopenharmony_ci caps->enhanced_framing = false; 1862306a36Sopenharmony_ci caps->tps3_supported = false; 1962306a36Sopenharmony_ci caps->fast_training = false; 2062306a36Sopenharmony_ci caps->channel_coding = false; 2162306a36Sopenharmony_ci caps->alternate_scrambler_reset = false; 2262306a36Sopenharmony_ci} 2362306a36Sopenharmony_ci 2462306a36Sopenharmony_civoid drm_dp_link_caps_copy(struct drm_dp_link_caps *dest, 2562306a36Sopenharmony_ci const struct drm_dp_link_caps *src) 2662306a36Sopenharmony_ci{ 2762306a36Sopenharmony_ci dest->enhanced_framing = src->enhanced_framing; 2862306a36Sopenharmony_ci dest->tps3_supported = src->tps3_supported; 2962306a36Sopenharmony_ci dest->fast_training = src->fast_training; 3062306a36Sopenharmony_ci dest->channel_coding = src->channel_coding; 3162306a36Sopenharmony_ci dest->alternate_scrambler_reset = src->alternate_scrambler_reset; 3262306a36Sopenharmony_ci} 3362306a36Sopenharmony_ci 3462306a36Sopenharmony_cistatic void drm_dp_link_reset(struct drm_dp_link *link) 3562306a36Sopenharmony_ci{ 3662306a36Sopenharmony_ci unsigned int i; 3762306a36Sopenharmony_ci 3862306a36Sopenharmony_ci if (!link) 3962306a36Sopenharmony_ci return; 4062306a36Sopenharmony_ci 4162306a36Sopenharmony_ci link->revision = 0; 4262306a36Sopenharmony_ci link->max_rate = 0; 4362306a36Sopenharmony_ci link->max_lanes = 0; 4462306a36Sopenharmony_ci 4562306a36Sopenharmony_ci drm_dp_link_caps_reset(&link->caps); 4662306a36Sopenharmony_ci link->aux_rd_interval.cr = 0; 4762306a36Sopenharmony_ci link->aux_rd_interval.ce = 0; 4862306a36Sopenharmony_ci link->edp = 0; 4962306a36Sopenharmony_ci 5062306a36Sopenharmony_ci link->rate = 0; 5162306a36Sopenharmony_ci link->lanes = 0; 5262306a36Sopenharmony_ci 5362306a36Sopenharmony_ci for (i = 0; i < DP_MAX_SUPPORTED_RATES; i++) 5462306a36Sopenharmony_ci link->rates[i] = 0; 5562306a36Sopenharmony_ci 5662306a36Sopenharmony_ci link->num_rates = 0; 5762306a36Sopenharmony_ci} 5862306a36Sopenharmony_ci 5962306a36Sopenharmony_ci/** 6062306a36Sopenharmony_ci * drm_dp_link_add_rate() - add a rate to the list of supported rates 6162306a36Sopenharmony_ci * @link: the link to add the rate to 6262306a36Sopenharmony_ci * @rate: the rate to add 6362306a36Sopenharmony_ci * 6462306a36Sopenharmony_ci * Add a link rate to the list of supported link rates. 6562306a36Sopenharmony_ci * 6662306a36Sopenharmony_ci * Returns: 6762306a36Sopenharmony_ci * 0 on success or one of the following negative error codes on failure: 6862306a36Sopenharmony_ci * - ENOSPC if the maximum number of supported rates has been reached 6962306a36Sopenharmony_ci * - EEXISTS if the link already supports this rate 7062306a36Sopenharmony_ci * 7162306a36Sopenharmony_ci * See also: 7262306a36Sopenharmony_ci * drm_dp_link_remove_rate() 7362306a36Sopenharmony_ci */ 7462306a36Sopenharmony_ciint drm_dp_link_add_rate(struct drm_dp_link *link, unsigned long rate) 7562306a36Sopenharmony_ci{ 7662306a36Sopenharmony_ci unsigned int i, pivot; 7762306a36Sopenharmony_ci 7862306a36Sopenharmony_ci if (link->num_rates == DP_MAX_SUPPORTED_RATES) 7962306a36Sopenharmony_ci return -ENOSPC; 8062306a36Sopenharmony_ci 8162306a36Sopenharmony_ci for (pivot = 0; pivot < link->num_rates; pivot++) 8262306a36Sopenharmony_ci if (rate <= link->rates[pivot]) 8362306a36Sopenharmony_ci break; 8462306a36Sopenharmony_ci 8562306a36Sopenharmony_ci if (pivot != link->num_rates && rate == link->rates[pivot]) 8662306a36Sopenharmony_ci return -EEXIST; 8762306a36Sopenharmony_ci 8862306a36Sopenharmony_ci for (i = link->num_rates; i > pivot; i--) 8962306a36Sopenharmony_ci link->rates[i] = link->rates[i - 1]; 9062306a36Sopenharmony_ci 9162306a36Sopenharmony_ci link->rates[pivot] = rate; 9262306a36Sopenharmony_ci link->num_rates++; 9362306a36Sopenharmony_ci 9462306a36Sopenharmony_ci return 0; 9562306a36Sopenharmony_ci} 9662306a36Sopenharmony_ci 9762306a36Sopenharmony_ci/** 9862306a36Sopenharmony_ci * drm_dp_link_remove_rate() - remove a rate from the list of supported rates 9962306a36Sopenharmony_ci * @link: the link from which to remove the rate 10062306a36Sopenharmony_ci * @rate: the rate to remove 10162306a36Sopenharmony_ci * 10262306a36Sopenharmony_ci * Removes a link rate from the list of supported link rates. 10362306a36Sopenharmony_ci * 10462306a36Sopenharmony_ci * Returns: 10562306a36Sopenharmony_ci * 0 on success or one of the following negative error codes on failure: 10662306a36Sopenharmony_ci * - EINVAL if the specified rate is not among the supported rates 10762306a36Sopenharmony_ci * 10862306a36Sopenharmony_ci * See also: 10962306a36Sopenharmony_ci * drm_dp_link_add_rate() 11062306a36Sopenharmony_ci */ 11162306a36Sopenharmony_ciint drm_dp_link_remove_rate(struct drm_dp_link *link, unsigned long rate) 11262306a36Sopenharmony_ci{ 11362306a36Sopenharmony_ci unsigned int i; 11462306a36Sopenharmony_ci 11562306a36Sopenharmony_ci for (i = 0; i < link->num_rates; i++) 11662306a36Sopenharmony_ci if (rate == link->rates[i]) 11762306a36Sopenharmony_ci break; 11862306a36Sopenharmony_ci 11962306a36Sopenharmony_ci if (i == link->num_rates) 12062306a36Sopenharmony_ci return -EINVAL; 12162306a36Sopenharmony_ci 12262306a36Sopenharmony_ci link->num_rates--; 12362306a36Sopenharmony_ci 12462306a36Sopenharmony_ci while (i < link->num_rates) { 12562306a36Sopenharmony_ci link->rates[i] = link->rates[i + 1]; 12662306a36Sopenharmony_ci i++; 12762306a36Sopenharmony_ci } 12862306a36Sopenharmony_ci 12962306a36Sopenharmony_ci return 0; 13062306a36Sopenharmony_ci} 13162306a36Sopenharmony_ci 13262306a36Sopenharmony_ci/** 13362306a36Sopenharmony_ci * drm_dp_link_update_rates() - normalize the supported link rates array 13462306a36Sopenharmony_ci * @link: the link for which to normalize the supported link rates 13562306a36Sopenharmony_ci * 13662306a36Sopenharmony_ci * Users should call this function after they've manually modified the array 13762306a36Sopenharmony_ci * of supported link rates. This function removes any stale entries, compacts 13862306a36Sopenharmony_ci * the array and updates the supported link rate count. Note that calling the 13962306a36Sopenharmony_ci * drm_dp_link_remove_rate() function already does this janitorial work. 14062306a36Sopenharmony_ci * 14162306a36Sopenharmony_ci * See also: 14262306a36Sopenharmony_ci * drm_dp_link_add_rate(), drm_dp_link_remove_rate() 14362306a36Sopenharmony_ci */ 14462306a36Sopenharmony_civoid drm_dp_link_update_rates(struct drm_dp_link *link) 14562306a36Sopenharmony_ci{ 14662306a36Sopenharmony_ci unsigned int i, count = 0; 14762306a36Sopenharmony_ci 14862306a36Sopenharmony_ci for (i = 0; i < link->num_rates; i++) { 14962306a36Sopenharmony_ci if (link->rates[i] != 0) 15062306a36Sopenharmony_ci link->rates[count++] = link->rates[i]; 15162306a36Sopenharmony_ci } 15262306a36Sopenharmony_ci 15362306a36Sopenharmony_ci for (i = count; i < link->num_rates; i++) 15462306a36Sopenharmony_ci link->rates[i] = 0; 15562306a36Sopenharmony_ci 15662306a36Sopenharmony_ci link->num_rates = count; 15762306a36Sopenharmony_ci} 15862306a36Sopenharmony_ci 15962306a36Sopenharmony_ci/** 16062306a36Sopenharmony_ci * drm_dp_link_probe() - probe a DisplayPort link for capabilities 16162306a36Sopenharmony_ci * @aux: DisplayPort AUX channel 16262306a36Sopenharmony_ci * @link: pointer to structure in which to return link capabilities 16362306a36Sopenharmony_ci * 16462306a36Sopenharmony_ci * The structure filled in by this function can usually be passed directly 16562306a36Sopenharmony_ci * into drm_dp_link_power_up() and drm_dp_link_configure() to power up and 16662306a36Sopenharmony_ci * configure the link based on the link's capabilities. 16762306a36Sopenharmony_ci * 16862306a36Sopenharmony_ci * Returns 0 on success or a negative error code on failure. 16962306a36Sopenharmony_ci */ 17062306a36Sopenharmony_ciint drm_dp_link_probe(struct drm_dp_aux *aux, struct drm_dp_link *link) 17162306a36Sopenharmony_ci{ 17262306a36Sopenharmony_ci u8 dpcd[DP_RECEIVER_CAP_SIZE], value; 17362306a36Sopenharmony_ci unsigned int rd_interval; 17462306a36Sopenharmony_ci int err; 17562306a36Sopenharmony_ci 17662306a36Sopenharmony_ci drm_dp_link_reset(link); 17762306a36Sopenharmony_ci 17862306a36Sopenharmony_ci err = drm_dp_dpcd_read(aux, DP_DPCD_REV, dpcd, sizeof(dpcd)); 17962306a36Sopenharmony_ci if (err < 0) 18062306a36Sopenharmony_ci return err; 18162306a36Sopenharmony_ci 18262306a36Sopenharmony_ci link->revision = dpcd[DP_DPCD_REV]; 18362306a36Sopenharmony_ci link->max_rate = drm_dp_max_link_rate(dpcd); 18462306a36Sopenharmony_ci link->max_lanes = drm_dp_max_lane_count(dpcd); 18562306a36Sopenharmony_ci 18662306a36Sopenharmony_ci link->caps.enhanced_framing = drm_dp_enhanced_frame_cap(dpcd); 18762306a36Sopenharmony_ci link->caps.tps3_supported = drm_dp_tps3_supported(dpcd); 18862306a36Sopenharmony_ci link->caps.fast_training = drm_dp_fast_training_cap(dpcd); 18962306a36Sopenharmony_ci link->caps.channel_coding = drm_dp_channel_coding_supported(dpcd); 19062306a36Sopenharmony_ci 19162306a36Sopenharmony_ci if (drm_dp_alternate_scrambler_reset_cap(dpcd)) { 19262306a36Sopenharmony_ci link->caps.alternate_scrambler_reset = true; 19362306a36Sopenharmony_ci 19462306a36Sopenharmony_ci err = drm_dp_dpcd_readb(aux, DP_EDP_DPCD_REV, &value); 19562306a36Sopenharmony_ci if (err < 0) 19662306a36Sopenharmony_ci return err; 19762306a36Sopenharmony_ci 19862306a36Sopenharmony_ci if (value >= ARRAY_SIZE(drm_dp_edp_revisions)) 19962306a36Sopenharmony_ci DRM_ERROR("unsupported eDP version: %02x\n", value); 20062306a36Sopenharmony_ci else 20162306a36Sopenharmony_ci link->edp = drm_dp_edp_revisions[value]; 20262306a36Sopenharmony_ci } 20362306a36Sopenharmony_ci 20462306a36Sopenharmony_ci /* 20562306a36Sopenharmony_ci * The DPCD stores the AUX read interval in units of 4 ms. There are 20662306a36Sopenharmony_ci * two special cases: 20762306a36Sopenharmony_ci * 20862306a36Sopenharmony_ci * 1) if the TRAINING_AUX_RD_INTERVAL field is 0, the clock recovery 20962306a36Sopenharmony_ci * and channel equalization should use 100 us or 400 us AUX read 21062306a36Sopenharmony_ci * intervals, respectively 21162306a36Sopenharmony_ci * 21262306a36Sopenharmony_ci * 2) for DP v1.4 and above, clock recovery should always use 100 us 21362306a36Sopenharmony_ci * AUX read intervals 21462306a36Sopenharmony_ci */ 21562306a36Sopenharmony_ci rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] & 21662306a36Sopenharmony_ci DP_TRAINING_AUX_RD_MASK; 21762306a36Sopenharmony_ci 21862306a36Sopenharmony_ci if (rd_interval > 4) { 21962306a36Sopenharmony_ci DRM_DEBUG_KMS("AUX interval %u out of range (max. 4)\n", 22062306a36Sopenharmony_ci rd_interval); 22162306a36Sopenharmony_ci rd_interval = 4; 22262306a36Sopenharmony_ci } 22362306a36Sopenharmony_ci 22462306a36Sopenharmony_ci rd_interval *= 4 * USEC_PER_MSEC; 22562306a36Sopenharmony_ci 22662306a36Sopenharmony_ci if (rd_interval == 0 || link->revision >= DP_DPCD_REV_14) 22762306a36Sopenharmony_ci link->aux_rd_interval.cr = 100; 22862306a36Sopenharmony_ci 22962306a36Sopenharmony_ci if (rd_interval == 0) 23062306a36Sopenharmony_ci link->aux_rd_interval.ce = 400; 23162306a36Sopenharmony_ci 23262306a36Sopenharmony_ci link->rate = link->max_rate; 23362306a36Sopenharmony_ci link->lanes = link->max_lanes; 23462306a36Sopenharmony_ci 23562306a36Sopenharmony_ci /* Parse SUPPORTED_LINK_RATES from eDP 1.4 */ 23662306a36Sopenharmony_ci if (link->edp >= 0x14) { 23762306a36Sopenharmony_ci u8 supported_rates[DP_MAX_SUPPORTED_RATES * 2]; 23862306a36Sopenharmony_ci unsigned int i; 23962306a36Sopenharmony_ci u16 rate; 24062306a36Sopenharmony_ci 24162306a36Sopenharmony_ci err = drm_dp_dpcd_read(aux, DP_SUPPORTED_LINK_RATES, 24262306a36Sopenharmony_ci supported_rates, 24362306a36Sopenharmony_ci sizeof(supported_rates)); 24462306a36Sopenharmony_ci if (err < 0) 24562306a36Sopenharmony_ci return err; 24662306a36Sopenharmony_ci 24762306a36Sopenharmony_ci for (i = 0; i < DP_MAX_SUPPORTED_RATES; i++) { 24862306a36Sopenharmony_ci rate = supported_rates[i * 2 + 1] << 8 | 24962306a36Sopenharmony_ci supported_rates[i * 2 + 0]; 25062306a36Sopenharmony_ci 25162306a36Sopenharmony_ci drm_dp_link_add_rate(link, rate * 200); 25262306a36Sopenharmony_ci } 25362306a36Sopenharmony_ci } 25462306a36Sopenharmony_ci 25562306a36Sopenharmony_ci return 0; 25662306a36Sopenharmony_ci} 25762306a36Sopenharmony_ci 25862306a36Sopenharmony_ci/** 25962306a36Sopenharmony_ci * drm_dp_link_power_up() - power up a DisplayPort link 26062306a36Sopenharmony_ci * @aux: DisplayPort AUX channel 26162306a36Sopenharmony_ci * @link: pointer to a structure containing the link configuration 26262306a36Sopenharmony_ci * 26362306a36Sopenharmony_ci * Returns 0 on success or a negative error code on failure. 26462306a36Sopenharmony_ci */ 26562306a36Sopenharmony_ciint drm_dp_link_power_up(struct drm_dp_aux *aux, struct drm_dp_link *link) 26662306a36Sopenharmony_ci{ 26762306a36Sopenharmony_ci u8 value; 26862306a36Sopenharmony_ci int err; 26962306a36Sopenharmony_ci 27062306a36Sopenharmony_ci /* DP_SET_POWER register is only available on DPCD v1.1 and later */ 27162306a36Sopenharmony_ci if (link->revision < 0x11) 27262306a36Sopenharmony_ci return 0; 27362306a36Sopenharmony_ci 27462306a36Sopenharmony_ci err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value); 27562306a36Sopenharmony_ci if (err < 0) 27662306a36Sopenharmony_ci return err; 27762306a36Sopenharmony_ci 27862306a36Sopenharmony_ci value &= ~DP_SET_POWER_MASK; 27962306a36Sopenharmony_ci value |= DP_SET_POWER_D0; 28062306a36Sopenharmony_ci 28162306a36Sopenharmony_ci err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value); 28262306a36Sopenharmony_ci if (err < 0) 28362306a36Sopenharmony_ci return err; 28462306a36Sopenharmony_ci 28562306a36Sopenharmony_ci /* 28662306a36Sopenharmony_ci * According to the DP 1.1 specification, a "Sink Device must exit the 28762306a36Sopenharmony_ci * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink 28862306a36Sopenharmony_ci * Control Field" (register 0x600). 28962306a36Sopenharmony_ci */ 29062306a36Sopenharmony_ci usleep_range(1000, 2000); 29162306a36Sopenharmony_ci 29262306a36Sopenharmony_ci return 0; 29362306a36Sopenharmony_ci} 29462306a36Sopenharmony_ci 29562306a36Sopenharmony_ci/** 29662306a36Sopenharmony_ci * drm_dp_link_power_down() - power down a DisplayPort link 29762306a36Sopenharmony_ci * @aux: DisplayPort AUX channel 29862306a36Sopenharmony_ci * @link: pointer to a structure containing the link configuration 29962306a36Sopenharmony_ci * 30062306a36Sopenharmony_ci * Returns 0 on success or a negative error code on failure. 30162306a36Sopenharmony_ci */ 30262306a36Sopenharmony_ciint drm_dp_link_power_down(struct drm_dp_aux *aux, struct drm_dp_link *link) 30362306a36Sopenharmony_ci{ 30462306a36Sopenharmony_ci u8 value; 30562306a36Sopenharmony_ci int err; 30662306a36Sopenharmony_ci 30762306a36Sopenharmony_ci /* DP_SET_POWER register is only available on DPCD v1.1 and later */ 30862306a36Sopenharmony_ci if (link->revision < 0x11) 30962306a36Sopenharmony_ci return 0; 31062306a36Sopenharmony_ci 31162306a36Sopenharmony_ci err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value); 31262306a36Sopenharmony_ci if (err < 0) 31362306a36Sopenharmony_ci return err; 31462306a36Sopenharmony_ci 31562306a36Sopenharmony_ci value &= ~DP_SET_POWER_MASK; 31662306a36Sopenharmony_ci value |= DP_SET_POWER_D3; 31762306a36Sopenharmony_ci 31862306a36Sopenharmony_ci err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value); 31962306a36Sopenharmony_ci if (err < 0) 32062306a36Sopenharmony_ci return err; 32162306a36Sopenharmony_ci 32262306a36Sopenharmony_ci return 0; 32362306a36Sopenharmony_ci} 32462306a36Sopenharmony_ci 32562306a36Sopenharmony_ci/** 32662306a36Sopenharmony_ci * drm_dp_link_configure() - configure a DisplayPort link 32762306a36Sopenharmony_ci * @aux: DisplayPort AUX channel 32862306a36Sopenharmony_ci * @link: pointer to a structure containing the link configuration 32962306a36Sopenharmony_ci * 33062306a36Sopenharmony_ci * Returns 0 on success or a negative error code on failure. 33162306a36Sopenharmony_ci */ 33262306a36Sopenharmony_ciint drm_dp_link_configure(struct drm_dp_aux *aux, struct drm_dp_link *link) 33362306a36Sopenharmony_ci{ 33462306a36Sopenharmony_ci u8 values[2], value; 33562306a36Sopenharmony_ci int err; 33662306a36Sopenharmony_ci 33762306a36Sopenharmony_ci if (link->ops && link->ops->configure) { 33862306a36Sopenharmony_ci err = link->ops->configure(link); 33962306a36Sopenharmony_ci if (err < 0) { 34062306a36Sopenharmony_ci DRM_ERROR("failed to configure DP link: %d\n", err); 34162306a36Sopenharmony_ci return err; 34262306a36Sopenharmony_ci } 34362306a36Sopenharmony_ci } 34462306a36Sopenharmony_ci 34562306a36Sopenharmony_ci values[0] = drm_dp_link_rate_to_bw_code(link->rate); 34662306a36Sopenharmony_ci values[1] = link->lanes; 34762306a36Sopenharmony_ci 34862306a36Sopenharmony_ci if (link->caps.enhanced_framing) 34962306a36Sopenharmony_ci values[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; 35062306a36Sopenharmony_ci 35162306a36Sopenharmony_ci err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, values, sizeof(values)); 35262306a36Sopenharmony_ci if (err < 0) 35362306a36Sopenharmony_ci return err; 35462306a36Sopenharmony_ci 35562306a36Sopenharmony_ci if (link->caps.channel_coding) 35662306a36Sopenharmony_ci value = DP_SET_ANSI_8B10B; 35762306a36Sopenharmony_ci else 35862306a36Sopenharmony_ci value = 0; 35962306a36Sopenharmony_ci 36062306a36Sopenharmony_ci err = drm_dp_dpcd_writeb(aux, DP_MAIN_LINK_CHANNEL_CODING_SET, value); 36162306a36Sopenharmony_ci if (err < 0) 36262306a36Sopenharmony_ci return err; 36362306a36Sopenharmony_ci 36462306a36Sopenharmony_ci if (link->caps.alternate_scrambler_reset) { 36562306a36Sopenharmony_ci err = drm_dp_dpcd_writeb(aux, DP_EDP_CONFIGURATION_SET, 36662306a36Sopenharmony_ci DP_ALTERNATE_SCRAMBLER_RESET_ENABLE); 36762306a36Sopenharmony_ci if (err < 0) 36862306a36Sopenharmony_ci return err; 36962306a36Sopenharmony_ci } 37062306a36Sopenharmony_ci 37162306a36Sopenharmony_ci return 0; 37262306a36Sopenharmony_ci} 37362306a36Sopenharmony_ci 37462306a36Sopenharmony_ci/** 37562306a36Sopenharmony_ci * drm_dp_link_choose() - choose the lowest possible configuration for a mode 37662306a36Sopenharmony_ci * @link: DRM DP link object 37762306a36Sopenharmony_ci * @mode: DRM display mode 37862306a36Sopenharmony_ci * @info: DRM display information 37962306a36Sopenharmony_ci * 38062306a36Sopenharmony_ci * According to the eDP specification, a source should select a configuration 38162306a36Sopenharmony_ci * with the lowest number of lanes and the lowest possible link rate that can 38262306a36Sopenharmony_ci * match the bitrate requirements of a video mode. However it must ensure not 38362306a36Sopenharmony_ci * to exceed the capabilities of the sink. 38462306a36Sopenharmony_ci * 38562306a36Sopenharmony_ci * Returns: 0 on success or a negative error code on failure. 38662306a36Sopenharmony_ci */ 38762306a36Sopenharmony_ciint drm_dp_link_choose(struct drm_dp_link *link, 38862306a36Sopenharmony_ci const struct drm_display_mode *mode, 38962306a36Sopenharmony_ci const struct drm_display_info *info) 39062306a36Sopenharmony_ci{ 39162306a36Sopenharmony_ci /* available link symbol clock rates */ 39262306a36Sopenharmony_ci static const unsigned int rates[3] = { 162000, 270000, 540000 }; 39362306a36Sopenharmony_ci /* available number of lanes */ 39462306a36Sopenharmony_ci static const unsigned int lanes[3] = { 1, 2, 4 }; 39562306a36Sopenharmony_ci unsigned long requirement, capacity; 39662306a36Sopenharmony_ci unsigned int rate = link->max_rate; 39762306a36Sopenharmony_ci unsigned int i, j; 39862306a36Sopenharmony_ci 39962306a36Sopenharmony_ci /* bandwidth requirement */ 40062306a36Sopenharmony_ci requirement = mode->clock * info->bpc * 3; 40162306a36Sopenharmony_ci 40262306a36Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(lanes) && lanes[i] <= link->max_lanes; i++) { 40362306a36Sopenharmony_ci for (j = 0; j < ARRAY_SIZE(rates) && rates[j] <= rate; j++) { 40462306a36Sopenharmony_ci /* 40562306a36Sopenharmony_ci * Capacity for this combination of lanes and rate, 40662306a36Sopenharmony_ci * factoring in the ANSI 8B/10B encoding. 40762306a36Sopenharmony_ci * 40862306a36Sopenharmony_ci * Link rates in the DRM DP helpers are really link 40962306a36Sopenharmony_ci * symbol frequencies, so a tenth of the actual rate 41062306a36Sopenharmony_ci * of the link. 41162306a36Sopenharmony_ci */ 41262306a36Sopenharmony_ci capacity = lanes[i] * (rates[j] * 10) * 8 / 10; 41362306a36Sopenharmony_ci 41462306a36Sopenharmony_ci if (capacity >= requirement) { 41562306a36Sopenharmony_ci DRM_DEBUG_KMS("using %u lanes at %u kHz (%lu/%lu kbps)\n", 41662306a36Sopenharmony_ci lanes[i], rates[j], requirement, 41762306a36Sopenharmony_ci capacity); 41862306a36Sopenharmony_ci link->lanes = lanes[i]; 41962306a36Sopenharmony_ci link->rate = rates[j]; 42062306a36Sopenharmony_ci return 0; 42162306a36Sopenharmony_ci } 42262306a36Sopenharmony_ci } 42362306a36Sopenharmony_ci } 42462306a36Sopenharmony_ci 42562306a36Sopenharmony_ci return -ERANGE; 42662306a36Sopenharmony_ci} 42762306a36Sopenharmony_ci 42862306a36Sopenharmony_ci/** 42962306a36Sopenharmony_ci * DOC: Link training 43062306a36Sopenharmony_ci * 43162306a36Sopenharmony_ci * These functions contain common logic and helpers to implement DisplayPort 43262306a36Sopenharmony_ci * link training. 43362306a36Sopenharmony_ci */ 43462306a36Sopenharmony_ci 43562306a36Sopenharmony_ci/** 43662306a36Sopenharmony_ci * drm_dp_link_train_init() - initialize DisplayPort link training state 43762306a36Sopenharmony_ci * @train: DisplayPort link training state 43862306a36Sopenharmony_ci */ 43962306a36Sopenharmony_civoid drm_dp_link_train_init(struct drm_dp_link_train *train) 44062306a36Sopenharmony_ci{ 44162306a36Sopenharmony_ci struct drm_dp_link_train_set *request = &train->request; 44262306a36Sopenharmony_ci struct drm_dp_link_train_set *adjust = &train->adjust; 44362306a36Sopenharmony_ci unsigned int i; 44462306a36Sopenharmony_ci 44562306a36Sopenharmony_ci for (i = 0; i < 4; i++) { 44662306a36Sopenharmony_ci request->voltage_swing[i] = 0; 44762306a36Sopenharmony_ci adjust->voltage_swing[i] = 0; 44862306a36Sopenharmony_ci 44962306a36Sopenharmony_ci request->pre_emphasis[i] = 0; 45062306a36Sopenharmony_ci adjust->pre_emphasis[i] = 0; 45162306a36Sopenharmony_ci 45262306a36Sopenharmony_ci request->post_cursor[i] = 0; 45362306a36Sopenharmony_ci adjust->post_cursor[i] = 0; 45462306a36Sopenharmony_ci } 45562306a36Sopenharmony_ci 45662306a36Sopenharmony_ci train->pattern = DP_TRAINING_PATTERN_DISABLE; 45762306a36Sopenharmony_ci train->clock_recovered = false; 45862306a36Sopenharmony_ci train->channel_equalized = false; 45962306a36Sopenharmony_ci} 46062306a36Sopenharmony_ci 46162306a36Sopenharmony_cistatic bool drm_dp_link_train_valid(const struct drm_dp_link_train *train) 46262306a36Sopenharmony_ci{ 46362306a36Sopenharmony_ci return train->clock_recovered && train->channel_equalized; 46462306a36Sopenharmony_ci} 46562306a36Sopenharmony_ci 46662306a36Sopenharmony_cistatic int drm_dp_link_apply_training(struct drm_dp_link *link) 46762306a36Sopenharmony_ci{ 46862306a36Sopenharmony_ci struct drm_dp_link_train_set *request = &link->train.request; 46962306a36Sopenharmony_ci unsigned int lanes = link->lanes, *vs, *pe, *pc, i; 47062306a36Sopenharmony_ci struct drm_dp_aux *aux = link->aux; 47162306a36Sopenharmony_ci u8 values[4], pattern = 0; 47262306a36Sopenharmony_ci int err; 47362306a36Sopenharmony_ci 47462306a36Sopenharmony_ci err = link->ops->apply_training(link); 47562306a36Sopenharmony_ci if (err < 0) { 47662306a36Sopenharmony_ci DRM_ERROR("failed to apply link training: %d\n", err); 47762306a36Sopenharmony_ci return err; 47862306a36Sopenharmony_ci } 47962306a36Sopenharmony_ci 48062306a36Sopenharmony_ci vs = request->voltage_swing; 48162306a36Sopenharmony_ci pe = request->pre_emphasis; 48262306a36Sopenharmony_ci pc = request->post_cursor; 48362306a36Sopenharmony_ci 48462306a36Sopenharmony_ci /* write currently selected voltage-swing and pre-emphasis levels */ 48562306a36Sopenharmony_ci for (i = 0; i < lanes; i++) 48662306a36Sopenharmony_ci values[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL(vs[i]) | 48762306a36Sopenharmony_ci DP_TRAIN_PRE_EMPHASIS_LEVEL(pe[i]); 48862306a36Sopenharmony_ci 48962306a36Sopenharmony_ci err = drm_dp_dpcd_write(aux, DP_TRAINING_LANE0_SET, values, lanes); 49062306a36Sopenharmony_ci if (err < 0) { 49162306a36Sopenharmony_ci DRM_ERROR("failed to set training parameters: %d\n", err); 49262306a36Sopenharmony_ci return err; 49362306a36Sopenharmony_ci } 49462306a36Sopenharmony_ci 49562306a36Sopenharmony_ci /* write currently selected post-cursor level (if supported) */ 49662306a36Sopenharmony_ci if (link->revision >= 0x12 && link->rate == 540000) { 49762306a36Sopenharmony_ci values[0] = values[1] = 0; 49862306a36Sopenharmony_ci 49962306a36Sopenharmony_ci for (i = 0; i < lanes; i++) 50062306a36Sopenharmony_ci values[i / 2] |= DP_LANE_POST_CURSOR(i, pc[i]); 50162306a36Sopenharmony_ci 50262306a36Sopenharmony_ci err = drm_dp_dpcd_write(aux, DP_TRAINING_LANE0_1_SET2, values, 50362306a36Sopenharmony_ci DIV_ROUND_UP(lanes, 2)); 50462306a36Sopenharmony_ci if (err < 0) { 50562306a36Sopenharmony_ci DRM_ERROR("failed to set post-cursor: %d\n", err); 50662306a36Sopenharmony_ci return err; 50762306a36Sopenharmony_ci } 50862306a36Sopenharmony_ci } 50962306a36Sopenharmony_ci 51062306a36Sopenharmony_ci /* write link pattern */ 51162306a36Sopenharmony_ci if (link->train.pattern != DP_TRAINING_PATTERN_DISABLE) 51262306a36Sopenharmony_ci pattern |= DP_LINK_SCRAMBLING_DISABLE; 51362306a36Sopenharmony_ci 51462306a36Sopenharmony_ci pattern |= link->train.pattern; 51562306a36Sopenharmony_ci 51662306a36Sopenharmony_ci err = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET, pattern); 51762306a36Sopenharmony_ci if (err < 0) { 51862306a36Sopenharmony_ci DRM_ERROR("failed to set training pattern: %d\n", err); 51962306a36Sopenharmony_ci return err; 52062306a36Sopenharmony_ci } 52162306a36Sopenharmony_ci 52262306a36Sopenharmony_ci return 0; 52362306a36Sopenharmony_ci} 52462306a36Sopenharmony_ci 52562306a36Sopenharmony_cistatic void drm_dp_link_train_wait(struct drm_dp_link *link) 52662306a36Sopenharmony_ci{ 52762306a36Sopenharmony_ci unsigned long min = 0; 52862306a36Sopenharmony_ci 52962306a36Sopenharmony_ci switch (link->train.pattern) { 53062306a36Sopenharmony_ci case DP_TRAINING_PATTERN_1: 53162306a36Sopenharmony_ci min = link->aux_rd_interval.cr; 53262306a36Sopenharmony_ci break; 53362306a36Sopenharmony_ci 53462306a36Sopenharmony_ci case DP_TRAINING_PATTERN_2: 53562306a36Sopenharmony_ci case DP_TRAINING_PATTERN_3: 53662306a36Sopenharmony_ci min = link->aux_rd_interval.ce; 53762306a36Sopenharmony_ci break; 53862306a36Sopenharmony_ci 53962306a36Sopenharmony_ci default: 54062306a36Sopenharmony_ci break; 54162306a36Sopenharmony_ci } 54262306a36Sopenharmony_ci 54362306a36Sopenharmony_ci if (min > 0) 54462306a36Sopenharmony_ci usleep_range(min, 2 * min); 54562306a36Sopenharmony_ci} 54662306a36Sopenharmony_ci 54762306a36Sopenharmony_cistatic void drm_dp_link_get_adjustments(struct drm_dp_link *link, 54862306a36Sopenharmony_ci u8 status[DP_LINK_STATUS_SIZE]) 54962306a36Sopenharmony_ci{ 55062306a36Sopenharmony_ci struct drm_dp_link_train_set *adjust = &link->train.adjust; 55162306a36Sopenharmony_ci unsigned int i; 55262306a36Sopenharmony_ci u8 post_cursor; 55362306a36Sopenharmony_ci int err; 55462306a36Sopenharmony_ci 55562306a36Sopenharmony_ci err = drm_dp_dpcd_read(link->aux, DP_ADJUST_REQUEST_POST_CURSOR2, 55662306a36Sopenharmony_ci &post_cursor, sizeof(post_cursor)); 55762306a36Sopenharmony_ci if (err < 0) { 55862306a36Sopenharmony_ci DRM_ERROR("failed to read post_cursor2: %d\n", err); 55962306a36Sopenharmony_ci post_cursor = 0; 56062306a36Sopenharmony_ci } 56162306a36Sopenharmony_ci 56262306a36Sopenharmony_ci for (i = 0; i < link->lanes; i++) { 56362306a36Sopenharmony_ci adjust->voltage_swing[i] = 56462306a36Sopenharmony_ci drm_dp_get_adjust_request_voltage(status, i) >> 56562306a36Sopenharmony_ci DP_TRAIN_VOLTAGE_SWING_SHIFT; 56662306a36Sopenharmony_ci 56762306a36Sopenharmony_ci adjust->pre_emphasis[i] = 56862306a36Sopenharmony_ci drm_dp_get_adjust_request_pre_emphasis(status, i) >> 56962306a36Sopenharmony_ci DP_TRAIN_PRE_EMPHASIS_SHIFT; 57062306a36Sopenharmony_ci 57162306a36Sopenharmony_ci adjust->post_cursor[i] = 57262306a36Sopenharmony_ci (post_cursor >> (i << 1)) & 0x3; 57362306a36Sopenharmony_ci } 57462306a36Sopenharmony_ci} 57562306a36Sopenharmony_ci 57662306a36Sopenharmony_cistatic void drm_dp_link_train_adjust(struct drm_dp_link_train *train) 57762306a36Sopenharmony_ci{ 57862306a36Sopenharmony_ci struct drm_dp_link_train_set *request = &train->request; 57962306a36Sopenharmony_ci struct drm_dp_link_train_set *adjust = &train->adjust; 58062306a36Sopenharmony_ci unsigned int i; 58162306a36Sopenharmony_ci 58262306a36Sopenharmony_ci for (i = 0; i < 4; i++) 58362306a36Sopenharmony_ci if (request->voltage_swing[i] != adjust->voltage_swing[i]) 58462306a36Sopenharmony_ci request->voltage_swing[i] = adjust->voltage_swing[i]; 58562306a36Sopenharmony_ci 58662306a36Sopenharmony_ci for (i = 0; i < 4; i++) 58762306a36Sopenharmony_ci if (request->pre_emphasis[i] != adjust->pre_emphasis[i]) 58862306a36Sopenharmony_ci request->pre_emphasis[i] = adjust->pre_emphasis[i]; 58962306a36Sopenharmony_ci 59062306a36Sopenharmony_ci for (i = 0; i < 4; i++) 59162306a36Sopenharmony_ci if (request->post_cursor[i] != adjust->post_cursor[i]) 59262306a36Sopenharmony_ci request->post_cursor[i] = adjust->post_cursor[i]; 59362306a36Sopenharmony_ci} 59462306a36Sopenharmony_ci 59562306a36Sopenharmony_cistatic int drm_dp_link_recover_clock(struct drm_dp_link *link) 59662306a36Sopenharmony_ci{ 59762306a36Sopenharmony_ci u8 status[DP_LINK_STATUS_SIZE]; 59862306a36Sopenharmony_ci int err; 59962306a36Sopenharmony_ci 60062306a36Sopenharmony_ci err = drm_dp_link_apply_training(link); 60162306a36Sopenharmony_ci if (err < 0) 60262306a36Sopenharmony_ci return err; 60362306a36Sopenharmony_ci 60462306a36Sopenharmony_ci drm_dp_link_train_wait(link); 60562306a36Sopenharmony_ci 60662306a36Sopenharmony_ci err = drm_dp_dpcd_read_link_status(link->aux, status); 60762306a36Sopenharmony_ci if (err < 0) { 60862306a36Sopenharmony_ci DRM_ERROR("failed to read link status: %d\n", err); 60962306a36Sopenharmony_ci return err; 61062306a36Sopenharmony_ci } 61162306a36Sopenharmony_ci 61262306a36Sopenharmony_ci if (!drm_dp_clock_recovery_ok(status, link->lanes)) 61362306a36Sopenharmony_ci drm_dp_link_get_adjustments(link, status); 61462306a36Sopenharmony_ci else 61562306a36Sopenharmony_ci link->train.clock_recovered = true; 61662306a36Sopenharmony_ci 61762306a36Sopenharmony_ci return 0; 61862306a36Sopenharmony_ci} 61962306a36Sopenharmony_ci 62062306a36Sopenharmony_cistatic int drm_dp_link_clock_recovery(struct drm_dp_link *link) 62162306a36Sopenharmony_ci{ 62262306a36Sopenharmony_ci unsigned int repeat; 62362306a36Sopenharmony_ci int err; 62462306a36Sopenharmony_ci 62562306a36Sopenharmony_ci /* start clock recovery using training pattern 1 */ 62662306a36Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_1; 62762306a36Sopenharmony_ci 62862306a36Sopenharmony_ci for (repeat = 1; repeat < 5; repeat++) { 62962306a36Sopenharmony_ci err = drm_dp_link_recover_clock(link); 63062306a36Sopenharmony_ci if (err < 0) { 63162306a36Sopenharmony_ci DRM_ERROR("failed to recover clock: %d\n", err); 63262306a36Sopenharmony_ci return err; 63362306a36Sopenharmony_ci } 63462306a36Sopenharmony_ci 63562306a36Sopenharmony_ci if (link->train.clock_recovered) 63662306a36Sopenharmony_ci break; 63762306a36Sopenharmony_ci 63862306a36Sopenharmony_ci drm_dp_link_train_adjust(&link->train); 63962306a36Sopenharmony_ci } 64062306a36Sopenharmony_ci 64162306a36Sopenharmony_ci return 0; 64262306a36Sopenharmony_ci} 64362306a36Sopenharmony_ci 64462306a36Sopenharmony_cistatic int drm_dp_link_equalize_channel(struct drm_dp_link *link) 64562306a36Sopenharmony_ci{ 64662306a36Sopenharmony_ci struct drm_dp_aux *aux = link->aux; 64762306a36Sopenharmony_ci u8 status[DP_LINK_STATUS_SIZE]; 64862306a36Sopenharmony_ci int err; 64962306a36Sopenharmony_ci 65062306a36Sopenharmony_ci err = drm_dp_link_apply_training(link); 65162306a36Sopenharmony_ci if (err < 0) 65262306a36Sopenharmony_ci return err; 65362306a36Sopenharmony_ci 65462306a36Sopenharmony_ci drm_dp_link_train_wait(link); 65562306a36Sopenharmony_ci 65662306a36Sopenharmony_ci err = drm_dp_dpcd_read_link_status(aux, status); 65762306a36Sopenharmony_ci if (err < 0) { 65862306a36Sopenharmony_ci DRM_ERROR("failed to read link status: %d\n", err); 65962306a36Sopenharmony_ci return err; 66062306a36Sopenharmony_ci } 66162306a36Sopenharmony_ci 66262306a36Sopenharmony_ci if (!drm_dp_clock_recovery_ok(status, link->lanes)) { 66362306a36Sopenharmony_ci DRM_ERROR("clock recovery lost while equalizing channel\n"); 66462306a36Sopenharmony_ci link->train.clock_recovered = false; 66562306a36Sopenharmony_ci return 0; 66662306a36Sopenharmony_ci } 66762306a36Sopenharmony_ci 66862306a36Sopenharmony_ci if (!drm_dp_channel_eq_ok(status, link->lanes)) 66962306a36Sopenharmony_ci drm_dp_link_get_adjustments(link, status); 67062306a36Sopenharmony_ci else 67162306a36Sopenharmony_ci link->train.channel_equalized = true; 67262306a36Sopenharmony_ci 67362306a36Sopenharmony_ci return 0; 67462306a36Sopenharmony_ci} 67562306a36Sopenharmony_ci 67662306a36Sopenharmony_cistatic int drm_dp_link_channel_equalization(struct drm_dp_link *link) 67762306a36Sopenharmony_ci{ 67862306a36Sopenharmony_ci unsigned int repeat; 67962306a36Sopenharmony_ci int err; 68062306a36Sopenharmony_ci 68162306a36Sopenharmony_ci /* start channel equalization using pattern 2 or 3 */ 68262306a36Sopenharmony_ci if (link->caps.tps3_supported) 68362306a36Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_3; 68462306a36Sopenharmony_ci else 68562306a36Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_2; 68662306a36Sopenharmony_ci 68762306a36Sopenharmony_ci for (repeat = 1; repeat < 5; repeat++) { 68862306a36Sopenharmony_ci err = drm_dp_link_equalize_channel(link); 68962306a36Sopenharmony_ci if (err < 0) { 69062306a36Sopenharmony_ci DRM_ERROR("failed to equalize channel: %d\n", err); 69162306a36Sopenharmony_ci return err; 69262306a36Sopenharmony_ci } 69362306a36Sopenharmony_ci 69462306a36Sopenharmony_ci if (link->train.channel_equalized) 69562306a36Sopenharmony_ci break; 69662306a36Sopenharmony_ci 69762306a36Sopenharmony_ci drm_dp_link_train_adjust(&link->train); 69862306a36Sopenharmony_ci } 69962306a36Sopenharmony_ci 70062306a36Sopenharmony_ci return 0; 70162306a36Sopenharmony_ci} 70262306a36Sopenharmony_ci 70362306a36Sopenharmony_cistatic int drm_dp_link_downgrade(struct drm_dp_link *link) 70462306a36Sopenharmony_ci{ 70562306a36Sopenharmony_ci switch (link->rate) { 70662306a36Sopenharmony_ci case 162000: 70762306a36Sopenharmony_ci return -EINVAL; 70862306a36Sopenharmony_ci 70962306a36Sopenharmony_ci case 270000: 71062306a36Sopenharmony_ci link->rate = 162000; 71162306a36Sopenharmony_ci break; 71262306a36Sopenharmony_ci 71362306a36Sopenharmony_ci case 540000: 71462306a36Sopenharmony_ci link->rate = 270000; 71562306a36Sopenharmony_ci return 0; 71662306a36Sopenharmony_ci } 71762306a36Sopenharmony_ci 71862306a36Sopenharmony_ci return 0; 71962306a36Sopenharmony_ci} 72062306a36Sopenharmony_ci 72162306a36Sopenharmony_cistatic void drm_dp_link_train_disable(struct drm_dp_link *link) 72262306a36Sopenharmony_ci{ 72362306a36Sopenharmony_ci int err; 72462306a36Sopenharmony_ci 72562306a36Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_DISABLE; 72662306a36Sopenharmony_ci 72762306a36Sopenharmony_ci err = drm_dp_link_apply_training(link); 72862306a36Sopenharmony_ci if (err < 0) 72962306a36Sopenharmony_ci DRM_ERROR("failed to disable link training: %d\n", err); 73062306a36Sopenharmony_ci} 73162306a36Sopenharmony_ci 73262306a36Sopenharmony_cistatic int drm_dp_link_train_full(struct drm_dp_link *link) 73362306a36Sopenharmony_ci{ 73462306a36Sopenharmony_ci int err; 73562306a36Sopenharmony_ci 73662306a36Sopenharmony_ciretry: 73762306a36Sopenharmony_ci DRM_DEBUG_KMS("full-training link: %u lane%s at %u MHz\n", 73862306a36Sopenharmony_ci link->lanes, (link->lanes > 1) ? "s" : "", 73962306a36Sopenharmony_ci link->rate / 100); 74062306a36Sopenharmony_ci 74162306a36Sopenharmony_ci err = drm_dp_link_configure(link->aux, link); 74262306a36Sopenharmony_ci if (err < 0) { 74362306a36Sopenharmony_ci DRM_ERROR("failed to configure DP link: %d\n", err); 74462306a36Sopenharmony_ci return err; 74562306a36Sopenharmony_ci } 74662306a36Sopenharmony_ci 74762306a36Sopenharmony_ci err = drm_dp_link_clock_recovery(link); 74862306a36Sopenharmony_ci if (err < 0) { 74962306a36Sopenharmony_ci DRM_ERROR("clock recovery failed: %d\n", err); 75062306a36Sopenharmony_ci goto out; 75162306a36Sopenharmony_ci } 75262306a36Sopenharmony_ci 75362306a36Sopenharmony_ci if (!link->train.clock_recovered) { 75462306a36Sopenharmony_ci DRM_ERROR("clock recovery failed, downgrading link\n"); 75562306a36Sopenharmony_ci 75662306a36Sopenharmony_ci err = drm_dp_link_downgrade(link); 75762306a36Sopenharmony_ci if (err < 0) 75862306a36Sopenharmony_ci goto out; 75962306a36Sopenharmony_ci 76062306a36Sopenharmony_ci goto retry; 76162306a36Sopenharmony_ci } 76262306a36Sopenharmony_ci 76362306a36Sopenharmony_ci DRM_DEBUG_KMS("clock recovery succeeded\n"); 76462306a36Sopenharmony_ci 76562306a36Sopenharmony_ci err = drm_dp_link_channel_equalization(link); 76662306a36Sopenharmony_ci if (err < 0) { 76762306a36Sopenharmony_ci DRM_ERROR("channel equalization failed: %d\n", err); 76862306a36Sopenharmony_ci goto out; 76962306a36Sopenharmony_ci } 77062306a36Sopenharmony_ci 77162306a36Sopenharmony_ci if (!link->train.channel_equalized) { 77262306a36Sopenharmony_ci DRM_ERROR("channel equalization failed, downgrading link\n"); 77362306a36Sopenharmony_ci 77462306a36Sopenharmony_ci err = drm_dp_link_downgrade(link); 77562306a36Sopenharmony_ci if (err < 0) 77662306a36Sopenharmony_ci goto out; 77762306a36Sopenharmony_ci 77862306a36Sopenharmony_ci goto retry; 77962306a36Sopenharmony_ci } 78062306a36Sopenharmony_ci 78162306a36Sopenharmony_ci DRM_DEBUG_KMS("channel equalization succeeded\n"); 78262306a36Sopenharmony_ci 78362306a36Sopenharmony_ciout: 78462306a36Sopenharmony_ci drm_dp_link_train_disable(link); 78562306a36Sopenharmony_ci return err; 78662306a36Sopenharmony_ci} 78762306a36Sopenharmony_ci 78862306a36Sopenharmony_cistatic int drm_dp_link_train_fast(struct drm_dp_link *link) 78962306a36Sopenharmony_ci{ 79062306a36Sopenharmony_ci u8 status[DP_LINK_STATUS_SIZE]; 79162306a36Sopenharmony_ci int err; 79262306a36Sopenharmony_ci 79362306a36Sopenharmony_ci DRM_DEBUG_KMS("fast-training link: %u lane%s at %u MHz\n", 79462306a36Sopenharmony_ci link->lanes, (link->lanes > 1) ? "s" : "", 79562306a36Sopenharmony_ci link->rate / 100); 79662306a36Sopenharmony_ci 79762306a36Sopenharmony_ci err = drm_dp_link_configure(link->aux, link); 79862306a36Sopenharmony_ci if (err < 0) { 79962306a36Sopenharmony_ci DRM_ERROR("failed to configure DP link: %d\n", err); 80062306a36Sopenharmony_ci return err; 80162306a36Sopenharmony_ci } 80262306a36Sopenharmony_ci 80362306a36Sopenharmony_ci /* transmit training pattern 1 for 500 microseconds */ 80462306a36Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_1; 80562306a36Sopenharmony_ci 80662306a36Sopenharmony_ci err = drm_dp_link_apply_training(link); 80762306a36Sopenharmony_ci if (err < 0) 80862306a36Sopenharmony_ci goto out; 80962306a36Sopenharmony_ci 81062306a36Sopenharmony_ci usleep_range(500, 1000); 81162306a36Sopenharmony_ci 81262306a36Sopenharmony_ci /* transmit training pattern 2 or 3 for 500 microseconds */ 81362306a36Sopenharmony_ci if (link->caps.tps3_supported) 81462306a36Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_3; 81562306a36Sopenharmony_ci else 81662306a36Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_2; 81762306a36Sopenharmony_ci 81862306a36Sopenharmony_ci err = drm_dp_link_apply_training(link); 81962306a36Sopenharmony_ci if (err < 0) 82062306a36Sopenharmony_ci goto out; 82162306a36Sopenharmony_ci 82262306a36Sopenharmony_ci usleep_range(500, 1000); 82362306a36Sopenharmony_ci 82462306a36Sopenharmony_ci err = drm_dp_dpcd_read_link_status(link->aux, status); 82562306a36Sopenharmony_ci if (err < 0) { 82662306a36Sopenharmony_ci DRM_ERROR("failed to read link status: %d\n", err); 82762306a36Sopenharmony_ci goto out; 82862306a36Sopenharmony_ci } 82962306a36Sopenharmony_ci 83062306a36Sopenharmony_ci if (!drm_dp_clock_recovery_ok(status, link->lanes)) { 83162306a36Sopenharmony_ci DRM_ERROR("clock recovery failed\n"); 83262306a36Sopenharmony_ci err = -EIO; 83362306a36Sopenharmony_ci } 83462306a36Sopenharmony_ci 83562306a36Sopenharmony_ci if (!drm_dp_channel_eq_ok(status, link->lanes)) { 83662306a36Sopenharmony_ci DRM_ERROR("channel equalization failed\n"); 83762306a36Sopenharmony_ci err = -EIO; 83862306a36Sopenharmony_ci } 83962306a36Sopenharmony_ci 84062306a36Sopenharmony_ciout: 84162306a36Sopenharmony_ci drm_dp_link_train_disable(link); 84262306a36Sopenharmony_ci return err; 84362306a36Sopenharmony_ci} 84462306a36Sopenharmony_ci 84562306a36Sopenharmony_ci/** 84662306a36Sopenharmony_ci * drm_dp_link_train() - perform DisplayPort link training 84762306a36Sopenharmony_ci * @link: a DP link object 84862306a36Sopenharmony_ci * 84962306a36Sopenharmony_ci * Uses the context stored in the DP link object to perform link training. It 85062306a36Sopenharmony_ci * is expected that drivers will call drm_dp_link_probe() to obtain the link 85162306a36Sopenharmony_ci * capabilities before performing link training. 85262306a36Sopenharmony_ci * 85362306a36Sopenharmony_ci * If the sink supports fast link training (no AUX CH handshake) and valid 85462306a36Sopenharmony_ci * training settings are available, this function will try to perform fast 85562306a36Sopenharmony_ci * link training and fall back to full link training on failure. 85662306a36Sopenharmony_ci * 85762306a36Sopenharmony_ci * Returns: 0 on success or a negative error code on failure. 85862306a36Sopenharmony_ci */ 85962306a36Sopenharmony_ciint drm_dp_link_train(struct drm_dp_link *link) 86062306a36Sopenharmony_ci{ 86162306a36Sopenharmony_ci int err; 86262306a36Sopenharmony_ci 86362306a36Sopenharmony_ci drm_dp_link_train_init(&link->train); 86462306a36Sopenharmony_ci 86562306a36Sopenharmony_ci if (link->caps.fast_training) { 86662306a36Sopenharmony_ci if (drm_dp_link_train_valid(&link->train)) { 86762306a36Sopenharmony_ci err = drm_dp_link_train_fast(link); 86862306a36Sopenharmony_ci if (err < 0) 86962306a36Sopenharmony_ci DRM_ERROR("fast link training failed: %d\n", 87062306a36Sopenharmony_ci err); 87162306a36Sopenharmony_ci else 87262306a36Sopenharmony_ci return 0; 87362306a36Sopenharmony_ci } else { 87462306a36Sopenharmony_ci DRM_DEBUG_KMS("training parameters not available\n"); 87562306a36Sopenharmony_ci } 87662306a36Sopenharmony_ci } else { 87762306a36Sopenharmony_ci DRM_DEBUG_KMS("fast link training not supported\n"); 87862306a36Sopenharmony_ci } 87962306a36Sopenharmony_ci 88062306a36Sopenharmony_ci err = drm_dp_link_train_full(link); 88162306a36Sopenharmony_ci if (err < 0) 88262306a36Sopenharmony_ci DRM_ERROR("full link training failed: %d\n", err); 88362306a36Sopenharmony_ci 88462306a36Sopenharmony_ci return err; 88562306a36Sopenharmony_ci} 886