*
  */
 
+/*
+ * Laptops with Intel GPUs which have panels that support controlling the
+ * backlight through DP AUX can actually use two different interfaces: Intel's
+ * proprietary DP AUX backlight interface, and the standard VESA backlight
+ * interface. Unfortunately, at the time of writing this a lot of laptops will
+ * advertise support for the standard VESA backlight interface when they
+ * don't properly support it. However, on these systems the Intel backlight
+ * interface generally does work properly. Additionally, these systems will
+ * usually just indicate that they use PWM backlight controls in their VBIOS
+ * for some reason.
+ */
+
 #include "intel_display_types.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
 
 #define INTEL_EDP_BRIGHTNESS_OPTIMIZATION_1                            0x359
 
+/* 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_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];
+
+       ret = drm_dp_dpcd_read(aux, INTEL_EDP_HDR_TCON_CAP0, tcon_cap, sizeof(tcon_cap));
+       if (ret < 0)
+               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]);
+               return false;
+       }
+
+       panel->backlight.edp.intel.sdr_uses_aux =
+               tcon_cap[2] & INTEL_EDP_SDR_TCON_BRIGHTNESS_AUX_CAP;
+
+       return true;
+}
+
+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_panel *panel = &connector->panel;
+       struct intel_dp *intel_dp = enc_to_intel_dp(connector->encoder);
+       u8 tmp;
+       u8 buf[2] = { 0 };
+
+       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");
+               return 0;
+       }
+
+       if (!(tmp & INTEL_EDP_HDR_TCON_BRIGHTNESS_AUX_ENABLE)) {
+               if (!panel->backlight.edp.intel.sdr_uses_aux) {
+                       u32 pwm_level = panel->backlight.pwm_funcs->get(connector, pipe);
+
+                       return intel_panel_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");
+               return 0;
+       }
+
+       return (buf[1] << 8 | buf[0]);
+}
+
+static void
+intel_dp_aux_hdr_set_aux_backlight(const struct drm_connector_state *conn_state, u32 level)
+{
+       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 };
+
+       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");
+}
+
+static void
+intel_dp_aux_hdr_set_backlight(const struct drm_connector_state *conn_state, u32 level)
+{
+       struct intel_connector *connector = to_intel_connector(conn_state->connector);
+       struct intel_panel *panel = &connector->panel;
+
+       if (panel->backlight.edp.intel.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);
+
+               intel_panel_set_pwm_level(conn_state, pwm_level);
+       }
+}
+
+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_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);
+       int ret;
+       u8 old_ctrl, ctrl;
+
+       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);
+               return;
+       }
+
+       ctrl = old_ctrl;
+       if (panel->backlight.edp.intel.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);
+
+               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");
+}
+
+static void
+intel_dp_aux_hdr_disable_backlight(const struct drm_connector_state *conn_state, u32 level)
+{
+       struct intel_connector *connector = to_intel_connector(conn_state->connector);
+       struct intel_panel *panel = &connector->panel;
+
+       /* Nothing to do for AUX based backlight controls */
+       if (panel->backlight.edp.intel.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));
+}
+
+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_panel *panel = &connector->panel;
+       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");
+
+               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);
+                       return ret;
+               }
+       }
+
+       panel->backlight.max = 512;
+       panel->backlight.min = 0;
+       panel->backlight.level = intel_dp_aux_hdr_get_backlight(connector, pipe);
+       panel->backlight.enabled = panel->backlight.level != 0;
+
+       return 0;
+}
+
 /* VESA backlight callbacks */
 static void set_vesa_backlight_enable(struct intel_dp *intel_dp, bool enable)
 {
 {
        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.pwmgen_bit_count;
+       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;
        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) {
 
                if (drm_dp_dpcd_writeb(&intel_dp->aux,
                                       DP_EDP_PWMGEN_BIT_COUNT,
-                                      panel->backlight.pwmgen_bit_count) < 0)
+                                      pwmgen_bit_count) < 0)
                        drm_dbg_kms(&i915->drm,
                                    "Failed to write aux pwmgen bit count\n");
 
                            "Failed to write aux pwmgen bit count\n");
                return max_backlight;
        }
-       panel->backlight.pwmgen_bit_count = pn;
+       panel->backlight.edp.vesa.pwmgen_bit_count = pn;
 
        max_backlight = (1 << pn) - 1;
 
        return false;
 }
 
+static const struct intel_panel_bl_funcs intel_dp_hdr_bl_funcs = {
+       .setup = intel_dp_aux_hdr_setup_backlight,
+       .enable = intel_dp_aux_hdr_enable_backlight,
+       .disable = intel_dp_aux_hdr_disable_backlight,
+       .set = intel_dp_aux_hdr_set_backlight,
+       .get = intel_dp_aux_hdr_get_backlight,
+};
+
 static const struct intel_panel_bl_funcs intel_dp_vesa_bl_funcs = {
        .setup = intel_dp_aux_vesa_setup_backlight,
        .enable = intel_dp_aux_vesa_enable_backlight,
        .get = intel_dp_aux_vesa_get_backlight,
 };
 
-int intel_dp_aux_init_backlight_funcs(struct intel_connector *intel_connector)
+int intel_dp_aux_init_backlight_funcs(struct intel_connector *connector)
 {
-       struct intel_panel *panel = &intel_connector->panel;
-       struct intel_dp *intel_dp = enc_to_intel_dp(intel_connector->encoder);
+       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);
 
-       if (i915->params.enable_dpcd_backlight == 0 ||
-           !intel_dp_aux_supports_vesa_backlight(intel_connector))
+       if (i915->params.enable_dpcd_backlight == 0)
                return -ENODEV;
 
        /*
-        * There are a lot of machines that don't advertise the backlight
-        * control interface to use properly in their VBIOS, :\
+        * 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.
         */
-       if (i915->vbt.backlight.type !=
-           INTEL_BACKLIGHT_VESA_EDP_AUX_INTERFACE &&
-           i915->params.enable_dpcd_backlight != 1 &&
-           !drm_dp_has_quirk(&intel_dp->desc, intel_dp->edid_quirks,
-                             DP_QUIRK_FORCE_DPCD_BACKLIGHT)) {
-               drm_info(&i915->drm,
-                        "Panel advertises DPCD backlight support, but "
-                        "VBT disagrees. If your backlight controls "
-                        "don't work try booting with "
-                        "i915.enable_dpcd_backlight=1. If your machine "
-                        "needs this, please file a _new_ bug report on "
-                        "drm/i915, see " FDO_BUG_URL " for details.\n");
-               return -ENODEV;
+       if (intel_dp_aux_supports_hdr_backlight(connector)) {
+               drm_dbg_kms(dev, "Using Intel proprietary eDP backlight controls\n");
+               panel->backlight.funcs = &intel_dp_hdr_bl_funcs;
+               return 0;
        }
 
-       panel->backlight.funcs = &intel_dp_vesa_bl_funcs;
+       if (intel_dp_aux_supports_vesa_backlight(connector)) {
+               drm_dbg_kms(dev, "Using VESA eDP backlight controls\n");
+               panel->backlight.funcs = &intel_dp_vesa_bl_funcs;
+               return 0;
+       }
 
-       return 0;
+       return -ENODEV;
 }
 
                     0, user_max);
 }
 
-static u32 intel_panel_invert_pwm_level(struct intel_connector *connector, u32 val)
+u32 intel_panel_invert_pwm_level(struct intel_connector *connector, u32 val)
 {
        struct drm_i915_private *dev_priv = to_i915(connector->base.dev);
        struct intel_panel *panel = &connector->panel;
        panel->backlight.pwm_funcs->set(conn_state, val);
 }
 
+u32 intel_panel_backlight_level_to_pwm(struct intel_connector *connector, u32 val)
+{
+       struct drm_i915_private *dev_priv = to_i915(connector->base.dev);
+       struct intel_panel *panel = &connector->panel;
+
+       drm_WARN_ON_ONCE(&dev_priv->drm,
+                        panel->backlight.max == 0 || panel->backlight.pwm_level_max == 0);
+
+       val = scale(val, panel->backlight.min, panel->backlight.max,
+                   panel->backlight.pwm_level_min, panel->backlight.pwm_level_max);
+
+       return intel_panel_invert_pwm_level(connector, val);
+}
+
+u32 intel_panel_backlight_level_from_pwm(struct intel_connector *connector, u32 val)
+{
+       struct drm_i915_private *dev_priv = to_i915(connector->base.dev);
+       struct intel_panel *panel = &connector->panel;
+
+       drm_WARN_ON_ONCE(&dev_priv->drm,
+                        panel->backlight.max == 0 || panel->backlight.pwm_level_max == 0);
+
+       if (dev_priv->params.invert_brightness > 0 ||
+           (dev_priv->params.invert_brightness == 0 && dev_priv->quirks & QUIRK_INVERT_BRIGHTNESS))
+               val = panel->backlight.pwm_level_max - (val - panel->backlight.pwm_level_min);
+
+       return scale(val, panel->backlight.pwm_level_min, panel->backlight.pwm_level_max,
+                    panel->backlight.min, panel->backlight.max);
+}
+
 static u32 lpt_get_backlight(struct intel_connector *connector, enum pipe unused)
 {
        struct drm_i915_private *dev_priv = to_i915(connector->base.dev);
                container_of(panel, struct intel_connector, panel);
        struct drm_i915_private *dev_priv = to_i915(connector->base.dev);
 
-       if (connector->base.connector_type == DRM_MODE_CONNECTOR_eDP &&
-           intel_dp_aux_init_backlight_funcs(connector) == 0)
-               return;
-
        if (connector->base.connector_type == DRM_MODE_CONNECTOR_DSI &&
            intel_dsi_dcs_init_backlight_funcs(connector) == 0)
                return;
                panel->backlight.pwm_funcs = &i9xx_pwm_funcs;
        }
 
+       if (connector->base.connector_type == DRM_MODE_CONNECTOR_eDP &&
+           intel_dp_aux_init_backlight_funcs(connector) == 0)
+               return;
+
        /* We're using a standard PWM backlight interface */
        panel->backlight.funcs = &pwm_bl_funcs;
 }