]> git.dujemihanovic.xyz Git - u-boot.git/commitdiff
tegra124: video: Add full link training for eDP
authorSimon Glass <sjg@chromium.org>
Wed, 15 Apr 2015 03:03:44 +0000 (21:03 -0600)
committerTom Warren <twarren@nvidia.com>
Wed, 13 May 2015 16:24:12 +0000 (09:24 -0700)
Add full link training as a fallback in case the fast link training
fails.

Signed-off-by: Simon Glass <sjg@chromium.org>
Acked-by: Anatolij Gustschin <agust@denx.de>
Signed-off-by: Tom Warren <twarren@nvidia.com>
arch/arm/include/asm/arch-tegra/dc.h
drivers/video/tegra124/display.c
drivers/video/tegra124/displayport.h
drivers/video/tegra124/dp.c
drivers/video/tegra124/sor.c
drivers/video/tegra124/sor.h
include/linux/drm_dp_helper.h

index 8803c11602f247213df706d03e7bc75e934301ce..6ffb4683959e92d206fe9f4600d496595a3d7330 100644 (file)
@@ -564,6 +564,10 @@ enum {
 #define V_DDA_INC_SHIFT                16
 #define V_DDA_INC_MASK         (0xFFFF << V_DDA_INC_SHIFT)
 
+#define DC_POLL_TIMEOUT_MS             50
+#define DC_N_WINDOWS                   5
+#define DC_REG_SAVE_SPACE              (DC_N_WINDOWS + 5)
+
 struct display_timing;
 
 int display_init(void *lcdbase, int fb_bits_per_pixel,
index 372ad3ea9b57299c02a041898a81410fdc064971..7179dbfe3cd4eb62cbde75661f62ded3010e8356 100644 (file)
@@ -95,6 +95,120 @@ static int update_display_mode(struct dc_ctlr *disp_ctrl,
        return 0;
 }
 
+static u32 tegra_dc_poll_register(void *reg,
+       u32 mask, u32 exp_val, u32 poll_interval_us, u32 timeout_us)
+{
+       u32 temp = timeout_us;
+       u32 reg_val = 0;
+
+       do {
+               udelay(poll_interval_us);
+               reg_val = readl(reg);
+               if (timeout_us > poll_interval_us)
+                       timeout_us -= poll_interval_us;
+               else
+                       break;
+       } while ((reg_val & mask) != exp_val);
+
+       if ((reg_val & mask) == exp_val)
+               return 0;       /* success */
+
+       return temp;
+}
+
+int tegra_dc_sor_general_act(struct dc_ctlr *disp_ctrl)
+{
+       writel(GENERAL_ACT_REQ, &disp_ctrl->cmd.state_ctrl);
+
+       if (tegra_dc_poll_register(&disp_ctrl->cmd.state_ctrl,
+                                  GENERAL_ACT_REQ, 0, 100,
+                                  DC_POLL_TIMEOUT_MS * 1000)) {
+               debug("dc timeout waiting for DC to stop\n");
+               return -ETIMEDOUT;
+       }
+
+       return 0;
+}
+
+static struct display_timing min_mode = {
+       .hsync_len = { .typ = 1 },
+       .vsync_len = { .typ = 1 },
+       .hback_porch = { .typ = 20 },
+       .vback_porch = { .typ = 0 },
+       .hactive = { .typ = 16 },
+       .vactive = { .typ = 16 },
+       .hfront_porch = { .typ = 1 },
+       .vfront_porch = { .typ = 2 },
+};
+
+/* Disable windows and set minimum raster timings */
+void tegra_dc_sor_disable_win_short_raster(struct dc_ctlr *disp_ctrl,
+                                          int *dc_reg_ctx)
+{
+       const int href_to_sync = 0, vref_to_sync = 1;
+       int selected_windows, i;
+
+       selected_windows = readl(&disp_ctrl->cmd.disp_win_header);
+
+       /* Store and clear window options */
+       for (i = 0; i < DC_N_WINDOWS; ++i) {
+               writel(WINDOW_A_SELECT << i, &disp_ctrl->cmd.disp_win_header);
+               dc_reg_ctx[i] = readl(&disp_ctrl->win.win_opt);
+               writel(0, &disp_ctrl->win.win_opt);
+               writel(WIN_A_ACT_REQ << i, &disp_ctrl->cmd.state_ctrl);
+       }
+
+       writel(selected_windows, &disp_ctrl->cmd.disp_win_header);
+
+       /* Store current raster timings and set minimum timings */
+       dc_reg_ctx[i++] = readl(&disp_ctrl->disp.ref_to_sync);
+       writel(href_to_sync | (vref_to_sync << 16),
+              &disp_ctrl->disp.ref_to_sync);
+
+       dc_reg_ctx[i++] = readl(&disp_ctrl->disp.sync_width);
+       writel(min_mode.hsync_len.typ | (min_mode.vsync_len.typ << 16),
+              &disp_ctrl->disp.sync_width);
+
+       dc_reg_ctx[i++] = readl(&disp_ctrl->disp.back_porch);
+       writel(min_mode.hback_porch.typ | (min_mode.vback_porch.typ << 16),
+              &disp_ctrl->disp.back_porch);
+
+       dc_reg_ctx[i++] = readl(&disp_ctrl->disp.front_porch);
+       writel(min_mode.hfront_porch.typ | (min_mode.vfront_porch.typ << 16),
+              &disp_ctrl->disp.front_porch);
+
+       dc_reg_ctx[i++] = readl(&disp_ctrl->disp.disp_active);
+       writel(min_mode.hactive.typ | (min_mode.vactive.typ << 16),
+              &disp_ctrl->disp.disp_active);
+
+       writel(GENERAL_ACT_REQ, &disp_ctrl->cmd.state_ctrl);
+}
+
+/* Restore previous windows status and raster timings */
+void tegra_dc_sor_restore_win_and_raster(struct dc_ctlr *disp_ctrl,
+                                        int *dc_reg_ctx)
+{
+       int selected_windows, i;
+
+       selected_windows = readl(&disp_ctrl->cmd.disp_win_header);
+
+       for (i = 0; i < DC_N_WINDOWS; ++i) {
+               writel(WINDOW_A_SELECT << i, &disp_ctrl->cmd.disp_win_header);
+               writel(dc_reg_ctx[i], &disp_ctrl->win.win_opt);
+               writel(WIN_A_ACT_REQ << i, &disp_ctrl->cmd.state_ctrl);
+       }
+
+       writel(selected_windows, &disp_ctrl->cmd.disp_win_header);
+
+       writel(dc_reg_ctx[i++], &disp_ctrl->disp.ref_to_sync);
+       writel(dc_reg_ctx[i++], &disp_ctrl->disp.sync_width);
+       writel(dc_reg_ctx[i++], &disp_ctrl->disp.back_porch);
+       writel(dc_reg_ctx[i++], &disp_ctrl->disp.front_porch);
+       writel(dc_reg_ctx[i++], &disp_ctrl->disp.disp_active);
+
+       writel(GENERAL_UPDATE, &disp_ctrl->cmd.state_ctrl);
+}
+
 static int tegra_depth_for_bpp(int bpp)
 {
        switch (bpp) {
index c70bbe32ed08628a9d340f925c4dae0ced974175..ace6ab02a9d5ddecc029872db707f525c6cf91be 100644 (file)
@@ -128,6 +128,183 @@ struct dpaux_ctlr {
 #define DP_AUX_TIMEOUT_MS              40
 #define DP_DPCP_RETRY_SLEEP_NS         400
 
+static const u32 tegra_dp_vs_regs[][4][4] = {
+       /* postcursor2 L0 */
+       {
+               /* pre-emphasis: L0, L1, L2, L3 */
+               {0x13, 0x19, 0x1e, 0x28}, /* voltage swing: L0 */
+               {0x1e, 0x25, 0x2d}, /* L1 */
+               {0x28, 0x32}, /* L2 */
+               {0x3c}, /* L3 */
+       },
+
+       /* postcursor2 L1 */
+       {
+               {0x12, 0x17, 0x1b, 0x25},
+               {0x1c, 0x23, 0x2a},
+               {0x25, 0x2f},
+               {0x39},
+       },
+
+       /* postcursor2 L2 */
+       {
+               {0x12, 0x16, 0x1a, 0x22},
+               {0x1b, 0x20, 0x27},
+               {0x24, 0x2d},
+               {0x36},
+       },
+
+       /* postcursor2 L3 */
+       {
+               {0x11, 0x14, 0x17, 0x1f},
+               {0x19, 0x1e, 0x24},
+               {0x22, 0x2a},
+               {0x32},
+       },
+};
+
+static const u32 tegra_dp_pe_regs[][4][4] = {
+       /* postcursor2 L0 */
+       {
+               /* pre-emphasis: L0, L1, L2, L3 */
+               {0x00, 0x09, 0x13, 0x25}, /* voltage swing: L0 */
+               {0x00, 0x0f, 0x1e}, /* L1 */
+               {0x00, 0x14}, /* L2 */
+               {0x00}, /* L3 */
+       },
+
+       /* postcursor2 L1 */
+       {
+               {0x00, 0x0a, 0x14, 0x28},
+               {0x00, 0x0f, 0x1e},
+               {0x00, 0x14},
+               {0x00},
+       },
+
+       /* postcursor2 L2 */
+       {
+               {0x00, 0x0a, 0x14, 0x28},
+               {0x00, 0x0f, 0x1e},
+               {0x00, 0x14},
+               {0x00},
+       },
+
+       /* postcursor2 L3 */
+       {
+               {0x00, 0x0a, 0x14, 0x28},
+               {0x00, 0x0f, 0x1e},
+               {0x00, 0x14},
+               {0x00},
+       },
+};
+
+static const u32 tegra_dp_pc_regs[][4][4] = {
+       /* postcursor2 L0 */
+       {
+               /* pre-emphasis: L0, L1, L2, L3 */
+               {0x00, 0x00, 0x00, 0x00}, /* voltage swing: L0 */
+               {0x00, 0x00, 0x00}, /* L1 */
+               {0x00, 0x00}, /* L2 */
+               {0x00}, /* L3 */
+       },
+
+       /* postcursor2 L1 */
+       {
+               {0x02, 0x02, 0x04, 0x05},
+               {0x02, 0x04, 0x05},
+               {0x04, 0x05},
+               {0x05},
+       },
+
+       /* postcursor2 L2 */
+       {
+               {0x04, 0x05, 0x08, 0x0b},
+               {0x05, 0x09, 0x0b},
+               {0x08, 0x0a},
+               {0x0b},
+       },
+
+       /* postcursor2 L3 */
+       {
+               {0x05, 0x09, 0x0b, 0x12},
+               {0x09, 0x0d, 0x12},
+               {0x0b, 0x0f},
+               {0x12},
+       },
+};
+
+static const u32 tegra_dp_tx_pu[][4][4] = {
+       /* postcursor2 L0 */
+       {
+               /* pre-emphasis: L0, L1, L2, L3 */
+               {0x20, 0x30, 0x40, 0x60}, /* voltage swing: L0 */
+               {0x30, 0x40, 0x60}, /* L1 */
+               {0x40, 0x60}, /* L2 */
+               {0x60}, /* L3 */
+       },
+
+       /* postcursor2 L1 */
+       {
+               {0x20, 0x20, 0x30, 0x50},
+               {0x30, 0x40, 0x50},
+               {0x40, 0x50},
+               {0x60},
+       },
+
+       /* postcursor2 L2 */
+       {
+               {0x20, 0x20, 0x30, 0x40},
+               {0x30, 0x30, 0x40},
+               {0x40, 0x50},
+               {0x60},
+       },
+
+       /* postcursor2 L3 */
+       {
+               {0x20, 0x20, 0x20, 0x40},
+               {0x30, 0x30, 0x40},
+               {0x40, 0x40},
+               {0x60},
+       },
+};
+
+enum {
+       DRIVECURRENT_LEVEL0 = 0,
+       DRIVECURRENT_LEVEL1 = 1,
+       DRIVECURRENT_LEVEL2 = 2,
+       DRIVECURRENT_LEVEL3 = 3,
+};
+
+enum {
+       PREEMPHASIS_DISABLED = 0,
+       PREEMPHASIS_LEVEL1   = 1,
+       PREEMPHASIS_LEVEL2   = 2,
+       PREEMPHASIS_LEVEL3   = 3,
+};
+
+enum {
+       POSTCURSOR2_LEVEL0 = 0,
+       POSTCURSOR2_LEVEL1 = 1,
+       POSTCURSOR2_LEVEL2 = 2,
+       POSTCURSOR2_LEVEL3 = 3,
+       POSTCURSOR2_SUPPORTED
+};
+
+static inline int tegra_dp_is_max_vs(u32 pe, u32 vs)
+{
+       return (vs < (DRIVECURRENT_LEVEL3 - pe)) ? 0 : 1;
+}
+
+static inline int tegra_dp_is_max_pe(u32 pe, u32 vs)
+{
+       return (pe < (PREEMPHASIS_LEVEL3 - vs)) ? 0 : 1;
+}
+
+static inline int tegra_dp_is_max_pc(u32 pc)
+{
+       return (pc < POSTCURSOR2_LEVEL3) ? 0 : 1;
+}
+
 /* DPCD definitions which are not defined in drm_dp_helper.h */
 #define DP_DPCD_REV_MAJOR_SHIFT                                4
 #define DP_DPCD_REV_MAJOR_MASK                         (0xf << 4)
@@ -141,8 +318,16 @@ struct dpaux_ctlr {
 #define DP_MAX_LANE_COUNT_LANE_1                       0x1
 #define DP_MAX_LANE_COUNT_LANE_2                       0x2
 #define DP_MAX_LANE_COUNT_LANE_4                       0x4
+#define DP_MAX_LANE_COUNT_TPS3_SUPPORTED_YES           (1 << 6)
 #define DP_MAX_LANE_COUNT_ENHANCED_FRAMING_YES         (1 << 7)
 
+#define NV_DPCD_TRAINING_LANEX_SET_DC_SHIFT            0
+#define NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_T    (0x00000001 << 2)
+#define NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_F     (0x00000000 << 2)
+#define NV_DPCD_TRAINING_LANEX_SET_PE_SHIFT            3
+#define NV_DPCD_TRAINING_LANEX_SET_PE_MAX_REACHED_T    (0x00000001 << 5)
+#define NV_DPCD_TRAINING_LANEX_SET_PE_MAX_REACHED_F    (0x00000000 << 5)
+
 #define DP_MAX_DOWNSPREAD_VAL_NONE                     0
 #define DP_MAX_DOWNSPREAD_VAL_0_5_PCT                  1
 #define DP_MAX_DOWNSPREAD_NO_AUX_HANDSHAKE_LT_T                (1 << 6)
@@ -153,6 +338,8 @@ struct dpaux_ctlr {
 #define DP_LANE_COUNT_SET_ENHANCEDFRAMING_T            (1 << 7)
 
 #define DP_TRAINING_PATTERN_SET_SC_DISABLED_T          (1 << 5)
+#define NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_F     (0x00000000 << 5)
+#define NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_T     (0x00000001 << 5)
 
 #define DP_MAIN_LINK_CHANNEL_CODING_SET_ASC_RESET_DISABLE      0
 #define DP_MAIN_LINK_CHANNEL_CODING_SET_ASC_RESET_ENABLE       1
@@ -160,7 +347,11 @@ struct dpaux_ctlr {
 #define NV_DPCD_TRAINING_LANE0_1_SET2                  0x10f
 #define NV_DPCD_TRAINING_LANE2_3_SET2                  0x110
 #define NV_DPCD_LANEX_SET2_PC2_MAX_REACHED_T           (1 << 2)
+#define NV_DPCD_LANEX_SET2_PC2_MAX_REACHED_F           (0 << 2)
 #define NV_DPCD_LANEXPLUS1_SET2_PC2_MAX_REACHED_T      (1 << 6)
+#define NV_DPCD_LANEXPLUS1_SET2_PC2_MAX_REACHED_F      (0 << 6)
+#define NV_DPCD_LANEX_SET2_PC2_SHIFT                   0
+#define NV_DPCD_LANEXPLUS1_SET2_PC2_SHIFT              4
 
 #define NV_DPCD_STATUS_LANEX_CR_DONE_SHIFT             0
 #define NV_DPCD_STATUS_LANEX_CR_DONE_NO                        (0x00000000)
@@ -181,4 +372,41 @@ struct dpaux_ctlr {
 #define NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_NO     (0x00000000 << 6)
 #define NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_YES    (0x00000001 << 6)
 
+#define NV_DPCD_LANE_ALIGN_STATUS_UPDATED              (0x00000204)
+#define NV_DPCD_LANE_ALIGN_STATUS_UPDATED_DONE_NO      (0x00000000)
+#define NV_DPCD_LANE_ALIGN_STATUS_UPDATED_DONE_YES     (0x00000001)
+
+#define NV_DPCD_STATUS_LANEX_CR_DONE_SHIFT             0
+#define NV_DPCD_STATUS_LANEX_CR_DONE_NO                        (0x00000000)
+#define NV_DPCD_STATUS_LANEX_CR_DONE_YES               (0x00000001)
+#define NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_SHIFT         1
+#define NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_NO            (0x00000000 << 1)
+#define NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_YES           (0x00000001 << 1)
+#define NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_SHFIT       2
+#define NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_NO          (0x00000000 << 2)
+#define NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_YES         (0x00000001 << 2)
+#define NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_SHIFT                4
+#define NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_NO           (0x00000000 << 4)
+#define NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_YES          (0x00000001 << 4)
+#define NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_SHIFT    5
+#define NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_NO       (0x00000000 << 5)
+#define NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_YES      (0x00000001 << 5)
+#define NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_SHIFT  6
+#define NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_NO     (0x00000000 << 6)
+#define NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_YES    (0x00000001 << 6)
+
+#define NV_DPCD_ADJUST_REQ_LANEX_DC_SHIFT              0
+#define NV_DPCD_ADJUST_REQ_LANEX_DC_MASK               0x3
+#define NV_DPCD_ADJUST_REQ_LANEX_PE_SHIFT              2
+#define NV_DPCD_ADJUST_REQ_LANEX_PE_MASK               (0x3 << 2)
+#define NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_SHIFT         4
+#define NV_DPCD_ADJUST_REQ_LANEXPLUS1_DC_MASK          (0x3 << 4)
+#define NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_SHIFT         6
+#define NV_DPCD_ADJUST_REQ_LANEXPLUS1_PE_MASK          (0x3 << 6)
+#define NV_DPCD_ADJUST_REQ_POST_CURSOR2                        (0x0000020C)
+#define NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_MASK      0x3
+#define NV_DPCD_ADJUST_REQ_POST_CURSOR2_LANE_SHIFT(i)  (i*2)
+
+#define NV_DPCD_TRAINING_AUX_RD_INTERVAL               (0x0000000E)
+#define NV_DPCD_TRAINING_LANEX_SET_DC_MAX_REACHED_F     (0x00000000 << 2)
 #endif
index a318568f5ff4f22898c21f4bd195be306de2f5bb..3c0b721e3b8226d024d39a31270ceed2d69fab2d 100644 (file)
@@ -19,6 +19,8 @@
 
 DECLARE_GLOBAL_DATA_PTR;
 
+#define DO_FAST_LINK_TRAINING          1
+
 struct tegra_dp_plat {
        ulong base;
 };
@@ -440,6 +442,34 @@ static void tegra_dc_dp_dump_link_cfg(struct tegra_dp_priv *dp,
 }
 #endif
 
+static int _tegra_dp_lower_link_config(struct tegra_dp_priv *dp,
+                                      struct tegra_dp_link_config *cfg)
+{
+       switch (cfg->link_bw) {
+       case SOR_LINK_SPEED_G1_62:
+               if (cfg->max_link_bw > SOR_LINK_SPEED_G1_62)
+                       cfg->link_bw = SOR_LINK_SPEED_G2_7;
+               cfg->lane_count /= 2;
+               break;
+       case SOR_LINK_SPEED_G2_7:
+               cfg->link_bw = SOR_LINK_SPEED_G1_62;
+               break;
+       case SOR_LINK_SPEED_G5_4:
+               if (cfg->lane_count == 1) {
+                       cfg->link_bw = SOR_LINK_SPEED_G2_7;
+                       cfg->lane_count = cfg->max_lane_count;
+               } else {
+                       cfg->lane_count /= 2;
+               }
+               break;
+       default:
+               debug("dp: Error link rate %d\n", cfg->link_bw);
+               return -ENOLINK;
+       }
+
+       return (cfg->lane_count > 0) ? 0 : -ENOLINK;
+}
+
 /*
  * Calcuate if given cfg can meet the mode request.
  * Return 0 if mode is possible, -1 otherwise
@@ -629,6 +659,8 @@ static int tegra_dc_dp_init_max_link_cfg(
        if (ret)
                return ret;
        link_cfg->max_lane_count = dpcd_data & DP_MAX_LANE_COUNT_MASK;
+       link_cfg->tps3_supported = (dpcd_data &
+                       DP_MAX_LANE_COUNT_TPS3_SUPPORTED_YES) ? 1 : 0;
 
        link_cfg->support_enhanced_framing =
                (dpcd_data & DP_MAX_LANE_COUNT_ENHANCED_FRAMING_YES) ?
@@ -640,6 +672,10 @@ static int tegra_dc_dp_init_max_link_cfg(
        link_cfg->downspread = (dpcd_data & DP_MAX_DOWNSPREAD_VAL_0_5_PCT) ?
                                1 : 0;
 
+       ret = tegra_dc_dp_dpcd_read(dp, NV_DPCD_TRAINING_AUX_RD_INTERVAL,
+                                   &link_cfg->aux_rd_interval);
+       if (ret)
+               return ret;
        ret = tegra_dc_dp_dpcd_read(dp, DP_MAX_LINK_RATE,
                                    &link_cfg->max_link_bw);
        if (ret)
@@ -667,6 +703,7 @@ static int tegra_dc_dp_init_max_link_cfg(
        link_cfg->lane_count = link_cfg->max_lane_count;
        link_cfg->link_bw = link_cfg->max_link_bw;
        link_cfg->enhanced_framing = link_cfg->support_enhanced_framing;
+       link_cfg->frame_in_ms = (1000 / 60) + 1;
 
        tegra_dc_dp_calc_config(dp, timing, link_cfg);
        return 0;
@@ -749,6 +786,442 @@ static int tegra_dc_dp_link_trained(struct tegra_dp_priv *dp,
        return 0;
 }
 
+static int tegra_dp_channel_eq_status(struct tegra_dp_priv *dp,
+                                     const struct tegra_dp_link_config *cfg)
+{
+       u32 cnt;
+       u32 n_lanes = cfg->lane_count;
+       u8 data;
+       u8 ce_done = 1;
+       int ret;
+
+       for (cnt = 0; cnt < n_lanes / 2; cnt++) {
+               ret = tegra_dc_dp_dpcd_read(dp, DP_LANE0_1_STATUS + cnt, &data);
+               if (ret)
+                       return ret;
+
+               if (n_lanes == 1) {
+                       ce_done = (data & (0x1 <<
+                       NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_SHIFT)) &&
+                       (data & (0x1 <<
+                       NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_SHFIT));
+                       break;
+               } else if (!(data & (0x1 <<
+                               NV_DPCD_STATUS_LANEX_CHN_EQ_DONE_SHIFT)) ||
+                          !(data & (0x1 <<
+                               NV_DPCD_STATUS_LANEX_SYMBOL_LOCKED_SHFIT)) ||
+                          !(data & (0x1 <<
+                               NV_DPCD_STATUS_LANEXPLUS1_CHN_EQ_DONE_SHIFT)) ||
+                          !(data & (0x1 <<
+                               NV_DPCD_STATUS_LANEXPLUS1_SYMBOL_LOCKED_SHIFT)))
+                       return -EIO;
+       }
+
+       if (ce_done) {
+               ret = tegra_dc_dp_dpcd_read(dp,
+                                           DP_LANE_ALIGN_STATUS_UPDATED,
+                                           &data);
+               if (ret)
+                       return ret;
+               if (!(data & NV_DPCD_LANE_ALIGN_STATUS_UPDATED_DONE_YES))
+                       ce_done = 0;
+       }
+
+       return ce_done ? 0 : -EIO;
+}
+
+static int tegra_dp_clock_recovery_status(struct tegra_dp_priv *dp,
+                                        const struct tegra_dp_link_config *cfg)
+{
+       u32 cnt;
+       u32 n_lanes = cfg->lane_count;
+       u8 data_ptr;
+       int ret;
+
+       for (cnt = 0; cnt < n_lanes / 2; cnt++) {
+               ret = tegra_dc_dp_dpcd_read(dp, (DP_LANE0_1_STATUS + cnt),
+                                           &data_ptr);
+               if (ret)
+                       return ret;
+
+               if (n_lanes == 1)
+                       return (data_ptr & NV_DPCD_STATUS_LANEX_CR_DONE_YES) ?
+                               1 : 0;
+               else if (!(data_ptr & NV_DPCD_STATUS_LANEX_CR_DONE_YES) ||
+                        !(data_ptr & (NV_DPCD_STATUS_LANEXPLUS1_CR_DONE_YES)))
+                       return 0;
+       }
+
+       return 1;
+}
+
+static int tegra_dp_lt_adjust(struct tegra_dp_priv *dp, u32 pe[4], u32 vs[4],
+                             u32 pc[4], u8 pc_supported,
+                             const struct tegra_dp_link_config *cfg)
+{
+       size_t cnt;
+       u8 data_ptr;
+       u32 n_lanes = cfg->lane_count;
+       int ret;
+
+       for (cnt = 0; cnt < n_lanes / 2; cnt++) {
+               ret = tegra_dc_dp_dpcd_read(dp, DP_ADJUST_REQUEST_LANE0_1 + cnt,
+                                           &data_ptr);
+               if (ret)
+                       return ret;
+               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 (pc_supported) {
+               ret = tegra_dc_dp_dpcd_read(dp, NV_DPCD_ADJUST_REQ_POST_CURSOR2,
+                                           &data_ptr);
+               if (ret)
+                       return ret;
+               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;
+               }
+       }
+
+       return 0;
+}
+
+static void tegra_dp_wait_aux_training(struct tegra_dp_priv *dp,
+                                       bool is_clk_recovery,
+                                       const struct tegra_dp_link_config *cfg)
+{
+       if (!cfg->aux_rd_interval)
+               udelay(is_clk_recovery ? 200 : 500);
+       else
+               mdelay(cfg->aux_rd_interval * 4);
+}
+
+static void tegra_dp_tpg(struct tegra_dp_priv *dp, u32 tp, u32 n_lanes,
+                        const struct tegra_dp_link_config *cfg)
+{
+       u8 data = (tp == training_pattern_disabled)
+               ? (tp | NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_F)
+               : (tp | NV_DPCD_TRAINING_PATTERN_SET_SC_DISABLED_T);
+
+       tegra_dc_sor_set_dp_linkctl(dp->sor, 1, tp, cfg);
+       tegra_dc_dp_dpcd_write(dp, DP_TRAINING_PATTERN_SET, data);
+}
+
+static int tegra_dp_link_config(struct tegra_dp_priv *dp,
+                               const struct tegra_dp_link_config *link_cfg)
+{
+       u8 dpcd_data;
+       u32 retry;
+       int ret;
+
+       if (link_cfg->lane_count == 0) {
+               debug("dp: error: lane count is 0. Can not set link config.\n");
+               return -ENOLINK;
+       }
+
+       /* Set power state if it is not in normal level */
+       ret = tegra_dc_dp_dpcd_read(dp, DP_SET_POWER, &dpcd_data);
+       if (ret)
+               return ret;
+
+       if (dpcd_data == DP_SET_POWER_D3) {
+               dpcd_data = DP_SET_POWER_D0;
+
+               /* DP spec requires 3 retries */
+               for (retry = 3; retry > 0; --retry) {
+                       ret = tegra_dc_dp_dpcd_write(dp, DP_SET_POWER,
+                                                    dpcd_data);
+                       if (!ret)
+                               break;
+                       if (retry == 1) {
+                               debug("dp: Failed to set DP panel power\n");
+                               return ret;
+                       }
+               }
+       }
+
+       /* Enable ASSR if possible */
+       if (link_cfg->alt_scramber_reset_cap) {
+               ret = tegra_dc_dp_set_assr(dp, dp->sor, 1);
+               if (ret)
+                       return ret;
+       }
+
+       ret = tegra_dp_set_link_bandwidth(dp, dp->sor, link_cfg->link_bw);
+       if (ret) {
+               debug("dp: Failed to set link bandwidth\n");
+               return ret;
+       }
+       ret = tegra_dp_set_lane_count(dp, link_cfg, dp->sor);
+       if (ret) {
+               debug("dp: Failed to set lane count\n");
+               return ret;
+       }
+       tegra_dc_sor_set_dp_linkctl(dp->sor, 1, training_pattern_none,
+                                   link_cfg);
+
+       return 0;
+}
+
+static int tegra_dp_lower_link_config(struct tegra_dp_priv *dp,
+                                     const struct display_timing *timing,
+                                     struct tegra_dp_link_config *cfg)
+{
+       struct tegra_dp_link_config tmp_cfg;
+       int ret;
+
+       tmp_cfg = *cfg;
+       cfg->is_valid = 0;
+
+       ret = _tegra_dp_lower_link_config(dp, cfg);
+       if (!ret)
+               ret = tegra_dc_dp_calc_config(dp, timing, cfg);
+       if (!ret)
+               ret = tegra_dp_link_config(dp, cfg);
+       if (ret)
+               goto fail;
+
+       return 0;
+
+fail:
+       *cfg = tmp_cfg;
+       tegra_dp_link_config(dp, &tmp_cfg);
+       return ret;
+}
+
+static int tegra_dp_lt_config(struct tegra_dp_priv *dp, u32 pe[4], u32 vs[4],
+                             u32 pc[4], const struct tegra_dp_link_config *cfg)
+{
+       struct tegra_dc_sor_data *sor = dp->sor;
+       u32 n_lanes = cfg->lane_count;
+       u8 pc_supported = cfg->tps3_supported;
+       u32 cnt;
+       u32 val;
+
+       for (cnt = 0; cnt < n_lanes; cnt++) {
+               u32 mask = 0;
+               u32 pe_reg, vs_reg, pc_reg;
+               u32 shift = 0;
+
+               switch (cnt) {
+               case 0:
+                       mask = PR_LANE2_DP_LANE0_MASK;
+                       shift = PR_LANE2_DP_LANE0_SHIFT;
+                       break;
+               case 1:
+                       mask = PR_LANE1_DP_LANE1_MASK;
+                       shift = PR_LANE1_DP_LANE1_SHIFT;
+                       break;
+               case 2:
+                       mask = PR_LANE0_DP_LANE2_MASK;
+                       shift = PR_LANE0_DP_LANE2_SHIFT;
+                       break;
+               case 3:
+                       mask = PR_LANE3_DP_LANE3_MASK;
+                       shift = PR_LANE3_DP_LANE3_SHIFT;
+                       break;
+               default:
+                       debug("dp: incorrect lane cnt\n");
+                       return -EINVAL;
+               }
+
+               pe_reg = tegra_dp_pe_regs[pc[cnt]][vs[cnt]][pe[cnt]];
+               vs_reg = tegra_dp_vs_regs[pc[cnt]][vs[cnt]][pe[cnt]];
+               pc_reg = tegra_dp_pc_regs[pc[cnt]][vs[cnt]][pe[cnt]];
+
+               tegra_dp_set_pe_vs_pc(sor, mask, pe_reg << shift,
+                                     vs_reg << shift, pc_reg << shift,
+                                     pc_supported);
+       }
+
+       tegra_dp_disable_tx_pu(dp->sor);
+       udelay(20);
+
+       for (cnt = 0; cnt < n_lanes; 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);
+               tegra_dc_dp_dpcd_write(dp, (DP_TRAINING_LANE0_SET + cnt), val);
+       }
+
+       if (pc_supported) {
+               for (cnt = 0; cnt < n_lanes / 2; 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);
+               }
+       }
+
+       return 0;
+}
+
+static int _tegra_dp_channel_eq(struct tegra_dp_priv *dp, u32 pe[4],
+                               u32 vs[4], u32 pc[4], u8 pc_supported,
+                               u32 n_lanes,
+                               const struct tegra_dp_link_config *cfg)
+{
+       u32 retry_cnt;
+
+       for (retry_cnt = 0; retry_cnt < 4; retry_cnt++) {
+               int ret;
+
+               if (retry_cnt) {
+                       ret = tegra_dp_lt_adjust(dp, pe, vs, pc, pc_supported,
+                                                cfg);
+                       if (ret)
+                               return ret;
+                       tegra_dp_lt_config(dp, pe, vs, pc, cfg);
+               }
+
+               tegra_dp_wait_aux_training(dp, false, cfg);
+
+               if (!tegra_dp_clock_recovery_status(dp, cfg)) {
+                       debug("dp: CR failed in channel EQ sequence!\n");
+                       break;
+               }
+
+               if (!tegra_dp_channel_eq_status(dp, cfg))
+                       return 0;
+       }
+
+       return -EIO;
+}
+
+static int tegra_dp_channel_eq(struct tegra_dp_priv *dp, u32 pe[4], u32 vs[4],
+                              u32 pc[4],
+                              const struct tegra_dp_link_config *cfg)
+{
+       u32 n_lanes = cfg->lane_count;
+       u8 pc_supported = cfg->tps3_supported;
+       int ret;
+       u32 tp_src = training_pattern_2;
+
+       if (pc_supported)
+               tp_src = training_pattern_3;
+
+       tegra_dp_tpg(dp, tp_src, n_lanes, cfg);
+
+       ret = _tegra_dp_channel_eq(dp, pe, vs, pc, pc_supported, n_lanes, cfg);
+
+       tegra_dp_tpg(dp, training_pattern_disabled, n_lanes, cfg);
+
+       return ret;
+}
+
+static int _tegra_dp_clk_recovery(struct tegra_dp_priv *dp, u32 pe[4],
+                                 u32 vs[4], u32 pc[4], u8 pc_supported,
+                                 u32 n_lanes,
+                                 const struct tegra_dp_link_config *cfg)
+{
+       u32 vs_temp[4];
+       u32 retry_cnt = 0;
+
+       do {
+               tegra_dp_lt_config(dp, pe, vs, pc, cfg);
+               tegra_dp_wait_aux_training(dp, true, cfg);
+
+               if (tegra_dp_clock_recovery_status(dp, cfg))
+                       return 0;
+
+               memcpy(vs_temp, vs, sizeof(vs_temp));
+               tegra_dp_lt_adjust(dp, pe, vs, pc, pc_supported, cfg);
+
+               if (memcmp(vs_temp, vs, sizeof(vs_temp)))
+                       retry_cnt = 0;
+               else
+                       ++retry_cnt;
+       } while (retry_cnt < 5);
+
+       return -EIO;
+}
+
+static int tegra_dp_clk_recovery(struct tegra_dp_priv *dp, u32 pe[4],
+                                u32 vs[4], u32 pc[4],
+                                const struct tegra_dp_link_config *cfg)
+{
+       u32 n_lanes = cfg->lane_count;
+       u8 pc_supported = cfg->tps3_supported;
+       int err;
+
+       tegra_dp_tpg(dp, training_pattern_1, n_lanes, cfg);
+
+       err = _tegra_dp_clk_recovery(dp, pe, vs, pc, pc_supported, n_lanes,
+                                    cfg);
+       if (err < 0)
+               tegra_dp_tpg(dp, training_pattern_disabled, n_lanes, cfg);
+
+       return err;
+}
+
+static int tegra_dc_dp_full_link_training(struct tegra_dp_priv *dp,
+                                         const struct display_timing *timing,
+                                         struct tegra_dp_link_config *cfg)
+{
+       struct tegra_dc_sor_data *sor = dp->sor;
+       int err;
+       u32 pe[4], vs[4], pc[4];
+
+       tegra_sor_precharge_lanes(sor, cfg);
+
+retry_cr:
+       memset(pe, PREEMPHASIS_DISABLED, sizeof(pe));
+       memset(vs, DRIVECURRENT_LEVEL0, sizeof(vs));
+       memset(pc, POSTCURSOR2_LEVEL0, sizeof(pc));
+
+       err = tegra_dp_clk_recovery(dp, pe, vs, pc, cfg);
+       if (err) {
+               if (!tegra_dp_lower_link_config(dp, timing, cfg))
+                       goto retry_cr;
+
+               debug("dp: clk recovery failed\n");
+               goto fail;
+       }
+
+       err = tegra_dp_channel_eq(dp, pe, vs, pc, cfg);
+       if (err) {
+               if (!tegra_dp_lower_link_config(dp, timing, cfg))
+                       goto retry_cr;
+
+               debug("dp: channel equalization failed\n");
+               goto fail;
+       }
+#ifdef DEBUG
+       tegra_dc_dp_dump_link_cfg(dp, cfg);
+#endif
+       return 0;
+
+fail:
+       return err;
+}
+
 /*
  * All link training functions are ported from kernel dc driver.
  * See more details at drivers/video/tegra/dc/dp.c
@@ -824,62 +1297,38 @@ static int tegra_dc_dp_fast_link_training(struct tegra_dp_priv *dp,
        return 0;
 }
 
-static int tegra_dp_link_config(struct tegra_dp_priv *dp,
-       const struct tegra_dp_link_config *link_cfg,
-       struct tegra_dc_sor_data *sor)
+static int tegra_dp_do_link_training(struct tegra_dp_priv *dp,
+               struct tegra_dp_link_config *link_cfg,
+               const struct display_timing *timing,
+               struct tegra_dc_sor_data *sor)
 {
-       u8      dpcd_data;
        u8      link_bw;
        u8      lane_count;
-       u32     retry;
        int     ret;
 
-       if (link_cfg->lane_count == 0) {
-               debug("dp: error: lane count is 0. Can not set link config.\n");
-               return -1;
-       }
-
-       /* Set power state if it is not in normal level */
-       ret = tegra_dc_dp_dpcd_read(dp, DP_SET_POWER, &dpcd_data);
-       if (ret)
-               return ret;
-       if (dpcd_data == DP_SET_POWER_D3) {
-               dpcd_data = DP_SET_POWER_D0;
-               retry = 3;      /* DP spec requires 3 retries */
-               do {
-                       ret = tegra_dc_dp_dpcd_write(dp,
-                               DP_SET_POWER, dpcd_data);
-               } while ((--retry > 0) && ret);
+       if (DO_FAST_LINK_TRAINING) {
+               ret = tegra_dc_dp_fast_link_training(dp, link_cfg, sor);
                if (ret) {
-                       debug("dp: Failed to set DP panel power\n");
-                       return ret;
+                       debug("dp: fast link training failed\n");
+               } else {
+                       /*
+                       * set to a known-good drive setting if fast link
+                       * succeeded. Ignore any error.
+                       */
+                       ret = tegra_dc_sor_set_voltage_swing(dp->sor, link_cfg);
+                       if (ret)
+                               debug("Failed to set voltage swing\n");
                }
+       } else {
+               ret = -ENOSYS;
        }
-
-       /* Enable ASSR if possible */
-       if (link_cfg->alt_scramber_reset_cap) {
-               ret = tegra_dc_dp_set_assr(dp, sor, 1);
-               if (ret)
-                       return ret;
-       }
-
-       ret = tegra_dp_set_link_bandwidth(dp, sor, link_cfg->link_bw);
-       if (ret) {
-               debug("dp: Failed to set link bandwidth\n");
-               return ret;
-       }
-       ret = tegra_dp_set_lane_count(dp, link_cfg, sor);
-       if (ret) {
-               debug("dp: Failed to set lane count\n");
-               return ret;
-       }
-       tegra_dc_sor_set_dp_linkctl(sor, 1, training_pattern_none, link_cfg);
-
-       /* Now do the fast link training for eDP */
-       ret = tegra_dc_dp_fast_link_training(dp, link_cfg, sor);
        if (ret) {
-               debug("dp: fast link training failed\n");
-               return ret;
+               /* Try full link training then */
+               ret = tegra_dc_dp_full_link_training(dp, timing, link_cfg);
+               if (ret) {
+                       debug("dp: full link training failed\n");
+                       return ret;
+               }
        }
 
        /* Everything is good; double check the link config */
@@ -920,7 +1369,8 @@ static int tegra_dc_dp_explore_link_cfg(struct tegra_dp_priv *dp,
         * set to max link config
         */
        if ((!tegra_dc_dp_calc_config(dp, timing, &temp_cfg)) &&
-           (!(tegra_dp_link_config(dp, &temp_cfg, sor))))
+           (!tegra_dp_link_config(dp, &temp_cfg)) &&
+               (!tegra_dp_do_link_training(dp, &temp_cfg, timing, sor)))
                /* the max link cfg is doable */
                memcpy(link_cfg, &temp_cfg, sizeof(temp_cfg));
 
@@ -944,6 +1394,73 @@ static int tegra_dp_hpd_plug(struct tegra_dp_priv *dp)
        return -EIO;
 }
 
+static int tegra_dc_dp_sink_out_of_sync(struct tegra_dp_priv *dp, u32 delay_ms)
+{
+       u8 dpcd_data;
+       int out_of_sync;
+       int ret;
+
+       debug("%s: delay=%d\n", __func__, delay_ms);
+       mdelay(delay_ms);
+       ret = tegra_dc_dp_dpcd_read(dp, DP_SINK_STATUS, &dpcd_data);
+       if (ret)
+               return ret;
+
+       out_of_sync = !(dpcd_data & DP_SINK_STATUS_PORT0_IN_SYNC);
+       if (out_of_sync)
+               debug("SINK receive port 0 out of sync, data=%x\n", dpcd_data);
+       else
+               debug("SINK is in synchronization\n");
+
+       return out_of_sync;
+}
+
+static int tegra_dc_dp_check_sink(struct tegra_dp_priv *dp,
+                                 struct tegra_dp_link_config *link_cfg,
+                                 const struct display_timing *timing)
+{
+       const int max_retry = 5;
+       int delay_frame;
+       int retries;
+
+       /*
+        * DP TCON may skip some main stream frames, thus we need to wait
+        * some delay before reading the DPCD SINK STATUS register, starting
+        * from 5
+        */
+       delay_frame = 5;
+
+       retries = max_retry;
+       do {
+               int ret;
+
+               if (!tegra_dc_dp_sink_out_of_sync(dp, link_cfg->frame_in_ms *
+                                                 delay_frame))
+                       return 0;
+
+               debug("%s: retries left %d\n", __func__, retries);
+               if (!retries--) {
+                       printf("DP: Out of sync after %d retries\n", max_retry);
+                       return -EIO;
+               }
+               ret = tegra_dc_sor_detach(dp->sor);
+               if (ret)
+                       return ret;
+               if (tegra_dc_dp_explore_link_cfg(dp, link_cfg, dp->sor,
+                                                timing)) {
+                       debug("dp: %s: error to configure link\n", __func__);
+                       continue;
+               }
+
+               tegra_dc_sor_set_power_state(dp->sor, 1);
+               tegra_dc_sor_attach(dp->sor, link_cfg, timing);
+
+               /* Increase delay_frame for next try in case the sink is
+                  skipping more frames */
+               delay_frame += 10;
+       } while (1);
+}
+
 int tegra_dp_enable(struct udevice *dev, int panel_bpp,
                    const struct display_timing *timing)
 {
@@ -1017,6 +1534,16 @@ int tegra_dp_enable(struct udevice *dev, int panel_bpp,
        if (ret && ret != -EEXIST)
                return ret;
 
+       /*
+        * This takes a long time, but can apparently resolve a failure to
+        * bring up the display correctly.
+        */
+       if (0) {
+               ret = tegra_dc_dp_check_sink(priv, link_cfg, timing);
+               if (ret)
+                       return ret;
+       }
+
        /* Power down the unused lanes to save power - a few hundred mW */
        tegra_dc_sor_power_down_unused_lanes(sor, link_cfg);
 
index faeda4cb7b37822038218b69f5d59ceef06596a8..aa3d80c4c0fd97944b8abd4ef96202a1ba0106c2 100644 (file)
@@ -57,6 +57,23 @@ static inline void tegra_sor_write_field(struct tegra_dc_sor_data *sor,
        tegra_sor_writel(sor, reg, reg_val);
 }
 
+void tegra_dp_disable_tx_pu(struct tegra_dc_sor_data *sor)
+{
+       tegra_sor_write_field(sor, DP_PADCTL(sor->portnum),
+                             DP_PADCTL_TX_PU_MASK, DP_PADCTL_TX_PU_DISABLE);
+}
+
+void tegra_dp_set_pe_vs_pc(struct tegra_dc_sor_data *sor, u32 mask, u32 pe_reg,
+                          u32 vs_reg, u32 pc_reg, u8 pc_supported)
+{
+       tegra_sor_write_field(sor, PR(sor->portnum), mask, pe_reg);
+       tegra_sor_write_field(sor, DC(sor->portnum), mask, vs_reg);
+       if (pc_supported) {
+               tegra_sor_write_field(sor, POSTCURSOR(sor->portnum), mask,
+                                     pc_reg);
+       }
+}
+
 static int tegra_dc_sor_poll_register(struct tegra_dc_sor_data *sor, u32 reg,
                                      u32 mask, u32 exp_val,
                                      int poll_interval_us, int timeout_ms)
@@ -808,12 +825,36 @@ void tegra_dc_sor_set_lane_parm(struct tegra_dc_sor_data *sor,
        tegra_sor_write_field(sor, DP_PADCTL(sor->portnum), 0xf0, 0x0);
 }
 
+int tegra_dc_sor_set_voltage_swing(struct tegra_dc_sor_data *sor,
+                                   const struct tegra_dp_link_config *link_cfg)
+{
+       u32 drive_current = 0;
+       u32 pre_emphasis = 0;
+
+       /* Set to a known-good pre-calibrated setting */
+       switch (link_cfg->link_bw) {
+       case SOR_LINK_SPEED_G1_62:
+       case SOR_LINK_SPEED_G2_7:
+               drive_current = 0x13131313;
+               pre_emphasis = 0;
+               break;
+       case SOR_LINK_SPEED_G5_4:
+               debug("T124 does not support 5.4G link clock.\n");
+       default:
+               debug("Invalid sor link bandwidth: %d\n", link_cfg->link_bw);
+               return -ENOLINK;
+       }
+
+       tegra_sor_writel(sor, LANE_DRIVE_CURRENT(sor->portnum), drive_current);
+       tegra_sor_writel(sor, PR(sor->portnum), pre_emphasis);
+
+       return 0;
+}
+
 void tegra_dc_sor_power_down_unused_lanes(struct tegra_dc_sor_data *sor,
                        const struct tegra_dp_link_config *link_cfg)
 {
        u32 pad_ctrl = 0;
-       u32 drive_current = 0;
-       u32 pre_emphasis = 0;
        int err = 0;
 
        switch (link_cfg->lane_count) {
@@ -848,25 +889,112 @@ void tegra_dc_sor_power_down_unused_lanes(struct tegra_dc_sor_data *sor,
                debug("Wait for lane power down failed: %d\n", err);
                return;
        }
+}
 
-       /* Set to a known-good pre-calibrated setting */
-       switch (link_cfg->link_bw) {
-       case SOR_LINK_SPEED_G1_62:
-       case SOR_LINK_SPEED_G2_7:
-               drive_current = 0x13131313;
-               pre_emphasis = 0;
+int tegra_sor_precharge_lanes(struct tegra_dc_sor_data *sor,
+                             const struct tegra_dp_link_config *cfg)
+{
+       u32 val = 0;
+
+       switch (cfg->lane_count) {
+       case 4:
+               val |= (DP_PADCTL_PD_TXD_3_NO |
+                       DP_PADCTL_PD_TXD_2_NO);
+               /* fall through */
+       case 2:
+               val |= DP_PADCTL_PD_TXD_1_NO;
+               /* fall through */
+       case 1:
+               val |= DP_PADCTL_PD_TXD_0_NO;
                break;
-       case SOR_LINK_SPEED_G5_4:
-               drive_current = 0x19191919;
-               pre_emphasis = 0x09090909;
        default:
-               printf("Invalid sor link bandwidth: %d\n", link_cfg->link_bw);
-               return;
+               debug("dp: invalid lane number %d\n", cfg->lane_count);
+               return -EINVAL;
        }
 
-       tegra_sor_writel(sor, LANE_DRIVE_CURRENT(sor->portnum),
-                        drive_current);
-       tegra_sor_writel(sor, PR(sor->portnum), pre_emphasis);
+       tegra_sor_write_field(sor, DP_PADCTL(sor->portnum),
+                             (0xf << DP_PADCTL_COMODE_TXD_0_DP_TXD_2_SHIFT),
+                             (val << DP_PADCTL_COMODE_TXD_0_DP_TXD_2_SHIFT));
+       udelay(100);
+       tegra_sor_write_field(sor, DP_PADCTL(sor->portnum),
+                             (0xf << DP_PADCTL_COMODE_TXD_0_DP_TXD_2_SHIFT),
+                             0);
+
+       return 0;
+}
+
+static void tegra_dc_sor_enable_sor(struct dc_ctlr *disp_ctrl, bool enable)
+{
+       u32 reg_val = readl(&disp_ctrl->disp.disp_win_opt);
+
+       reg_val = enable ? reg_val | SOR_ENABLE : reg_val & ~SOR_ENABLE;
+       writel(reg_val, &disp_ctrl->disp.disp_win_opt);
+}
+
+int tegra_dc_sor_detach(struct tegra_dc_sor_data *sor)
+{
+       int dc_reg_ctx[DC_REG_SAVE_SPACE];
+       const void *blob = gd->fdt_blob;
+       struct dc_ctlr *disp_ctrl;
+       unsigned long dc_int_mask;
+       int node;
+       int ret;
+
+       debug("%s\n", __func__);
+       /* Use the first display controller */
+       node = fdtdec_next_compatible(blob, 0, COMPAT_NVIDIA_TEGRA124_DC);
+       if (node < 0) {
+               ret = -ENOENT;
+               goto err;
+       }
+       disp_ctrl = (struct dc_ctlr *)fdtdec_get_addr(blob, node, "reg");
+
+       /* Sleep mode */
+       tegra_sor_writel(sor, SUPER_STATE1, SUPER_STATE1_ASY_HEAD_OP_SLEEP |
+                        SUPER_STATE1_ASY_ORMODE_SAFE |
+                        SUPER_STATE1_ATTACHED_YES);
+       tegra_dc_sor_super_update(sor);
+
+       tegra_dc_sor_disable_win_short_raster(disp_ctrl, dc_reg_ctx);
+
+       if (tegra_dc_sor_poll_register(sor, TEST,
+                                      TEST_ACT_HEAD_OPMODE_DEFAULT_MASK,
+                                      TEST_ACT_HEAD_OPMODE_SLEEP, 100,
+                                      TEGRA_SOR_ATTACH_TIMEOUT_MS)) {
+               debug("dc timeout waiting for OPMOD = SLEEP\n");
+               ret = -ETIMEDOUT;
+               goto err;
+       }
+
+       tegra_sor_writel(sor, SUPER_STATE1, SUPER_STATE1_ASY_HEAD_OP_SLEEP |
+                        SUPER_STATE1_ASY_ORMODE_SAFE |
+                        SUPER_STATE1_ATTACHED_NO);
+
+       /* Mask DC interrupts during the 2 dummy frames required for detach */
+       dc_int_mask = readl(&disp_ctrl->cmd.int_mask);
+       writel(0, &disp_ctrl->cmd.int_mask);
+
+       /* Stop DC->SOR path */
+       tegra_dc_sor_enable_sor(disp_ctrl, false);
+       ret = tegra_dc_sor_general_act(disp_ctrl);
+       if (ret)
+               goto err;
+
+       /* Stop DC */
+       writel(CTRL_MODE_STOP << CTRL_MODE_SHIFT, &disp_ctrl->cmd.disp_cmd);
+       ret = tegra_dc_sor_general_act(disp_ctrl);
+       if (ret)
+               goto err;
+
+       tegra_dc_sor_restore_win_and_raster(disp_ctrl, dc_reg_ctx);
+
+       writel(dc_int_mask, &disp_ctrl->cmd.int_mask);
+
+       return 0;
+err:
+       debug("%s: ret=%d\n", __func__, ret);
+
+       return ret;
 }
 
 int tegra_dc_sor_init(struct tegra_dc_sor_data **sorp)
index 7f1255c2b59f86281f70abe8b1c6ba41b6a96785..dc8fd03d808da491d141eb899fbbb96add664ee2 100644 (file)
@@ -848,6 +848,7 @@ struct tegra_dp_link_config {
        u32     bits_per_pixel;
        int     alt_scramber_reset_cap; /* true for eDP */
        int     only_enhanced_framing;  /* enhanced_frame_en ignored */
+       int     frame_in_ms;
 
        /* Actual configuration */
        u8      link_bw;
@@ -868,6 +869,8 @@ struct tegra_dp_link_config {
        u32     drive_current;
        u32     preemphasis;
        u32     postcursor;
+       u8      aux_rd_interval;
+       u8      tps3_supported;
 };
 
 struct tegra_dc_sor_data {
@@ -896,9 +899,24 @@ void tegra_dc_sor_set_lane_parm(struct tegra_dc_sor_data *sor,
                        const struct tegra_dp_link_config *link_cfg);
 void tegra_dc_sor_power_down_unused_lanes(struct tegra_dc_sor_data *sor,
                        const struct tegra_dp_link_config *link_cfg);
+int tegra_dc_sor_set_voltage_swing(struct tegra_dc_sor_data *sor,
+                               const struct tegra_dp_link_config *link_cfg);
+int tegra_sor_precharge_lanes(struct tegra_dc_sor_data *sor,
+                             const struct tegra_dp_link_config *cfg);
+void tegra_dp_disable_tx_pu(struct tegra_dc_sor_data *sor);
+void tegra_dp_set_pe_vs_pc(struct tegra_dc_sor_data *sor, u32 mask,
+                          u32 pe_reg, u32 vs_reg, u32 pc_reg, u8 pc_supported);
 
 int tegra_dc_sor_attach(struct tegra_dc_sor_data *sor,
                        const struct tegra_dp_link_config *link_cfg,
                        const struct display_timing *timing);
+int tegra_dc_sor_detach(struct tegra_dc_sor_data *sor);
+
+void tegra_dc_sor_disable_win_short_raster(struct dc_ctlr *disp_ctrl,
+                                          int *dc_reg_ctx);
+int tegra_dc_sor_general_act(struct dc_ctlr *disp_ctrl);
+void tegra_dc_sor_restore_win_and_raster(struct dc_ctlr *disp_ctrl,
+                                        int *dc_reg_ctx);
+
 int tegra_dc_sor_init(struct tegra_dc_sor_data **sorp);
 #endif
index 86b06e11ccbbeeae07a05ac8a71b7b55794c58b3..758e4a4015a728b68c8a882c0e81a4e6cb9f6b3e 100644 (file)
 #define DP_LINK_STATUS_UPDATED             (1 << 7)
 
 #define DP_SINK_STATUS                     0x205
+#define DP_SINK_STATUS_PORT0_IN_SYNC       (1 << 0)
 
 #define DP_RECEIVE_PORT_0_STATUS           (1 << 0)
 #define DP_RECEIVE_PORT_1_STATUS           (1 << 1)