linux/drivers/gpu/drm/i915/display/g4x_hdmi.c

789 lines
23 KiB
C
Raw Permalink Normal View History

// SPDX-License-Identifier: MIT
/*
* Copyright © 2020 Intel Corporation
*
* HDMI support for G4x,ILK,SNB,IVB,VLV,CHV (HSW+ handled by the DDI code).
*/
#include <drm/drm_print.h>
#include "g4x_hdmi.h"
#include "i915_reg.h"
#include "intel_atomic.h"
#include "intel_audio.h"
#include "intel_connector.h"
#include "intel_crtc.h"
#include "intel_de.h"
#include "intel_display_power.h"
#include "intel_display_regs.h"
#include "intel_display_types.h"
#include "intel_dp_aux.h"
#include "intel_dpio_phy.h"
drm/i915/fdi: Improve FDI BW sharing between pipe B and C At the moment modesetting pipe C on IVB will fail if pipe B uses 4 FDI lanes. Make the BW sharing more dynamic by trying to reduce pipe B's link bpp in this case, until pipe B uses only up to 2 FDI lanes. For this instead of the encoder compute config retry loop - which reduced link bpp only for the encoder's pipe - reduce the maximum link bpp for pipe B/C as required after all CRTC states are computed and recompute the CRTC states with the new bpp limit. Atm, all FDI encoder's compute config function returns an error if a BW constrain prevents increasing the pipe bpp value. The corresponding crtc_state->bw_constrained check can be replaced with checking crtc_state->max_link_bpp_x16, add TODO comments for this. SDVO is an exception where this case is only handled in the outer config retry loop, failing the modeset with a WARN, add a FIXME comment to handle this in the encoder code similarly to other encoders. v2: - Don't assume that a CRTC is already in the atomic state, while reducing its link bpp. - Add DocBook description to intel_fdi_atomic_check_link(). v3: - Enable BW management for FDI links in a separate patch. (Ville) v4: (Ville) - Fail the SDVO encoder config computation if it doesn't support the link bpp limit. - Add TODO: comments about checking link_bpp_x16 instead of bw_constrained. v5: - Replace link bpp limit check with a FIXME: comment in intel_sdvo_compute_config(). (Ville) Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> [Amended commit message wrt. changes in v5] Signed-off-by: Imre Deak <imre.deak@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20230921195159.2646027-11-imre.deak@intel.com
2023-09-21 22:51:58 +03:00
#include "intel_fdi.h"
#include "intel_fifo_underrun.h"
#include "intel_hdmi.h"
#include "intel_hotplug.h"
#include "intel_sdvo.h"
static void intel_hdmi_prepare(struct intel_encoder *encoder,
const struct intel_crtc_state *crtc_state)
{
struct intel_display *display = to_intel_display(encoder);
struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
const struct drm_display_mode *adjusted_mode = &crtc_state->hw.adjusted_mode;
u32 hdmi_val;
intel_dp_dual_mode_set_tmds_output(intel_hdmi, true);
hdmi_val = SDVO_ENCODING_HDMI;
if (!HAS_PCH_SPLIT(display) && crtc_state->limited_color_range)
hdmi_val |= HDMI_COLOR_RANGE_16_235;
if (adjusted_mode->flags & DRM_MODE_FLAG_PVSYNC)
hdmi_val |= SDVO_VSYNC_ACTIVE_HIGH;
if (adjusted_mode->flags & DRM_MODE_FLAG_PHSYNC)
hdmi_val |= SDVO_HSYNC_ACTIVE_HIGH;
if (crtc_state->pipe_bpp > 24)
hdmi_val |= HDMI_COLOR_FORMAT_12bpc;
else
hdmi_val |= SDVO_COLOR_FORMAT_8bpc;
if (crtc_state->has_hdmi_sink)
hdmi_val |= HDMI_MODE_SELECT_HDMI;
if (HAS_PCH_CPT(display))
hdmi_val |= SDVO_PIPE_SEL_CPT(crtc->pipe);
else if (display->platform.cherryview)
hdmi_val |= SDVO_PIPE_SEL_CHV(crtc->pipe);
else
hdmi_val |= SDVO_PIPE_SEL(crtc->pipe);
intel_de_write(display, intel_hdmi->hdmi_reg, hdmi_val);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
}
static bool intel_hdmi_get_hw_state(struct intel_encoder *encoder,
enum pipe *pipe)
{
struct intel_display *display = to_intel_display(encoder);
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
intel_wakeref_t wakeref;
bool ret;
wakeref = intel_display_power_get_if_enabled(display,
encoder->power_domain);
if (!wakeref)
return false;
ret = intel_sdvo_port_enabled(display, intel_hdmi->hdmi_reg, pipe);
intel_display_power_put(display, encoder->power_domain, wakeref);
return ret;
}
static bool connector_is_hdmi(struct drm_connector *connector)
{
struct intel_encoder *encoder =
intel_attached_encoder(to_intel_connector(connector));
return encoder && encoder->type == INTEL_OUTPUT_HDMI;
}
static bool g4x_compute_has_hdmi_sink(struct intel_atomic_state *state,
struct intel_crtc *this_crtc)
{
const struct drm_connector_state *conn_state;
struct drm_connector *connector;
int i;
/*
* On g4x only one HDMI port can transmit infoframes/audio at
* any given time. Select the first suitable port for this duty.
*
* See also g4x_hdmi_connector_atomic_check().
*/
for_each_new_connector_in_state(&state->base, connector, conn_state, i) {
struct intel_encoder *encoder = to_intel_encoder(conn_state->best_encoder);
const struct intel_crtc_state *crtc_state;
struct intel_crtc *crtc;
if (!connector_is_hdmi(connector))
continue;
crtc = to_intel_crtc(conn_state->crtc);
if (!crtc)
continue;
crtc_state = intel_atomic_get_new_crtc_state(state, crtc);
if (!intel_hdmi_compute_has_hdmi_sink(encoder, crtc_state, conn_state))
continue;
return crtc == this_crtc;
}
return false;
}
static int g4x_hdmi_compute_config(struct intel_encoder *encoder,
struct intel_crtc_state *crtc_state,
struct drm_connector_state *conn_state)
{
struct intel_display *display = to_intel_display(encoder);
struct intel_atomic_state *state = to_intel_atomic_state(crtc_state->uapi.state);
struct intel_crtc *crtc = to_intel_crtc(crtc_state->uapi.crtc);
if (HAS_PCH_SPLIT(display)) {
crtc_state->has_pch_encoder = true;
drm/i915/fdi: Improve FDI BW sharing between pipe B and C At the moment modesetting pipe C on IVB will fail if pipe B uses 4 FDI lanes. Make the BW sharing more dynamic by trying to reduce pipe B's link bpp in this case, until pipe B uses only up to 2 FDI lanes. For this instead of the encoder compute config retry loop - which reduced link bpp only for the encoder's pipe - reduce the maximum link bpp for pipe B/C as required after all CRTC states are computed and recompute the CRTC states with the new bpp limit. Atm, all FDI encoder's compute config function returns an error if a BW constrain prevents increasing the pipe bpp value. The corresponding crtc_state->bw_constrained check can be replaced with checking crtc_state->max_link_bpp_x16, add TODO comments for this. SDVO is an exception where this case is only handled in the outer config retry loop, failing the modeset with a WARN, add a FIXME comment to handle this in the encoder code similarly to other encoders. v2: - Don't assume that a CRTC is already in the atomic state, while reducing its link bpp. - Add DocBook description to intel_fdi_atomic_check_link(). v3: - Enable BW management for FDI links in a separate patch. (Ville) v4: (Ville) - Fail the SDVO encoder config computation if it doesn't support the link bpp limit. - Add TODO: comments about checking link_bpp_x16 instead of bw_constrained. v5: - Replace link bpp limit check with a FIXME: comment in intel_sdvo_compute_config(). (Ville) Cc: Ville Syrjälä <ville.syrjala@linux.intel.com> Reviewed-by: Ville Syrjälä <ville.syrjala@linux.intel.com> [Amended commit message wrt. changes in v5] Signed-off-by: Imre Deak <imre.deak@intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20230921195159.2646027-11-imre.deak@intel.com
2023-09-21 22:51:58 +03:00
if (!intel_fdi_compute_pipe_bpp(crtc_state))
return -EINVAL;
}
if (display->platform.g4x)
crtc_state->has_hdmi_sink = g4x_compute_has_hdmi_sink(state, crtc);
else
crtc_state->has_hdmi_sink =
intel_hdmi_compute_has_hdmi_sink(encoder, crtc_state, conn_state);
return intel_hdmi_compute_config(encoder, crtc_state, conn_state);
}
static void intel_hdmi_get_config(struct intel_encoder *encoder,
struct intel_crtc_state *pipe_config)
{
struct intel_display *display = to_intel_display(encoder);
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
u32 tmp, flags = 0;
int dotclock;
pipe_config->output_types |= BIT(INTEL_OUTPUT_HDMI);
tmp = intel_de_read(display, intel_hdmi->hdmi_reg);
if (tmp & SDVO_HSYNC_ACTIVE_HIGH)
flags |= DRM_MODE_FLAG_PHSYNC;
else
flags |= DRM_MODE_FLAG_NHSYNC;
if (tmp & SDVO_VSYNC_ACTIVE_HIGH)
flags |= DRM_MODE_FLAG_PVSYNC;
else
flags |= DRM_MODE_FLAG_NVSYNC;
if (tmp & HDMI_MODE_SELECT_HDMI)
pipe_config->has_hdmi_sink = true;
pipe_config->infoframes.enable |=
intel_hdmi_infoframes_enabled(encoder, pipe_config);
if (pipe_config->infoframes.enable)
pipe_config->has_infoframe = true;
if (tmp & HDMI_AUDIO_ENABLE)
pipe_config->has_audio = true;
if (!HAS_PCH_SPLIT(display) &&
tmp & HDMI_COLOR_RANGE_16_235)
pipe_config->limited_color_range = true;
pipe_config->hw.adjusted_mode.flags |= flags;
if ((tmp & SDVO_COLOR_FORMAT_MASK) == HDMI_COLOR_FORMAT_12bpc)
dotclock = DIV_ROUND_CLOSEST(pipe_config->port_clock * 2, 3);
else
dotclock = pipe_config->port_clock;
if (pipe_config->pixel_multiplier)
dotclock /= pipe_config->pixel_multiplier;
pipe_config->hw.adjusted_mode.crtc_clock = dotclock;
pipe_config->lane_count = 4;
intel_hdmi_read_gcp_infoframe(encoder, pipe_config);
intel_read_infoframe(encoder, pipe_config,
HDMI_INFOFRAME_TYPE_AVI,
&pipe_config->infoframes.avi);
intel_read_infoframe(encoder, pipe_config,
HDMI_INFOFRAME_TYPE_SPD,
&pipe_config->infoframes.spd);
intel_read_infoframe(encoder, pipe_config,
HDMI_INFOFRAME_TYPE_VENDOR,
&pipe_config->infoframes.hdmi);
intel_audio_codec_get_config(encoder, pipe_config);
}
static void g4x_hdmi_enable_port(struct intel_encoder *encoder,
const struct intel_crtc_state *pipe_config)
{
struct intel_display *display = to_intel_display(encoder);
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
u32 temp;
temp = intel_de_read(display, intel_hdmi->hdmi_reg);
temp |= SDVO_ENABLE;
intel_de_write(display, intel_hdmi->hdmi_reg, temp);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
}
static void g4x_hdmi_audio_enable(struct intel_encoder *encoder,
const struct intel_crtc_state *crtc_state,
const struct drm_connector_state *conn_state)
{
struct intel_display *display = to_intel_display(encoder);
struct intel_hdmi *hdmi = enc_to_intel_hdmi(encoder);
if (!crtc_state->has_audio)
return;
drm_WARN_ON(display->drm, !crtc_state->has_hdmi_sink);
/* Enable audio presence detect */
intel_de_rmw(display, hdmi->hdmi_reg, 0, HDMI_AUDIO_ENABLE);
intel_audio_codec_enable(encoder, crtc_state, conn_state);
}
static void g4x_hdmi_audio_disable(struct intel_encoder *encoder,
const struct intel_crtc_state *old_crtc_state,
const struct drm_connector_state *old_conn_state)
{
struct intel_display *display = to_intel_display(encoder);
struct intel_hdmi *hdmi = enc_to_intel_hdmi(encoder);
if (!old_crtc_state->has_audio)
return;
intel_audio_codec_disable(encoder, old_crtc_state, old_conn_state);
/* Disable audio presence detect */
intel_de_rmw(display, hdmi->hdmi_reg, HDMI_AUDIO_ENABLE, 0);
}
static void g4x_enable_hdmi(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *pipe_config,
const struct drm_connector_state *conn_state)
{
g4x_hdmi_enable_port(encoder, pipe_config);
}
static void ibx_enable_hdmi(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *pipe_config,
const struct drm_connector_state *conn_state)
{
struct intel_display *display = to_intel_display(encoder);
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
u32 temp;
temp = intel_de_read(display, intel_hdmi->hdmi_reg);
temp |= SDVO_ENABLE;
/*
* HW workaround, need to write this twice for issue
* that may result in first write getting masked.
*/
intel_de_write(display, intel_hdmi->hdmi_reg, temp);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
intel_de_write(display, intel_hdmi->hdmi_reg, temp);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
/*
* HW workaround, need to toggle enable bit off and on
* for 12bpc with pixel repeat.
*
* FIXME: BSpec says this should be done at the end of
* the modeset sequence, so not sure if this isn't too soon.
*/
if (pipe_config->pipe_bpp > 24 &&
pipe_config->pixel_multiplier > 1) {
intel_de_write(display, intel_hdmi->hdmi_reg,
temp & ~SDVO_ENABLE);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
/*
* HW workaround, need to write this twice for issue
* that may result in first write getting masked.
*/
intel_de_write(display, intel_hdmi->hdmi_reg, temp);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
intel_de_write(display, intel_hdmi->hdmi_reg, temp);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
}
}
static void cpt_enable_hdmi(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *pipe_config,
const struct drm_connector_state *conn_state)
{
struct intel_display *display = to_intel_display(encoder);
struct intel_crtc *crtc = to_intel_crtc(pipe_config->uapi.crtc);
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
enum pipe pipe = crtc->pipe;
u32 temp;
temp = intel_de_read(display, intel_hdmi->hdmi_reg);
temp |= SDVO_ENABLE;
/*
* WaEnableHDMI8bpcBefore12bpc:snb,ivb
*
* The procedure for 12bpc is as follows:
* 1. disable HDMI clock gating
* 2. enable HDMI with 8bpc
* 3. enable HDMI with 12bpc
* 4. enable HDMI clock gating
*/
if (pipe_config->pipe_bpp > 24) {
intel_de_rmw(display, TRANS_CHICKEN1(pipe),
0, TRANS_CHICKEN1_HDMIUNIT_GC_DISABLE);
temp &= ~SDVO_COLOR_FORMAT_MASK;
temp |= SDVO_COLOR_FORMAT_8bpc;
}
intel_de_write(display, intel_hdmi->hdmi_reg, temp);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
if (pipe_config->pipe_bpp > 24) {
temp &= ~SDVO_COLOR_FORMAT_MASK;
temp |= HDMI_COLOR_FORMAT_12bpc;
intel_de_write(display, intel_hdmi->hdmi_reg, temp);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
intel_de_rmw(display, TRANS_CHICKEN1(pipe),
TRANS_CHICKEN1_HDMIUNIT_GC_DISABLE, 0);
}
}
static void vlv_enable_hdmi(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *pipe_config,
const struct drm_connector_state *conn_state)
{
}
static void intel_disable_hdmi(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *old_crtc_state,
const struct drm_connector_state *old_conn_state)
{
struct intel_display *display = to_intel_display(encoder);
struct intel_hdmi *intel_hdmi = enc_to_intel_hdmi(encoder);
struct intel_digital_port *dig_port =
hdmi_to_dig_port(intel_hdmi);
struct intel_crtc *crtc = to_intel_crtc(old_crtc_state->uapi.crtc);
u32 temp;
temp = intel_de_read(display, intel_hdmi->hdmi_reg);
temp &= ~SDVO_ENABLE;
intel_de_write(display, intel_hdmi->hdmi_reg, temp);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
/*
* HW workaround for IBX, we need to move the port
* to transcoder A after disabling it to allow the
* matching DP port to be enabled on transcoder A.
*/
if (HAS_PCH_IBX(display) && crtc->pipe == PIPE_B) {
/*
* We get CPU/PCH FIFO underruns on the other pipe when
* doing the workaround. Sweep them under the rug.
*/
intel_set_cpu_fifo_underrun_reporting(display, PIPE_A, false);
intel_set_pch_fifo_underrun_reporting(display, PIPE_A, false);
temp &= ~SDVO_PIPE_SEL_MASK;
temp |= SDVO_ENABLE | SDVO_PIPE_SEL(PIPE_A);
/*
* HW workaround, need to write this twice for issue
* that may result in first write getting masked.
*/
intel_de_write(display, intel_hdmi->hdmi_reg, temp);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
intel_de_write(display, intel_hdmi->hdmi_reg, temp);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
temp &= ~SDVO_ENABLE;
intel_de_write(display, intel_hdmi->hdmi_reg, temp);
intel_de_posting_read(display, intel_hdmi->hdmi_reg);
intel_wait_for_vblank_if_active(display, PIPE_A);
intel_set_cpu_fifo_underrun_reporting(display, PIPE_A, true);
intel_set_pch_fifo_underrun_reporting(display, PIPE_A, true);
}
dig_port->set_infoframes(encoder,
false,
old_crtc_state, old_conn_state);
intel_dp_dual_mode_set_tmds_output(intel_hdmi, false);
}
static void g4x_disable_hdmi(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *old_crtc_state,
const struct drm_connector_state *old_conn_state)
{
intel_disable_hdmi(state, encoder, old_crtc_state, old_conn_state);
}
static void pch_disable_hdmi(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *old_crtc_state,
const struct drm_connector_state *old_conn_state)
{
}
static void pch_post_disable_hdmi(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *old_crtc_state,
const struct drm_connector_state *old_conn_state)
{
intel_disable_hdmi(state, encoder, old_crtc_state, old_conn_state);
}
static void intel_hdmi_pre_enable(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *pipe_config,
const struct drm_connector_state *conn_state)
{
struct intel_digital_port *dig_port =
enc_to_dig_port(encoder);
intel_hdmi_prepare(encoder, pipe_config);
dig_port->set_infoframes(encoder,
pipe_config->has_infoframe,
pipe_config, conn_state);
}
static void vlv_hdmi_pre_enable(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *pipe_config,
const struct drm_connector_state *conn_state)
{
struct intel_digital_port *dig_port = enc_to_dig_port(encoder);
vlv_phy_pre_encoder_enable(encoder, pipe_config);
/* HDMI 1.0V-2dB */
vlv_set_phy_signal_level(encoder, pipe_config,
0x2b245f5f, 0x00002000,
0x5578b83a, 0x2b247878);
dig_port->set_infoframes(encoder,
pipe_config->has_infoframe,
pipe_config, conn_state);
g4x_hdmi_enable_port(encoder, pipe_config);
vlv_wait_port_ready(encoder, 0x0);
}
static void vlv_hdmi_pre_pll_enable(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *pipe_config,
const struct drm_connector_state *conn_state)
{
intel_hdmi_prepare(encoder, pipe_config);
vlv_phy_pre_pll_enable(encoder, pipe_config);
}
static void chv_hdmi_pre_pll_enable(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *pipe_config,
const struct drm_connector_state *conn_state)
{
intel_hdmi_prepare(encoder, pipe_config);
chv_phy_pre_pll_enable(encoder, pipe_config);
}
static void chv_hdmi_post_pll_disable(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *old_crtc_state,
const struct drm_connector_state *old_conn_state)
{
chv_phy_post_pll_disable(encoder, old_crtc_state);
}
static void vlv_hdmi_post_disable(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *old_crtc_state,
const struct drm_connector_state *old_conn_state)
{
/* Reset lanes to avoid HDMI flicker (VLV w/a) */
vlv_phy_reset_lanes(encoder, old_crtc_state);
}
static void chv_hdmi_post_disable(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *old_crtc_state,
const struct drm_connector_state *old_conn_state)
{
/* Assert data lane reset */
chv_data_lane_soft_reset(encoder, old_crtc_state, true);
}
static void chv_hdmi_pre_enable(struct intel_atomic_state *state,
struct intel_encoder *encoder,
const struct intel_crtc_state *pipe_config,
const struct drm_connector_state *conn_state)
{
struct intel_digital_port *dig_port = enc_to_dig_port(encoder);
chv_phy_pre_encoder_enable(encoder, pipe_config);
/* FIXME: Program the support xxx V-dB */
/* Use 800mV-0dB */
chv_set_phy_signal_level(encoder, pipe_config, 128, 102, false);
dig_port->set_infoframes(encoder,
pipe_config->has_infoframe,
pipe_config, conn_state);
g4x_hdmi_enable_port(encoder, pipe_config);
vlv_wait_port_ready(encoder, 0x0);
/* Second common lane will stay alive on its own now */
chv_phy_release_cl2_override(encoder);
}
static const struct drm_encoder_funcs intel_hdmi_enc_funcs = {
.destroy = intel_encoder_destroy,
};
static enum intel_hotplug_state
intel_hdmi_hotplug(struct intel_encoder *encoder,
struct intel_connector *connector)
{
enum intel_hotplug_state state;
state = intel_encoder_hotplug(encoder, connector);
/*
* On many platforms the HDMI live state signal is known to be
* unreliable, so we can't use it to detect if a sink is connected or
* not. Instead we detect if it's connected based on whether we can
* read the EDID or not. That in turn has a problem during disconnect,
* since the HPD interrupt may be raised before the DDC lines get
* disconnected (due to how the required length of DDC vs. HPD
* connector pins are specified) and so we'll still be able to get a
* valid EDID. To solve this schedule another detection cycle if this
* time around we didn't detect any change in the sink's connection
* status.
*/
if (state == INTEL_HOTPLUG_UNCHANGED && !connector->hotplug_retries)
state = INTEL_HOTPLUG_RETRY;
return state;
}
int g4x_hdmi_connector_atomic_check(struct drm_connector *connector,
struct drm_atomic_state *state)
{
struct intel_display *display = to_intel_display(connector->dev);
struct drm_connector_list_iter conn_iter;
struct drm_connector *conn;
int ret;
ret = intel_digital_connector_atomic_check(connector, state);
if (ret)
return ret;
if (!display->platform.g4x)
return 0;
if (!intel_connector_needs_modeset(to_intel_atomic_state(state), connector))
return 0;
/*
* On g4x only one HDMI port can transmit infoframes/audio
* at any given time. Make sure all enabled HDMI ports are
* included in the state so that it's possible to select
* one of them for this duty.
*
* See also g4x_compute_has_hdmi_sink().
*/
drm_connector_list_iter_begin(display->drm, &conn_iter);
drm_for_each_connector_iter(conn, &conn_iter) {
struct drm_connector_state *conn_state;
struct drm_crtc_state *crtc_state;
struct drm_crtc *crtc;
if (!connector_is_hdmi(conn))
continue;
drm_dbg_kms(display->drm, "Adding [CONNECTOR:%d:%s]\n",
conn->base.id, conn->name);
conn_state = drm_atomic_get_connector_state(state, conn);
if (IS_ERR(conn_state)) {
ret = PTR_ERR(conn_state);
break;
}
crtc = conn_state->crtc;
if (!crtc)
continue;
crtc_state = drm_atomic_get_new_crtc_state(state, crtc);
crtc_state->mode_changed = true;
ret = drm_atomic_add_affected_planes(state, crtc);
if (ret)
break;
}
drm_connector_list_iter_end(&conn_iter);
return ret;
}
static bool is_hdmi_port_valid(struct intel_display *display, enum port port)
{
if (display->platform.g4x || display->platform.valleyview)
return port == PORT_B || port == PORT_C;
else
return port == PORT_B || port == PORT_C || port == PORT_D;
}
static bool assert_hdmi_port_valid(struct intel_display *display, enum port port)
{
return !drm_WARN(display->drm, !is_hdmi_port_valid(display, port),
"Platform does not support HDMI %c\n", port_name(port));
}
bool g4x_hdmi_init(struct intel_display *display,
i915_reg_t hdmi_reg, enum port port)
{
const struct intel_bios_encoder_data *devdata;
struct intel_digital_port *dig_port;
struct intel_encoder *intel_encoder;
struct intel_connector *intel_connector;
if (!assert_port_valid(display, port))
return false;
if (!assert_hdmi_port_valid(display, port))
return false;
devdata = intel_bios_encoder_data_lookup(display, port);
/* FIXME bail? */
if (!devdata)
drm_dbg_kms(display->drm, "No VBT child device for HDMI-%c\n",
port_name(port));
dig_port = kzalloc(sizeof(*dig_port), GFP_KERNEL);
if (!dig_port)
return false;
dig_port->aux_ch = AUX_CH_NONE;
intel_connector = intel_connector_alloc();
if (!intel_connector)
goto err_connector_alloc;
intel_encoder = &dig_port->base;
intel_encoder->devdata = devdata;
mutex_init(&dig_port->hdcp.mutex);
if (drm_encoder_init(display->drm, &intel_encoder->base,
&intel_hdmi_enc_funcs, DRM_MODE_ENCODER_TMDS,
"HDMI %c", port_name(port)))
goto err_encoder_init;
intel_encoder->hotplug = intel_hdmi_hotplug;
intel_encoder->compute_config = g4x_hdmi_compute_config;
if (HAS_PCH_SPLIT(display)) {
intel_encoder->disable = pch_disable_hdmi;
intel_encoder->post_disable = pch_post_disable_hdmi;
} else {
intel_encoder->disable = g4x_disable_hdmi;
}
intel_encoder->get_hw_state = intel_hdmi_get_hw_state;
intel_encoder->get_config = intel_hdmi_get_config;
if (display->platform.cherryview) {
intel_encoder->pre_pll_enable = chv_hdmi_pre_pll_enable;
intel_encoder->pre_enable = chv_hdmi_pre_enable;
intel_encoder->enable = vlv_enable_hdmi;
intel_encoder->post_disable = chv_hdmi_post_disable;
intel_encoder->post_pll_disable = chv_hdmi_post_pll_disable;
} else if (display->platform.valleyview) {
intel_encoder->pre_pll_enable = vlv_hdmi_pre_pll_enable;
intel_encoder->pre_enable = vlv_hdmi_pre_enable;
intel_encoder->enable = vlv_enable_hdmi;
intel_encoder->post_disable = vlv_hdmi_post_disable;
} else {
intel_encoder->pre_enable = intel_hdmi_pre_enable;
if (HAS_PCH_CPT(display))
intel_encoder->enable = cpt_enable_hdmi;
else if (HAS_PCH_IBX(display))
intel_encoder->enable = ibx_enable_hdmi;
else
intel_encoder->enable = g4x_enable_hdmi;
}
intel_encoder->audio_enable = g4x_hdmi_audio_enable;
intel_encoder->audio_disable = g4x_hdmi_audio_disable;
drm/i915/hdmi: Turn DP++ TMDS output buffers back on in encoder->shutdown() Looks like our VBIOS/GOP generally fail to turn the DP dual mode adater TMDS output buffers back on after a reboot. This leads to a black screen after reboot if we turned the TMDS output buffers off prior to reboot. And if i915 decides to do a fastboot the black screen will persist even after i915 takes over. Apparently this has been a problem ever since commit b2ccb822d376 ("drm/i915: Enable/disable TMDS output buffers in DP++ adaptor as needed") if one rebooted while the display was turned off. And things became worse with commit fe0f1e3bfdfe ("drm/i915: Shut down displays gracefully on reboot") since now we always turn the display off before a reboot. This was reported on a RKL, but I confirmed the same behaviour on my SNB as well. So looks pretty universal. Let's fix this by explicitly turning the TMDS output buffers back on in the encoder->shutdown() hook. Note that this gets called after irqs have been disabled, so the i2c communication with the DP dual mode adapter has to be performed via polling (which the gmbus code is perfectly happy to do for us). We also need a bit of care in handling DDI encoders which may or may not be set up for HDMI output. Specifically ddc_pin will not be populated for a DP only DDI encoder, in which case we don't want to call intel_gmbus_get_adapter(). We can handle that by simply doing the dual mode adapter type check before calling intel_gmbus_get_adapter(). Cc: <stable@vger.kernel.org> # v5.11+ Fixes: fe0f1e3bfdfe ("drm/i915: Shut down displays gracefully on reboot") Closes: https://gitlab.freedesktop.org/drm/intel/-/issues/4371 Signed-off-by: Ville Syrjälä <ville.syrjala@linux.intel.com> Link: https://patchwork.freedesktop.org/patch/msgid/20211029191802.18448-2-ville.syrjala@linux.intel.com Reviewed-by: Stanislav Lisovskiy <stanislav.lisovskiy@intel.com>
2021-10-29 22:18:02 +03:00
intel_encoder->shutdown = intel_hdmi_encoder_shutdown;
intel_encoder->type = INTEL_OUTPUT_HDMI;
intel_encoder->power_domain = intel_display_power_ddi_lanes_domain(display, port);
intel_encoder->port = port;
if (display->platform.cherryview) {
if (port == PORT_D)
intel_encoder->pipe_mask = BIT(PIPE_C);
else
intel_encoder->pipe_mask = BIT(PIPE_A) | BIT(PIPE_B);
} else {
intel_encoder->pipe_mask = ~0;
}
intel_encoder->cloneable = BIT(INTEL_OUTPUT_ANALOG);
intel_encoder->hpd_pin = intel_hpd_pin_default(port);
/*
* BSpec is unclear about HDMI+HDMI cloning on g4x, but it seems
* to work on real hardware. And since g4x can send infoframes to
* only one port anyway, nothing is lost by allowing it.
*/
if (display->platform.g4x)
intel_encoder->cloneable |= BIT(INTEL_OUTPUT_HDMI);
dig_port->hdmi.hdmi_reg = hdmi_reg;
dig_port->dp.output_reg = INVALID_MMIO_REG;
dig_port->max_lanes = 4;
intel_infoframe_init(dig_port);
if (!intel_hdmi_init_connector(dig_port, intel_connector))
goto err_init_connector;
return true;
err_init_connector:
drm_encoder_cleanup(&intel_encoder->base);
err_encoder_init:
kfree(intel_connector);
err_connector_alloc:
kfree(dig_port);
return false;
}