diff mbox series

[v2] drm/i915: customize DPCD brightness control for specific panel

Message ID 20191009161346.21306-1-shawn.c.lee@intel.com (mailing list archive)
State New, archived
Headers show
Series [v2] drm/i915: customize DPCD brightness control for specific panel | expand

Commit Message

Lee Shawn C Oct. 9, 2019, 4:13 p.m. UTC
This panel (manufacturer is SDC, product ID is 0x4141)
used manufacturer defined DPCD register to control brightness
that not defined in eDP spec so far. This change follow panel
vendor's instruction to support brightness adjustment.

Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=97883

V2: To check sink OUI instead of EDID quirk.
    According to TCON's capability to decide to enable this
    method for brightness control.

Cc: Jani Nikula <jani.nikula@intel.com>
Cc: Adam Jackson <ajax@redhat.com>
Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
Cc: Gustavo Padovan <gustavo@padovan.org>
Cc: Cooper Chiou <cooper.chiou@intel.com>

Signed-off-by: Lee Shawn C <shawn.c.lee@intel.com>
---
 drivers/gpu/drm/drm_dp_helper.c               |  18 ++-
 drivers/gpu/drm/i915/display/intel_dp.c       |   7 +
 .../drm/i915/display/intel_dp_aux_backlight.c | 138 +++++++++++++++++-
 include/drm/drm_dp_helper.h                   |  20 +++
 4 files changed, 176 insertions(+), 7 deletions(-)

Comments

Lyude Paul Nov. 27, 2019, 12:25 a.m. UTC | #1
Hey! Saw your comment on the RHBZ regarding this, figured I'd take a look
since I've got some DPCD backlight related fixes for one of your other laptops
on the list as well.

On Thu, 2019-10-10 at 00:13 +0800, Lee Shawn C wrote:
> This panel (manufacturer is SDC, product ID is 0x4141)
> used manufacturer defined DPCD register to control brightness
> that not defined in eDP spec so far. This change follow panel
> vendor's instruction to support brightness adjustment.
> 
> Bugzilla: https://bugs.freedesktop.org/show_bug.cgi?id=97883
> 
> V2: To check sink OUI instead of EDID quirk.
>     According to TCON's capability to decide to enable this
>     method for brightness control.
> 
> Cc: Jani Nikula <jani.nikula@intel.com>
> Cc: Adam Jackson <ajax@redhat.com>
> Cc: Maarten Lankhorst <maarten.lankhorst@linux.intel.com>
> Cc: Gustavo Padovan <gustavo@padovan.org>
> Cc: Cooper Chiou <cooper.chiou@intel.com>
> 
> Signed-off-by: Lee Shawn C <shawn.c.lee@intel.com>
> ---
>  drivers/gpu/drm/drm_dp_helper.c               |  18 ++-
>  drivers/gpu/drm/i915/display/intel_dp.c       |   7 +
>  .../drm/i915/display/intel_dp_aux_backlight.c | 138 +++++++++++++++++-
>  include/drm/drm_dp_helper.h                   |  20 +++
>  4 files changed, 176 insertions(+), 7 deletions(-)
> 
> diff --git a/drivers/gpu/drm/drm_dp_helper.c
> b/drivers/gpu/drm/drm_dp_helper.c
> index f373798d82f6..02f79587c337 100644
> --- a/drivers/gpu/drm/drm_dp_helper.c
> +++ b/drivers/gpu/drm/drm_dp_helper.c
> @@ -1268,6 +1268,8 @@ static const struct dpcd_quirk dpcd_quirk_list[] = {
>  	{ OUI(0x00, 0x10, 0xfa), DEVICE_ID_ANY, false,
> BIT(DP_DPCD_QUIRK_NO_PSR) },
>  	/* CH7511 seems to leave SINK_COUNT zeroed */
>  	{ OUI(0x00, 0x00, 0x00), DEVICE_ID('C', 'H', '7', '5', '1', '1'),
> false, BIT(DP_DPCD_QUIRK_NO_SINK_COUNT) },
> +	/* Samsung eDP panel */
> +	{ OUI(0xba, 0x41, 0x59), DEVICE_ID_ANY, false,
> BIT(DP_DPCD_QUIRK_CUSTOMIZE_BRIGHTNESS_CONTROL) },

We definitely need some more info on how this is supposed to work, since it's
not in the eDP spec and it would be hard for anyone to fix or move around code
on this upstream if upstream doesn't really know how it works.

Additionally, we really should take care to label these macros and quirks
correctly. CUSTOMIZE_BRIGHTNESS_CONTROL is pretty vague, and doesn't really
make it clear that these are non-eDP compliant displays with proprietary
backlight controls, not part of the VESA spec. If there's a name for the
backlight protocol that Samsung uses on these panels we should name it after
that, or just name it after samsung (something like
DP_DPCD_QUIRK_INTEL_BRIGHTNESS_CONTROL).

>  };
>  
>  #undef OUI
> @@ -1281,7 +1283,7 @@ static const struct dpcd_quirk dpcd_quirk_list[] = {
>   * to device identification string and hardware/firmware revisions later.
>   */
>  static u32
> -drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch)
> +drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch, u8
> *tcon_cap)
>  {
>  	const struct dpcd_quirk *quirk;
>  	u32 quirks = 0;
> @@ -1301,6 +1303,11 @@ drm_dp_get_quirks(const struct drm_dp_dpcd_ident
> *ident, bool is_branch)
>  		    memcmp(quirk->device_id, ident->device_id, sizeof(ident-
> >device_id)) != 0)
>  			continue;
>  
> +		if (quirk->quirks ==
> DP_DPCD_QUIRK_CUSTOMIZE_BRIGHTNESS_CONTROL &&
> +		    (!(tcon_cap[1] & DP_BRIGHTNESS_CONTROL_NITS) ||
> +		     !(tcon_cap[2] & DP_BRIGHTNESS_CONTROL_BY_AUX)))
> +				continue;
> +
>  		quirks |= quirk->quirks;
>  	}
>  
> @@ -1327,12 +1334,19 @@ int drm_dp_read_desc(struct drm_dp_aux *aux, struct
> drm_dp_desc *desc,
>  	struct drm_dp_dpcd_ident *ident = &desc->ident;
>  	unsigned int offset = is_branch ? DP_BRANCH_OUI : DP_SINK_OUI;
>  	int ret, dev_id_len;
> +	u8 tcon_cap[4] = {0};
>  
>  	ret = drm_dp_dpcd_read(aux, offset, ident, sizeof(*ident));
>  	if (ret < 0)
>  		return ret;
>  
> -	desc->quirks = drm_dp_get_quirks(ident, is_branch);
> +	if (offset == DP_SINK_OUI) {
> +		ret = drm_dp_dpcd_read(aux, DP_EDP_TCON_CAPABILITY_BYTE0,
> tcon_cap, sizeof(tcon_cap));
> +		if (ret < 0)
> +			return ret;
> +	}
> +
> +	desc->quirks = drm_dp_get_quirks(ident, is_branch, tcon_cap);
>  
>  	dev_id_len = strnlen(ident->device_id, sizeof(ident->device_id));
>  
> diff --git a/drivers/gpu/drm/i915/display/intel_dp.c
> b/drivers/gpu/drm/i915/display/intel_dp.c
> index 2b1e71f992b0..766b4b252147 100644
> --- a/drivers/gpu/drm/i915/display/intel_dp.c
> +++ b/drivers/gpu/drm/i915/display/intel_dp.c
> @@ -7082,6 +7082,13 @@ static bool intel_edp_init_connector(struct intel_dp
> *intel_dp,
>  		goto out_vdd_off;
>  	}
>  
> +	if (drm_dp_has_quirk(&intel_dp->desc,
> +	    DP_DPCD_QUIRK_CUSTOMIZE_BRIGHTNESS_CONTROL)) {
> +		i915_modparams.enable_dpcd_backlight = true;
> +		i915_modparams.fastboot = false;
> +		DRM_DEBUG_KMS("Using specific DPCD to control brightness\n");
> +	}
> +
mhhhh, changing kernel module parameters like this in code is probably not a
great idea. I agree with Jani as well that for something like this, we should 
I would take a look at how I implemented the quirk to work around the X1
Extreme 2nd Generation's broken vbios (which forgets to specify that the
built-in display uses standard DPCD backlight controls):

https://patchwork.freedesktop.org/patch/342166/?series=69914&rev=1

Also, we should probably just change the default for enable_dpcd_backlight to
-1 anyway, and do so in a separate patch, since Intel sounds OK with doing
that from the past discussions I've had with them. I've actually got a patch
on the mailing list that's awaiting review which does this as well:

https://patchwork.freedesktop.org/patch/342165/?series=69914&rev=1

If intel's OK with it, we probably could push this along with your patch if
yours gets reviewed before my series

>  	mutex_lock(&dev->mode_config.mutex);
>  	edid = drm_get_edid(connector, &intel_dp->aux.ddc);
>  	if (edid) {
> 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 020422da2ae2..09480e772bd4 100644
> --- a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c
> +++ b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c
> @@ -25,6 +25,111 @@
>  #include "intel_display_types.h"
>  #include "intel_dp_aux_backlight.h"
>  
> +#define EDP_CUSTOMIZE_MAX_BRIGHTNESS_LEVEL	(512)
> +
> +static uint32_t intel_dp_aux_get_customize_backlight(struct intel_connector
> *connector)
> +{
> +	struct intel_dp *intel_dp = enc_to_intel_dp(&connector->encoder-
> >base);
> +	uint8_t read_val[2] = { 0x0 };
> +
> +	if (drm_dp_dpcd_read(&intel_dp->aux, DP_EDP_BRIGHTNESS_NITS,
> +			     &read_val, sizeof(read_val)) < 0) {
> +		DRM_DEBUG_KMS("Failed to read DPCD register %x\n",
> +			DP_EDP_BRIGHTNESS_NITS);
> +		return 0;
> +	}
> +
> +	return (read_val[1] << 8 | read_val[0]);
> +}
> +
> +static void
> +intel_dp_aux_set_customize_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 = enc_to_intel_dp(&connector->encoder-
> >base);
> +	struct intel_panel *panel = &connector->panel;
> +	uint8_t new_vals[4];
> +
> +	if (level > panel->backlight.max)
> +		level = panel->backlight.max;
> +
> +	new_vals[0] = level & 0xFF;
> +	new_vals[1] = (level & 0xFF00) >> 8;
> +	new_vals[2] = 0;
> +	new_vals[3] = 0;
> +
> +	if (drm_dp_dpcd_write(&intel_dp->aux, DP_EDP_BRIGHTNESS_NITS,
> new_vals, 4) < 0)
> +		DRM_DEBUG_KMS("Failed to write aux backlight level\n");
> +}
> +
> +static void intel_dp_aux_enable_customize_backlight(const struct
> intel_crtc_state *crtc_state,
> +					  const struct drm_connector_state
> *conn_state)
> +{
> +	struct intel_connector *connector = to_intel_connector(conn_state-
> >connector);
> +	struct intel_dp *intel_dp = enc_to_intel_dp(&connector->encoder-
> >base);
> +	uint8_t read_val[4], i;
> +	uint8_t write_val[8] = {0x00, 0x00, 0xF0, 0x01, 0x90, 0x01, 0x00,
> 0x00};
> +
> +	if (drm_dp_dpcd_write(&intel_dp->aux, DP_EDP_PANEL_LUMINANCE_OVERRIDE,
> write_val, sizeof(write_val)) < 0)
> +		DRM_DEBUG_KMS("Failed to write panel luminance.\n");
> +
> +	if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_EDP_BRIGHTNESS_OPTIMIZATION,
> 0x01) < 0)
> +		DRM_DEBUG_KMS("Failed to write %x\n",
> +			DP_EDP_BRIGHTNESS_OPTIMIZATION);

> +	/* write source OUI */
> +	write_val[0] = 0x00;
> +	write_val[1] = 0xaa;
> +	write_val[2] = 0x01;

This made me do one big double take for a moment, but I believe I understand
what it does now that I've read through the rest of this thread. We should
definitely leave a comment here though.

> +	if (drm_dp_dpcd_write(&intel_dp->aux, DP_SOURCE_OUI, write_val, 3) <
> 0)
> +		DRM_DEBUG_KMS("Failed to write OUI\n");
> +
> +	if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_GETSET_CTRL_PARAMS,
> read_val) != 1)
> +		DRM_DEBUG_KMS("Failed to read %x\n",
> +			DP_EDP_GETSET_CTRL_PARAMS);
> +
> +	if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_EDP_GETSET_CTRL_PARAMS, 0)
> != 1)
> +		DRM_DEBUG_KMS("Failed to write %x\n",
> +			DP_EDP_GETSET_CTRL_PARAMS);
> +
> +	if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_GETSET_CTRL_PARAMS,
> read_val) != 1)
> +		DRM_DEBUG_KMS("Failed to read %x\n",
> +			DP_EDP_GETSET_CTRL_PARAMS);
> +
> +	if (drm_dp_dpcd_read(&intel_dp->aux, DP_EDP_CONTENT_LUMINANCE,
> &read_val, sizeof(read_val)) < 0)
> +		DRM_DEBUG_KMS("Failed to read %x\n",
> +			DP_EDP_CONTENT_LUMINANCE);
> +
> +	memset(read_val, 0x0, 4);
> +	if (drm_dp_dpcd_write(&intel_dp->aux, DP_EDP_CONTENT_LUMINANCE,
> read_val, sizeof(read_val)) < 0)
> +		DRM_DEBUG_KMS("Failed to write %x\n",
> +			DP_EDP_CONTENT_LUMINANCE);
> +
> +	if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_GETSET_CTRL_PARAMS,
> read_val) != 1)
> +		DRM_DEBUG_KMS("Failed to read %x\n",
> +			DP_EDP_GETSET_CTRL_PARAMS);
> +
> +	if (drm_dp_dpcd_read(&intel_dp->aux, DP_SOURCE_OUI, read_val, 4) < 0)
> +		DRM_DEBUG_KMS("Failed to read OUI\n");
> +
> +	DRM_DEBUG_KMS("got OUI %3ph\n", read_val);
> +
> +	for (i = 0; i < 6; i++) {
> +		intel_dp_aux_set_customize_backlight(conn_state, connector-
> >panel.backlight.level);
> +
> +		msleep(60);
> +		if (intel_dp_aux_get_customize_backlight(connector))
> +			return;
> +	}
> +
> +	DRM_DEBUG_KMS("Restore brightness may have problem.\n");
Why not just "Failed to restore brightness\n"

> +}
> +
> +static void intel_dp_aux_disable_customize_backlight(const struct
> drm_connector_state *old_conn_state)
> +{
> +	// do nothing
> +	return;
> +}
> +
>  static void set_aux_backlight_enable(struct intel_dp *intel_dp, bool
> enable)
>  {
>  	u8 reg_val = 0;
> @@ -225,6 +330,19 @@ static void intel_dp_aux_disable_backlight(const struct
> drm_connector_state *old
>  	set_aux_backlight_enable(enc_to_intel_dp(old_conn_state-
> >best_encoder), false);
>  }
>  
> +static int intel_dp_aux_setup_customize_backlight(struct intel_connector
> *connector,
> +					enum pipe pipe)
> +{
> +	struct intel_panel *panel = &connector->panel;
> +
> +	panel->backlight.max = EDP_CUSTOMIZE_MAX_BRIGHTNESS_LEVEL;
> +	panel->backlight.min = 0;
> +	panel->backlight.level = panel->backlight.get(connector);

I can't really test this locally since I don't have this laptop, did you make
sure this part actually returns the currently set brightness? I know on the X1
Carbon Extreme 2nd Gen (which uses standard eDP compliant DPCD backlight
controls) the initial backlight value is reported as 0 when it's actually max,
and we have to check the control register to guess what the actual brightness
is:

https://patchwork.freedesktop.org/patch/342160/?series=69914&rev=1

> +	panel->backlight.enabled = panel->backlight.level != 0;
> +
> +	return 0;
> +}
> +
>  static int intel_dp_aux_setup_backlight(struct intel_connector *connector,
>  					enum pipe pipe)
>  {
> @@ -265,6 +383,7 @@ int intel_dp_aux_init_backlight_funcs(struct
> intel_connector *intel_connector)
>  {
>  	struct intel_panel *panel = &intel_connector->panel;
>  	struct drm_i915_private *dev_priv = to_i915(intel_connector-
> >base.dev);
> +	struct intel_dp *intel_dp = enc_to_intel_dp(&intel_connector->encoder-
> >base);
>  
>  	if (i915_modparams.enable_dpcd_backlight == 0 ||
>  	    (i915_modparams.enable_dpcd_backlight == -1 &&
> @@ -274,11 +393,20 @@ int intel_dp_aux_init_backlight_funcs(struct
> intel_connector *intel_connector)
>  	if (!intel_dp_aux_display_control_capable(intel_connector))
>  		return -ENODEV;
>  
> -	panel->backlight.setup = intel_dp_aux_setup_backlight;
> -	panel->backlight.enable = intel_dp_aux_enable_backlight;
> -	panel->backlight.disable = intel_dp_aux_disable_backlight;
> -	panel->backlight.set = intel_dp_aux_set_backlight;
> -	panel->backlight.get = intel_dp_aux_get_backlight;
> +	if (drm_dp_has_quirk(&intel_dp->desc,
> +	    DP_DPCD_QUIRK_CUSTOMIZE_BRIGHTNESS_CONTROL)) {
> +		panel->backlight.setup   =
> intel_dp_aux_setup_customize_backlight;
> +		panel->backlight.enable  =
> intel_dp_aux_enable_customize_backlight;
> +		panel->backlight.disable =
> intel_dp_aux_disable_customize_backlight;
> +		panel->backlight.set = intel_dp_aux_set_customize_backlight;
> +		panel->backlight.get = intel_dp_aux_get_customize_backlight;
> +	} else {
> +		panel->backlight.setup   = intel_dp_aux_setup_backlight;
> +		panel->backlight.enable  = intel_dp_aux_enable_backlight;
> +		panel->backlight.disable = intel_dp_aux_disable_backlight;
> +		panel->backlight.set = intel_dp_aux_set_backlight;
> +		panel->backlight.get = intel_dp_aux_get_backlight;
> +	}
>  
>  	return 0;
>  }
> diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h
> index ed1a985745ba..581d1ba8435d 100644
> --- a/include/drm/drm_dp_helper.h
> +++ b/include/drm/drm_dp_helper.h
> @@ -740,6 +740,20 @@
>  /* up to ID_SLOT_63 at 0x2ff */
>  
>  #define DP_SOURCE_OUI			    0x300
> +#define DP_EDP_TCON_CAPABILITY_BYTE0	    0x340
> +
> +#define DP_EDP_TCON_CAPABILITY_BYTE1	    0x341
> +# define DP_BRIGHTNESS_CONTROL_NITS	    0x10
> +
> +#define DP_EDP_TCON_CAPABILITY_BYTE2	    0x342
> +# define DP_BRIGHTNESS_CONTROL_BY_AUX	    0x01
> +
> +#define DP_EDP_GETSET_CTRL_PARAMS	    0x344
> +#define DP_EDP_CONTENT_LUMINANCE	    0x346
> +#define DP_EDP_PANEL_LUMINANCE_OVERRIDE     0x34A
> +#define DP_EDP_BRIGHTNESS_NITS		    0x354
> +#define DP_EDP_BRIGHTNESS_OPTIMIZATION	    0x358
> +

These definitely need to be renamed as well. A prefix like DP_EDP_INTEL_BL or
something named after the name of this protocol would work. I'd also leave a
comment explaining what these are for

>  #define DP_SINK_OUI			    0x400
>  #define DP_BRANCH_OUI			    0x500
>  #define DP_BRANCH_ID                        0x503
> @@ -1475,6 +1489,12 @@ enum drm_dp_quirk {
>  	 * The driver should ignore SINK_COUNT during detection.
>  	 */
>  	DP_DPCD_QUIRK_NO_SINK_COUNT,
> +	/**
> +	 * @DP_DPCD_QUIRK_NON_STANDARD_BRIGHTNESS_CONTROL:
> +	 *
> +	 * The device used specific DPCD register to control brightness.
> +	 */
> +	DP_DPCD_QUIRK_CUSTOMIZE_BRIGHTNESS_CONTROL,
This should be renamed as well, and should have a comment explaining that this
is a non-standard form of brightness control over DPCD
>  };
>  
>  /**
diff mbox series

Patch

diff --git a/drivers/gpu/drm/drm_dp_helper.c b/drivers/gpu/drm/drm_dp_helper.c
index f373798d82f6..02f79587c337 100644
--- a/drivers/gpu/drm/drm_dp_helper.c
+++ b/drivers/gpu/drm/drm_dp_helper.c
@@ -1268,6 +1268,8 @@  static const struct dpcd_quirk dpcd_quirk_list[] = {
 	{ OUI(0x00, 0x10, 0xfa), DEVICE_ID_ANY, false, BIT(DP_DPCD_QUIRK_NO_PSR) },
 	/* CH7511 seems to leave SINK_COUNT zeroed */
 	{ OUI(0x00, 0x00, 0x00), DEVICE_ID('C', 'H', '7', '5', '1', '1'), false, BIT(DP_DPCD_QUIRK_NO_SINK_COUNT) },
+	/* Samsung eDP panel */
+	{ OUI(0xba, 0x41, 0x59), DEVICE_ID_ANY, false, BIT(DP_DPCD_QUIRK_CUSTOMIZE_BRIGHTNESS_CONTROL) },
 };
 
 #undef OUI
@@ -1281,7 +1283,7 @@  static const struct dpcd_quirk dpcd_quirk_list[] = {
  * to device identification string and hardware/firmware revisions later.
  */
 static u32
-drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch)
+drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch, u8 *tcon_cap)
 {
 	const struct dpcd_quirk *quirk;
 	u32 quirks = 0;
@@ -1301,6 +1303,11 @@  drm_dp_get_quirks(const struct drm_dp_dpcd_ident *ident, bool is_branch)
 		    memcmp(quirk->device_id, ident->device_id, sizeof(ident->device_id)) != 0)
 			continue;
 
+		if (quirk->quirks == DP_DPCD_QUIRK_CUSTOMIZE_BRIGHTNESS_CONTROL &&
+		    (!(tcon_cap[1] & DP_BRIGHTNESS_CONTROL_NITS) ||
+		     !(tcon_cap[2] & DP_BRIGHTNESS_CONTROL_BY_AUX)))
+				continue;
+
 		quirks |= quirk->quirks;
 	}
 
@@ -1327,12 +1334,19 @@  int drm_dp_read_desc(struct drm_dp_aux *aux, struct drm_dp_desc *desc,
 	struct drm_dp_dpcd_ident *ident = &desc->ident;
 	unsigned int offset = is_branch ? DP_BRANCH_OUI : DP_SINK_OUI;
 	int ret, dev_id_len;
+	u8 tcon_cap[4] = {0};
 
 	ret = drm_dp_dpcd_read(aux, offset, ident, sizeof(*ident));
 	if (ret < 0)
 		return ret;
 
-	desc->quirks = drm_dp_get_quirks(ident, is_branch);
+	if (offset == DP_SINK_OUI) {
+		ret = drm_dp_dpcd_read(aux, DP_EDP_TCON_CAPABILITY_BYTE0, tcon_cap, sizeof(tcon_cap));
+		if (ret < 0)
+			return ret;
+	}
+
+	desc->quirks = drm_dp_get_quirks(ident, is_branch, tcon_cap);
 
 	dev_id_len = strnlen(ident->device_id, sizeof(ident->device_id));
 
diff --git a/drivers/gpu/drm/i915/display/intel_dp.c b/drivers/gpu/drm/i915/display/intel_dp.c
index 2b1e71f992b0..766b4b252147 100644
--- a/drivers/gpu/drm/i915/display/intel_dp.c
+++ b/drivers/gpu/drm/i915/display/intel_dp.c
@@ -7082,6 +7082,13 @@  static bool intel_edp_init_connector(struct intel_dp *intel_dp,
 		goto out_vdd_off;
 	}
 
+	if (drm_dp_has_quirk(&intel_dp->desc,
+	    DP_DPCD_QUIRK_CUSTOMIZE_BRIGHTNESS_CONTROL)) {
+		i915_modparams.enable_dpcd_backlight = true;
+		i915_modparams.fastboot = false;
+		DRM_DEBUG_KMS("Using specific DPCD to control brightness\n");
+	}
+
 	mutex_lock(&dev->mode_config.mutex);
 	edid = drm_get_edid(connector, &intel_dp->aux.ddc);
 	if (edid) {
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 020422da2ae2..09480e772bd4 100644
--- a/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c
+++ b/drivers/gpu/drm/i915/display/intel_dp_aux_backlight.c
@@ -25,6 +25,111 @@ 
 #include "intel_display_types.h"
 #include "intel_dp_aux_backlight.h"
 
+#define EDP_CUSTOMIZE_MAX_BRIGHTNESS_LEVEL	(512)
+
+static uint32_t intel_dp_aux_get_customize_backlight(struct intel_connector *connector)
+{
+	struct intel_dp *intel_dp = enc_to_intel_dp(&connector->encoder->base);
+	uint8_t read_val[2] = { 0x0 };
+
+	if (drm_dp_dpcd_read(&intel_dp->aux, DP_EDP_BRIGHTNESS_NITS,
+			     &read_val, sizeof(read_val)) < 0) {
+		DRM_DEBUG_KMS("Failed to read DPCD register %x\n",
+			DP_EDP_BRIGHTNESS_NITS);
+		return 0;
+	}
+
+	return (read_val[1] << 8 | read_val[0]);
+}
+
+static void
+intel_dp_aux_set_customize_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 = enc_to_intel_dp(&connector->encoder->base);
+	struct intel_panel *panel = &connector->panel;
+	uint8_t new_vals[4];
+
+	if (level > panel->backlight.max)
+		level = panel->backlight.max;
+
+	new_vals[0] = level & 0xFF;
+	new_vals[1] = (level & 0xFF00) >> 8;
+	new_vals[2] = 0;
+	new_vals[3] = 0;
+
+	if (drm_dp_dpcd_write(&intel_dp->aux, DP_EDP_BRIGHTNESS_NITS, new_vals, 4) < 0)
+		DRM_DEBUG_KMS("Failed to write aux backlight level\n");
+}
+
+static void intel_dp_aux_enable_customize_backlight(const struct intel_crtc_state *crtc_state,
+					  const struct drm_connector_state *conn_state)
+{
+	struct intel_connector *connector = to_intel_connector(conn_state->connector);
+	struct intel_dp *intel_dp = enc_to_intel_dp(&connector->encoder->base);
+	uint8_t read_val[4], i;
+	uint8_t write_val[8] = {0x00, 0x00, 0xF0, 0x01, 0x90, 0x01, 0x00, 0x00};
+
+	if (drm_dp_dpcd_write(&intel_dp->aux, DP_EDP_PANEL_LUMINANCE_OVERRIDE, write_val, sizeof(write_val)) < 0)
+		DRM_DEBUG_KMS("Failed to write panel luminance.\n");
+
+	if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_EDP_BRIGHTNESS_OPTIMIZATION, 0x01) < 0)
+		DRM_DEBUG_KMS("Failed to write %x\n",
+			DP_EDP_BRIGHTNESS_OPTIMIZATION);
+	/* write source OUI */
+	write_val[0] = 0x00;
+	write_val[1] = 0xaa;
+	write_val[2] = 0x01;
+	if (drm_dp_dpcd_write(&intel_dp->aux, DP_SOURCE_OUI, write_val, 3) < 0)
+		DRM_DEBUG_KMS("Failed to write OUI\n");
+
+	if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_GETSET_CTRL_PARAMS, read_val) != 1)
+		DRM_DEBUG_KMS("Failed to read %x\n",
+			DP_EDP_GETSET_CTRL_PARAMS);
+
+	if (drm_dp_dpcd_writeb(&intel_dp->aux, DP_EDP_GETSET_CTRL_PARAMS, 0) != 1)
+		DRM_DEBUG_KMS("Failed to write %x\n",
+			DP_EDP_GETSET_CTRL_PARAMS);
+
+	if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_GETSET_CTRL_PARAMS, read_val) != 1)
+		DRM_DEBUG_KMS("Failed to read %x\n",
+			DP_EDP_GETSET_CTRL_PARAMS);
+
+	if (drm_dp_dpcd_read(&intel_dp->aux, DP_EDP_CONTENT_LUMINANCE, &read_val, sizeof(read_val)) < 0)
+		DRM_DEBUG_KMS("Failed to read %x\n",
+			DP_EDP_CONTENT_LUMINANCE);
+
+	memset(read_val, 0x0, 4);
+	if (drm_dp_dpcd_write(&intel_dp->aux, DP_EDP_CONTENT_LUMINANCE, read_val, sizeof(read_val)) < 0)
+		DRM_DEBUG_KMS("Failed to write %x\n",
+			DP_EDP_CONTENT_LUMINANCE);
+
+	if (drm_dp_dpcd_readb(&intel_dp->aux, DP_EDP_GETSET_CTRL_PARAMS, read_val) != 1)
+		DRM_DEBUG_KMS("Failed to read %x\n",
+			DP_EDP_GETSET_CTRL_PARAMS);
+
+	if (drm_dp_dpcd_read(&intel_dp->aux, DP_SOURCE_OUI, read_val, 4) < 0)
+		DRM_DEBUG_KMS("Failed to read OUI\n");
+
+	DRM_DEBUG_KMS("got OUI %3ph\n", read_val);
+
+	for (i = 0; i < 6; i++) {
+		intel_dp_aux_set_customize_backlight(conn_state, connector->panel.backlight.level);
+
+		msleep(60);
+		if (intel_dp_aux_get_customize_backlight(connector))
+			return;
+	}
+
+	DRM_DEBUG_KMS("Restore brightness may have problem.\n");
+}
+
+static void intel_dp_aux_disable_customize_backlight(const struct drm_connector_state *old_conn_state)
+{
+	// do nothing
+	return;
+}
+
 static void set_aux_backlight_enable(struct intel_dp *intel_dp, bool enable)
 {
 	u8 reg_val = 0;
@@ -225,6 +330,19 @@  static void intel_dp_aux_disable_backlight(const struct drm_connector_state *old
 	set_aux_backlight_enable(enc_to_intel_dp(old_conn_state->best_encoder), false);
 }
 
+static int intel_dp_aux_setup_customize_backlight(struct intel_connector *connector,
+					enum pipe pipe)
+{
+	struct intel_panel *panel = &connector->panel;
+
+	panel->backlight.max = EDP_CUSTOMIZE_MAX_BRIGHTNESS_LEVEL;
+	panel->backlight.min = 0;
+	panel->backlight.level = panel->backlight.get(connector);
+	panel->backlight.enabled = panel->backlight.level != 0;
+
+	return 0;
+}
+
 static int intel_dp_aux_setup_backlight(struct intel_connector *connector,
 					enum pipe pipe)
 {
@@ -265,6 +383,7 @@  int intel_dp_aux_init_backlight_funcs(struct intel_connector *intel_connector)
 {
 	struct intel_panel *panel = &intel_connector->panel;
 	struct drm_i915_private *dev_priv = to_i915(intel_connector->base.dev);
+	struct intel_dp *intel_dp = enc_to_intel_dp(&intel_connector->encoder->base);
 
 	if (i915_modparams.enable_dpcd_backlight == 0 ||
 	    (i915_modparams.enable_dpcd_backlight == -1 &&
@@ -274,11 +393,20 @@  int intel_dp_aux_init_backlight_funcs(struct intel_connector *intel_connector)
 	if (!intel_dp_aux_display_control_capable(intel_connector))
 		return -ENODEV;
 
-	panel->backlight.setup = intel_dp_aux_setup_backlight;
-	panel->backlight.enable = intel_dp_aux_enable_backlight;
-	panel->backlight.disable = intel_dp_aux_disable_backlight;
-	panel->backlight.set = intel_dp_aux_set_backlight;
-	panel->backlight.get = intel_dp_aux_get_backlight;
+	if (drm_dp_has_quirk(&intel_dp->desc,
+	    DP_DPCD_QUIRK_CUSTOMIZE_BRIGHTNESS_CONTROL)) {
+		panel->backlight.setup   = intel_dp_aux_setup_customize_backlight;
+		panel->backlight.enable  = intel_dp_aux_enable_customize_backlight;
+		panel->backlight.disable = intel_dp_aux_disable_customize_backlight;
+		panel->backlight.set = intel_dp_aux_set_customize_backlight;
+		panel->backlight.get = intel_dp_aux_get_customize_backlight;
+	} else {
+		panel->backlight.setup   = intel_dp_aux_setup_backlight;
+		panel->backlight.enable  = intel_dp_aux_enable_backlight;
+		panel->backlight.disable = intel_dp_aux_disable_backlight;
+		panel->backlight.set = intel_dp_aux_set_backlight;
+		panel->backlight.get = intel_dp_aux_get_backlight;
+	}
 
 	return 0;
 }
diff --git a/include/drm/drm_dp_helper.h b/include/drm/drm_dp_helper.h
index ed1a985745ba..581d1ba8435d 100644
--- a/include/drm/drm_dp_helper.h
+++ b/include/drm/drm_dp_helper.h
@@ -740,6 +740,20 @@ 
 /* up to ID_SLOT_63 at 0x2ff */
 
 #define DP_SOURCE_OUI			    0x300
+#define DP_EDP_TCON_CAPABILITY_BYTE0	    0x340
+
+#define DP_EDP_TCON_CAPABILITY_BYTE1	    0x341
+# define DP_BRIGHTNESS_CONTROL_NITS	    0x10
+
+#define DP_EDP_TCON_CAPABILITY_BYTE2	    0x342
+# define DP_BRIGHTNESS_CONTROL_BY_AUX	    0x01
+
+#define DP_EDP_GETSET_CTRL_PARAMS	    0x344
+#define DP_EDP_CONTENT_LUMINANCE	    0x346
+#define DP_EDP_PANEL_LUMINANCE_OVERRIDE     0x34A
+#define DP_EDP_BRIGHTNESS_NITS		    0x354
+#define DP_EDP_BRIGHTNESS_OPTIMIZATION	    0x358
+
 #define DP_SINK_OUI			    0x400
 #define DP_BRANCH_OUI			    0x500
 #define DP_BRANCH_ID                        0x503
@@ -1475,6 +1489,12 @@  enum drm_dp_quirk {
 	 * The driver should ignore SINK_COUNT during detection.
 	 */
 	DP_DPCD_QUIRK_NO_SINK_COUNT,
+	/**
+	 * @DP_DPCD_QUIRK_NON_STANDARD_BRIGHTNESS_CONTROL:
+	 *
+	 * The device used specific DPCD register to control brightness.
+	 */
+	DP_DPCD_QUIRK_CUSTOMIZE_BRIGHTNESS_CONTROL,
 };
 
 /**