/* * dp_lt.c: DP Link Training functions. * * Copyright (c) 2015-2018, NVIDIA CORPORATION, All rights reserved. * Author: Animesh Kishore * * This software is licensed under the terms of the GNU General Public * License version 2, as published by the Free Software Foundation, and * may be copied, distributed, and modified under those terms. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. */ #include #include #include #include #include #include "dc.h" #include #include "dp_lt.h" #include "dpaux.h" #include "dp.h" #include "sor.h" #include "sor_regs.h" /* * By default we do not trigger LT if link is already stable. * However, analyzer tests in their latest firmware are explicitly * checking for LT. Add param to suppress this optimization. It * has no usecase other than analyzer specific requirement. */ static bool force_trigger_lt; module_param(force_trigger_lt, bool, 0644); MODULE_PARM_DESC(force_trigger_lt, "Retrigger LT even if link is already stable"); static int lane_fallback_table[] = {1, 2, 4}; static void set_lt_state(struct tegra_dp_lt_data *lt_data, int target_state, int delay_ms); static void set_lt_tpg(struct tegra_dp_lt_data *lt_data, u32 tp); /* Check if post-cursor2 programming is supported */ static inline bool is_pc2_supported(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_link_config *cfg = <_data->dp->link_cfg; struct tegra_dp_out *dp_out = lt_data->dp->dc->out->dp_out; return (!dp_out->pc2_disabled && cfg->tps == TEGRA_DC_DP_TRAINING_PATTERN_3); } /* * Wait period before reading link status. * If dpcd addr 0xe TRAINING_AUX_RD_INTERVAL absent or zero, * wait for 100us for CR status and 400us for CE status. * Otherwise, use values as specified. */ static inline u32 wait_aux_training(struct tegra_dp_lt_data *lt_data, bool is_clk_recovery) { if (!lt_data->aux_rd_interval) is_clk_recovery ? usleep_range(150, 200) : usleep_range(450, 500); else msleep(lt_data->aux_rd_interval * 4); return lt_data->aux_rd_interval; } static inline int get_next_lower_lane_count(struct tegra_dc_dp_data *dp) { u8 cur_lanes = dp->link_cfg.lane_count; int k; for (k = ARRAY_SIZE(lane_fallback_table) - 1; k >= 0; k--) { if (cur_lanes == lane_fallback_table[k]) break; } if (k <= 0) return -ENOENT; return lane_fallback_table[k - 1]; } static inline int get_next_lower_link_rate(struct tegra_dc_dp_data *dp) { struct tegra_dc_sor_data *sor = dp->sor; u8 cur_link_rate = dp->link_cfg.link_bw; int k; for (k = sor->num_link_speeds - 1; k >= 0; k--) { if (cur_link_rate == sor->link_speeds[k].link_rate) break; } if (k <= 0) return -ENOENT; return sor->link_speeds[k - 1].link_rate; } static bool get_clock_recovery_status(struct tegra_dp_lt_data *lt_data, u32 *cr_lane_mask) { u32 cnt; u32 n_lanes = lt_data->n_lanes; u8 data_ptr = 0; u8 cr_done = 1; u32 loopcnt = (n_lanes == 1) ? 1 : n_lanes >> 1; *cr_lane_mask = 0; for (cnt = 0; cnt < loopcnt; cnt++) { tegra_dc_dp_dpcd_read(lt_data->dp, (NV_DPCD_LANE0_1_STATUS + cnt), &data_ptr); if (n_lanes == 1) { cr_done = data_ptr & 0x1; *cr_lane_mask = cr_done; } else { u32 tmp; /* Lane 0 or 2 */ tmp = data_ptr & 0x1; cr_done &= tmp; *cr_lane_mask |= tmp << (cnt * 2); /* Lane 1 or 3 */ data_ptr >>= NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_SHIFT; tmp = data_ptr & 0x1; cr_done &= tmp; *cr_lane_mask |= (tmp << 1) << (cnt * 2); } } return cr_done; } static bool get_channel_eq_status(struct tegra_dp_lt_data *lt_data) { u32 cnt; u32 n_lanes = lt_data->n_lanes; u8 data_ptr = 0; bool ce_done = true; /* support for 1 lane */ u32 loopcnt = (n_lanes == 1) ? 1 : n_lanes >> 1; for (cnt = 0; cnt < loopcnt; cnt++) { tegra_dc_dp_dpcd_read(lt_data->dp, (NV_DPCD_LANE0_1_STATUS + cnt), &data_ptr); if (n_lanes == 1) { ce_done = (data_ptr & (0x1 << NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_SHIFT)) && (data_ptr & (0x1 << NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_SHFIT)); break; } else if (!(data_ptr & (0x1 << NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_SHIFT)) || !(data_ptr & (0x1 << NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_SHFIT)) || !(data_ptr & (0x1 << NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_SHIFT)) || !(data_ptr & (0x1 << NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_SHIFT))) { ce_done = false; break; } } if (ce_done) { tegra_dc_dp_dpcd_read(lt_data->dp, NV_DPCD_LANE_ALIGN_STATUS_UPDATED, &data_ptr); if (!(data_ptr & NV_DPCD_LANE_ALIGN_STATUS_INTERLANE_ALIGN_DONE_YES)) ce_done = false; } return ce_done; } static bool get_lt_status(struct tegra_dp_lt_data *lt_data) { bool cr_done, ce_done; u32 cr_lane_mask; cr_done = get_clock_recovery_status(lt_data, &cr_lane_mask); if (!cr_done) return false; ce_done = get_channel_eq_status(lt_data); return ce_done; } /* * get updated voltage swing, pre-emphasis and * post-cursor2 settings from panel */ static void get_lt_new_config(struct tegra_dp_lt_data *lt_data) { u32 cnt; u8 data_ptr; u32 n_lanes = lt_data->n_lanes; u32 *vs = lt_data->drive_current; u32 *pe = lt_data->pre_emphasis; u32 *pc = lt_data->post_cursor2; bool pc2_supported = is_pc2_supported(lt_data); /* support for 1 lane */ u32 loopcnt = (n_lanes == 1) ? 1 : n_lanes >> 1; for (cnt = 0; cnt < loopcnt; cnt++) { tegra_dc_dp_dpcd_read(lt_data->dp, (NV_DPCD_LANE0_1_ADJUST_REQ + cnt), &data_ptr); pe[2 * cnt] = (data_ptr & NV_DPCD_ADJUST_REQ_LANEX_PE_MASK) >> NV_DPCD_ADJUST_REQ_LANEX_PE_SHIFT; vs[2 * cnt] = (data_ptr & NV_DPCD_ADJUST_REQ_LANEX_DC_MASK) >> NV_DPCD_ADJUST_REQ_LANEX_DC_SHIFT; pe[1 + 2 * cnt] = (data_ptr & NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_MASK) >> NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_SHIFT; vs[1 + 2 * cnt] = (data_ptr & NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_MASK) >> NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_SHIFT; } if (pc2_supported) { tegra_dc_dp_dpcd_read(lt_data->dp, NV_DPCD_ADJUST_REQ_POST_CURSOR2, &data_ptr); for (cnt = 0; cnt < n_lanes; cnt++) { pc[cnt] = (data_ptr >> NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_SHIFT(cnt)) & NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_MASK; } } for (cnt = 0; cnt < n_lanes; cnt++) pr_info("dp lt: new config: lane %d: " "vs level: %d, pe level: %d, pc2 level: %d\n", cnt, vs[cnt], pe[cnt], pc2_supported ? pc[cnt] : 0); } static void set_tx_pu(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_data *dp = lt_data->dp; struct tegra_dc_sor_data *sor = dp->sor; u32 n_lanes = lt_data->n_lanes; int cnt = 1; u32 *vs = lt_data->drive_current; u32 *pe = lt_data->pre_emphasis; u32 *pc = lt_data->post_cursor2; u32 max_tx_pu; u32 nv_sor_dp_padctl_reg = nv_sor_dp_padctl(sor->portnum); if (!dp->pdata || (dp->pdata && dp->pdata->tx_pu_disable)) { tegra_sor_write_field(dp->sor, nv_sor_dp_padctl_reg, NV_SOR_DP_PADCTL_TX_PU_ENABLE, NV_SOR_DP_PADCTL_TX_PU_DISABLE); lt_data->tx_pu = 0; return; } max_tx_pu = dp->pdata->lt_data[DP_TX_PU].data[pc[0]][vs[0]][pe[0]]; for (; cnt < n_lanes; cnt++) { max_tx_pu = (max_tx_pu < dp->pdata->lt_data[DP_TX_PU].data[pc[cnt]][vs[cnt]][pe[cnt]]) ? dp->pdata->lt_data[DP_TX_PU].data[pc[cnt]][vs[cnt]][pe[cnt]] : max_tx_pu; } lt_data->tx_pu = max_tx_pu; tegra_sor_write_field(sor, nv_sor_dp_padctl_reg, NV_SOR_DP_PADCTL_TX_PU_VALUE_DEFAULT_MASK, (max_tx_pu << NV_SOR_DP_PADCTL_TX_PU_VALUE_SHIFT | NV_SOR_DP_PADCTL_TX_PU_ENABLE)); pr_info("dp lt: tx_pu: 0x%x\n", max_tx_pu); } /* * configure voltage swing, pre-emphasis, * post-cursor2 and tx_pu on host and sink */ static void set_lt_config(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_data *dp = lt_data->dp; struct tegra_dc_sor_data *sor = dp->sor; u32 n_lanes = lt_data->n_lanes; bool pc2_supported = is_pc2_supported(lt_data); int i, cnt; u32 val; u32 *vs = lt_data->drive_current; u32 *pe = lt_data->pre_emphasis; u32 *pc = lt_data->post_cursor2; u32 aux_stat = 0; u8 training_lanex_set[4] = {0, 0, 0, 0}; u32 training_lanex_set_size = sizeof(training_lanex_set); /* support for 1 lane */ u32 loopcnt = (n_lanes == 1) ? 1 : n_lanes >> 1; /* * apply voltage swing, preemphasis, postcursor2 and tx_pu * prod settings to each lane based on levels */ for (i = 0; i < n_lanes; i++) { u32 mask = 0; u32 pe_reg, vs_reg, pc_reg; u32 shift = 0; cnt = sor->xbar_ctrl[i]; switch (cnt) { case 0: mask = NV_SOR_PR_LANE2_DP_LANE0_MASK; shift = NV_SOR_PR_LANE2_DP_LANE0_SHIFT; break; case 1: mask = NV_SOR_PR_LANE1_DP_LANE1_MASK; shift = NV_SOR_PR_LANE1_DP_LANE1_SHIFT; break; case 2: mask = NV_SOR_PR_LANE0_DP_LANE2_MASK; shift = NV_SOR_PR_LANE0_DP_LANE2_SHIFT; break; case 3: mask = NV_SOR_PR_LANE3_DP_LANE3_MASK; shift = NV_SOR_PR_LANE3_DP_LANE3_SHIFT; break; default: dev_err(&dp->dc->ndev->dev, "dp: incorrect lane cnt\n"); } pe_reg = dp->pdata->lt_data[DP_PE].data[pc[i]][vs[i]][pe[i]]; vs_reg = dp->pdata->lt_data[DP_VS].data[pc[i]][vs[i]][pe[i]]; pc_reg = dp->pdata->lt_data[DP_PC].data[pc[i]][vs[i]][pe[i]]; tegra_sor_write_field(sor, NV_SOR_PR(sor->portnum), mask, (pe_reg << shift)); tegra_sor_write_field(sor, NV_SOR_DC(sor->portnum), mask, (vs_reg << shift)); if (pc2_supported) { tegra_sor_write_field( sor, NV_SOR_POSTCURSOR(sor->portnum), mask, (pc_reg << shift)); } pr_info("dp lt: config: lane %d: " "vs level: %d, pe level: %d, pc2 level: %d\n", i, vs[i], pe[i], pc2_supported ? pc[i] : 0); } set_tx_pu(lt_data); usleep_range(15, 20); /* HW stabilization delay */ /* apply voltage swing and preemphasis levels to panel for each lane */ for (cnt = n_lanes - 1; cnt >= 0; cnt--) { u32 max_vs_flag = tegra_dp_is_max_vs(pe[cnt], vs[cnt]); u32 max_pe_flag = tegra_dp_is_max_pe(pe[cnt], vs[cnt]); val = (vs[cnt] << NV_DPCD_TRAINING_LANEX_SET_DC_SHIFT) | (max_vs_flag ? NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_T : NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_F) | (pe[cnt] << NV_DPCD_TRAINING_LANEX_SET_PE_SHIFT) | (max_pe_flag ? NV_DPCD_TRAINING_LANEX_SET_PE_MAX_REACHED_T : NV_DPCD_TRAINING_LANEX_SET_PE_MAX_REACHED_F); training_lanex_set[cnt] = val; } tegra_dc_dpaux_write(dp->dpaux, DPAUX_DP_AUXCTL_CMD_AUXWR, NV_DPCD_TRAINING_LANE0_SET, training_lanex_set, &training_lanex_set_size, &aux_stat); /* apply postcursor2 levels to panel for each lane */ if (pc2_supported) { for (cnt = 0; cnt < loopcnt; cnt++) { u32 max_pc_flag0 = tegra_dp_is_max_pc(pc[cnt]); u32 max_pc_flag1 = tegra_dp_is_max_pc(pc[cnt + 1]); val = (pc[cnt] << NV_DPCD_LANEX_SET2_PC2_SHIFT) | (max_pc_flag0 ? NV_DPCD_LANEX_SET2_PC2_MAX_REACHED_T : NV_DPCD_LANEX_SET2_PC2_MAX_REACHED_F) | (pc[cnt + 1] << NV_DPCD_LANEXPLUS1_SET2_PC2_SHIFT) | (max_pc_flag1 ? NV_DPCD_LANEXPLUS1_SET2_PC2_MAX_REACHED_T : NV_DPCD_LANEXPLUS1_SET2_PC2_MAX_REACHED_F); tegra_dc_dp_dpcd_write(dp, (NV_DPCD_TRAINING_LANE0_1_SET2 + cnt), val); } } } static int do_fast_lt_no_handshake(struct tegra_dp_lt_data *lt_data) { BUG_ON(!lt_data->lt_config_valid); /* transmit link training pattern 1 for min of 500us */ set_lt_tpg(lt_data, TEGRA_DC_DP_TRAINING_PATTERN_1); usleep_range(500, 600); /* transmit channel equalization training pattern for min of 500us */ set_lt_tpg(lt_data, lt_data->dp->link_cfg.tps); usleep_range(500, 600); return 0; } static void lt_data_sw_reset(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_data *dp = lt_data->dp; BUG_ON(!dp); lt_data->lt_config_valid = false; lt_data->cr_adj_retry = 0; lt_data->cr_max_retry = 0; lt_data->ce_retry = 0; lt_data->tx_pu = 0; lt_data->n_lanes = dp->link_cfg.lane_count; lt_data->link_bw = dp->link_cfg.link_bw; lt_data->no_aux_handshake = dp->link_cfg.support_fast_lt; lt_data->aux_rd_interval = dp->link_cfg.aux_rd_interval; memset(lt_data->pre_emphasis, PRE_EMPHASIS_L0, sizeof(lt_data->pre_emphasis)); memset(lt_data->drive_current, DRIVE_CURRENT_L0, sizeof(lt_data->drive_current)); memset(lt_data->post_cursor2, POST_CURSOR2_L0, sizeof(lt_data->post_cursor2)); } static void lt_data_reset(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_data *dp = lt_data->dp; BUG_ON(!dp); lt_data_sw_reset(lt_data); /* reset LT data on controller and panel only if hpd is asserted */ if (tegra_dc_hpd(dp->dc)) { /* * Training pattern is disabled here. Do not HW reset * lt config i.e. vs, pe, pc2. CTS mandates modifying these * only when training pattern is enabled. */ tegra_dp_update_link_config(dp); } } static void set_lt_tpg(struct tegra_dp_lt_data *lt_data, u32 tp) { bool cur_hpd = tegra_dc_hpd(lt_data->dp->dc); struct tegra_dc_dp_data *dp = lt_data->dp; if (lt_data->tps == tp) return; if (cur_hpd) tegra_dp_tpg(dp, tp, lt_data->n_lanes); else /* * hpd deasserted. Just set training * sequence from host side and exit */ tegra_sor_tpg(dp->sor, tp, lt_data->n_lanes); lt_data->tps = tp; } static void lt_failed(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_data *dp = lt_data->dp; mutex_lock(<_data->lock); tegra_dc_sor_detach(dp->sor); set_lt_tpg(lt_data, TEGRA_DC_DP_TRAINING_PATTERN_DISABLE); lt_data_reset(lt_data); mutex_unlock(<_data->lock); } static void lt_passed(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_data *dp = lt_data->dp; mutex_lock(<_data->lock); lt_data->lt_config_valid = true; set_lt_tpg(lt_data, TEGRA_DC_DP_TRAINING_PATTERN_DISABLE); tegra_dc_sor_attach(dp->sor); mutex_unlock(<_data->lock); } static void lt_reset_state(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_data *dp; struct tegra_dc_sor_data *sor; bool cur_hpd; int tgt_state; int timeout; BUG_ON(!lt_data || !lt_data->dp || !lt_data->dp->sor); dp = lt_data->dp; sor = dp->sor; cur_hpd = tegra_dc_hpd(dp->dc); if (lt_data->force_disable) { pr_info("dp lt: link training force disable\n"); lt_data->force_disable = false; lt_data->force_trigger = false; if (!lt_data->no_aux_handshake) lt_data->lt_config_valid = false; tgt_state = STATE_DONE_FAIL; timeout = -1; goto done; } else if (!cur_hpd || !dp->link_cfg.is_valid) { pr_info("dp lt: cur_hpd: %d, link cfg valid: %d\n", !!cur_hpd, !!dp->link_cfg.is_valid); lt_failed(lt_data); lt_data->force_disable = false; lt_data->force_trigger = false; tgt_state = STATE_DONE_FAIL; timeout = -1; goto done; } if (!force_trigger_lt && !lt_data->force_trigger && lt_data->lt_config_valid && get_lt_status(lt_data)) { pr_info("dp_lt: link stable, do nothing\n"); lt_passed(lt_data); tgt_state = STATE_DONE_PASS; timeout = -1; goto done; } lt_data->force_trigger = false; /* * detach SOR early. * DP lane count can be changed only * when SOR is asleep. * DP link bandwidth can be changed only * when SOR is in safe mode. */ mutex_lock(<_data->lock); tegra_dc_sor_detach(sor); mutex_unlock(<_data->lock); lt_data_reset(lt_data); tgt_state = STATE_CLOCK_RECOVERY; timeout = 0; WARN_ON(lt_data->tps != TEGRA_DC_DP_TRAINING_PATTERN_DISABLE); /* * pre-charge main link for at * least 10us before initiating * link training */ mutex_lock(<_data->lock); tegra_sor_precharge_lanes(sor); mutex_unlock(<_data->lock); done: set_lt_state(lt_data, tgt_state, timeout); } static void fast_lt_state(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_data *dp; struct tegra_dc_sor_data *sor; int tgt_state; int timeout; bool lt_status; BUG_ON(!lt_data || !lt_data->dp || !lt_data->dp->sor); dp = lt_data->dp; sor = dp->sor; BUG_ON(!lt_data->no_aux_handshake); WARN_ON(lt_data->tps != TEGRA_DC_DP_TRAINING_PATTERN_DISABLE); mutex_lock(<_data->lock); tegra_dc_sor_detach(sor); set_lt_config(lt_data); tegra_sor_precharge_lanes(sor); mutex_unlock(<_data->lock); do_fast_lt_no_handshake(lt_data); lt_status = get_lt_status(lt_data); if (lt_status) { lt_passed(lt_data); tgt_state = STATE_DONE_PASS; timeout = -1; } else { lt_failed(lt_data); tgt_state = STATE_RESET; timeout = 0; } pr_info("dp lt: fast link training %s\n", tgt_state == STATE_DONE_PASS ? "pass" : "fail"); set_lt_state(lt_data, tgt_state, timeout); } static inline int set_new_link_cfg(struct tegra_dp_lt_data *lt_data, struct tegra_dc_dp_link_config *new_cfg) { struct tegra_dc_dp_data *dp = lt_data->dp; if (!tegra_dc_dp_calc_config(dp, dp->mode, new_cfg)) return -EINVAL; new_cfg->is_valid = true; dp->link_cfg = *new_cfg; /* * Disable LT before updating the current lane count and/or link rate. */ set_lt_tpg(lt_data, TEGRA_DC_DP_TRAINING_PATTERN_DISABLE); tegra_dp_update_link_config(dp); lt_data->n_lanes = dp->link_cfg.lane_count; lt_data->link_bw = dp->link_cfg.link_bw; return 0; } static void lt_reduce_link_rate_state(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_data *dp = lt_data->dp; struct tegra_dc_dp_link_config tmp_cfg; dp->link_cfg.is_valid = false; tmp_cfg = dp->link_cfg; tmp_cfg.link_bw = get_next_lower_link_rate(dp); if (tmp_cfg.link_bw < NV_DPCD_MAX_LINK_BANDWIDTH_VAL_1_62_GBPS) goto fail; if (set_new_link_cfg(lt_data, &tmp_cfg)) goto fail; pr_info("dp lt: retry CR, lanes: %d, link rate: 0x%x\n", lt_data->n_lanes, lt_data->link_bw); set_lt_state(lt_data, STATE_CLOCK_RECOVERY, 0); return; fail: pr_info("dp lt: link rate already lowest\n"); lt_failed(lt_data); set_lt_state(lt_data, STATE_DONE_FAIL, -1); } static void lt_reduce_lane_count_state(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_data *dp = lt_data->dp; struct tegra_dc_dp_link_config tmp_cfg; dp->link_cfg.is_valid = false; tmp_cfg = dp->link_cfg; tmp_cfg.lane_count = get_next_lower_lane_count(dp); if (tmp_cfg.lane_count < 1) goto fail; if (set_new_link_cfg(lt_data, &tmp_cfg)) goto fail; pr_info("dp lt: retry CR, lanes: %d, link rate: 0x%x\n", lt_data->n_lanes, lt_data->link_bw); set_lt_state(lt_data, STATE_CLOCK_RECOVERY, 0); return; fail: pr_info("dp lt: lane count already lowest\n"); lt_failed(lt_data); set_lt_state(lt_data, STATE_DONE_FAIL, -1); } static void lt_channel_equalization_fallback(struct tegra_dp_lt_data *lt_data, int *tgt_state, int *timeout, u32 cr_lane_mask) { struct tegra_dc_dp_data *dp = lt_data->dp; /* * If we fallback during EQ, there are two paths that we can end up in: * 1) If 4 lanes are enabled, reduce to 2 lanes and go back to the start * of CR. If 2 lanes are enabled, reduce to 1 lane and go back to the * start of CR. If only 1 lane is currently enabled, go to step 2. * 2) If the current link rate is > RBR, downshift to the next lowest * link rate and reset the lane count to the highest needed. * * Before deciding which path to take, we need to look at the current * CR status on all lanes: * - If *all* the target LANEX_CR_DONE bits are still set, then we * didn't transition to this state due to loss of CR during EQ. Try * fallback path #1 first. * - If *at least one* of the LANEX_CR_DONE bits is still set, it's * likely that the earlier CR phase passed due to crosstalk issues on * some unconnected lanes. In this case, prioritize lane count * reduction, and choose fallback path #1 first. * - If *none* of the LANEX_CR_DONE bits are still set, it's likely that * CR was lost due to the bandwidth that we're trying to drive. Try * reducing the link rate first, and choose fallback path #2. * * If neither of the above two paths are valid, end link training and * fail. */ if (lt_data->n_lanes > 1 && cr_lane_mask) { /* Fallback path #1 */ lt_data_sw_reset(lt_data); *tgt_state = STATE_REDUCE_LANE_COUNT; } else if (lt_data->link_bw > NV_DPCD_MAX_LINK_BANDWIDTH_VAL_1_62_GBPS) { /* Fallback path #2 */ lt_data_sw_reset(lt_data); dp->link_cfg.lane_count = dp->max_link_cfg.lane_count; *tgt_state = STATE_REDUCE_LINK_RATE; } else { lt_failed(lt_data); *tgt_state = STATE_DONE_FAIL; } *timeout = 0; } static void lt_channel_equalization_state(struct tegra_dp_lt_data *lt_data) { int tgt_state = STATE_CHANNEL_EQUALIZATION; int timeout = 0; bool cr_done = true; bool ce_done = true; u32 cr_lane_mask; /* * See comment above the TEGRA_DC_DP_TRAINING_PATTERN_BS_CSTM entry in * sor.c for why this WAR is required. */ if (lt_data->dp->link_cfg.tps == TEGRA_DC_DP_TRAINING_PATTERN_4) { struct tegra_dc_sor_data *sor = lt_data->dp->sor; tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_0, 0x1BC6F1BC); tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_1, 0xC6F1BC6F); tegra_sor_writel(sor, NV_SOR_DP_LQ_CSTM_2, 0x6F1B); tegra_sor_write_field(sor, NV_SOR_DP_CONFIG(sor->portnum), NV_SOR_DP_CONFIG_RD_RESET_VAL_NEGATIVE, NV_SOR_DP_CONFIG_RD_RESET_VAL_POSITIVE); tegra_sor_tpg(sor, TEGRA_DC_DP_TRAINING_PATTERN_BS_CSTM, lt_data->n_lanes); } set_lt_tpg(lt_data, lt_data->dp->link_cfg.tps); wait_aux_training(lt_data, false); /* * Fallback if any of these two conditions are true: * 1) CR is lost on any of the currently enabled lanes. * 2) The LANEx_CHANNEL_EQ_DONE, LANEx_SYMBOL_LOCKED, or * INTERLANE_ALIGN_DONE bits have not been set for all enabled lanes * after 5 retry attempts. */ /* Fallback condition #1 */ cr_done = get_clock_recovery_status(lt_data, &cr_lane_mask); if (!cr_done) { pr_info("dp lt: CR lost\n"); lt_channel_equalization_fallback(lt_data, &tgt_state, &timeout, cr_lane_mask); goto done; } ce_done = get_channel_eq_status(lt_data); if (ce_done) { lt_passed(lt_data); tgt_state = STATE_DONE_PASS; timeout = -1; pr_info("dp lt: CE done\n"); goto done; } pr_info("dp lt: CE not done\n"); /* Fallback condition #2 */ if (++(lt_data->ce_retry) > (CE_RETRY_LIMIT + 1)) { pr_info("dp lt: CE retry limit %d reached\n", lt_data->ce_retry - 2); lt_channel_equalization_fallback(lt_data, &tgt_state, &timeout, cr_lane_mask); goto done; } get_lt_new_config(lt_data); set_lt_config(lt_data); pr_info("dp lt: CE retry\n"); done: set_lt_state(lt_data, tgt_state, timeout); } static inline bool is_vs_already_max(struct tegra_dp_lt_data *lt_data, u32 old_vs[4], u32 new_vs[4]) { u32 n_lanes = lt_data->n_lanes; int cnt; for (cnt = 0; cnt < n_lanes; cnt++) { if (old_vs[cnt] == DRIVE_CURRENT_L3 && new_vs[cnt] == DRIVE_CURRENT_L3) continue; return false; } return true; } static bool check_cr_fallback(struct tegra_dp_lt_data *lt_data, u32 *vs_temp, size_t vs_temp_size) { /* * Fallback if any of these three conditions are true: * 1) CR_DONE is not set for all enabled lanes after 10 total retries. * 2) The max voltage swing has been reached. * 3) The same ADJ_REQ voltage swing values have been requested by the * sink for 5 consecutive retry attempts. */ u32 *vs = lt_data->drive_current; bool need_fallback = false; /* Fallback condition #1 */ if ((lt_data->cr_max_retry)++ >= (CR_MAX_RETRY_LIMIT - 1)) { need_fallback = true; pr_info("dp lt: CR max retry limit %d reached\n", lt_data->cr_max_retry); } if (!memcmp(vs_temp, vs, vs_temp_size)) { /* Fallback condition #2 */ if (is_vs_already_max(lt_data, vs_temp, vs)) { need_fallback = true; pr_info("dp lt: max vs reached\n"); } /* Fallback condition #3 */ if ((lt_data->cr_adj_retry)++ >= (CR_ADJ_RETRY_LIMIT - 1)) { need_fallback = true; pr_info("dp lt: CR adj retry limit %d reached\n", lt_data->cr_adj_retry); } } else { lt_data->cr_adj_retry = 1; } return need_fallback; } static void lt_clock_recovery_state(struct tegra_dp_lt_data *lt_data) { struct tegra_dc_dp_data *dp; struct tegra_dc_sor_data *sor; int tgt_state = STATE_CLOCK_RECOVERY; int timeout = 0; u32 *vs = lt_data->drive_current; bool cr_done; u32 vs_temp[4], cr_lane_mask; bool need_fallback = false; BUG_ON(!lt_data || !lt_data->dp || !lt_data->dp->sor); dp = lt_data->dp; sor = dp->sor; set_lt_tpg(lt_data, TEGRA_DC_DP_TRAINING_PATTERN_1); set_lt_config(lt_data); wait_aux_training(lt_data, true); cr_done = get_clock_recovery_status(lt_data, &cr_lane_mask); if (cr_done) { pr_info("dp lt: CR done\n"); lt_data->cr_adj_retry = 0; lt_data->cr_max_retry = 0; tgt_state = STATE_CHANNEL_EQUALIZATION; goto done; } pr_info("dp lt: CR not done\n"); memcpy(vs_temp, vs, sizeof(vs_temp)); get_lt_new_config(lt_data); /* * If we choose to fallback during CR, there are two paths: * 1) If the current link rate is > RBR, downshift to the next lower * link rate. Prioritize link rate reduction over lane count * reduction. * 2) If the current link rate is already RBR, check if only the * lower-numbered lanes have their CR_DONE bits set. For example, in * a 4-lane configuration, the lower-numbered lanes would be lanes 0 * and 1. In a 2-lane configuration, the lower-numbered lane is lane * 0 only. If this is the case, reduce the lane count accordingly * and reset the link rate to the highest needed. * * If neither of the above two paths are valid, end link training and * fail. */ need_fallback = check_cr_fallback(lt_data, vs_temp, sizeof(vs_temp)); if (need_fallback) { if (lt_data->link_bw > NV_DPCD_MAX_LINK_BANDWIDTH_VAL_1_62_GBPS) { /* Fallback path #1 */ lt_data_sw_reset(lt_data); tgt_state = STATE_REDUCE_LINK_RATE; goto done; } else if (lt_data->n_lanes > 1) { /* Fallback path #2 */ u32 expected_cr_mask; expected_cr_mask = (lt_data->n_lanes == 4) ? 0x3 : 0x1; if (expected_cr_mask == cr_lane_mask) { lt_data_sw_reset(lt_data); dp->link_cfg.link_bw = dp->max_link_cfg.link_bw; tgt_state = STATE_REDUCE_LANE_COUNT; goto done; } } lt_failed(lt_data); tgt_state = STATE_DONE_FAIL; goto done; } else { pr_info("dp lt: CR retry\n"); } done: set_lt_state(lt_data, tgt_state, timeout); } typedef void (*dispatch_func_t)(struct tegra_dp_lt_data *lt_data); static const dispatch_func_t state_machine_dispatch[] = { lt_reset_state, /* STATE_RESET */ fast_lt_state, /* STATE_FAST_LT */ lt_clock_recovery_state, /* STATE_CLOCK_RECOVERY */ lt_channel_equalization_state, /* STATE_CHANNEL_EQUALIZATION */ NULL, /* STATE_DONE_FAIL */ NULL, /* STATE_DONE_PASS */ lt_reduce_link_rate_state, /* STATE_REDUCE_LINK_RATE */ lt_reduce_lane_count_state, /* STATE_REDUCE_LANE_COUNT */ }; static void handle_lt_hpd_evt(struct tegra_dp_lt_data *lt_data) { int tgt_state = STATE_RESET; if (lt_data->lt_config_valid && lt_data->no_aux_handshake && !lt_data->force_disable) tgt_state = STATE_FAST_LT; set_lt_state(lt_data, tgt_state, 0); } static void lt_worker(struct work_struct *work) { int pending_lt_evt; struct tegra_dp_lt_data *lt_data = container_of(to_delayed_work(work), struct tegra_dp_lt_data, dwork); /* * Observe and clear pending flag * and latch the current HPD state. */ mutex_lock(<_data->lock); pending_lt_evt = lt_data->pending_evt; lt_data->pending_evt = 0; mutex_unlock(<_data->lock); pr_info("dp lt: state %d (%s), pending_lt_evt %d\n", lt_data->state, tegra_dp_lt_state_names[lt_data->state], pending_lt_evt); if (pending_lt_evt) { handle_lt_hpd_evt(lt_data); } else if (lt_data->state < ARRAY_SIZE(state_machine_dispatch)) { dispatch_func_t func = state_machine_dispatch[lt_data->state]; if (!func) pr_warn("dp lt: NULL state handler in state %d\n", lt_data->state); else func(lt_data); } else { pr_warn("dp lt: unexpected state scheduled %d", lt_data->state); } } static void sched_lt_work(struct tegra_dp_lt_data *lt_data, int delay_ms) { cancel_delayed_work(<_data->dwork); if ((delay_ms >= 0) && !lt_data->shutdown) schedule_delayed_work(<_data->dwork, msecs_to_jiffies(delay_ms)); } static void set_lt_state(struct tegra_dp_lt_data *lt_data, int target_state, int delay_ms) { mutex_lock(<_data->lock); pr_info("dp lt: switching from state %d (%s) to state %d (%s)\n", lt_data->state, tegra_dp_lt_state_names[lt_data->state], target_state, tegra_dp_lt_state_names[target_state]); lt_data->state = target_state; /* we have reached final state. notify others. */ if (target_state == STATE_DONE_PASS || target_state == STATE_DONE_FAIL) complete_all(<_data->lt_complete); /* * If the pending_hpd_evt flag is already set, don't bother to * reschedule the state machine worker. We should be able to assert * that there is a worker callback already scheduled, and that it is * scheduled to run immediately */ if (!lt_data->pending_evt) sched_lt_work(lt_data, delay_ms); mutex_unlock(<_data->lock); } int tegra_dp_get_lt_state(struct tegra_dp_lt_data *lt_data) { int ret; mutex_lock(<_data->lock); ret = lt_data->state; mutex_unlock(<_data->lock); return ret; } void tegra_dp_lt_set_pending_evt(struct tegra_dp_lt_data *lt_data) { mutex_lock(<_data->lock); /* always schedule work any time there is a pending lt event */ lt_data->pending_evt = 1; sched_lt_work(lt_data, 0); mutex_unlock(<_data->lock); } /* * Marks previous LT configuration data as invalid. * Full LT is required to get new LT config data. */ void tegra_dp_lt_invalidate(struct tegra_dp_lt_data *lt_data) { mutex_lock(<_data->lock); lt_data->lt_config_valid = false; mutex_unlock(<_data->lock); } /* block till link training has reached final state */ long tegra_dp_lt_wait_for_completion(struct tegra_dp_lt_data *lt_data, int target_state, unsigned long timeout_ms) { might_sleep(); if (target_state == tegra_dp_get_lt_state(lt_data)) return 1; mutex_lock(<_data->lock); reinit_completion(<_data->lt_complete); mutex_unlock(<_data->lock); return wait_for_completion_timeout(<_data->lt_complete, msecs_to_jiffies(timeout_ms)); } void tegra_dp_lt_force_disable(struct tegra_dp_lt_data *lt_data) { mutex_lock(<_data->lock); lt_data->force_disable = true; mutex_unlock(<_data->lock); tegra_dp_lt_set_pending_evt(lt_data); } void tegra_dp_lt_init(struct tegra_dp_lt_data *lt_data, struct tegra_dc_dp_data *dp) { BUG_ON(!dp || !lt_data || !dp->dc); lt_data->dp = dp; /* Change for seamless */ if (tegra_dc_hpd(dp->dc) && dp->dc->initialized) { lt_data->state = STATE_DONE_PASS; /* Set config valid to false */ /* So that fresh LT starts */ lt_data->lt_config_valid = false; } else { lt_data->state = STATE_RESET; lt_data->pending_evt = 0; lt_data->shutdown = 0; lt_data_sw_reset(lt_data); } mutex_init(<_data->lock); INIT_DELAYED_WORK(<_data->dwork, lt_worker); init_completion(<_data->lt_complete); }