diff options
Diffstat (limited to 'drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c')
| -rw-r--r-- | drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c | 722 |
1 files changed, 374 insertions, 348 deletions
diff --git a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c index 8e9ac9ba1d38..eb05ef4bd9f6 100644 --- a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c +++ b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c @@ -34,14 +34,13 @@ * for some reason. */ +#include <drm/drm_print.h> + +#include "intel_backlight.h" +#include "intel_display_core.h" #include "intel_display_types.h" +#include "intel_dp.h" #include "intel_dp_aux_backlight.h" -#include "intel_panel.h" - -/* TODO: - * Implement HDR, right now we just implement the bare minimum to bring us back into SDR mode so we - * can make people's backlights work in the mean time - */ /* * DP AUX registers for Intel's proprietary HDR backlight interface. We define @@ -67,14 +66,14 @@ #define INTEL_EDP_HDR_GETSET_CTRL_PARAMS 0x344 # define INTEL_EDP_HDR_TCON_2084_DECODE_ENABLE BIT(0) # define INTEL_EDP_HDR_TCON_2020_GAMUT_ENABLE BIT(1) -# define INTEL_EDP_HDR_TCON_TONE_MAPPING_ENABLE BIT(2) /* Pre-TGL+ */ +# define INTEL_EDP_HDR_TCON_TONE_MAPPING_ENABLE BIT(2) # define INTEL_EDP_HDR_TCON_SEGMENTED_BACKLIGHT_ENABLE BIT(3) # define INTEL_EDP_HDR_TCON_BRIGHTNESS_AUX_ENABLE BIT(4) # define INTEL_EDP_HDR_TCON_SRGB_TO_PANEL_GAMUT_ENABLE BIT(5) /* Bit 6 is reserved */ -# define INTEL_EDP_HDR_TCON_SDP_COLORIMETRY_ENABLE BIT(7) +# define INTEL_EDP_HDR_TCON_SDP_OVERRIDE_AUX BIT(7) -#define INTEL_EDP_HDR_CONTENT_LUMINANCE 0x346 /* Pre-TGL+ */ +#define INTEL_EDP_HDR_CONTENT_LUMINANCE 0x346 #define INTEL_EDP_HDR_PANEL_LUMINANCE_OVERRIDE 0x34A #define INTEL_EDP_SDR_LUMINANCE_LEVEL 0x352 #define INTEL_EDP_BRIGHTNESS_NITS_LSB 0x354 @@ -95,35 +94,78 @@ #define INTEL_EDP_BRIGHTNESS_OPTIMIZATION_1 0x359 +enum intel_dp_aux_backlight_modparam { + INTEL_DP_AUX_BACKLIGHT_AUTO = -1, + INTEL_DP_AUX_BACKLIGHT_OFF = 0, + INTEL_DP_AUX_BACKLIGHT_ON = 1, + INTEL_DP_AUX_BACKLIGHT_FORCE_VESA = 2, + INTEL_DP_AUX_BACKLIGHT_FORCE_INTEL = 3, +}; + +static bool is_intel_tcon_cap(const u8 tcon_cap[4]) +{ + return tcon_cap[0] >= 1; +} + /* Intel EDP backlight callbacks */ static bool intel_dp_aux_supports_hdr_backlight(struct intel_connector *connector) { - struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_display *display = to_intel_display(connector); struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); struct drm_dp_aux *aux = &intel_dp->aux; struct intel_panel *panel = &connector->panel; int ret; u8 tcon_cap[4]; + intel_dp_wait_source_oui(intel_dp); + ret = drm_dp_dpcd_read(aux, INTEL_EDP_HDR_TCON_CAP0, tcon_cap, sizeof(tcon_cap)); - if (ret < 0) + if (ret != sizeof(tcon_cap)) + return false; + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Detected %s HDR backlight interface version %d\n", + connector->base.base.id, connector->base.name, + is_intel_tcon_cap(tcon_cap) ? "Intel" : "unsupported", tcon_cap[0]); + + if (!is_intel_tcon_cap(tcon_cap)) return false; if (!(tcon_cap[1] & INTEL_EDP_HDR_TCON_BRIGHTNESS_NITS_CAP)) return false; - if (tcon_cap[0] >= 1) { - drm_dbg_kms(&i915->drm, "Detected Intel HDR backlight interface version %d\n", - tcon_cap[0]); - } else { - drm_dbg_kms(&i915->drm, "Detected unsupported HDR backlight interface version %d\n", - tcon_cap[0]); + /* + * If we don't have HDR static metadata there is no way to + * runtime detect used range for nits based control. For now + * do not use Intel proprietary eDP backlight control if we + * don't have this data in panel EDID. In case we find panel + * which supports only nits based control, but doesn't provide + * HDR static metadata we need to start maintaining table of + * ranges for such panels. + */ + if (display->params.enable_dpcd_backlight != INTEL_DP_AUX_BACKLIGHT_FORCE_INTEL && + !(connector->base.display_info.hdr_sink_metadata.hdmi_type1.metadata_type & + BIT(HDMI_STATIC_METADATA_TYPE1))) { + drm_info(display->drm, + "[CONNECTOR:%d:%s] Panel is missing HDR static metadata. Possible support for Intel HDR backlight interface is not used. If your backlight controls don't work try booting with i915.enable_dpcd_backlight=%d.\n", + connector->base.base.id, connector->base.name, + INTEL_DP_AUX_BACKLIGHT_FORCE_INTEL); return false; } - panel->backlight.edp.intel.sdr_uses_aux = + panel->backlight.edp.intel_cap.sdr_uses_aux = tcon_cap[2] & INTEL_EDP_SDR_TCON_BRIGHTNESS_AUX_CAP; + panel->backlight.edp.intel_cap.supports_2084_decode = + tcon_cap[1] & INTEL_EDP_HDR_TCON_2084_DECODE_CAP; + panel->backlight.edp.intel_cap.supports_2020_gamut = + tcon_cap[1] & INTEL_EDP_HDR_TCON_2020_GAMUT_CAP; + panel->backlight.edp.intel_cap.supports_segmented_backlight = + tcon_cap[1] & INTEL_EDP_HDR_TCON_SEGMENTED_BACKLIGHT_CAP; + panel->backlight.edp.intel_cap.supports_sdp_colorimetry = + tcon_cap[1] & INTEL_EDP_HDR_TCON_SDP_COLORIMETRY_CAP; + panel->backlight.edp.intel_cap.supports_tone_mapping = + tcon_cap[1] & INTEL_EDP_HDR_TCON_TONE_MAPPING_CAP; return true; } @@ -131,30 +173,35 @@ intel_dp_aux_supports_hdr_backlight(struct intel_connector *connector) static u32 intel_dp_aux_hdr_get_backlight(struct intel_connector *connector, enum pipe pipe) { - struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_display *display = to_intel_display(connector); struct intel_panel *panel = &connector->panel; struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); u8 tmp; - u8 buf[2] = { 0 }; + u8 buf[2] = {}; - if (drm_dp_dpcd_readb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, &tmp) < 0) { - drm_err(&i915->drm, "Failed to read current backlight mode from DPCD\n"); + if (drm_dp_dpcd_readb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, &tmp) != 1) { + drm_err(display->drm, + "[CONNECTOR:%d:%s] Failed to read current backlight mode from DPCD\n", + connector->base.base.id, connector->base.name); return 0; } if (!(tmp & INTEL_EDP_HDR_TCON_BRIGHTNESS_AUX_ENABLE)) { - if (!panel->backlight.edp.intel.sdr_uses_aux) { + if (!panel->backlight.edp.intel_cap.sdr_uses_aux) { u32 pwm_level = panel->backlight.pwm_funcs->get(connector, pipe); - return intel_panel_backlight_level_from_pwm(connector, pwm_level); + return intel_backlight_level_from_pwm(connector, pwm_level); } /* Assume 100% brightness if backlight controls aren't enabled yet */ return panel->backlight.max; } - if (drm_dp_dpcd_read(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, sizeof(buf)) < 0) { - drm_err(&i915->drm, "Failed to read brightness from DPCD\n"); + if (drm_dp_dpcd_read(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, + sizeof(buf)) != sizeof(buf)) { + drm_err(display->drm, + "[CONNECTOR:%d:%s] Failed to read brightness from DPCD\n", + connector->base.base.id, connector->base.name); return 0; } @@ -167,13 +214,15 @@ intel_dp_aux_hdr_set_aux_backlight(const struct drm_connector_state *conn_state, struct intel_connector *connector = to_intel_connector(conn_state->connector); struct drm_device *dev = connector->base.dev; struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); - u8 buf[4] = { 0 }; + u8 buf[4] = {}; buf[0] = level & 0xFF; buf[1] = (level & 0xFF00) >> 8; - if (drm_dp_dpcd_write(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, 4) < 0) - drm_err(dev, "Failed to write brightness level to DPCD\n"); + if (drm_dp_dpcd_write(&intel_dp->aux, INTEL_EDP_BRIGHTNESS_NITS_LSB, buf, + sizeof(buf)) != sizeof(buf)) + drm_err(dev, "[CONNECTOR:%d:%s] Failed to write brightness level to DPCD\n", + connector->base.base.id, connector->base.name); } static void @@ -182,47 +231,122 @@ intel_dp_aux_hdr_set_backlight(const struct drm_connector_state *conn_state, u32 struct intel_connector *connector = to_intel_connector(conn_state->connector); struct intel_panel *panel = &connector->panel; - if (panel->backlight.edp.intel.sdr_uses_aux) { + if (intel_dp_in_hdr_mode(conn_state) || + panel->backlight.edp.intel_cap.sdr_uses_aux) { intel_dp_aux_hdr_set_aux_backlight(conn_state, level); } else { - const u32 pwm_level = intel_panel_backlight_level_to_pwm(connector, level); + const u32 pwm_level = intel_backlight_level_to_pwm(connector, level); + + intel_backlight_set_pwm_level(conn_state, pwm_level); + } +} + +static void +intel_dp_aux_write_content_luminance(struct intel_connector *connector, + struct hdr_output_metadata *hdr_metadata) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + int ret; + u8 buf[4]; + + if (!intel_dp_has_gamut_metadata_dip(connector->encoder)) + return; + + buf[0] = hdr_metadata->hdmi_metadata_type1.max_cll & 0xFF; + buf[1] = (hdr_metadata->hdmi_metadata_type1.max_cll & 0xFF00) >> 8; + buf[2] = hdr_metadata->hdmi_metadata_type1.max_fall & 0xFF; + buf[3] = (hdr_metadata->hdmi_metadata_type1.max_fall & 0xFF00) >> 8; + + ret = drm_dp_dpcd_write(&intel_dp->aux, + INTEL_EDP_HDR_CONTENT_LUMINANCE, + buf, sizeof(buf)); + if (ret < 0) + drm_dbg_kms(display->drm, + "Content Luminance DPCD reg write failed, err:-%d\n", + ret); +} - intel_panel_set_pwm_level(conn_state, pwm_level); +static void +intel_dp_aux_fill_hdr_tcon_params(const struct drm_connector_state *conn_state, u8 *ctrl) +{ + struct intel_connector *connector = to_intel_connector(conn_state->connector); + struct intel_panel *panel = &connector->panel; + struct intel_display *display = to_intel_display(connector); + + /* + * According to spec segmented backlight needs to be set whenever panel is in + * HDR mode. + */ + if (intel_dp_in_hdr_mode(conn_state)) { + *ctrl |= INTEL_EDP_HDR_TCON_SEGMENTED_BACKLIGHT_ENABLE; + *ctrl |= INTEL_EDP_HDR_TCON_2084_DECODE_ENABLE; } + + if (DISPLAY_VER(display) < 11) + *ctrl &= ~INTEL_EDP_HDR_TCON_TONE_MAPPING_ENABLE; + + if (panel->backlight.edp.intel_cap.supports_2020_gamut && + (conn_state->colorspace == DRM_MODE_COLORIMETRY_BT2020_RGB || + conn_state->colorspace == DRM_MODE_COLORIMETRY_BT2020_YCC || + conn_state->colorspace == DRM_MODE_COLORIMETRY_BT2020_CYCC)) + *ctrl |= INTEL_EDP_HDR_TCON_2020_GAMUT_ENABLE; + + if (panel->backlight.edp.intel_cap.supports_sdp_colorimetry && + intel_dp_has_gamut_metadata_dip(connector->encoder)) + *ctrl |= INTEL_EDP_HDR_TCON_SDP_OVERRIDE_AUX; + else + *ctrl &= ~INTEL_EDP_HDR_TCON_SDP_OVERRIDE_AUX; } static void intel_dp_aux_hdr_enable_backlight(const struct intel_crtc_state *crtc_state, const struct drm_connector_state *conn_state, u32 level) { + struct intel_display *display = to_intel_display(crtc_state); struct intel_connector *connector = to_intel_connector(conn_state->connector); struct intel_panel *panel = &connector->panel; - struct drm_i915_private *i915 = to_i915(connector->base.dev); struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + struct hdr_output_metadata *hdr_metadata; int ret; u8 old_ctrl, ctrl; + intel_dp_wait_source_oui(intel_dp); + ret = drm_dp_dpcd_readb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, &old_ctrl); - if (ret < 0) { - drm_err(&i915->drm, "Failed to read current backlight control mode: %d\n", ret); + if (ret != 1) { + drm_err(display->drm, + "[CONNECTOR:%d:%s] Failed to read current backlight control mode: %d\n", + connector->base.base.id, connector->base.name, ret); return; } ctrl = old_ctrl; - if (panel->backlight.edp.intel.sdr_uses_aux) { + if (intel_dp_in_hdr_mode(conn_state) || + panel->backlight.edp.intel_cap.sdr_uses_aux) { ctrl |= INTEL_EDP_HDR_TCON_BRIGHTNESS_AUX_ENABLE; + intel_dp_aux_hdr_set_aux_backlight(conn_state, level); } else { - u32 pwm_level = intel_panel_backlight_level_to_pwm(connector, level); + u32 pwm_level = intel_backlight_level_to_pwm(connector, level); panel->backlight.pwm_funcs->enable(crtc_state, conn_state, pwm_level); ctrl &= ~INTEL_EDP_HDR_TCON_BRIGHTNESS_AUX_ENABLE; } - if (ctrl != old_ctrl) - if (drm_dp_dpcd_writeb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, ctrl) < 0) - drm_err(&i915->drm, "Failed to configure DPCD brightness controls\n"); + intel_dp_aux_fill_hdr_tcon_params(conn_state, &ctrl); + + if (ctrl != old_ctrl && + drm_dp_dpcd_writeb(&intel_dp->aux, INTEL_EDP_HDR_GETSET_CTRL_PARAMS, ctrl) != 1) + drm_err(display->drm, + "[CONNECTOR:%d:%s] Failed to configure DPCD brightness controls\n", + connector->base.base.id, connector->base.name); + + if (intel_dp_in_hdr_mode(conn_state)) { + hdr_metadata = conn_state->hdr_output_metadata->data; + intel_dp_aux_write_content_luminance(connector, hdr_metadata); + } } static void @@ -232,35 +356,80 @@ intel_dp_aux_hdr_disable_backlight(const struct drm_connector_state *conn_state, struct intel_panel *panel = &connector->panel; /* Nothing to do for AUX based backlight controls */ - if (panel->backlight.edp.intel.sdr_uses_aux) + if (panel->backlight.edp.intel_cap.sdr_uses_aux) return; /* Note we want the actual pwm_level to be 0, regardless of pwm_min */ - panel->backlight.pwm_funcs->disable(conn_state, intel_panel_invert_pwm_level(connector, 0)); + panel->backlight.pwm_funcs->disable(conn_state, intel_backlight_invert_pwm_level(connector, 0)); +} + +static const char *dpcd_vs_pwm_str(bool aux) +{ + return aux ? "DPCD" : "PWM"; +} + +static void +intel_dp_aux_write_panel_luminance_override(struct intel_connector *connector) +{ + struct intel_display *display = to_intel_display(connector); + struct intel_panel *panel = &connector->panel; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + int ret; + u8 buf[4] = {}; + + buf[0] = panel->backlight.min & 0xFF; + buf[1] = (panel->backlight.min & 0xFF00) >> 8; + buf[2] = panel->backlight.max & 0xFF; + buf[3] = (panel->backlight.max & 0xFF00) >> 8; + + ret = drm_dp_dpcd_write(&intel_dp->aux, + INTEL_EDP_HDR_PANEL_LUMINANCE_OVERRIDE, + buf, sizeof(buf)); + if (ret < 0) + drm_dbg_kms(display->drm, + "Panel Luminance DPCD reg write failed, err:-%d\n", + ret); } static int intel_dp_aux_hdr_setup_backlight(struct intel_connector *connector, enum pipe pipe) { - struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_display *display = to_intel_display(connector); struct intel_panel *panel = &connector->panel; + struct drm_luminance_range_info *luminance_range = + &connector->base.display_info.luminance_range; int ret; - if (panel->backlight.edp.intel.sdr_uses_aux) { - drm_dbg_kms(&i915->drm, "SDR backlight is controlled through DPCD\n"); - } else { - drm_dbg_kms(&i915->drm, "SDR backlight is controlled through PWM\n"); + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] SDR backlight is controlled through %s\n", + connector->base.base.id, connector->base.name, + dpcd_vs_pwm_str(panel->backlight.edp.intel_cap.sdr_uses_aux)); + if (!panel->backlight.edp.intel_cap.sdr_uses_aux) { ret = panel->backlight.pwm_funcs->setup(connector, pipe); if (ret < 0) { - drm_err(&i915->drm, - "Failed to setup SDR backlight controls through PWM: %d\n", ret); + drm_err(display->drm, + "[CONNECTOR:%d:%s] Failed to setup SDR backlight controls through PWM: %d\n", + connector->base.base.id, connector->base.name, ret); return ret; } } - panel->backlight.max = 512; - panel->backlight.min = 0; + if (luminance_range->max_luminance) { + panel->backlight.max = luminance_range->max_luminance; + panel->backlight.min = luminance_range->min_luminance; + } else { + panel->backlight.max = 512; + panel->backlight.min = 0; + } + + intel_dp_aux_write_panel_luminance_override(connector); + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Using AUX HDR interface for backlight control (range %d..%d)\n", + connector->base.base.id, connector->base.name, + panel->backlight.min, panel->backlight.max); + panel->backlight.level = intel_dp_aux_hdr_get_backlight(connector, pipe); panel->backlight.enabled = panel->backlight.level != 0; @@ -268,153 +437,45 @@ intel_dp_aux_hdr_setup_backlight(struct intel_connector *connector, enum pipe pi } /* VESA backlight callbacks */ -static void set_vesa_backlight_enable(struct intel_dp *intel_dp, bool enable) -{ - struct drm_i915_private *i915 = dp_to_i915(intel_dp); - u8 reg_val = 0; - - /* Early return when display use other mechanism to enable backlight. */ - if (!(intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP)) - return; - - if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER, - ®_val) < 0) { - drm_dbg_kms(&i915->drm, "Failed to read DPCD register 0x%x\n", - DP_EDP_DISPLAY_CONTROL_REGISTER); - return; - } - if (enable) - reg_val |= DP_EDP_BACKLIGHT_ENABLE; - else - reg_val &= ~(DP_EDP_BACKLIGHT_ENABLE); - - if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_EDP_DISPLAY_CONTROL_REGISTER, - reg_val) != 1) { - drm_dbg_kms(&i915->drm, "Failed to %s aux backlight\n", - enabledisable(enable)); - } -} - -static bool intel_dp_aux_vesa_backlight_dpcd_mode(struct intel_connector *connector) -{ - struct intel_dp *intel_dp = intel_attached_dp(connector); - struct drm_i915_private *i915 = dp_to_i915(intel_dp); - u8 mode_reg; - - if (drm_dp_dpcd_readb(&intel_dp->aux, - DP_EDP_BACKLIGHT_MODE_SET_REGISTER, - &mode_reg) != 1) { - drm_dbg_kms(&i915->drm, - "Failed to read the DPCD register 0x%x\n", - DP_EDP_BACKLIGHT_MODE_SET_REGISTER); - return false; - } - - return (mode_reg & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK) == - DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; -} - -/* - * Read the current backlight value from DPCD register(s) based - * on if 8-bit(MSB) or 16-bit(MSB and LSB) values are supported - */ static u32 intel_dp_aux_vesa_get_backlight(struct intel_connector *connector, enum pipe unused) { - struct intel_dp *intel_dp = intel_attached_dp(connector); - struct drm_i915_private *i915 = dp_to_i915(intel_dp); - u8 read_val[2] = { 0x0 }; - u16 level = 0; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + struct intel_panel *panel = &connector->panel; + u8 buf[3]; + u32 val = 0; + int ret; - /* - * If we're not in DPCD control mode yet, the programmed brightness - * value is meaningless and we should assume max brightness - */ - if (!intel_dp_aux_vesa_backlight_dpcd_mode(connector)) - return connector->panel.backlight.max; + if (panel->backlight.edp.vesa.luminance_control_support) { + ret = drm_dp_dpcd_read(&intel_dp->aux, DP_EDP_PANEL_TARGET_LUMINANCE_VALUE, buf, + sizeof(buf)); + if (ret < 0) { + drm_err(intel_dp->aux.drm_dev, + "[CONNECTOR:%d:%s] Failed to read Luminance from DPCD\n", + connector->base.base.id, connector->base.name); + return 0; + } - if (drm_dp_dpcd_read(&intel_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, - &read_val, sizeof(read_val)) < 0) { - drm_dbg_kms(&i915->drm, "Failed to read DPCD register 0x%x\n", - DP_EDP_BACKLIGHT_BRIGHTNESS_MSB); - return 0; + val |= buf[0] | buf[1] << 8 | buf[2] << 16; + return val / 1000; } - level = read_val[0]; - if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) - level = (read_val[0] << 8 | read_val[1]); - return level; + return connector->panel.backlight.level; } -/* - * Sends the current backlight level over the aux channel, checking if its using - * 8-bit or 16 bit value (MSB and LSB) - */ static void -intel_dp_aux_vesa_set_backlight(const struct drm_connector_state *conn_state, - u32 level) +intel_dp_aux_vesa_set_backlight(const struct drm_connector_state *conn_state, u32 level) { struct intel_connector *connector = to_intel_connector(conn_state->connector); - struct intel_dp *intel_dp = intel_attached_dp(connector); - struct drm_i915_private *i915 = dp_to_i915(intel_dp); - u8 vals[2] = { 0x0 }; - - vals[0] = level; - - /* Write the MSB and/or LSB */ - if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_BYTE_COUNT) { - vals[0] = (level & 0xFF00) >> 8; - vals[1] = (level & 0xFF); - } - if (drm_dp_dpcd_write(&intel_dp->aux, DP_EDP_BACKLIGHT_BRIGHTNESS_MSB, - vals, sizeof(vals)) < 0) { - drm_dbg_kms(&i915->drm, - "Failed to write aux backlight level\n"); - return; - } -} - -/* - * Set PWM Frequency divider to match desired frequency in vbt. - * The PWM Frequency is calculated as 27Mhz / (F x P). - * - Where F = PWM Frequency Pre-Divider value programmed by field 7:0 of the - * EDP_BACKLIGHT_FREQ_SET register (DPCD Address 00728h) - * - Where P = 2^Pn, where Pn is the value programmed by field 4:0 of the - * EDP_PWMGEN_BIT_COUNT register (DPCD Address 00724h) - */ -static bool intel_dp_aux_vesa_set_pwm_freq(struct intel_connector *connector) -{ - struct drm_i915_private *dev_priv = to_i915(connector->base.dev); - struct intel_dp *intel_dp = intel_attached_dp(connector); - const u8 pn = connector->panel.backlight.edp.vesa.pwmgen_bit_count; - int freq, fxp, f, fxp_actual, fxp_min, fxp_max; - - freq = dev_priv->vbt.backlight.pwm_freq_hz; - if (!freq) { - drm_dbg_kms(&dev_priv->drm, - "Use panel default backlight frequency\n"); - return false; - } - - fxp = DIV_ROUND_CLOSEST(KHz(DP_EDP_BACKLIGHT_FREQ_BASE_KHZ), freq); - f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255); - fxp_actual = f << pn; + struct intel_panel *panel = &connector->panel; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); - /* Ensure frequency is within 25% of desired value */ - fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); - fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4); + if (!panel->backlight.edp.vesa.info.aux_set) { + const u32 pwm_level = intel_backlight_level_to_pwm(connector, level); - if (fxp_min > fxp_actual || fxp_actual > fxp_max) { - drm_dbg_kms(&dev_priv->drm, "Actual frequency out of range\n"); - return false; + intel_backlight_set_pwm_level(conn_state, pwm_level); } - if (drm_dp_dpcd_writeb(&intel_dp->aux, - DP_EDP_BACKLIGHT_FREQ_SET, (u8) f) < 0) { - drm_dbg_kms(&dev_priv->drm, - "Failed to write aux backlight freq\n"); - return false; - } - return true; + drm_edp_backlight_set_level(&intel_dp->aux, &panel->backlight.edp.vesa.info, level); } static void @@ -422,159 +483,116 @@ intel_dp_aux_vesa_enable_backlight(const struct intel_crtc_state *crtc_state, const struct drm_connector_state *conn_state, u32 level) { struct intel_connector *connector = to_intel_connector(conn_state->connector); - struct intel_dp *intel_dp = intel_attached_dp(connector); - struct drm_i915_private *i915 = dp_to_i915(intel_dp); struct intel_panel *panel = &connector->panel; - u8 dpcd_buf, new_dpcd_buf, edp_backlight_mode; - u8 pwmgen_bit_count = panel->backlight.edp.vesa.pwmgen_bit_count; - - if (drm_dp_dpcd_readb(&intel_dp->aux, - DP_EDP_BACKLIGHT_MODE_SET_REGISTER, &dpcd_buf) != 1) { - drm_dbg_kms(&i915->drm, "Failed to read DPCD register 0x%x\n", - DP_EDP_BACKLIGHT_MODE_SET_REGISTER); - return; - } - - new_dpcd_buf = dpcd_buf; - edp_backlight_mode = dpcd_buf & DP_EDP_BACKLIGHT_CONTROL_MODE_MASK; - - switch (edp_backlight_mode) { - case DP_EDP_BACKLIGHT_CONTROL_MODE_PWM: - case DP_EDP_BACKLIGHT_CONTROL_MODE_PRESET: - case DP_EDP_BACKLIGHT_CONTROL_MODE_PRODUCT: - new_dpcd_buf &= ~DP_EDP_BACKLIGHT_CONTROL_MODE_MASK; - new_dpcd_buf |= DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD; - - if (drm_dp_dpcd_writeb(&intel_dp->aux, - DP_EDP_PWMGEN_BIT_COUNT, - pwmgen_bit_count) < 0) - drm_dbg_kms(&i915->drm, - "Failed to write aux pwmgen bit count\n"); - - break; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); - /* Do nothing when it is already DPCD mode */ - case DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD: - default: - break; - } + if (!panel->backlight.edp.vesa.info.aux_enable) { + u32 pwm_level; - if (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_FREQ_AUX_SET_CAP) - if (intel_dp_aux_vesa_set_pwm_freq(connector)) - new_dpcd_buf |= DP_EDP_BACKLIGHT_FREQ_AUX_SET_ENABLE; + if (!panel->backlight.edp.vesa.info.aux_set) + pwm_level = intel_backlight_level_to_pwm(connector, level); + else + pwm_level = intel_backlight_invert_pwm_level(connector, + panel->backlight.pwm_level_max); - if (new_dpcd_buf != dpcd_buf) { - if (drm_dp_dpcd_writeb(&intel_dp->aux, - DP_EDP_BACKLIGHT_MODE_SET_REGISTER, new_dpcd_buf) < 0) { - drm_dbg_kms(&i915->drm, - "Failed to write aux backlight mode\n"); - } + panel->backlight.pwm_funcs->enable(crtc_state, conn_state, pwm_level); } - intel_dp_aux_vesa_set_backlight(conn_state, level); - set_vesa_backlight_enable(intel_dp, true); + drm_edp_backlight_enable(&intel_dp->aux, &panel->backlight.edp.vesa.info, level); } static void intel_dp_aux_vesa_disable_backlight(const struct drm_connector_state *old_conn_state, u32 level) { - set_vesa_backlight_enable(enc_to_intel_dp(to_intel_encoder(old_conn_state->best_encoder)), - false); + struct intel_connector *connector = to_intel_connector(old_conn_state->connector); + struct intel_panel *panel = &connector->panel; + struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); + + drm_edp_backlight_disable(&intel_dp->aux, &panel->backlight.edp.vesa.info); + + if (!panel->backlight.edp.vesa.info.aux_enable) + panel->backlight.pwm_funcs->disable(old_conn_state, + intel_backlight_invert_pwm_level(connector, 0)); } -static u32 intel_dp_aux_vesa_calc_max_backlight(struct intel_connector *connector) +static int intel_dp_aux_vesa_setup_backlight(struct intel_connector *connector, enum pipe pipe) { - struct drm_i915_private *i915 = to_i915(connector->base.dev); + struct intel_display *display = to_intel_display(connector); + struct drm_luminance_range_info *luminance_range = + &connector->base.display_info.luminance_range; struct intel_dp *intel_dp = intel_attached_dp(connector); struct intel_panel *panel = &connector->panel; - u32 max_backlight = 0; - int freq, fxp, fxp_min, fxp_max, fxp_actual, f = 1; - u8 pn, pn_min, pn_max; - - if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_PWMGEN_BIT_COUNT, &pn) == 1) { - pn &= DP_EDP_PWMGEN_BIT_COUNT_MASK; - max_backlight = (1 << pn) - 1; - } - - /* Find desired value of (F x P) - * Note that, if F x P is out of supported range, the maximum value or - * minimum value will applied automatically. So no need to check that. - */ - freq = i915->vbt.backlight.pwm_freq_hz; - drm_dbg_kms(&i915->drm, "VBT defined backlight frequency %u Hz\n", - freq); - if (!freq) { - drm_dbg_kms(&i915->drm, - "Use panel default backlight frequency\n"); - return max_backlight; - } - - fxp = DIV_ROUND_CLOSEST(KHz(DP_EDP_BACKLIGHT_FREQ_BASE_KHZ), freq); - - /* Use highest possible value of Pn for more granularity of brightness - * adjustment while satifying the conditions below. - * - Pn is in the range of Pn_min and Pn_max - * - F is in the range of 1 and 255 - * - FxP is within 25% of desired value. - * Note: 25% is arbitrary value and may need some tweak. - */ - if (drm_dp_dpcd_readb(&intel_dp->aux, - DP_EDP_PWMGEN_BIT_COUNT_CAP_MIN, &pn_min) != 1) { - drm_dbg_kms(&i915->drm, - "Failed to read pwmgen bit count cap min\n"); - return max_backlight; - } - if (drm_dp_dpcd_readb(&intel_dp->aux, - DP_EDP_PWMGEN_BIT_COUNT_CAP_MAX, &pn_max) != 1) { - drm_dbg_kms(&i915->drm, - "Failed to read pwmgen bit count cap max\n"); - return max_backlight; - } - pn_min &= DP_EDP_PWMGEN_BIT_COUNT_MASK; - pn_max &= DP_EDP_PWMGEN_BIT_COUNT_MASK; - - fxp_min = DIV_ROUND_CLOSEST(fxp * 3, 4); - fxp_max = DIV_ROUND_CLOSEST(fxp * 5, 4); - if (fxp_min < (1 << pn_min) || (255 << pn_max) < fxp_max) { - drm_dbg_kms(&i915->drm, - "VBT defined backlight frequency out of range\n"); - return max_backlight; - } + u32 current_level; + u8 current_mode; + int ret; - for (pn = pn_max; pn >= pn_min; pn--) { - f = clamp(DIV_ROUND_CLOSEST(fxp, 1 << pn), 1, 255); - fxp_actual = f << pn; - if (fxp_min <= fxp_actual && fxp_actual <= fxp_max) - break; + ret = drm_edp_backlight_init(&intel_dp->aux, &panel->backlight.edp.vesa.info, + luminance_range->max_luminance, + panel->vbt.backlight.pwm_freq_hz, + intel_dp->edp_dpcd, ¤t_level, ¤t_mode, + panel->backlight.edp.vesa.luminance_control_support); + if (ret < 0) + return ret; + + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] AUX VESA backlight enable is controlled through %s\n", + connector->base.base.id, connector->base.name, + dpcd_vs_pwm_str(panel->backlight.edp.vesa.info.aux_enable)); + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] AUX VESA backlight level is controlled through %s\n", + connector->base.base.id, connector->base.name, + dpcd_vs_pwm_str(panel->backlight.edp.vesa.info.aux_set)); + + if (!panel->backlight.edp.vesa.info.aux_set || + !panel->backlight.edp.vesa.info.aux_enable) { + ret = panel->backlight.pwm_funcs->setup(connector, pipe); + if (ret < 0) { + drm_err(display->drm, + "[CONNECTOR:%d:%s] Failed to setup PWM backlight controls for eDP backlight: %d\n", + connector->base.base.id, connector->base.name, ret); + return ret; + } } - drm_dbg_kms(&i915->drm, "Using eDP pwmgen bit count of %d\n", pn); - if (drm_dp_dpcd_writeb(&intel_dp->aux, - DP_EDP_PWMGEN_BIT_COUNT, pn) < 0) { - drm_dbg_kms(&i915->drm, - "Failed to write aux pwmgen bit count\n"); - return max_backlight; + if (panel->backlight.edp.vesa.info.luminance_set) { + if (luminance_range->max_luminance) { + panel->backlight.max = panel->backlight.edp.vesa.info.max; + panel->backlight.min = luminance_range->min_luminance; + } else { + panel->backlight.max = 512; + panel->backlight.min = 0; + } + panel->backlight.level = intel_dp_aux_vesa_get_backlight(connector, 0); + panel->backlight.enabled = panel->backlight.level != 0; + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] AUX VESA Nits backlight level is controlled through DPCD\n", + connector->base.base.id, connector->base.name); + } else if (panel->backlight.edp.vesa.info.aux_set) { + panel->backlight.max = panel->backlight.edp.vesa.info.max; + panel->backlight.min = 0; + if (current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_DPCD) { + panel->backlight.level = current_level; + panel->backlight.enabled = panel->backlight.level != 0; + } else { + panel->backlight.level = panel->backlight.max; + panel->backlight.enabled = false; + } + } else { + panel->backlight.max = panel->backlight.pwm_level_max; + panel->backlight.min = panel->backlight.pwm_level_min; + if (current_mode == DP_EDP_BACKLIGHT_CONTROL_MODE_PWM) { + panel->backlight.level = + panel->backlight.pwm_funcs->get(connector, pipe); + panel->backlight.enabled = panel->backlight.pwm_enabled; + } else { + panel->backlight.level = panel->backlight.max; + panel->backlight.enabled = false; + } } - panel->backlight.edp.vesa.pwmgen_bit_count = pn; - - max_backlight = (1 << pn) - 1; - return max_backlight; -} - -static int intel_dp_aux_vesa_setup_backlight(struct intel_connector *connector, - enum pipe pipe) -{ - struct intel_panel *panel = &connector->panel; - - panel->backlight.max = intel_dp_aux_vesa_calc_max_backlight(connector); - if (!panel->backlight.max) - return -ENODEV; - - panel->backlight.min = 0; - panel->backlight.level = intel_dp_aux_vesa_get_backlight(connector, pipe); - panel->backlight.enabled = intel_dp_aux_vesa_backlight_dpcd_mode(connector) && - panel->backlight.level != 0; + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] Using AUX VESA interface for backlight control\n", + connector->base.base.id, connector->base.name); return 0; } @@ -582,20 +600,23 @@ static int intel_dp_aux_vesa_setup_backlight(struct intel_connector *connector, static bool intel_dp_aux_supports_vesa_backlight(struct intel_connector *connector) { + struct intel_display *display = to_intel_display(connector); struct intel_dp *intel_dp = intel_attached_dp(connector); - struct drm_i915_private *i915 = dp_to_i915(intel_dp); + struct intel_panel *panel = &connector->panel; - /* Check the eDP Display control capabilities registers to determine if - * the panel can support backlight control over the aux channel. - * - * TODO: We currently only support AUX only backlight configurations, not backlights which - * require a mix of PWM and AUX controls to work. In the mean time, these machines typically - * work just fine using normal PWM controls anyway. - */ - if (intel_dp->edp_dpcd[1] & DP_EDP_TCON_BACKLIGHT_ADJUSTMENT_CAP && - (intel_dp->edp_dpcd[1] & DP_EDP_BACKLIGHT_AUX_ENABLE_CAP) && - (intel_dp->edp_dpcd[2] & DP_EDP_BACKLIGHT_BRIGHTNESS_AUX_SET_CAP)) { - drm_dbg_kms(&i915->drm, "AUX Backlight Control Supported!\n"); + if ((intel_dp->edp_dpcd[3] & DP_EDP_PANEL_LUMINANCE_CONTROL_CAPABLE) && + (intel_dp->edp_dpcd[3] & DP_EDP_SMOOTH_BRIGHTNESS_CAPABLE)) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] AUX Luminance Based Backlight Control Supported!\n", + connector->base.base.id, connector->base.name); + panel->backlight.edp.vesa.luminance_control_support = true; + return true; + } + + if (drm_edp_backlight_supported(intel_dp->edp_dpcd)) { + drm_dbg_kms(display->drm, + "[CONNECTOR:%d:%s] AUX Backlight Control Supported!\n", + connector->base.base.id, connector->base.name); return true; } return false; @@ -617,30 +638,22 @@ static const struct intel_panel_bl_funcs intel_dp_vesa_bl_funcs = { .get = intel_dp_aux_vesa_get_backlight, }; -enum intel_dp_aux_backlight_modparam { - INTEL_DP_AUX_BACKLIGHT_AUTO = -1, - INTEL_DP_AUX_BACKLIGHT_OFF = 0, - INTEL_DP_AUX_BACKLIGHT_ON = 1, - INTEL_DP_AUX_BACKLIGHT_FORCE_VESA = 2, - INTEL_DP_AUX_BACKLIGHT_FORCE_INTEL = 3, -}; - int intel_dp_aux_init_backlight_funcs(struct intel_connector *connector) { + struct intel_display *display = to_intel_display(connector); + struct intel_dp *intel_dp = intel_attached_dp(connector); struct drm_device *dev = connector->base.dev; struct intel_panel *panel = &connector->panel; - struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder); - struct drm_i915_private *i915 = dp_to_i915(intel_dp); bool try_intel_interface = false, try_vesa_interface = false; /* Check the VBT and user's module parameters to figure out which * interfaces to probe */ - switch (i915->params.enable_dpcd_backlight) { + switch (display->params.enable_dpcd_backlight) { case INTEL_DP_AUX_BACKLIGHT_OFF: return -ENODEV; case INTEL_DP_AUX_BACKLIGHT_AUTO: - switch (i915->vbt.backlight.type) { + switch (panel->vbt.backlight.type) { case INTEL_BACKLIGHT_VESA_EDP_AUX_INTERFACE: try_vesa_interface = true; break; @@ -652,7 +665,7 @@ int intel_dp_aux_init_backlight_funcs(struct intel_connector *connector) } break; case INTEL_DP_AUX_BACKLIGHT_ON: - if (i915->vbt.backlight.type != INTEL_BACKLIGHT_VESA_EDP_AUX_INTERFACE) + if (panel->vbt.backlight.type != INTEL_BACKLIGHT_VESA_EDP_AUX_INTERFACE) try_intel_interface = true; try_vesa_interface = true; @@ -665,21 +678,34 @@ int intel_dp_aux_init_backlight_funcs(struct intel_connector *connector) break; } + /* For eDP 1.5 and above we are supposed to use VESA interface for brightness control */ + if (intel_dp->edp_dpcd[0] >= DP_EDP_15) + try_vesa_interface = true; + /* - * A lot of eDP panels in the wild will report supporting both the - * Intel proprietary backlight control interface, and the VESA - * backlight control interface. Many of these panels are liars though, - * and will only work with the Intel interface. So, always probe for - * that first. + * Since Intel has their own backlight control interface, the majority of machines out there + * using DPCD backlight controls with Intel GPUs will be using this interface as opposed to + * the VESA interface. However, other GPUs (such as Nvidia's) will always use the VESA + * interface. This means that there's quite a number of panels out there that will advertise + * support for both interfaces, primarily systems with Intel/Nvidia hybrid GPU setups. + * + * There's a catch to this though: on many panels that advertise support for both + * interfaces, the VESA backlight interface will stop working once we've programmed the + * panel with Intel's OUI - which is also required for us to be able to detect Intel's + * backlight interface at all. This means that the only sensible way for us to detect both + * interfaces is to probe for Intel's first, and VESA's second. */ - if (try_intel_interface && intel_dp_aux_supports_hdr_backlight(connector)) { - drm_dbg_kms(dev, "Using Intel proprietary eDP backlight controls\n"); + if (try_intel_interface && intel_dp_aux_supports_hdr_backlight(connector) && + intel_dp->edp_dpcd[0] <= DP_EDP_14b) { + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Using Intel proprietary eDP backlight controls\n", + connector->base.base.id, connector->base.name); panel->backlight.funcs = &intel_dp_hdr_bl_funcs; return 0; } if (try_vesa_interface && intel_dp_aux_supports_vesa_backlight(connector)) { - drm_dbg_kms(dev, "Using VESA eDP backlight controls\n"); + drm_dbg_kms(dev, "[CONNECTOR:%d:%s] Using VESA eDP backlight controls\n", + connector->base.base.id, connector->base.name); panel->backlight.funcs = &intel_dp_vesa_bl_funcs; return 0; } |
