18c2ecf20Sopenharmony_ci// SPDX-License-Identifier: MIT 28c2ecf20Sopenharmony_ci/* 38c2ecf20Sopenharmony_ci * Copyright (C) 2013-2019 NVIDIA Corporation 48c2ecf20Sopenharmony_ci * Copyright (C) 2015 Rob Clark 58c2ecf20Sopenharmony_ci */ 68c2ecf20Sopenharmony_ci 78c2ecf20Sopenharmony_ci#include <drm/drm_crtc.h> 88c2ecf20Sopenharmony_ci#include <drm/drm_dp_helper.h> 98c2ecf20Sopenharmony_ci#include <drm/drm_print.h> 108c2ecf20Sopenharmony_ci 118c2ecf20Sopenharmony_ci#include "dp.h" 128c2ecf20Sopenharmony_ci 138c2ecf20Sopenharmony_cistatic const u8 drm_dp_edp_revisions[] = { 0x11, 0x12, 0x13, 0x14 }; 148c2ecf20Sopenharmony_ci 158c2ecf20Sopenharmony_cistatic void drm_dp_link_caps_reset(struct drm_dp_link_caps *caps) 168c2ecf20Sopenharmony_ci{ 178c2ecf20Sopenharmony_ci caps->enhanced_framing = false; 188c2ecf20Sopenharmony_ci caps->tps3_supported = false; 198c2ecf20Sopenharmony_ci caps->fast_training = false; 208c2ecf20Sopenharmony_ci caps->channel_coding = false; 218c2ecf20Sopenharmony_ci caps->alternate_scrambler_reset = false; 228c2ecf20Sopenharmony_ci} 238c2ecf20Sopenharmony_ci 248c2ecf20Sopenharmony_civoid drm_dp_link_caps_copy(struct drm_dp_link_caps *dest, 258c2ecf20Sopenharmony_ci const struct drm_dp_link_caps *src) 268c2ecf20Sopenharmony_ci{ 278c2ecf20Sopenharmony_ci dest->enhanced_framing = src->enhanced_framing; 288c2ecf20Sopenharmony_ci dest->tps3_supported = src->tps3_supported; 298c2ecf20Sopenharmony_ci dest->fast_training = src->fast_training; 308c2ecf20Sopenharmony_ci dest->channel_coding = src->channel_coding; 318c2ecf20Sopenharmony_ci dest->alternate_scrambler_reset = src->alternate_scrambler_reset; 328c2ecf20Sopenharmony_ci} 338c2ecf20Sopenharmony_ci 348c2ecf20Sopenharmony_cistatic void drm_dp_link_reset(struct drm_dp_link *link) 358c2ecf20Sopenharmony_ci{ 368c2ecf20Sopenharmony_ci unsigned int i; 378c2ecf20Sopenharmony_ci 388c2ecf20Sopenharmony_ci if (!link) 398c2ecf20Sopenharmony_ci return; 408c2ecf20Sopenharmony_ci 418c2ecf20Sopenharmony_ci link->revision = 0; 428c2ecf20Sopenharmony_ci link->max_rate = 0; 438c2ecf20Sopenharmony_ci link->max_lanes = 0; 448c2ecf20Sopenharmony_ci 458c2ecf20Sopenharmony_ci drm_dp_link_caps_reset(&link->caps); 468c2ecf20Sopenharmony_ci link->aux_rd_interval.cr = 0; 478c2ecf20Sopenharmony_ci link->aux_rd_interval.ce = 0; 488c2ecf20Sopenharmony_ci link->edp = 0; 498c2ecf20Sopenharmony_ci 508c2ecf20Sopenharmony_ci link->rate = 0; 518c2ecf20Sopenharmony_ci link->lanes = 0; 528c2ecf20Sopenharmony_ci 538c2ecf20Sopenharmony_ci for (i = 0; i < DP_MAX_SUPPORTED_RATES; i++) 548c2ecf20Sopenharmony_ci link->rates[i] = 0; 558c2ecf20Sopenharmony_ci 568c2ecf20Sopenharmony_ci link->num_rates = 0; 578c2ecf20Sopenharmony_ci} 588c2ecf20Sopenharmony_ci 598c2ecf20Sopenharmony_ci/** 608c2ecf20Sopenharmony_ci * drm_dp_link_add_rate() - add a rate to the list of supported rates 618c2ecf20Sopenharmony_ci * @link: the link to add the rate to 628c2ecf20Sopenharmony_ci * @rate: the rate to add 638c2ecf20Sopenharmony_ci * 648c2ecf20Sopenharmony_ci * Add a link rate to the list of supported link rates. 658c2ecf20Sopenharmony_ci * 668c2ecf20Sopenharmony_ci * Returns: 678c2ecf20Sopenharmony_ci * 0 on success or one of the following negative error codes on failure: 688c2ecf20Sopenharmony_ci * - ENOSPC if the maximum number of supported rates has been reached 698c2ecf20Sopenharmony_ci * - EEXISTS if the link already supports this rate 708c2ecf20Sopenharmony_ci * 718c2ecf20Sopenharmony_ci * See also: 728c2ecf20Sopenharmony_ci * drm_dp_link_remove_rate() 738c2ecf20Sopenharmony_ci */ 748c2ecf20Sopenharmony_ciint drm_dp_link_add_rate(struct drm_dp_link *link, unsigned long rate) 758c2ecf20Sopenharmony_ci{ 768c2ecf20Sopenharmony_ci unsigned int i, pivot; 778c2ecf20Sopenharmony_ci 788c2ecf20Sopenharmony_ci if (link->num_rates == DP_MAX_SUPPORTED_RATES) 798c2ecf20Sopenharmony_ci return -ENOSPC; 808c2ecf20Sopenharmony_ci 818c2ecf20Sopenharmony_ci for (pivot = 0; pivot < link->num_rates; pivot++) 828c2ecf20Sopenharmony_ci if (rate <= link->rates[pivot]) 838c2ecf20Sopenharmony_ci break; 848c2ecf20Sopenharmony_ci 858c2ecf20Sopenharmony_ci if (pivot != link->num_rates && rate == link->rates[pivot]) 868c2ecf20Sopenharmony_ci return -EEXIST; 878c2ecf20Sopenharmony_ci 888c2ecf20Sopenharmony_ci for (i = link->num_rates; i > pivot; i--) 898c2ecf20Sopenharmony_ci link->rates[i] = link->rates[i - 1]; 908c2ecf20Sopenharmony_ci 918c2ecf20Sopenharmony_ci link->rates[pivot] = rate; 928c2ecf20Sopenharmony_ci link->num_rates++; 938c2ecf20Sopenharmony_ci 948c2ecf20Sopenharmony_ci return 0; 958c2ecf20Sopenharmony_ci} 968c2ecf20Sopenharmony_ci 978c2ecf20Sopenharmony_ci/** 988c2ecf20Sopenharmony_ci * drm_dp_link_remove_rate() - remove a rate from the list of supported rates 998c2ecf20Sopenharmony_ci * @link: the link from which to remove the rate 1008c2ecf20Sopenharmony_ci * @rate: the rate to remove 1018c2ecf20Sopenharmony_ci * 1028c2ecf20Sopenharmony_ci * Removes a link rate from the list of supported link rates. 1038c2ecf20Sopenharmony_ci * 1048c2ecf20Sopenharmony_ci * Returns: 1058c2ecf20Sopenharmony_ci * 0 on success or one of the following negative error codes on failure: 1068c2ecf20Sopenharmony_ci * - EINVAL if the specified rate is not among the supported rates 1078c2ecf20Sopenharmony_ci * 1088c2ecf20Sopenharmony_ci * See also: 1098c2ecf20Sopenharmony_ci * drm_dp_link_add_rate() 1108c2ecf20Sopenharmony_ci */ 1118c2ecf20Sopenharmony_ciint drm_dp_link_remove_rate(struct drm_dp_link *link, unsigned long rate) 1128c2ecf20Sopenharmony_ci{ 1138c2ecf20Sopenharmony_ci unsigned int i; 1148c2ecf20Sopenharmony_ci 1158c2ecf20Sopenharmony_ci for (i = 0; i < link->num_rates; i++) 1168c2ecf20Sopenharmony_ci if (rate == link->rates[i]) 1178c2ecf20Sopenharmony_ci break; 1188c2ecf20Sopenharmony_ci 1198c2ecf20Sopenharmony_ci if (i == link->num_rates) 1208c2ecf20Sopenharmony_ci return -EINVAL; 1218c2ecf20Sopenharmony_ci 1228c2ecf20Sopenharmony_ci link->num_rates--; 1238c2ecf20Sopenharmony_ci 1248c2ecf20Sopenharmony_ci while (i < link->num_rates) { 1258c2ecf20Sopenharmony_ci link->rates[i] = link->rates[i + 1]; 1268c2ecf20Sopenharmony_ci i++; 1278c2ecf20Sopenharmony_ci } 1288c2ecf20Sopenharmony_ci 1298c2ecf20Sopenharmony_ci return 0; 1308c2ecf20Sopenharmony_ci} 1318c2ecf20Sopenharmony_ci 1328c2ecf20Sopenharmony_ci/** 1338c2ecf20Sopenharmony_ci * drm_dp_link_update_rates() - normalize the supported link rates array 1348c2ecf20Sopenharmony_ci * @link: the link for which to normalize the supported link rates 1358c2ecf20Sopenharmony_ci * 1368c2ecf20Sopenharmony_ci * Users should call this function after they've manually modified the array 1378c2ecf20Sopenharmony_ci * of supported link rates. This function removes any stale entries, compacts 1388c2ecf20Sopenharmony_ci * the array and updates the supported link rate count. Note that calling the 1398c2ecf20Sopenharmony_ci * drm_dp_link_remove_rate() function already does this janitorial work. 1408c2ecf20Sopenharmony_ci * 1418c2ecf20Sopenharmony_ci * See also: 1428c2ecf20Sopenharmony_ci * drm_dp_link_add_rate(), drm_dp_link_remove_rate() 1438c2ecf20Sopenharmony_ci */ 1448c2ecf20Sopenharmony_civoid drm_dp_link_update_rates(struct drm_dp_link *link) 1458c2ecf20Sopenharmony_ci{ 1468c2ecf20Sopenharmony_ci unsigned int i, count = 0; 1478c2ecf20Sopenharmony_ci 1488c2ecf20Sopenharmony_ci for (i = 0; i < link->num_rates; i++) { 1498c2ecf20Sopenharmony_ci if (link->rates[i] != 0) 1508c2ecf20Sopenharmony_ci link->rates[count++] = link->rates[i]; 1518c2ecf20Sopenharmony_ci } 1528c2ecf20Sopenharmony_ci 1538c2ecf20Sopenharmony_ci for (i = count; i < link->num_rates; i++) 1548c2ecf20Sopenharmony_ci link->rates[i] = 0; 1558c2ecf20Sopenharmony_ci 1568c2ecf20Sopenharmony_ci link->num_rates = count; 1578c2ecf20Sopenharmony_ci} 1588c2ecf20Sopenharmony_ci 1598c2ecf20Sopenharmony_ci/** 1608c2ecf20Sopenharmony_ci * drm_dp_link_probe() - probe a DisplayPort link for capabilities 1618c2ecf20Sopenharmony_ci * @aux: DisplayPort AUX channel 1628c2ecf20Sopenharmony_ci * @link: pointer to structure in which to return link capabilities 1638c2ecf20Sopenharmony_ci * 1648c2ecf20Sopenharmony_ci * The structure filled in by this function can usually be passed directly 1658c2ecf20Sopenharmony_ci * into drm_dp_link_power_up() and drm_dp_link_configure() to power up and 1668c2ecf20Sopenharmony_ci * configure the link based on the link's capabilities. 1678c2ecf20Sopenharmony_ci * 1688c2ecf20Sopenharmony_ci * Returns 0 on success or a negative error code on failure. 1698c2ecf20Sopenharmony_ci */ 1708c2ecf20Sopenharmony_ciint drm_dp_link_probe(struct drm_dp_aux *aux, struct drm_dp_link *link) 1718c2ecf20Sopenharmony_ci{ 1728c2ecf20Sopenharmony_ci u8 dpcd[DP_RECEIVER_CAP_SIZE], value; 1738c2ecf20Sopenharmony_ci unsigned int rd_interval; 1748c2ecf20Sopenharmony_ci int err; 1758c2ecf20Sopenharmony_ci 1768c2ecf20Sopenharmony_ci drm_dp_link_reset(link); 1778c2ecf20Sopenharmony_ci 1788c2ecf20Sopenharmony_ci err = drm_dp_dpcd_read(aux, DP_DPCD_REV, dpcd, sizeof(dpcd)); 1798c2ecf20Sopenharmony_ci if (err < 0) 1808c2ecf20Sopenharmony_ci return err; 1818c2ecf20Sopenharmony_ci 1828c2ecf20Sopenharmony_ci link->revision = dpcd[DP_DPCD_REV]; 1838c2ecf20Sopenharmony_ci link->max_rate = drm_dp_max_link_rate(dpcd); 1848c2ecf20Sopenharmony_ci link->max_lanes = drm_dp_max_lane_count(dpcd); 1858c2ecf20Sopenharmony_ci 1868c2ecf20Sopenharmony_ci link->caps.enhanced_framing = drm_dp_enhanced_frame_cap(dpcd); 1878c2ecf20Sopenharmony_ci link->caps.tps3_supported = drm_dp_tps3_supported(dpcd); 1888c2ecf20Sopenharmony_ci link->caps.fast_training = drm_dp_fast_training_cap(dpcd); 1898c2ecf20Sopenharmony_ci link->caps.channel_coding = drm_dp_channel_coding_supported(dpcd); 1908c2ecf20Sopenharmony_ci 1918c2ecf20Sopenharmony_ci if (drm_dp_alternate_scrambler_reset_cap(dpcd)) { 1928c2ecf20Sopenharmony_ci link->caps.alternate_scrambler_reset = true; 1938c2ecf20Sopenharmony_ci 1948c2ecf20Sopenharmony_ci err = drm_dp_dpcd_readb(aux, DP_EDP_DPCD_REV, &value); 1958c2ecf20Sopenharmony_ci if (err < 0) 1968c2ecf20Sopenharmony_ci return err; 1978c2ecf20Sopenharmony_ci 1988c2ecf20Sopenharmony_ci if (value >= ARRAY_SIZE(drm_dp_edp_revisions)) 1998c2ecf20Sopenharmony_ci DRM_ERROR("unsupported eDP version: %02x\n", value); 2008c2ecf20Sopenharmony_ci else 2018c2ecf20Sopenharmony_ci link->edp = drm_dp_edp_revisions[value]; 2028c2ecf20Sopenharmony_ci } 2038c2ecf20Sopenharmony_ci 2048c2ecf20Sopenharmony_ci /* 2058c2ecf20Sopenharmony_ci * The DPCD stores the AUX read interval in units of 4 ms. There are 2068c2ecf20Sopenharmony_ci * two special cases: 2078c2ecf20Sopenharmony_ci * 2088c2ecf20Sopenharmony_ci * 1) if the TRAINING_AUX_RD_INTERVAL field is 0, the clock recovery 2098c2ecf20Sopenharmony_ci * and channel equalization should use 100 us or 400 us AUX read 2108c2ecf20Sopenharmony_ci * intervals, respectively 2118c2ecf20Sopenharmony_ci * 2128c2ecf20Sopenharmony_ci * 2) for DP v1.4 and above, clock recovery should always use 100 us 2138c2ecf20Sopenharmony_ci * AUX read intervals 2148c2ecf20Sopenharmony_ci */ 2158c2ecf20Sopenharmony_ci rd_interval = dpcd[DP_TRAINING_AUX_RD_INTERVAL] & 2168c2ecf20Sopenharmony_ci DP_TRAINING_AUX_RD_MASK; 2178c2ecf20Sopenharmony_ci 2188c2ecf20Sopenharmony_ci if (rd_interval > 4) { 2198c2ecf20Sopenharmony_ci DRM_DEBUG_KMS("AUX interval %u out of range (max. 4)\n", 2208c2ecf20Sopenharmony_ci rd_interval); 2218c2ecf20Sopenharmony_ci rd_interval = 4; 2228c2ecf20Sopenharmony_ci } 2238c2ecf20Sopenharmony_ci 2248c2ecf20Sopenharmony_ci rd_interval *= 4 * USEC_PER_MSEC; 2258c2ecf20Sopenharmony_ci 2268c2ecf20Sopenharmony_ci if (rd_interval == 0 || link->revision >= DP_DPCD_REV_14) 2278c2ecf20Sopenharmony_ci link->aux_rd_interval.cr = 100; 2288c2ecf20Sopenharmony_ci 2298c2ecf20Sopenharmony_ci if (rd_interval == 0) 2308c2ecf20Sopenharmony_ci link->aux_rd_interval.ce = 400; 2318c2ecf20Sopenharmony_ci 2328c2ecf20Sopenharmony_ci link->rate = link->max_rate; 2338c2ecf20Sopenharmony_ci link->lanes = link->max_lanes; 2348c2ecf20Sopenharmony_ci 2358c2ecf20Sopenharmony_ci /* Parse SUPPORTED_LINK_RATES from eDP 1.4 */ 2368c2ecf20Sopenharmony_ci if (link->edp >= 0x14) { 2378c2ecf20Sopenharmony_ci u8 supported_rates[DP_MAX_SUPPORTED_RATES * 2]; 2388c2ecf20Sopenharmony_ci unsigned int i; 2398c2ecf20Sopenharmony_ci u16 rate; 2408c2ecf20Sopenharmony_ci 2418c2ecf20Sopenharmony_ci err = drm_dp_dpcd_read(aux, DP_SUPPORTED_LINK_RATES, 2428c2ecf20Sopenharmony_ci supported_rates, 2438c2ecf20Sopenharmony_ci sizeof(supported_rates)); 2448c2ecf20Sopenharmony_ci if (err < 0) 2458c2ecf20Sopenharmony_ci return err; 2468c2ecf20Sopenharmony_ci 2478c2ecf20Sopenharmony_ci for (i = 0; i < DP_MAX_SUPPORTED_RATES; i++) { 2488c2ecf20Sopenharmony_ci rate = supported_rates[i * 2 + 1] << 8 | 2498c2ecf20Sopenharmony_ci supported_rates[i * 2 + 0]; 2508c2ecf20Sopenharmony_ci 2518c2ecf20Sopenharmony_ci drm_dp_link_add_rate(link, rate * 200); 2528c2ecf20Sopenharmony_ci } 2538c2ecf20Sopenharmony_ci } 2548c2ecf20Sopenharmony_ci 2558c2ecf20Sopenharmony_ci return 0; 2568c2ecf20Sopenharmony_ci} 2578c2ecf20Sopenharmony_ci 2588c2ecf20Sopenharmony_ci/** 2598c2ecf20Sopenharmony_ci * drm_dp_link_power_up() - power up a DisplayPort link 2608c2ecf20Sopenharmony_ci * @aux: DisplayPort AUX channel 2618c2ecf20Sopenharmony_ci * @link: pointer to a structure containing the link configuration 2628c2ecf20Sopenharmony_ci * 2638c2ecf20Sopenharmony_ci * Returns 0 on success or a negative error code on failure. 2648c2ecf20Sopenharmony_ci */ 2658c2ecf20Sopenharmony_ciint drm_dp_link_power_up(struct drm_dp_aux *aux, struct drm_dp_link *link) 2668c2ecf20Sopenharmony_ci{ 2678c2ecf20Sopenharmony_ci u8 value; 2688c2ecf20Sopenharmony_ci int err; 2698c2ecf20Sopenharmony_ci 2708c2ecf20Sopenharmony_ci /* DP_SET_POWER register is only available on DPCD v1.1 and later */ 2718c2ecf20Sopenharmony_ci if (link->revision < 0x11) 2728c2ecf20Sopenharmony_ci return 0; 2738c2ecf20Sopenharmony_ci 2748c2ecf20Sopenharmony_ci err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value); 2758c2ecf20Sopenharmony_ci if (err < 0) 2768c2ecf20Sopenharmony_ci return err; 2778c2ecf20Sopenharmony_ci 2788c2ecf20Sopenharmony_ci value &= ~DP_SET_POWER_MASK; 2798c2ecf20Sopenharmony_ci value |= DP_SET_POWER_D0; 2808c2ecf20Sopenharmony_ci 2818c2ecf20Sopenharmony_ci err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value); 2828c2ecf20Sopenharmony_ci if (err < 0) 2838c2ecf20Sopenharmony_ci return err; 2848c2ecf20Sopenharmony_ci 2858c2ecf20Sopenharmony_ci /* 2868c2ecf20Sopenharmony_ci * According to the DP 1.1 specification, a "Sink Device must exit the 2878c2ecf20Sopenharmony_ci * power saving state within 1 ms" (Section 2.5.3.1, Table 5-52, "Sink 2888c2ecf20Sopenharmony_ci * Control Field" (register 0x600). 2898c2ecf20Sopenharmony_ci */ 2908c2ecf20Sopenharmony_ci usleep_range(1000, 2000); 2918c2ecf20Sopenharmony_ci 2928c2ecf20Sopenharmony_ci return 0; 2938c2ecf20Sopenharmony_ci} 2948c2ecf20Sopenharmony_ci 2958c2ecf20Sopenharmony_ci/** 2968c2ecf20Sopenharmony_ci * drm_dp_link_power_down() - power down a DisplayPort link 2978c2ecf20Sopenharmony_ci * @aux: DisplayPort AUX channel 2988c2ecf20Sopenharmony_ci * @link: pointer to a structure containing the link configuration 2998c2ecf20Sopenharmony_ci * 3008c2ecf20Sopenharmony_ci * Returns 0 on success or a negative error code on failure. 3018c2ecf20Sopenharmony_ci */ 3028c2ecf20Sopenharmony_ciint drm_dp_link_power_down(struct drm_dp_aux *aux, struct drm_dp_link *link) 3038c2ecf20Sopenharmony_ci{ 3048c2ecf20Sopenharmony_ci u8 value; 3058c2ecf20Sopenharmony_ci int err; 3068c2ecf20Sopenharmony_ci 3078c2ecf20Sopenharmony_ci /* DP_SET_POWER register is only available on DPCD v1.1 and later */ 3088c2ecf20Sopenharmony_ci if (link->revision < 0x11) 3098c2ecf20Sopenharmony_ci return 0; 3108c2ecf20Sopenharmony_ci 3118c2ecf20Sopenharmony_ci err = drm_dp_dpcd_readb(aux, DP_SET_POWER, &value); 3128c2ecf20Sopenharmony_ci if (err < 0) 3138c2ecf20Sopenharmony_ci return err; 3148c2ecf20Sopenharmony_ci 3158c2ecf20Sopenharmony_ci value &= ~DP_SET_POWER_MASK; 3168c2ecf20Sopenharmony_ci value |= DP_SET_POWER_D3; 3178c2ecf20Sopenharmony_ci 3188c2ecf20Sopenharmony_ci err = drm_dp_dpcd_writeb(aux, DP_SET_POWER, value); 3198c2ecf20Sopenharmony_ci if (err < 0) 3208c2ecf20Sopenharmony_ci return err; 3218c2ecf20Sopenharmony_ci 3228c2ecf20Sopenharmony_ci return 0; 3238c2ecf20Sopenharmony_ci} 3248c2ecf20Sopenharmony_ci 3258c2ecf20Sopenharmony_ci/** 3268c2ecf20Sopenharmony_ci * drm_dp_link_configure() - configure a DisplayPort link 3278c2ecf20Sopenharmony_ci * @aux: DisplayPort AUX channel 3288c2ecf20Sopenharmony_ci * @link: pointer to a structure containing the link configuration 3298c2ecf20Sopenharmony_ci * 3308c2ecf20Sopenharmony_ci * Returns 0 on success or a negative error code on failure. 3318c2ecf20Sopenharmony_ci */ 3328c2ecf20Sopenharmony_ciint drm_dp_link_configure(struct drm_dp_aux *aux, struct drm_dp_link *link) 3338c2ecf20Sopenharmony_ci{ 3348c2ecf20Sopenharmony_ci u8 values[2], value; 3358c2ecf20Sopenharmony_ci int err; 3368c2ecf20Sopenharmony_ci 3378c2ecf20Sopenharmony_ci if (link->ops && link->ops->configure) { 3388c2ecf20Sopenharmony_ci err = link->ops->configure(link); 3398c2ecf20Sopenharmony_ci if (err < 0) { 3408c2ecf20Sopenharmony_ci DRM_ERROR("failed to configure DP link: %d\n", err); 3418c2ecf20Sopenharmony_ci return err; 3428c2ecf20Sopenharmony_ci } 3438c2ecf20Sopenharmony_ci } 3448c2ecf20Sopenharmony_ci 3458c2ecf20Sopenharmony_ci values[0] = drm_dp_link_rate_to_bw_code(link->rate); 3468c2ecf20Sopenharmony_ci values[1] = link->lanes; 3478c2ecf20Sopenharmony_ci 3488c2ecf20Sopenharmony_ci if (link->caps.enhanced_framing) 3498c2ecf20Sopenharmony_ci values[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; 3508c2ecf20Sopenharmony_ci 3518c2ecf20Sopenharmony_ci err = drm_dp_dpcd_write(aux, DP_LINK_BW_SET, values, sizeof(values)); 3528c2ecf20Sopenharmony_ci if (err < 0) 3538c2ecf20Sopenharmony_ci return err; 3548c2ecf20Sopenharmony_ci 3558c2ecf20Sopenharmony_ci if (link->caps.channel_coding) 3568c2ecf20Sopenharmony_ci value = DP_SET_ANSI_8B10B; 3578c2ecf20Sopenharmony_ci else 3588c2ecf20Sopenharmony_ci value = 0; 3598c2ecf20Sopenharmony_ci 3608c2ecf20Sopenharmony_ci err = drm_dp_dpcd_writeb(aux, DP_MAIN_LINK_CHANNEL_CODING_SET, value); 3618c2ecf20Sopenharmony_ci if (err < 0) 3628c2ecf20Sopenharmony_ci return err; 3638c2ecf20Sopenharmony_ci 3648c2ecf20Sopenharmony_ci if (link->caps.alternate_scrambler_reset) { 3658c2ecf20Sopenharmony_ci err = drm_dp_dpcd_writeb(aux, DP_EDP_CONFIGURATION_SET, 3668c2ecf20Sopenharmony_ci DP_ALTERNATE_SCRAMBLER_RESET_ENABLE); 3678c2ecf20Sopenharmony_ci if (err < 0) 3688c2ecf20Sopenharmony_ci return err; 3698c2ecf20Sopenharmony_ci } 3708c2ecf20Sopenharmony_ci 3718c2ecf20Sopenharmony_ci return 0; 3728c2ecf20Sopenharmony_ci} 3738c2ecf20Sopenharmony_ci 3748c2ecf20Sopenharmony_ci/** 3758c2ecf20Sopenharmony_ci * drm_dp_link_choose() - choose the lowest possible configuration for a mode 3768c2ecf20Sopenharmony_ci * @link: DRM DP link object 3778c2ecf20Sopenharmony_ci * @mode: DRM display mode 3788c2ecf20Sopenharmony_ci * @info: DRM display information 3798c2ecf20Sopenharmony_ci * 3808c2ecf20Sopenharmony_ci * According to the eDP specification, a source should select a configuration 3818c2ecf20Sopenharmony_ci * with the lowest number of lanes and the lowest possible link rate that can 3828c2ecf20Sopenharmony_ci * match the bitrate requirements of a video mode. However it must ensure not 3838c2ecf20Sopenharmony_ci * to exceed the capabilities of the sink. 3848c2ecf20Sopenharmony_ci * 3858c2ecf20Sopenharmony_ci * Returns: 0 on success or a negative error code on failure. 3868c2ecf20Sopenharmony_ci */ 3878c2ecf20Sopenharmony_ciint drm_dp_link_choose(struct drm_dp_link *link, 3888c2ecf20Sopenharmony_ci const struct drm_display_mode *mode, 3898c2ecf20Sopenharmony_ci const struct drm_display_info *info) 3908c2ecf20Sopenharmony_ci{ 3918c2ecf20Sopenharmony_ci /* available link symbol clock rates */ 3928c2ecf20Sopenharmony_ci static const unsigned int rates[3] = { 162000, 270000, 540000 }; 3938c2ecf20Sopenharmony_ci /* available number of lanes */ 3948c2ecf20Sopenharmony_ci static const unsigned int lanes[3] = { 1, 2, 4 }; 3958c2ecf20Sopenharmony_ci unsigned long requirement, capacity; 3968c2ecf20Sopenharmony_ci unsigned int rate = link->max_rate; 3978c2ecf20Sopenharmony_ci unsigned int i, j; 3988c2ecf20Sopenharmony_ci 3998c2ecf20Sopenharmony_ci /* bandwidth requirement */ 4008c2ecf20Sopenharmony_ci requirement = mode->clock * info->bpc * 3; 4018c2ecf20Sopenharmony_ci 4028c2ecf20Sopenharmony_ci for (i = 0; i < ARRAY_SIZE(lanes) && lanes[i] <= link->max_lanes; i++) { 4038c2ecf20Sopenharmony_ci for (j = 0; j < ARRAY_SIZE(rates) && rates[j] <= rate; j++) { 4048c2ecf20Sopenharmony_ci /* 4058c2ecf20Sopenharmony_ci * Capacity for this combination of lanes and rate, 4068c2ecf20Sopenharmony_ci * factoring in the ANSI 8B/10B encoding. 4078c2ecf20Sopenharmony_ci * 4088c2ecf20Sopenharmony_ci * Link rates in the DRM DP helpers are really link 4098c2ecf20Sopenharmony_ci * symbol frequencies, so a tenth of the actual rate 4108c2ecf20Sopenharmony_ci * of the link. 4118c2ecf20Sopenharmony_ci */ 4128c2ecf20Sopenharmony_ci capacity = lanes[i] * (rates[j] * 10) * 8 / 10; 4138c2ecf20Sopenharmony_ci 4148c2ecf20Sopenharmony_ci if (capacity >= requirement) { 4158c2ecf20Sopenharmony_ci DRM_DEBUG_KMS("using %u lanes at %u kHz (%lu/%lu kbps)\n", 4168c2ecf20Sopenharmony_ci lanes[i], rates[j], requirement, 4178c2ecf20Sopenharmony_ci capacity); 4188c2ecf20Sopenharmony_ci link->lanes = lanes[i]; 4198c2ecf20Sopenharmony_ci link->rate = rates[j]; 4208c2ecf20Sopenharmony_ci return 0; 4218c2ecf20Sopenharmony_ci } 4228c2ecf20Sopenharmony_ci } 4238c2ecf20Sopenharmony_ci } 4248c2ecf20Sopenharmony_ci 4258c2ecf20Sopenharmony_ci return -ERANGE; 4268c2ecf20Sopenharmony_ci} 4278c2ecf20Sopenharmony_ci 4288c2ecf20Sopenharmony_ci/** 4298c2ecf20Sopenharmony_ci * DOC: Link training 4308c2ecf20Sopenharmony_ci * 4318c2ecf20Sopenharmony_ci * These functions contain common logic and helpers to implement DisplayPort 4328c2ecf20Sopenharmony_ci * link training. 4338c2ecf20Sopenharmony_ci */ 4348c2ecf20Sopenharmony_ci 4358c2ecf20Sopenharmony_ci/** 4368c2ecf20Sopenharmony_ci * drm_dp_link_train_init() - initialize DisplayPort link training state 4378c2ecf20Sopenharmony_ci * @train: DisplayPort link training state 4388c2ecf20Sopenharmony_ci */ 4398c2ecf20Sopenharmony_civoid drm_dp_link_train_init(struct drm_dp_link_train *train) 4408c2ecf20Sopenharmony_ci{ 4418c2ecf20Sopenharmony_ci struct drm_dp_link_train_set *request = &train->request; 4428c2ecf20Sopenharmony_ci struct drm_dp_link_train_set *adjust = &train->adjust; 4438c2ecf20Sopenharmony_ci unsigned int i; 4448c2ecf20Sopenharmony_ci 4458c2ecf20Sopenharmony_ci for (i = 0; i < 4; i++) { 4468c2ecf20Sopenharmony_ci request->voltage_swing[i] = 0; 4478c2ecf20Sopenharmony_ci adjust->voltage_swing[i] = 0; 4488c2ecf20Sopenharmony_ci 4498c2ecf20Sopenharmony_ci request->pre_emphasis[i] = 0; 4508c2ecf20Sopenharmony_ci adjust->pre_emphasis[i] = 0; 4518c2ecf20Sopenharmony_ci 4528c2ecf20Sopenharmony_ci request->post_cursor[i] = 0; 4538c2ecf20Sopenharmony_ci adjust->post_cursor[i] = 0; 4548c2ecf20Sopenharmony_ci } 4558c2ecf20Sopenharmony_ci 4568c2ecf20Sopenharmony_ci train->pattern = DP_TRAINING_PATTERN_DISABLE; 4578c2ecf20Sopenharmony_ci train->clock_recovered = false; 4588c2ecf20Sopenharmony_ci train->channel_equalized = false; 4598c2ecf20Sopenharmony_ci} 4608c2ecf20Sopenharmony_ci 4618c2ecf20Sopenharmony_cistatic bool drm_dp_link_train_valid(const struct drm_dp_link_train *train) 4628c2ecf20Sopenharmony_ci{ 4638c2ecf20Sopenharmony_ci return train->clock_recovered && train->channel_equalized; 4648c2ecf20Sopenharmony_ci} 4658c2ecf20Sopenharmony_ci 4668c2ecf20Sopenharmony_cistatic int drm_dp_link_apply_training(struct drm_dp_link *link) 4678c2ecf20Sopenharmony_ci{ 4688c2ecf20Sopenharmony_ci struct drm_dp_link_train_set *request = &link->train.request; 4698c2ecf20Sopenharmony_ci unsigned int lanes = link->lanes, *vs, *pe, *pc, i; 4708c2ecf20Sopenharmony_ci struct drm_dp_aux *aux = link->aux; 4718c2ecf20Sopenharmony_ci u8 values[4], pattern = 0; 4728c2ecf20Sopenharmony_ci int err; 4738c2ecf20Sopenharmony_ci 4748c2ecf20Sopenharmony_ci err = link->ops->apply_training(link); 4758c2ecf20Sopenharmony_ci if (err < 0) { 4768c2ecf20Sopenharmony_ci DRM_ERROR("failed to apply link training: %d\n", err); 4778c2ecf20Sopenharmony_ci return err; 4788c2ecf20Sopenharmony_ci } 4798c2ecf20Sopenharmony_ci 4808c2ecf20Sopenharmony_ci vs = request->voltage_swing; 4818c2ecf20Sopenharmony_ci pe = request->pre_emphasis; 4828c2ecf20Sopenharmony_ci pc = request->post_cursor; 4838c2ecf20Sopenharmony_ci 4848c2ecf20Sopenharmony_ci /* write currently selected voltage-swing and pre-emphasis levels */ 4858c2ecf20Sopenharmony_ci for (i = 0; i < lanes; i++) 4868c2ecf20Sopenharmony_ci values[i] = DP_TRAIN_VOLTAGE_SWING_LEVEL(vs[i]) | 4878c2ecf20Sopenharmony_ci DP_TRAIN_PRE_EMPHASIS_LEVEL(pe[i]); 4888c2ecf20Sopenharmony_ci 4898c2ecf20Sopenharmony_ci err = drm_dp_dpcd_write(aux, DP_TRAINING_LANE0_SET, values, lanes); 4908c2ecf20Sopenharmony_ci if (err < 0) { 4918c2ecf20Sopenharmony_ci DRM_ERROR("failed to set training parameters: %d\n", err); 4928c2ecf20Sopenharmony_ci return err; 4938c2ecf20Sopenharmony_ci } 4948c2ecf20Sopenharmony_ci 4958c2ecf20Sopenharmony_ci /* write currently selected post-cursor level (if supported) */ 4968c2ecf20Sopenharmony_ci if (link->revision >= 0x12 && link->rate == 540000) { 4978c2ecf20Sopenharmony_ci values[0] = values[1] = 0; 4988c2ecf20Sopenharmony_ci 4998c2ecf20Sopenharmony_ci for (i = 0; i < lanes; i++) 5008c2ecf20Sopenharmony_ci values[i / 2] |= DP_LANE_POST_CURSOR(i, pc[i]); 5018c2ecf20Sopenharmony_ci 5028c2ecf20Sopenharmony_ci err = drm_dp_dpcd_write(aux, DP_TRAINING_LANE0_1_SET2, values, 5038c2ecf20Sopenharmony_ci DIV_ROUND_UP(lanes, 2)); 5048c2ecf20Sopenharmony_ci if (err < 0) { 5058c2ecf20Sopenharmony_ci DRM_ERROR("failed to set post-cursor: %d\n", err); 5068c2ecf20Sopenharmony_ci return err; 5078c2ecf20Sopenharmony_ci } 5088c2ecf20Sopenharmony_ci } 5098c2ecf20Sopenharmony_ci 5108c2ecf20Sopenharmony_ci /* write link pattern */ 5118c2ecf20Sopenharmony_ci if (link->train.pattern != DP_TRAINING_PATTERN_DISABLE) 5128c2ecf20Sopenharmony_ci pattern |= DP_LINK_SCRAMBLING_DISABLE; 5138c2ecf20Sopenharmony_ci 5148c2ecf20Sopenharmony_ci pattern |= link->train.pattern; 5158c2ecf20Sopenharmony_ci 5168c2ecf20Sopenharmony_ci err = drm_dp_dpcd_writeb(aux, DP_TRAINING_PATTERN_SET, pattern); 5178c2ecf20Sopenharmony_ci if (err < 0) { 5188c2ecf20Sopenharmony_ci DRM_ERROR("failed to set training pattern: %d\n", err); 5198c2ecf20Sopenharmony_ci return err; 5208c2ecf20Sopenharmony_ci } 5218c2ecf20Sopenharmony_ci 5228c2ecf20Sopenharmony_ci return 0; 5238c2ecf20Sopenharmony_ci} 5248c2ecf20Sopenharmony_ci 5258c2ecf20Sopenharmony_cistatic void drm_dp_link_train_wait(struct drm_dp_link *link) 5268c2ecf20Sopenharmony_ci{ 5278c2ecf20Sopenharmony_ci unsigned long min = 0; 5288c2ecf20Sopenharmony_ci 5298c2ecf20Sopenharmony_ci switch (link->train.pattern) { 5308c2ecf20Sopenharmony_ci case DP_TRAINING_PATTERN_1: 5318c2ecf20Sopenharmony_ci min = link->aux_rd_interval.cr; 5328c2ecf20Sopenharmony_ci break; 5338c2ecf20Sopenharmony_ci 5348c2ecf20Sopenharmony_ci case DP_TRAINING_PATTERN_2: 5358c2ecf20Sopenharmony_ci case DP_TRAINING_PATTERN_3: 5368c2ecf20Sopenharmony_ci min = link->aux_rd_interval.ce; 5378c2ecf20Sopenharmony_ci break; 5388c2ecf20Sopenharmony_ci 5398c2ecf20Sopenharmony_ci default: 5408c2ecf20Sopenharmony_ci break; 5418c2ecf20Sopenharmony_ci } 5428c2ecf20Sopenharmony_ci 5438c2ecf20Sopenharmony_ci if (min > 0) 5448c2ecf20Sopenharmony_ci usleep_range(min, 2 * min); 5458c2ecf20Sopenharmony_ci} 5468c2ecf20Sopenharmony_ci 5478c2ecf20Sopenharmony_cistatic void drm_dp_link_get_adjustments(struct drm_dp_link *link, 5488c2ecf20Sopenharmony_ci u8 status[DP_LINK_STATUS_SIZE]) 5498c2ecf20Sopenharmony_ci{ 5508c2ecf20Sopenharmony_ci struct drm_dp_link_train_set *adjust = &link->train.adjust; 5518c2ecf20Sopenharmony_ci unsigned int i; 5528c2ecf20Sopenharmony_ci 5538c2ecf20Sopenharmony_ci for (i = 0; i < link->lanes; i++) { 5548c2ecf20Sopenharmony_ci adjust->voltage_swing[i] = 5558c2ecf20Sopenharmony_ci drm_dp_get_adjust_request_voltage(status, i) >> 5568c2ecf20Sopenharmony_ci DP_TRAIN_VOLTAGE_SWING_SHIFT; 5578c2ecf20Sopenharmony_ci 5588c2ecf20Sopenharmony_ci adjust->pre_emphasis[i] = 5598c2ecf20Sopenharmony_ci drm_dp_get_adjust_request_pre_emphasis(status, i) >> 5608c2ecf20Sopenharmony_ci DP_TRAIN_PRE_EMPHASIS_SHIFT; 5618c2ecf20Sopenharmony_ci 5628c2ecf20Sopenharmony_ci adjust->post_cursor[i] = 5638c2ecf20Sopenharmony_ci drm_dp_get_adjust_request_post_cursor(status, i); 5648c2ecf20Sopenharmony_ci } 5658c2ecf20Sopenharmony_ci} 5668c2ecf20Sopenharmony_ci 5678c2ecf20Sopenharmony_cistatic void drm_dp_link_train_adjust(struct drm_dp_link_train *train) 5688c2ecf20Sopenharmony_ci{ 5698c2ecf20Sopenharmony_ci struct drm_dp_link_train_set *request = &train->request; 5708c2ecf20Sopenharmony_ci struct drm_dp_link_train_set *adjust = &train->adjust; 5718c2ecf20Sopenharmony_ci unsigned int i; 5728c2ecf20Sopenharmony_ci 5738c2ecf20Sopenharmony_ci for (i = 0; i < 4; i++) 5748c2ecf20Sopenharmony_ci if (request->voltage_swing[i] != adjust->voltage_swing[i]) 5758c2ecf20Sopenharmony_ci request->voltage_swing[i] = adjust->voltage_swing[i]; 5768c2ecf20Sopenharmony_ci 5778c2ecf20Sopenharmony_ci for (i = 0; i < 4; i++) 5788c2ecf20Sopenharmony_ci if (request->pre_emphasis[i] != adjust->pre_emphasis[i]) 5798c2ecf20Sopenharmony_ci request->pre_emphasis[i] = adjust->pre_emphasis[i]; 5808c2ecf20Sopenharmony_ci 5818c2ecf20Sopenharmony_ci for (i = 0; i < 4; i++) 5828c2ecf20Sopenharmony_ci if (request->post_cursor[i] != adjust->post_cursor[i]) 5838c2ecf20Sopenharmony_ci request->post_cursor[i] = adjust->post_cursor[i]; 5848c2ecf20Sopenharmony_ci} 5858c2ecf20Sopenharmony_ci 5868c2ecf20Sopenharmony_cistatic int drm_dp_link_recover_clock(struct drm_dp_link *link) 5878c2ecf20Sopenharmony_ci{ 5888c2ecf20Sopenharmony_ci u8 status[DP_LINK_STATUS_SIZE]; 5898c2ecf20Sopenharmony_ci int err; 5908c2ecf20Sopenharmony_ci 5918c2ecf20Sopenharmony_ci err = drm_dp_link_apply_training(link); 5928c2ecf20Sopenharmony_ci if (err < 0) 5938c2ecf20Sopenharmony_ci return err; 5948c2ecf20Sopenharmony_ci 5958c2ecf20Sopenharmony_ci drm_dp_link_train_wait(link); 5968c2ecf20Sopenharmony_ci 5978c2ecf20Sopenharmony_ci err = drm_dp_dpcd_read_link_status(link->aux, status); 5988c2ecf20Sopenharmony_ci if (err < 0) { 5998c2ecf20Sopenharmony_ci DRM_ERROR("failed to read link status: %d\n", err); 6008c2ecf20Sopenharmony_ci return err; 6018c2ecf20Sopenharmony_ci } 6028c2ecf20Sopenharmony_ci 6038c2ecf20Sopenharmony_ci if (!drm_dp_clock_recovery_ok(status, link->lanes)) 6048c2ecf20Sopenharmony_ci drm_dp_link_get_adjustments(link, status); 6058c2ecf20Sopenharmony_ci else 6068c2ecf20Sopenharmony_ci link->train.clock_recovered = true; 6078c2ecf20Sopenharmony_ci 6088c2ecf20Sopenharmony_ci return 0; 6098c2ecf20Sopenharmony_ci} 6108c2ecf20Sopenharmony_ci 6118c2ecf20Sopenharmony_cistatic int drm_dp_link_clock_recovery(struct drm_dp_link *link) 6128c2ecf20Sopenharmony_ci{ 6138c2ecf20Sopenharmony_ci unsigned int repeat; 6148c2ecf20Sopenharmony_ci int err; 6158c2ecf20Sopenharmony_ci 6168c2ecf20Sopenharmony_ci /* start clock recovery using training pattern 1 */ 6178c2ecf20Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_1; 6188c2ecf20Sopenharmony_ci 6198c2ecf20Sopenharmony_ci for (repeat = 1; repeat < 5; repeat++) { 6208c2ecf20Sopenharmony_ci err = drm_dp_link_recover_clock(link); 6218c2ecf20Sopenharmony_ci if (err < 0) { 6228c2ecf20Sopenharmony_ci DRM_ERROR("failed to recover clock: %d\n", err); 6238c2ecf20Sopenharmony_ci return err; 6248c2ecf20Sopenharmony_ci } 6258c2ecf20Sopenharmony_ci 6268c2ecf20Sopenharmony_ci if (link->train.clock_recovered) 6278c2ecf20Sopenharmony_ci break; 6288c2ecf20Sopenharmony_ci 6298c2ecf20Sopenharmony_ci drm_dp_link_train_adjust(&link->train); 6308c2ecf20Sopenharmony_ci } 6318c2ecf20Sopenharmony_ci 6328c2ecf20Sopenharmony_ci return 0; 6338c2ecf20Sopenharmony_ci} 6348c2ecf20Sopenharmony_ci 6358c2ecf20Sopenharmony_cistatic int drm_dp_link_equalize_channel(struct drm_dp_link *link) 6368c2ecf20Sopenharmony_ci{ 6378c2ecf20Sopenharmony_ci struct drm_dp_aux *aux = link->aux; 6388c2ecf20Sopenharmony_ci u8 status[DP_LINK_STATUS_SIZE]; 6398c2ecf20Sopenharmony_ci int err; 6408c2ecf20Sopenharmony_ci 6418c2ecf20Sopenharmony_ci err = drm_dp_link_apply_training(link); 6428c2ecf20Sopenharmony_ci if (err < 0) 6438c2ecf20Sopenharmony_ci return err; 6448c2ecf20Sopenharmony_ci 6458c2ecf20Sopenharmony_ci drm_dp_link_train_wait(link); 6468c2ecf20Sopenharmony_ci 6478c2ecf20Sopenharmony_ci err = drm_dp_dpcd_read_link_status(aux, status); 6488c2ecf20Sopenharmony_ci if (err < 0) { 6498c2ecf20Sopenharmony_ci DRM_ERROR("failed to read link status: %d\n", err); 6508c2ecf20Sopenharmony_ci return err; 6518c2ecf20Sopenharmony_ci } 6528c2ecf20Sopenharmony_ci 6538c2ecf20Sopenharmony_ci if (!drm_dp_clock_recovery_ok(status, link->lanes)) { 6548c2ecf20Sopenharmony_ci DRM_ERROR("clock recovery lost while equalizing channel\n"); 6558c2ecf20Sopenharmony_ci link->train.clock_recovered = false; 6568c2ecf20Sopenharmony_ci return 0; 6578c2ecf20Sopenharmony_ci } 6588c2ecf20Sopenharmony_ci 6598c2ecf20Sopenharmony_ci if (!drm_dp_channel_eq_ok(status, link->lanes)) 6608c2ecf20Sopenharmony_ci drm_dp_link_get_adjustments(link, status); 6618c2ecf20Sopenharmony_ci else 6628c2ecf20Sopenharmony_ci link->train.channel_equalized = true; 6638c2ecf20Sopenharmony_ci 6648c2ecf20Sopenharmony_ci return 0; 6658c2ecf20Sopenharmony_ci} 6668c2ecf20Sopenharmony_ci 6678c2ecf20Sopenharmony_cistatic int drm_dp_link_channel_equalization(struct drm_dp_link *link) 6688c2ecf20Sopenharmony_ci{ 6698c2ecf20Sopenharmony_ci unsigned int repeat; 6708c2ecf20Sopenharmony_ci int err; 6718c2ecf20Sopenharmony_ci 6728c2ecf20Sopenharmony_ci /* start channel equalization using pattern 2 or 3 */ 6738c2ecf20Sopenharmony_ci if (link->caps.tps3_supported) 6748c2ecf20Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_3; 6758c2ecf20Sopenharmony_ci else 6768c2ecf20Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_2; 6778c2ecf20Sopenharmony_ci 6788c2ecf20Sopenharmony_ci for (repeat = 1; repeat < 5; repeat++) { 6798c2ecf20Sopenharmony_ci err = drm_dp_link_equalize_channel(link); 6808c2ecf20Sopenharmony_ci if (err < 0) { 6818c2ecf20Sopenharmony_ci DRM_ERROR("failed to equalize channel: %d\n", err); 6828c2ecf20Sopenharmony_ci return err; 6838c2ecf20Sopenharmony_ci } 6848c2ecf20Sopenharmony_ci 6858c2ecf20Sopenharmony_ci if (link->train.channel_equalized) 6868c2ecf20Sopenharmony_ci break; 6878c2ecf20Sopenharmony_ci 6888c2ecf20Sopenharmony_ci drm_dp_link_train_adjust(&link->train); 6898c2ecf20Sopenharmony_ci } 6908c2ecf20Sopenharmony_ci 6918c2ecf20Sopenharmony_ci return 0; 6928c2ecf20Sopenharmony_ci} 6938c2ecf20Sopenharmony_ci 6948c2ecf20Sopenharmony_cistatic int drm_dp_link_downgrade(struct drm_dp_link *link) 6958c2ecf20Sopenharmony_ci{ 6968c2ecf20Sopenharmony_ci switch (link->rate) { 6978c2ecf20Sopenharmony_ci case 162000: 6988c2ecf20Sopenharmony_ci return -EINVAL; 6998c2ecf20Sopenharmony_ci 7008c2ecf20Sopenharmony_ci case 270000: 7018c2ecf20Sopenharmony_ci link->rate = 162000; 7028c2ecf20Sopenharmony_ci break; 7038c2ecf20Sopenharmony_ci 7048c2ecf20Sopenharmony_ci case 540000: 7058c2ecf20Sopenharmony_ci link->rate = 270000; 7068c2ecf20Sopenharmony_ci return 0; 7078c2ecf20Sopenharmony_ci } 7088c2ecf20Sopenharmony_ci 7098c2ecf20Sopenharmony_ci return 0; 7108c2ecf20Sopenharmony_ci} 7118c2ecf20Sopenharmony_ci 7128c2ecf20Sopenharmony_cistatic void drm_dp_link_train_disable(struct drm_dp_link *link) 7138c2ecf20Sopenharmony_ci{ 7148c2ecf20Sopenharmony_ci int err; 7158c2ecf20Sopenharmony_ci 7168c2ecf20Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_DISABLE; 7178c2ecf20Sopenharmony_ci 7188c2ecf20Sopenharmony_ci err = drm_dp_link_apply_training(link); 7198c2ecf20Sopenharmony_ci if (err < 0) 7208c2ecf20Sopenharmony_ci DRM_ERROR("failed to disable link training: %d\n", err); 7218c2ecf20Sopenharmony_ci} 7228c2ecf20Sopenharmony_ci 7238c2ecf20Sopenharmony_cistatic int drm_dp_link_train_full(struct drm_dp_link *link) 7248c2ecf20Sopenharmony_ci{ 7258c2ecf20Sopenharmony_ci int err; 7268c2ecf20Sopenharmony_ci 7278c2ecf20Sopenharmony_ciretry: 7288c2ecf20Sopenharmony_ci DRM_DEBUG_KMS("full-training link: %u lane%s at %u MHz\n", 7298c2ecf20Sopenharmony_ci link->lanes, (link->lanes > 1) ? "s" : "", 7308c2ecf20Sopenharmony_ci link->rate / 100); 7318c2ecf20Sopenharmony_ci 7328c2ecf20Sopenharmony_ci err = drm_dp_link_configure(link->aux, link); 7338c2ecf20Sopenharmony_ci if (err < 0) { 7348c2ecf20Sopenharmony_ci DRM_ERROR("failed to configure DP link: %d\n", err); 7358c2ecf20Sopenharmony_ci return err; 7368c2ecf20Sopenharmony_ci } 7378c2ecf20Sopenharmony_ci 7388c2ecf20Sopenharmony_ci err = drm_dp_link_clock_recovery(link); 7398c2ecf20Sopenharmony_ci if (err < 0) { 7408c2ecf20Sopenharmony_ci DRM_ERROR("clock recovery failed: %d\n", err); 7418c2ecf20Sopenharmony_ci goto out; 7428c2ecf20Sopenharmony_ci } 7438c2ecf20Sopenharmony_ci 7448c2ecf20Sopenharmony_ci if (!link->train.clock_recovered) { 7458c2ecf20Sopenharmony_ci DRM_ERROR("clock recovery failed, downgrading link\n"); 7468c2ecf20Sopenharmony_ci 7478c2ecf20Sopenharmony_ci err = drm_dp_link_downgrade(link); 7488c2ecf20Sopenharmony_ci if (err < 0) 7498c2ecf20Sopenharmony_ci goto out; 7508c2ecf20Sopenharmony_ci 7518c2ecf20Sopenharmony_ci goto retry; 7528c2ecf20Sopenharmony_ci } 7538c2ecf20Sopenharmony_ci 7548c2ecf20Sopenharmony_ci DRM_DEBUG_KMS("clock recovery succeeded\n"); 7558c2ecf20Sopenharmony_ci 7568c2ecf20Sopenharmony_ci err = drm_dp_link_channel_equalization(link); 7578c2ecf20Sopenharmony_ci if (err < 0) { 7588c2ecf20Sopenharmony_ci DRM_ERROR("channel equalization failed: %d\n", err); 7598c2ecf20Sopenharmony_ci goto out; 7608c2ecf20Sopenharmony_ci } 7618c2ecf20Sopenharmony_ci 7628c2ecf20Sopenharmony_ci if (!link->train.channel_equalized) { 7638c2ecf20Sopenharmony_ci DRM_ERROR("channel equalization failed, downgrading link\n"); 7648c2ecf20Sopenharmony_ci 7658c2ecf20Sopenharmony_ci err = drm_dp_link_downgrade(link); 7668c2ecf20Sopenharmony_ci if (err < 0) 7678c2ecf20Sopenharmony_ci goto out; 7688c2ecf20Sopenharmony_ci 7698c2ecf20Sopenharmony_ci goto retry; 7708c2ecf20Sopenharmony_ci } 7718c2ecf20Sopenharmony_ci 7728c2ecf20Sopenharmony_ci DRM_DEBUG_KMS("channel equalization succeeded\n"); 7738c2ecf20Sopenharmony_ci 7748c2ecf20Sopenharmony_ciout: 7758c2ecf20Sopenharmony_ci drm_dp_link_train_disable(link); 7768c2ecf20Sopenharmony_ci return err; 7778c2ecf20Sopenharmony_ci} 7788c2ecf20Sopenharmony_ci 7798c2ecf20Sopenharmony_cistatic int drm_dp_link_train_fast(struct drm_dp_link *link) 7808c2ecf20Sopenharmony_ci{ 7818c2ecf20Sopenharmony_ci u8 status[DP_LINK_STATUS_SIZE]; 7828c2ecf20Sopenharmony_ci int err; 7838c2ecf20Sopenharmony_ci 7848c2ecf20Sopenharmony_ci DRM_DEBUG_KMS("fast-training link: %u lane%s at %u MHz\n", 7858c2ecf20Sopenharmony_ci link->lanes, (link->lanes > 1) ? "s" : "", 7868c2ecf20Sopenharmony_ci link->rate / 100); 7878c2ecf20Sopenharmony_ci 7888c2ecf20Sopenharmony_ci err = drm_dp_link_configure(link->aux, link); 7898c2ecf20Sopenharmony_ci if (err < 0) { 7908c2ecf20Sopenharmony_ci DRM_ERROR("failed to configure DP link: %d\n", err); 7918c2ecf20Sopenharmony_ci return err; 7928c2ecf20Sopenharmony_ci } 7938c2ecf20Sopenharmony_ci 7948c2ecf20Sopenharmony_ci /* transmit training pattern 1 for 500 microseconds */ 7958c2ecf20Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_1; 7968c2ecf20Sopenharmony_ci 7978c2ecf20Sopenharmony_ci err = drm_dp_link_apply_training(link); 7988c2ecf20Sopenharmony_ci if (err < 0) 7998c2ecf20Sopenharmony_ci goto out; 8008c2ecf20Sopenharmony_ci 8018c2ecf20Sopenharmony_ci usleep_range(500, 1000); 8028c2ecf20Sopenharmony_ci 8038c2ecf20Sopenharmony_ci /* transmit training pattern 2 or 3 for 500 microseconds */ 8048c2ecf20Sopenharmony_ci if (link->caps.tps3_supported) 8058c2ecf20Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_3; 8068c2ecf20Sopenharmony_ci else 8078c2ecf20Sopenharmony_ci link->train.pattern = DP_TRAINING_PATTERN_2; 8088c2ecf20Sopenharmony_ci 8098c2ecf20Sopenharmony_ci err = drm_dp_link_apply_training(link); 8108c2ecf20Sopenharmony_ci if (err < 0) 8118c2ecf20Sopenharmony_ci goto out; 8128c2ecf20Sopenharmony_ci 8138c2ecf20Sopenharmony_ci usleep_range(500, 1000); 8148c2ecf20Sopenharmony_ci 8158c2ecf20Sopenharmony_ci err = drm_dp_dpcd_read_link_status(link->aux, status); 8168c2ecf20Sopenharmony_ci if (err < 0) { 8178c2ecf20Sopenharmony_ci DRM_ERROR("failed to read link status: %d\n", err); 8188c2ecf20Sopenharmony_ci goto out; 8198c2ecf20Sopenharmony_ci } 8208c2ecf20Sopenharmony_ci 8218c2ecf20Sopenharmony_ci if (!drm_dp_clock_recovery_ok(status, link->lanes)) { 8228c2ecf20Sopenharmony_ci DRM_ERROR("clock recovery failed\n"); 8238c2ecf20Sopenharmony_ci err = -EIO; 8248c2ecf20Sopenharmony_ci } 8258c2ecf20Sopenharmony_ci 8268c2ecf20Sopenharmony_ci if (!drm_dp_channel_eq_ok(status, link->lanes)) { 8278c2ecf20Sopenharmony_ci DRM_ERROR("channel equalization failed\n"); 8288c2ecf20Sopenharmony_ci err = -EIO; 8298c2ecf20Sopenharmony_ci } 8308c2ecf20Sopenharmony_ci 8318c2ecf20Sopenharmony_ciout: 8328c2ecf20Sopenharmony_ci drm_dp_link_train_disable(link); 8338c2ecf20Sopenharmony_ci return err; 8348c2ecf20Sopenharmony_ci} 8358c2ecf20Sopenharmony_ci 8368c2ecf20Sopenharmony_ci/** 8378c2ecf20Sopenharmony_ci * drm_dp_link_train() - perform DisplayPort link training 8388c2ecf20Sopenharmony_ci * @link: a DP link object 8398c2ecf20Sopenharmony_ci * 8408c2ecf20Sopenharmony_ci * Uses the context stored in the DP link object to perform link training. It 8418c2ecf20Sopenharmony_ci * is expected that drivers will call drm_dp_link_probe() to obtain the link 8428c2ecf20Sopenharmony_ci * capabilities before performing link training. 8438c2ecf20Sopenharmony_ci * 8448c2ecf20Sopenharmony_ci * If the sink supports fast link training (no AUX CH handshake) and valid 8458c2ecf20Sopenharmony_ci * training settings are available, this function will try to perform fast 8468c2ecf20Sopenharmony_ci * link training and fall back to full link training on failure. 8478c2ecf20Sopenharmony_ci * 8488c2ecf20Sopenharmony_ci * Returns: 0 on success or a negative error code on failure. 8498c2ecf20Sopenharmony_ci */ 8508c2ecf20Sopenharmony_ciint drm_dp_link_train(struct drm_dp_link *link) 8518c2ecf20Sopenharmony_ci{ 8528c2ecf20Sopenharmony_ci int err; 8538c2ecf20Sopenharmony_ci 8548c2ecf20Sopenharmony_ci drm_dp_link_train_init(&link->train); 8558c2ecf20Sopenharmony_ci 8568c2ecf20Sopenharmony_ci if (link->caps.fast_training) { 8578c2ecf20Sopenharmony_ci if (drm_dp_link_train_valid(&link->train)) { 8588c2ecf20Sopenharmony_ci err = drm_dp_link_train_fast(link); 8598c2ecf20Sopenharmony_ci if (err < 0) 8608c2ecf20Sopenharmony_ci DRM_ERROR("fast link training failed: %d\n", 8618c2ecf20Sopenharmony_ci err); 8628c2ecf20Sopenharmony_ci else 8638c2ecf20Sopenharmony_ci return 0; 8648c2ecf20Sopenharmony_ci } else { 8658c2ecf20Sopenharmony_ci DRM_DEBUG_KMS("training parameters not available\n"); 8668c2ecf20Sopenharmony_ci } 8678c2ecf20Sopenharmony_ci } else { 8688c2ecf20Sopenharmony_ci DRM_DEBUG_KMS("fast link training not supported\n"); 8698c2ecf20Sopenharmony_ci } 8708c2ecf20Sopenharmony_ci 8718c2ecf20Sopenharmony_ci err = drm_dp_link_train_full(link); 8728c2ecf20Sopenharmony_ci if (err < 0) 8738c2ecf20Sopenharmony_ci DRM_ERROR("full link training failed: %d\n", err); 8748c2ecf20Sopenharmony_ci 8758c2ecf20Sopenharmony_ci return err; 8768c2ecf20Sopenharmony_ci} 877