From: Paulo Zanoni <paulo.r.zanoni at intel.com> Add functions to make it follow the mode set sequence. Nothing is using this code yet. We still lack proper link training and port detection. Part of this patch is based on a previous patch made by Shobhit Kumar. Credits-to: Shobhit Kumar <shobhit.kumar at intel.com> Signed-off-by: Shobhit Kumar <shobhit.kumar at intel.com> Signed-off-by: Paulo Zanoni <paulo.r.zanoni at intel.com> --- drivers/gpu/drm/i915/i915_reg.h | 22 +++ drivers/gpu/drm/i915/intel_ddi.c | 267 ++++++++++++++++++++++++++++++++++ drivers/gpu/drm/i915/intel_display.c | 4 +- drivers/gpu/drm/i915/intel_dp.c | 26 +++- drivers/gpu/drm/i915/intel_drv.h | 8 + 5 files changed, 323 insertions(+), 4 deletions(-) Ok, so here comes a big explanation. This patch is inspired by the following patch from Shobhit: [PATCH 03/21] drm/i915: Add DP Helper functions for Haswell But if you notice, there's a big difference. One of the main differences is that in Haswell mode set sequence we really must set the port registers before the pipe registers. If we disable the port registers and then try to change the pipe registers, we can even get a full machine hang. The previous patch by Shobhit was not following the mode set sequence that should be done in our hardware (even though it was working for the common case). This patch tries to write the pipe and port registers strictly in the order they are meant to be written. Writing the registers in the correct order leads to another problem: setting the port registers before the pipe register is the reverse order of what the current DRM abstractions do. What I did was to direclty call ironlake_crtc_{en,dis}able from inside the encoder code, which can be considered as "breaking the abstraction". I talked to Daniel today about this, and it seems he has a better solution for this in his head, so let's wait and see his suggestions... There is one MSA register per pipe, not a sigle one. Patch 03/21 called it HSW_MSA_CTL. I call it PIPE_MSA_MISC(pipe). This patch sets the sync polarity. This patch does not assume a 450MHz clock always. This patch does not assume 8BPC always. I removed the intel_dp->TP variable. I do recognize it was created in an analogous way than intel_dp->DP, but I think these variables just make the code harder to read (should I check/change/use the value written in the register or the value written in the variable? when should I update the register and when should I update the variable?). My goal is to try to remove intel_dp->DP in the future. There's no SSC. I'm not sure that "dpcd[DP_MAX_DOWNSPREAD] & 0x1 == 0x1" means we should be using SSC, especially since DisplayPort v1.1 says that this statement should always be true. There's no IS_HASWELL() on a function that only runs on Haswell. There's DPMS now (and it's done by intel_dp_dpms, not intel_ddi_dpms). I'm also starting to think that we maybe should move DP code from intel_ddi to intel_dp. This will allow a few more functions to be static and may even remove the need for the patch that moves "struct intel_dp" declaration from intel_dp.c to intel_drv.h. diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h index 8202d48..ce3fc2c 100644 --- a/drivers/gpu/drm/i915/i915_reg.h +++ b/drivers/gpu/drm/i915/i915_reg.h @@ -4284,6 +4284,7 @@ /* Those bits are ignored by pipe EDP since it can only connect to DDI A */ #define PIPE_DDI_PORT_MASK (7<<28) #define PIPE_DDI_SELECT_PORT(x) ((x)<<28) +#define PIPE_DDI_PORT_NONE (0<<28) #define PIPE_DDI_MODE_SELECT_HDMI (0<<24) #define PIPE_DDI_MODE_SELECT_DVI (1<<24) #define PIPE_DDI_MODE_SELECT_DP_SST (2<<24) @@ -4293,6 +4294,8 @@ #define PIPE_DDI_BPC_10 (1<<20) #define PIPE_DDI_BPC_6 (2<<20) #define PIPE_DDI_BPC_12 (3<<20) +#define PIPE_DDI_PVSYNC (1<<17) +#define PIPE_DDI_PHSYNC (1<<16) #define PIPE_DDI_BFI_ENABLE (1<<4) #define PIPE_DDI_PORT_WIDTH_X1 (0<<1) #define PIPE_DDI_PORT_WIDTH_X2 (1<<1) @@ -4313,6 +4316,7 @@ #define DP_TP_CTL_LINK_TRAIN_PAT1 (0<<8) #define DP_TP_CTL_LINK_TRAIN_PAT2 (1<<8) #define DP_TP_CTL_LINK_TRAIN_NORMAL (3<<8) +#define DP_TP_CTL_SCRAMBLE_DISABLE (1<<7) /* DisplayPort Transport Status */ #define DP_TP_STATUS_A 0x64044 @@ -4385,6 +4389,9 @@ #define PIXCLK_GATE_UNGATE 1<<0 #define PIXCLK_GATE_GATE 0<<0 +#define CDCLK_FREQ 0x46200 +#define CDCLK_FREQ_MASK 0x3FF + /* SPLL */ #define SPLL_CTL 0x46020 #define SPLL_PLL_ENABLE (1<<31) @@ -4417,6 +4424,7 @@ #define PORT_CLK_SEL_SPLL (3<<29) #define PORT_CLK_SEL_WRPLL1 (4<<29) #define PORT_CLK_SEL_WRPLL2 (5<<29) +#define PORT_CLK_SEL_NONE (7<<29) /* Pipe clock selection */ #define PIPE_CLK_SEL_A 0x46140 @@ -4428,12 +4436,26 @@ #define PIPE_CLK_SEL_DISABLED (0x0<<29) #define PIPE_CLK_SEL_PORT(x) ((x+1)<<29) +#define _PIPEA_MSA_MISC 0x60410 +#define _PIPEB_MSA_MISC 0x61410 +#define PIPE_MSA_MISC(pipe) _PIPE(pipe, _PIPEA_MSA_MISC, _PIPEB_MSA_MISC) +#define PIPE_MSA_SYNC_CLK (1<<0) +#define PIPE_MSA_6_BPC (0<<5) +#define PIPE_MSA_8_BPC (1<<5) +#define PIPE_MSA_10_BPC (2<<5) +#define PIPE_MSA_12_BPC (3<<5) +#define PIPE_MSA_16_BPC (3<<5) + /* LCPLL Control */ #define LCPLL_CTL 0x130040 #define LCPLL_PLL_DISABLE (1<<31) #define LCPLL_PLL_LOCK (1<<30) +#define LCPLL_NON_SSC_REF (0) +#define LCPLL_CD_FREQ_450MHZ (0) +#define LCPLL_CD_FREQ_540MHZ (1<<26) #define LCPLL_CD_CLOCK_DISABLE (1<<25) #define LCPLL_CD2X_CLOCK_DISABLE (1<<23) +#define LCPLL_CD_SOURCE_LCPLL (0) /* Pipe WM_LINETIME - watermark line time */ #define PIPE_WM_LINETIME_A 0x45270 diff --git a/drivers/gpu/drm/i915/intel_ddi.c b/drivers/gpu/drm/i915/intel_ddi.c index ac62330..c98a3a7 100644 --- a/drivers/gpu/drm/i915/intel_ddi.c +++ b/drivers/gpu/drm/i915/intel_ddi.c @@ -739,6 +739,148 @@ void intel_ddi_mode_set_hdmi(struct drm_encoder *encoder, intel_hdmi->set_infoframes(encoder, adjusted_mode); } +static void assert_ddi_dp_disabled(struct drm_encoder *encoder) +{ + struct drm_device *dev = encoder->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + int port = intel_dp->port; + uint32_t val; + + val = I915_READ(PORT_CLK_SEL(port)); + WARN(val != PORT_CLK_SEL_NONE, "Port clock select not 'None'\n"); + val = I915_READ(DDI_BUF_CTL(port)); + WARN(val & DDI_BUF_CTL_ENABLE, "DP DDI buffer enabled\n"); + val = I915_READ(DP_TP_CTL(port)); + WARN(val & DP_TP_CTL_ENABLE, "DP TP control enabled\n"); +} + + +static void intel_ddi_enable_dp_port(struct intel_dp *intel_dp) +{ + struct drm_device *dev = intel_dp->base.base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + int port = intel_dp->port; + uint32_t tp_val, tmp_val; + + switch (intel_dp->link_bw) { + case DP_LINK_BW_1_62: + tmp_val = PORT_CLK_SEL_LCPLL_810; + break; + case DP_LINK_BW_2_7: + tmp_val = PORT_CLK_SEL_LCPLL_1350; + break; + case DP_LINK_BW_5_4: + tmp_val = PORT_CLK_SEL_LCPLL_2700; + break; + default: + tmp_val = PORT_CLK_SEL_LCPLL_1350; + WARN(1, "Link bandwidth %d unsupported\n", intel_dp->link_bw); + } + I915_WRITE(PORT_CLK_SEL(port), tmp_val); + POSTING_READ(PORT_CLK_SEL(port)); + + tp_val = DP_TP_CTL_ENABLE | DP_TP_CTL_MODE_SST | + DP_TP_CTL_LINK_TRAIN_PAT1 | DP_TP_CTL_SCRAMBLE_DISABLE; + if (intel_dp->link_configuration[1] & DP_LANE_COUNT_ENHANCED_FRAME_EN) + tp_val |= DP_TP_CTL_ENHANCED_FRAME_ENABLE; + I915_WRITE(DP_TP_CTL(port), tp_val); + POSTING_READ(DP_TP_CTL(port)); + + intel_dp->DP |= DDI_BUF_CTL_ENABLE; + I915_WRITE(DDI_BUF_CTL(port), intel_dp->DP); + POSTING_READ(DDI_BUF_CTL(port)); + + udelay(600); +} + +static void intel_ddi_disable_dp_port(struct intel_dp *intel_dp) +{ + struct drm_device *dev = intel_dp->base.base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + int port = intel_dp->port; + uint32_t val; + + intel_dp->DP &= ~DDI_BUF_CTL_ENABLE; + I915_WRITE(DDI_BUF_CTL(port), intel_dp->DP); + POSTING_READ(DDI_BUF_CTL(port)); + + val = I915_READ(DP_TP_CTL(port)); + val &= ~(DP_TP_CTL_ENABLE | DP_TP_CTL_LINK_TRAIN_MASK); + val |= DP_TP_CTL_LINK_TRAIN_PAT1; + I915_WRITE(DP_TP_CTL(port), val); + POSTING_READ(DP_TP_CTL(port)); + + udelay(8); + + I915_WRITE(PORT_CLK_SEL(port), PORT_CLK_SEL_NONE); + POSTING_READ(PORT_CLK_SEL(port)); +} + +void intel_ddi_mode_set_dp(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode) +{ + struct drm_device *dev = encoder->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + int port = intel_dp->port; + uint32_t lcpll_val, tmp_val; + + DRM_DEBUG_KMS("Preparing DP DDI mode on port %c\n", port_name(port)); + + assert_ddi_dp_disabled(encoder); + + intel_dp->DP = DDI_BUF_CTL_ENABLE | DDI_BUF_EMP_400MV_0DB_HSW; + switch (intel_dp->lane_count) { + case 1: + intel_dp->DP |= DDI_PORT_WIDTH_X1; + break; + case 2: + intel_dp->DP |= DDI_PORT_WIDTH_X2; + break; + case 4: + intel_dp->DP |= DDI_PORT_WIDTH_X4; + break; + default: + intel_dp->DP |= DDI_PORT_WIDTH_X4; + WARN(1, "Unexpected DP lane_count %d\n", intel_dp->lane_count); + break; + } + + if (intel_dp->has_audio) + DRM_DEBUG_DRIVER("HSW DP audio not supported yet.\n"); + + memset(intel_dp->link_configuration, 0, DP_LINK_CONFIGURATION_SIZE); + intel_dp->link_configuration[0] = intel_dp->link_bw; + intel_dp->link_configuration[1] = intel_dp->lane_count; + intel_dp->link_configuration[8] = DP_SET_ANSI_8B10B; + /* + * Check for DPCD version > 1.1 and enhanced framing support + */ + if (intel_dp->dpcd[DP_DPCD_REV] >= 0x11 && + (intel_dp->dpcd[DP_MAX_LANE_COUNT] & DP_ENHANCED_FRAME_CAP)) + intel_dp->link_configuration[1] |= DP_LANE_COUNT_ENHANCED_FRAME_EN; + + lcpll_val = LCPLL_NON_SSC_REF | LCPLL_CD_SOURCE_LCPLL; + + tmp_val = I915_READ(CDCLK_FREQ) & CDCLK_FREQ_MASK; + if (tmp_val == 449) { + lcpll_val |= LCPLL_CD_FREQ_450MHZ; + } else if (tmp_val == 539) { + lcpll_val |= LCPLL_CD_FREQ_540MHZ; + } else { + DRM_ERROR("CDCLK_FREQ is neither 450MHz nor 540MHz\n"); + lcpll_val |= LCPLL_CD_FREQ_450MHZ; + } + + I915_WRITE(LCPLL_CTL, lcpll_val); + POSTING_READ(LCPLL_CTL); + udelay(20); + + intel_ddi_enable_dp_port(intel_dp); +} + void intel_ddi_dpms(struct drm_encoder *encoder, int mode) { struct drm_device *dev = encoder->dev; @@ -762,3 +904,128 @@ void intel_ddi_dpms(struct drm_encoder *encoder, int mode) I915_WRITE(DDI_BUF_CTL(port), temp); } + +void intel_ddi_enable_dp_pipe(struct drm_encoder *encoder) +{ + struct intel_dp *intel_dp = enc_to_intel_dp(encoder); + struct drm_device *dev = encoder->dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct drm_crtc *crtc = encoder->crtc; + struct intel_crtc *intel_crtc = to_intel_crtc(crtc); + int port = intel_dp->port; + int pipe = intel_crtc->pipe; + uint32_t func_val, msa_val; + + I915_WRITE(PIPE_CLK_SEL(intel_crtc->pipe), + PIPE_CLK_SEL_PORT(intel_dp->port)); + + func_val = PIPE_DDI_FUNC_ENABLE | PIPE_DDI_SELECT_PORT(port) | + PIPE_DDI_MODE_SELECT_DP_SST; + msa_val = PIPE_MSA_SYNC_CLK; + + switch (intel_crtc->bpp) { + case 18: + func_val |= PIPE_DDI_BPC_6; + msa_val |= PIPE_MSA_6_BPC; + break; + case 24: + func_val |= PIPE_DDI_BPC_8; + msa_val |= PIPE_MSA_8_BPC; + break; + case 30: + func_val |= PIPE_DDI_BPC_10; + msa_val |= PIPE_MSA_10_BPC; + break; + case 36: + func_val |= PIPE_DDI_BPC_12; + msa_val |= PIPE_MSA_12_BPC; + break; + default: + WARN(1, "BPP %d unsupported by pipe DDI function\n", + intel_crtc->bpp); + } + + if (crtc->mode.flags & DRM_MODE_FLAG_PVSYNC) + func_val |= PIPE_DDI_PVSYNC; + if (crtc->mode.flags & DRM_MODE_FLAG_PHSYNC) + func_val |= PIPE_DDI_PHSYNC; + + switch (intel_dp->lane_count) { + case 1: + func_val |= PIPE_DDI_PORT_WIDTH_X1; + break; + case 2: + func_val |= PIPE_DDI_PORT_WIDTH_X2; + break; + case 4: + func_val |= PIPE_DDI_PORT_WIDTH_X4; + break; + default: + WARN(1, "Unsupported lane count %d\n", intel_dp->lane_count); + } + + I915_WRITE(PIPE_MSA_MISC(pipe), msa_val); + I915_WRITE(DDI_FUNC_CTL(pipe), func_val); +} + +static void intel_ddi_disable_dp_pipe(struct drm_encoder *encoder) +{ + struct drm_i915_private *dev_priv = encoder->dev->dev_private; + struct intel_crtc *intel_crtc = to_intel_crtc(encoder->crtc); + int pipe = intel_crtc->pipe; + uint32_t val; + + val = I915_READ(DDI_FUNC_CTL(pipe)); + val &= ~(PIPE_DDI_FUNC_ENABLE | PIPE_DDI_PORT_MASK); + val |= PIPE_DDI_PORT_NONE; + I915_WRITE(DDI_FUNC_CTL(pipe), val); + + I915_WRITE(PIPE_CLK_SEL(pipe), PIPE_CLK_SEL_DISABLED); +} + + +/* If we disable PORT_CLK_SEL without disabling the pipe first, when we write to + * PIPECONF we'll freeze the machine. Make sure this won't happen. */ +static void intel_ddi_dp_disable_attached_pipes(struct intel_dp *intel_dp) +{ + struct drm_device *dev = intel_dp->base.base.dev; + struct drm_i915_private *dev_priv = dev->dev_private; + struct drm_crtc *crtc; + int port = intel_dp->port; + int pipe; + uint32_t val; + + for (pipe = 0; pipe < I915_MAX_PIPES; pipe++) { + val = I915_READ(DDI_FUNC_CTL(pipe)); + + if ((val & PIPE_DDI_PORT_MASK) == PIPE_DDI_SELECT_PORT(port)) { + DRM_DEBUG_KMS("Disabling pipe %c\n", pipe_name(pipe)); + crtc = dev_priv->pipe_to_crtc_mapping[pipe]; + ironlake_crtc_disable(crtc); + + val = I915_READ(DDI_FUNC_CTL(pipe)); + val &= ~(PIPE_DDI_FUNC_ENABLE | PIPE_DDI_PORT_MASK); + val |= PIPE_DDI_PORT_NONE; + I915_WRITE(DDI_FUNC_CTL(pipe), val); + + I915_WRITE(PIPE_CLK_SEL(pipe), PIPE_CLK_SEL_DISABLED); + } + } +} + +void intel_ddi_dp_link_down(struct intel_dp *intel_dp) +{ + struct drm_encoder *encoder = &intel_dp->base.base; + + /* If there's no CRTC it means we may be initializing the driver and we + * just don't know which pipes might be tied to this DP port, so we just + * disable everything to prevent hanging the machine. */ + if (!encoder->crtc) { + intel_ddi_dp_disable_attached_pipes(intel_dp); + return; + } + + ironlake_crtc_disable(encoder->crtc); + intel_ddi_disable_dp_pipe(encoder); + intel_ddi_disable_dp_port(intel_dp); +} diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index d6a3813..1475b18 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -3229,7 +3229,7 @@ void intel_cpt_verify_modeset(struct drm_device *dev, int pipe) } } -static void ironlake_crtc_enable(struct drm_crtc *crtc) +void ironlake_crtc_enable(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; struct drm_i915_private *dev_priv = dev->dev_private; @@ -3289,7 +3289,7 @@ static void ironlake_crtc_enable(struct drm_crtc *crtc) intel_crtc_update_cursor(crtc, true); } -static void ironlake_crtc_disable(struct drm_crtc *crtc) +void ironlake_crtc_disable(struct drm_crtc *crtc) { struct drm_device *dev = crtc->dev; struct drm_i915_private *dev_priv = dev->dev_private; diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c index a5aee26..2a8bf47 100644 --- a/drivers/gpu/drm/i915/intel_dp.c +++ b/drivers/gpu/drm/i915/intel_dp.c @@ -76,7 +76,7 @@ static bool is_cpu_edp(struct intel_dp *intel_dp) return is_edp(intel_dp) && !is_pch_edp(intel_dp); } -static struct intel_dp *enc_to_intel_dp(struct drm_encoder *encoder) +struct intel_dp *enc_to_intel_dp(struct drm_encoder *encoder) { return container_of(encoder, struct intel_dp, base.base); } @@ -1282,6 +1282,9 @@ static void intel_dp_commit(struct drm_encoder *encoder) intel_dp->dpms_mode = DRM_MODE_DPMS_ON; + if (IS_HASWELL(dev)) + intel_ddi_enable_dp_pipe(encoder); + if (HAS_PCH_CPT(dev)) intel_cpt_verify_modeset(dev, intel_crtc->pipe); } @@ -1884,6 +1887,11 @@ intel_dp_link_down(struct intel_dp *intel_dp) struct drm_i915_private *dev_priv = dev->dev_private; uint32_t DP = intel_dp->DP; + if (IS_HASWELL(dev)) { + intel_ddi_dp_link_down(intel_dp); + return; + } + if ((I915_READ(intel_dp->output_reg) & DP_PORT_EN) == 0) return; @@ -2358,6 +2366,14 @@ static const struct drm_encoder_helper_funcs intel_dp_helper_funcs = { .commit = intel_dp_commit, }; +static const struct drm_encoder_helper_funcs intel_dp_helper_funcs_hsw = { + .dpms = intel_dp_dpms, + .mode_fixup = intel_dp_mode_fixup, + .prepare = intel_dp_prepare, + .mode_set = intel_ddi_mode_set_dp, + .commit = intel_dp_commit, +}; + static const struct drm_connector_funcs intel_dp_connector_funcs = { .dpms = drm_helper_connector_dpms, .detect = intel_dp_detect, @@ -2501,7 +2517,13 @@ intel_dp_init(struct drm_device *dev, int output_reg) drm_encoder_init(dev, &intel_encoder->base, &intel_dp_enc_funcs, DRM_MODE_ENCODER_TMDS); - drm_encoder_helper_add(&intel_encoder->base, &intel_dp_helper_funcs); + + if (IS_HASWELL(dev)) + drm_encoder_helper_add(&intel_encoder->base, + &intel_dp_helper_funcs_hsw); + else + drm_encoder_helper_add(&intel_encoder->base, + &intel_dp_helper_funcs); intel_connector_attach_encoder(intel_connector, intel_encoder); drm_sysfs_connector_add(connector); diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h index 1145403..623d16c 100644 --- a/drivers/gpu/drm/i915/intel_drv.h +++ b/drivers/gpu/drm/i915/intel_drv.h @@ -387,6 +387,7 @@ extern void intel_mark_busy(struct drm_device *dev, struct drm_i915_gem_object *obj); extern bool intel_lvds_init(struct drm_device *dev); extern void intel_dp_init(struct drm_device *dev, int dp_reg); +extern struct intel_dp *enc_to_intel_dp(struct drm_encoder *encoder); void intel_dp_set_m_n(struct drm_crtc *crtc, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode); @@ -438,6 +439,8 @@ int intel_get_pipe_from_crtc_id(struct drm_device *dev, void *data, struct drm_file *file_priv); extern void intel_wait_for_vblank(struct drm_device *dev, int pipe); extern void intel_wait_for_pipe_off(struct drm_device *dev, int pipe); +extern void ironlake_crtc_enable(struct drm_crtc *crtc); +extern void ironlake_crtc_disable(struct drm_crtc *crtc); struct intel_load_detect_pipe { struct drm_framebuffer *release_fb; @@ -531,5 +534,10 @@ extern void intel_ddi_dpms(struct drm_encoder *encoder, int mode); extern void intel_ddi_mode_set(struct drm_encoder *encoder, struct drm_display_mode *mode, struct drm_display_mode *adjusted_mode); +extern void intel_ddi_mode_set_dp(struct drm_encoder *encoder, + struct drm_display_mode *mode, + struct drm_display_mode *adjusted_mode); +extern void intel_ddi_enable_dp_pipe(struct drm_encoder *encoder); +extern void intel_ddi_dp_link_down(struct intel_dp *intel_dp); #endif /* __INTEL_DRV_H__ */ -- 1.7.10.2