diff mbox

[CABC,v1,3/3,RFC] drm/i915: CABC support for backlight control

Message ID 1436156167-16127-4-git-send-email-m.deepak@intel.com (mailing list archive)
State New, archived
Headers show

Commit Message

Deepak M July 6, 2015, 4:16 a.m. UTC
In CABC (Content Adaptive Brightness Control) content grey level
scale can be increased while simultaneously decreasing
brightness of the backlight to achieve same perceived brightness.

The CABC is not standardized and panel vendors are free to follow
their implementation. The CABC implementaion here assumes that the
panels use standard SW register for control.

In this design there will be no PWM signal from the SoC and DCS
commands are sent to enable and control the backlight brightness.

Signed-off-by: Deepak M <m.deepak@intel.com>
---
 drivers/gpu/drm/i915/intel_dsi.h   |   13 +++
 drivers/gpu/drm/i915/intel_panel.c |  226 +++++++++++++++++++++++++++++++++++-
 include/video/mipi_display.h       |    8 ++
 3 files changed, 242 insertions(+), 5 deletions(-)

Comments

Daniel Vetter July 6, 2015, 12:51 p.m. UTC | #1
On Mon, Jul 06, 2015 at 09:46:07AM +0530, Deepak M wrote:
> In CABC (Content Adaptive Brightness Control) content grey level
> scale can be increased while simultaneously decreasing
> brightness of the backlight to achieve same perceived brightness.
> 
> The CABC is not standardized and panel vendors are free to follow
> their implementation. The CABC implementaion here assumes that the
> panels use standard SW register for control.
> 
> In this design there will be no PWM signal from the SoC and DCS
> commands are sent to enable and control the backlight brightness.
> 
> Signed-off-by: Deepak M <m.deepak@intel.com>
> ---
>  drivers/gpu/drm/i915/intel_dsi.h   |   13 +++
>  drivers/gpu/drm/i915/intel_panel.c |  226 +++++++++++++++++++++++++++++++++++-
>  include/video/mipi_display.h       |    8 ++
>  3 files changed, 242 insertions(+), 5 deletions(-)
> 
> diff --git a/drivers/gpu/drm/i915/intel_dsi.h b/drivers/gpu/drm/i915/intel_dsi.h
> index c39e39d..1f33cb2 100644
> --- a/drivers/gpu/drm/i915/intel_dsi.h
> +++ b/drivers/gpu/drm/i915/intel_dsi.h
> @@ -34,6 +34,19 @@
>  #define DSI_DUAL_LINK_FRONT_BACK	1
>  #define DSI_DUAL_LINK_PIXEL_ALT		2
>  
> +#define CABC_OFF			(0 << 0)
> +#define CABC_USER_INTERFACE_IMAGE	(1 << 0)
> +#define CABC_STILL_PICTURE		(2 << 0)
> +#define CABC_VIDEO_MODE			(3 << 0)
> +
> +#define CABC_BACKLIGHT			(1 << 2)
> +#define CABC_DIMMING_DISPLAY		(1 << 3)
> +#define CABC_BCTRL			(1 << 5)
> +
> +#define CABC_PORT_A			0x00
> +#define CABC_PORT_C			0x01
> +#define CABC_PORT_A_AND_C		0x02
> +
>  struct intel_dsi_host;
>  
>  struct intel_dsi {
> diff --git a/drivers/gpu/drm/i915/intel_panel.c b/drivers/gpu/drm/i915/intel_panel.c
> index 55aad23..7aa211e 100644
> --- a/drivers/gpu/drm/i915/intel_panel.c
> +++ b/drivers/gpu/drm/i915/intel_panel.c
> @@ -32,7 +32,10 @@
>  
>  #include <linux/kernel.h>
>  #include <linux/moduleparam.h>
> +#include <video/mipi_display.h>
> +#include <drm/drm_mipi_dsi.h>
>  #include "intel_drv.h"
> +#include "intel_dsi.h"
>  
>  void
>  intel_fixed_panel_mode(const struct drm_display_mode *fixed_mode,
> @@ -536,6 +539,37 @@ static u32 vlv_get_backlight(struct intel_connector *connector)
>  	return _vlv_get_backlight(dev, pipe);
>  }
>  
> +static u32 cabc_get_backlight(struct intel_connector *connector)
> +{
> +	struct drm_device *dev = connector->base.dev;
> +	struct intel_dsi *intel_dsi = NULL;
> +	struct drm_crtc *crtc = NULL;
> +	struct intel_encoder *encoder = NULL;
> +	struct mipi_dsi_device *dsi_device;
> +	u8 data[2] = {0};
> +	enum port port = PORT_A;
> +
> +	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
> +		for_each_encoder_on_crtc(dev, crtc, encoder) {
> +			if (encoder->type == INTEL_OUTPUT_DSI)
> +				intel_dsi = enc_to_intel_dsi(&encoder->base);
> +		}
> +	}

We seem to have a bit a layering violation going on here: The
dev_priv->display.*_backlight functions are for the device global
backlight. But this here controls a sink-specific backlight.

Another example of a sink-specific backlight control would be the optional
backlight stuff for eDP. In linux we have a few backlight classes like
firmware, platform or raw. The i915 backlight is a raw one and last in the
priority chain userspace uses to figure out which backlight to use.

I think the right approach here for the CABC dsi sink backlight is to
register a new dsi backlight driver (if and only if the panel supports it
of course) of type firmware (or something else suitable). Then it will
automatically take precendence over the i915 raw backlight. It would also
be good to have an explicit link from the sysfs connector to this
connector-specific backlight so that userspace knows about it.

Or something along those lines, linux backlight control is one giant mess.

At least with that approach we don't need to have any interaction with the
panel backlight code and this should all more-or-less cleanly integrate.
-Daniel

> +
> +	if (intel_dsi->dual_link) {
> +		if (intel_dsi->dl_cabc_port == CABC_PORT_A ||
> +				intel_dsi->dl_cabc_port == CABC_PORT_A_AND_C)
> +			port = PORT_A;
> +		else if (intel_dsi->dl_cabc_port == CABC_PORT_C)
> +			port = PORT_C;
> +	}
> +
> +	dsi_device = intel_dsi->dsi_hosts[port]->device;
> +	mipi_dsi_dcs_read(dsi_device, MIPI_DCS_CABC_LEVEL_RD, data, 2);
> +
> +	return data[1];
> +}
> +
>  static u32 bxt_get_backlight(struct intel_connector *connector)
>  {
>  	struct drm_device *dev = connector->base.dev;
> @@ -624,6 +658,47 @@ static void vlv_set_backlight(struct intel_connector *connector, u32 level)
>  	I915_WRITE(VLV_BLC_PWM_CTL(pipe), tmp | level);
>  }
>  
> +static void cabc_set_backlight(struct intel_connector *connector, u32 level)
> +{
> +	struct drm_device *dev = connector->base.dev;
> +	struct intel_dsi *intel_dsi = NULL;
> +	struct drm_crtc *crtc = NULL;
> +	struct intel_encoder *encoder = NULL;
> +	struct mipi_dsi_device *dsi_device;
> +	u8 data[2] = {0};
> +	enum port port = PORT_A;
> +	u8 count = 1;
> +
> +	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
> +		for_each_encoder_on_crtc(dev, crtc, encoder) {
> +			if (encoder->type == INTEL_OUTPUT_DSI)
> +				intel_dsi = enc_to_intel_dsi(&encoder->base);
> +		}
> +	}
> +
> +	if (intel_dsi->dual_link) {
> +		if (intel_dsi->dl_cabc_port == CABC_PORT_A)
> +			port = PORT_A;
> +		else if (intel_dsi->dl_cabc_port == CABC_PORT_C)
> +			port = PORT_C;
> +		else if (intel_dsi->dl_cabc_port == CABC_PORT_A_AND_C)
> +			port = PORT_A;
> +	}
> +
> +	if (intel_dsi->dual_link && intel_dsi->dl_cabc_port ==
> +			CABC_PORT_A_AND_C)
> +		count = 2;
> +
> +	do {
> +		dsi_device = intel_dsi->dsi_hosts[port]->device;
> +		data[1] = level;
> +		data[0] = MIPI_DCS_CABC_LEVEL_WR;
> +		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
> +		if (count == 2)
> +			port = PORT_C;
> +	} while (--count > 0);
> +}
> +
>  static void bxt_set_backlight(struct intel_connector *connector, u32 level)
>  {
>  	struct drm_device *dev = connector->base.dev;
> @@ -757,6 +832,52 @@ static void vlv_disable_backlight(struct intel_connector *connector)
>  	I915_WRITE(VLV_BLC_PWM_CTL2(pipe), tmp & ~BLM_PWM_ENABLE);
>  }
>  
> +static void cabc_disable_backlight(struct intel_connector *connector)
> +{
> +	struct drm_device *dev = connector->base.dev;
> +	struct intel_dsi *intel_dsi = NULL;
> +	struct drm_crtc *crtc = NULL;
> +	struct intel_encoder *encoder = NULL;
> +	struct mipi_dsi_device *dsi_device;
> +	enum port port = PORT_A;
> +	u8 data[2] = {0};
> +	u8 count = 1;
> +
> +	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
> +		for_each_encoder_on_crtc(dev, crtc, encoder) {
> +			if (encoder->type == INTEL_OUTPUT_DSI)
> +				intel_dsi = enc_to_intel_dsi(&encoder->base);
> +		}
> +	}
> +
> +	if (intel_dsi->dual_link) {
> +		if (intel_dsi->dl_cabc_port == CABC_PORT_A)
> +			port = PORT_A;
> +		else if (intel_dsi->dl_cabc_port == CABC_PORT_C)
> +			port = PORT_C;
> +		else if (intel_dsi->dl_cabc_port == CABC_PORT_A_AND_C)
> +			port = PORT_A;
> +	}
> +
> +	if (intel_dsi->dual_link && intel_dsi->dl_cabc_port ==
> +			CABC_PORT_A_AND_C)
> +		count = 2;
> +
> +	do {
> +		dsi_device = intel_dsi->dsi_hosts[port]->device;
> +
> +		intel_panel_actually_set_backlight(connector, 0);
> +		data[1] = CABC_OFF;
> +		data[0] = MIPI_DCS_CABC_CONTROL_WR;
> +		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
> +		data[0] = MIPI_DCS_CABC_CONTROL_BRIGHT_WR;
> +		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
> +
> +		if (count == 2)
> +			port = PORT_C;
> +	} while (--count > 0);
> +}
> +
>  static void bxt_disable_backlight(struct intel_connector *connector)
>  {
>  	struct drm_device *dev = connector->base.dev;
> @@ -1043,6 +1164,56 @@ void intel_panel_enable_backlight(struct intel_connector *connector)
>  	mutex_unlock(&dev_priv->backlight_lock);
>  }
>  
> +static void cabc_enable_backlight(struct intel_connector *connector)
> +{
> +	struct drm_device *dev = connector->base.dev;
> +	struct intel_dsi *intel_dsi = NULL;
> +	struct drm_crtc *crtc = NULL;
> +	struct intel_encoder *encoder = NULL;
> +	struct intel_panel *panel = &connector->panel;
> +	struct mipi_dsi_device *dsi_device;
> +	enum port port = PORT_A;
> +	u8 data[2] = {0};
> +	u8 count = 1;
> +
> +	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
> +		for_each_encoder_on_crtc(dev, crtc, encoder) {
> +			if (encoder->type == INTEL_OUTPUT_DSI)
> +				intel_dsi = enc_to_intel_dsi(&encoder->base);
> +		}
> +	}
> +
> +	if (intel_dsi->dual_link) {
> +		if (intel_dsi->dl_cabc_port == CABC_PORT_A)
> +			port = PORT_A;
> +		else if (intel_dsi->dl_cabc_port == CABC_PORT_C)
> +			port = PORT_C;
> +		else if (intel_dsi->dl_cabc_port == CABC_PORT_A_AND_C)
> +			port = PORT_A;
> +	}
> +
> +	if (intel_dsi->dual_link && intel_dsi->dl_cabc_port ==
> +			CABC_PORT_A_AND_C)
> +		count = 2;
> +
> +	do {
> +		dsi_device = intel_dsi->dsi_hosts[port]->device;
> +
> +
> +		data[0] = MIPI_DCS_CABC_CONTROL_BRIGHT_WR;
> +		data[1] = CABC_BACKLIGHT | CABC_DIMMING_DISPLAY | CABC_BCTRL;
> +		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
> +		data[0] = MIPI_DCS_CABC_CONTROL_WR;
> +		data[1] = CABC_VIDEO_MODE;
> +		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
> +
> +		if (count == 2)
> +			port = PORT_C;
> +	} while (--count > 0);
> +
> +	intel_panel_actually_set_backlight(connector, panel->backlight.level);
> +}
> +
>  #if IS_ENABLED(CONFIG_BACKLIGHT_CLASS_DEVICE)
>  static int intel_backlight_device_update_status(struct backlight_device *bd)
>  {
> @@ -1362,6 +1533,33 @@ static int vlv_setup_backlight(struct intel_connector *connector, enum pipe pipe
>  	return 0;
>  }
>  
> +static int cabc_setup_backlight(struct intel_connector *connector,
> +					enum pipe unused)
> +{
> +	struct drm_device *dev = connector->base.dev;
> +	struct intel_dsi *intel_dsi = NULL;
> +	struct drm_crtc *crtc = NULL;
> +	struct intel_encoder *encoder = NULL;
> +	struct intel_panel *panel = &connector->panel;
> +	struct mipi_dsi_device *dsi_device;
> +	u8 level;
> +
> +	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
> +		for_each_encoder_on_crtc(dev, crtc, encoder) {
> +			if (encoder->type == INTEL_OUTPUT_DSI)
> +				intel_dsi = enc_to_intel_dsi(&encoder->base);
> +		}
> +	}
> +	dsi_device = intel_dsi->dsi_hosts[0]->device;
> +	panel->backlight.max = 0xFF;
> +	panel->backlight.level = 0xFF;
> +
> +	level = 255;
> +	panel->backlight.enabled =
> +		(level & 0x02) && panel->backlight.level != 0;
> +	return 0;
> +}
> +
>  static int
>  bxt_setup_backlight(struct intel_connector *connector, enum pipe unused)
>  {
> @@ -1438,11 +1636,29 @@ void intel_panel_init_backlight_funcs(struct drm_device *dev)
>  	struct drm_i915_private *dev_priv = dev->dev_private;
>  
>  	if (IS_BROXTON(dev)) {
> -		dev_priv->display.setup_backlight = bxt_setup_backlight;
> -		dev_priv->display.enable_backlight = bxt_enable_backlight;
> -		dev_priv->display.disable_backlight = bxt_disable_backlight;
> -		dev_priv->display.set_backlight = bxt_set_backlight;
> -		dev_priv->display.get_backlight = bxt_get_backlight;
> +		if (dev_priv->vbt.dsi.config->cabc_supported) {
> +			dev_priv->display.setup_backlight =
> +					cabc_setup_backlight;
> +			dev_priv->display.enable_backlight =
> +					cabc_enable_backlight;
> +			dev_priv->display.disable_backlight =
> +					cabc_disable_backlight;
> +			dev_priv->display.set_backlight =
> +					cabc_set_backlight;
> +			dev_priv->display.get_backlight =
> +					cabc_get_backlight;
> +		} else {
> +			dev_priv->display.setup_backlight =
> +					bxt_setup_backlight;
> +			dev_priv->display.enable_backlight =
> +					bxt_enable_backlight;
> +			dev_priv->display.disable_backlight =
> +					bxt_disable_backlight;
> +			dev_priv->display.set_backlight =
> +					bxt_set_backlight;
> +			dev_priv->display.get_backlight =
> +					bxt_get_backlight;
> +		}
>  	} else if (IS_BROADWELL(dev) || IS_SKYLAKE(dev)) {
>  		dev_priv->display.setup_backlight = bdw_setup_backlight;
>  		dev_priv->display.enable_backlight = bdw_enable_backlight;
> diff --git a/include/video/mipi_display.h b/include/video/mipi_display.h
> index ddcc8ca..5b8eeec 100644
> --- a/include/video/mipi_display.h
> +++ b/include/video/mipi_display.h
> @@ -117,6 +117,14 @@ enum {
>  	MIPI_DCS_GET_SCANLINE		= 0x45,
>  	MIPI_DCS_READ_DDB_START		= 0xA1,
>  	MIPI_DCS_READ_DDB_CONTINUE	= 0xA8,
> +	MIPI_DCS_CABC_LEVEL_RD          = 0x52,
> +	MIPI_DCS_CABC_MIN_BRIGHTNESS_RD = 0x5F,
> +	MIPI_DCS_CABC_CONTROL_RD        = 0x56,
> +	MIPI_DCS_CABC_CONTROL_BRIGHT_RD = 0x54,
> +	MIPI_DCS_CABC_LEVEL_WR          = 0x51,
> +	MIPI_DCS_CABC_MIN_BRIGHTNESS_WR = 0x5E,
> +	MIPI_DCS_CABC_CONTROL_WR        = 0x55,
> +	MIPI_DCS_CABC_CONTROL_BRIGHT_WR = 0x53,
>  };
>  
>  /* MIPI DCS pixel formats */
> -- 
> 1.7.9.5
> 
> _______________________________________________
> Intel-gfx mailing list
> Intel-gfx@lists.freedesktop.org
> http://lists.freedesktop.org/mailman/listinfo/intel-gfx
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/intel_dsi.h b/drivers/gpu/drm/i915/intel_dsi.h
index c39e39d..1f33cb2 100644
--- a/drivers/gpu/drm/i915/intel_dsi.h
+++ b/drivers/gpu/drm/i915/intel_dsi.h
@@ -34,6 +34,19 @@ 
 #define DSI_DUAL_LINK_FRONT_BACK	1
 #define DSI_DUAL_LINK_PIXEL_ALT		2
 
+#define CABC_OFF			(0 << 0)
+#define CABC_USER_INTERFACE_IMAGE	(1 << 0)
+#define CABC_STILL_PICTURE		(2 << 0)
+#define CABC_VIDEO_MODE			(3 << 0)
+
+#define CABC_BACKLIGHT			(1 << 2)
+#define CABC_DIMMING_DISPLAY		(1 << 3)
+#define CABC_BCTRL			(1 << 5)
+
+#define CABC_PORT_A			0x00
+#define CABC_PORT_C			0x01
+#define CABC_PORT_A_AND_C		0x02
+
 struct intel_dsi_host;
 
 struct intel_dsi {
diff --git a/drivers/gpu/drm/i915/intel_panel.c b/drivers/gpu/drm/i915/intel_panel.c
index 55aad23..7aa211e 100644
--- a/drivers/gpu/drm/i915/intel_panel.c
+++ b/drivers/gpu/drm/i915/intel_panel.c
@@ -32,7 +32,10 @@ 
 
 #include <linux/kernel.h>
 #include <linux/moduleparam.h>
+#include <video/mipi_display.h>
+#include <drm/drm_mipi_dsi.h>
 #include "intel_drv.h"
+#include "intel_dsi.h"
 
 void
 intel_fixed_panel_mode(const struct drm_display_mode *fixed_mode,
@@ -536,6 +539,37 @@  static u32 vlv_get_backlight(struct intel_connector *connector)
 	return _vlv_get_backlight(dev, pipe);
 }
 
+static u32 cabc_get_backlight(struct intel_connector *connector)
+{
+	struct drm_device *dev = connector->base.dev;
+	struct intel_dsi *intel_dsi = NULL;
+	struct drm_crtc *crtc = NULL;
+	struct intel_encoder *encoder = NULL;
+	struct mipi_dsi_device *dsi_device;
+	u8 data[2] = {0};
+	enum port port = PORT_A;
+
+	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+		for_each_encoder_on_crtc(dev, crtc, encoder) {
+			if (encoder->type == INTEL_OUTPUT_DSI)
+				intel_dsi = enc_to_intel_dsi(&encoder->base);
+		}
+	}
+
+	if (intel_dsi->dual_link) {
+		if (intel_dsi->dl_cabc_port == CABC_PORT_A ||
+				intel_dsi->dl_cabc_port == CABC_PORT_A_AND_C)
+			port = PORT_A;
+		else if (intel_dsi->dl_cabc_port == CABC_PORT_C)
+			port = PORT_C;
+	}
+
+	dsi_device = intel_dsi->dsi_hosts[port]->device;
+	mipi_dsi_dcs_read(dsi_device, MIPI_DCS_CABC_LEVEL_RD, data, 2);
+
+	return data[1];
+}
+
 static u32 bxt_get_backlight(struct intel_connector *connector)
 {
 	struct drm_device *dev = connector->base.dev;
@@ -624,6 +658,47 @@  static void vlv_set_backlight(struct intel_connector *connector, u32 level)
 	I915_WRITE(VLV_BLC_PWM_CTL(pipe), tmp | level);
 }
 
+static void cabc_set_backlight(struct intel_connector *connector, u32 level)
+{
+	struct drm_device *dev = connector->base.dev;
+	struct intel_dsi *intel_dsi = NULL;
+	struct drm_crtc *crtc = NULL;
+	struct intel_encoder *encoder = NULL;
+	struct mipi_dsi_device *dsi_device;
+	u8 data[2] = {0};
+	enum port port = PORT_A;
+	u8 count = 1;
+
+	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+		for_each_encoder_on_crtc(dev, crtc, encoder) {
+			if (encoder->type == INTEL_OUTPUT_DSI)
+				intel_dsi = enc_to_intel_dsi(&encoder->base);
+		}
+	}
+
+	if (intel_dsi->dual_link) {
+		if (intel_dsi->dl_cabc_port == CABC_PORT_A)
+			port = PORT_A;
+		else if (intel_dsi->dl_cabc_port == CABC_PORT_C)
+			port = PORT_C;
+		else if (intel_dsi->dl_cabc_port == CABC_PORT_A_AND_C)
+			port = PORT_A;
+	}
+
+	if (intel_dsi->dual_link && intel_dsi->dl_cabc_port ==
+			CABC_PORT_A_AND_C)
+		count = 2;
+
+	do {
+		dsi_device = intel_dsi->dsi_hosts[port]->device;
+		data[1] = level;
+		data[0] = MIPI_DCS_CABC_LEVEL_WR;
+		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
+		if (count == 2)
+			port = PORT_C;
+	} while (--count > 0);
+}
+
 static void bxt_set_backlight(struct intel_connector *connector, u32 level)
 {
 	struct drm_device *dev = connector->base.dev;
@@ -757,6 +832,52 @@  static void vlv_disable_backlight(struct intel_connector *connector)
 	I915_WRITE(VLV_BLC_PWM_CTL2(pipe), tmp & ~BLM_PWM_ENABLE);
 }
 
+static void cabc_disable_backlight(struct intel_connector *connector)
+{
+	struct drm_device *dev = connector->base.dev;
+	struct intel_dsi *intel_dsi = NULL;
+	struct drm_crtc *crtc = NULL;
+	struct intel_encoder *encoder = NULL;
+	struct mipi_dsi_device *dsi_device;
+	enum port port = PORT_A;
+	u8 data[2] = {0};
+	u8 count = 1;
+
+	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+		for_each_encoder_on_crtc(dev, crtc, encoder) {
+			if (encoder->type == INTEL_OUTPUT_DSI)
+				intel_dsi = enc_to_intel_dsi(&encoder->base);
+		}
+	}
+
+	if (intel_dsi->dual_link) {
+		if (intel_dsi->dl_cabc_port == CABC_PORT_A)
+			port = PORT_A;
+		else if (intel_dsi->dl_cabc_port == CABC_PORT_C)
+			port = PORT_C;
+		else if (intel_dsi->dl_cabc_port == CABC_PORT_A_AND_C)
+			port = PORT_A;
+	}
+
+	if (intel_dsi->dual_link && intel_dsi->dl_cabc_port ==
+			CABC_PORT_A_AND_C)
+		count = 2;
+
+	do {
+		dsi_device = intel_dsi->dsi_hosts[port]->device;
+
+		intel_panel_actually_set_backlight(connector, 0);
+		data[1] = CABC_OFF;
+		data[0] = MIPI_DCS_CABC_CONTROL_WR;
+		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
+		data[0] = MIPI_DCS_CABC_CONTROL_BRIGHT_WR;
+		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
+
+		if (count == 2)
+			port = PORT_C;
+	} while (--count > 0);
+}
+
 static void bxt_disable_backlight(struct intel_connector *connector)
 {
 	struct drm_device *dev = connector->base.dev;
@@ -1043,6 +1164,56 @@  void intel_panel_enable_backlight(struct intel_connector *connector)
 	mutex_unlock(&dev_priv->backlight_lock);
 }
 
+static void cabc_enable_backlight(struct intel_connector *connector)
+{
+	struct drm_device *dev = connector->base.dev;
+	struct intel_dsi *intel_dsi = NULL;
+	struct drm_crtc *crtc = NULL;
+	struct intel_encoder *encoder = NULL;
+	struct intel_panel *panel = &connector->panel;
+	struct mipi_dsi_device *dsi_device;
+	enum port port = PORT_A;
+	u8 data[2] = {0};
+	u8 count = 1;
+
+	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+		for_each_encoder_on_crtc(dev, crtc, encoder) {
+			if (encoder->type == INTEL_OUTPUT_DSI)
+				intel_dsi = enc_to_intel_dsi(&encoder->base);
+		}
+	}
+
+	if (intel_dsi->dual_link) {
+		if (intel_dsi->dl_cabc_port == CABC_PORT_A)
+			port = PORT_A;
+		else if (intel_dsi->dl_cabc_port == CABC_PORT_C)
+			port = PORT_C;
+		else if (intel_dsi->dl_cabc_port == CABC_PORT_A_AND_C)
+			port = PORT_A;
+	}
+
+	if (intel_dsi->dual_link && intel_dsi->dl_cabc_port ==
+			CABC_PORT_A_AND_C)
+		count = 2;
+
+	do {
+		dsi_device = intel_dsi->dsi_hosts[port]->device;
+
+
+		data[0] = MIPI_DCS_CABC_CONTROL_BRIGHT_WR;
+		data[1] = CABC_BACKLIGHT | CABC_DIMMING_DISPLAY | CABC_BCTRL;
+		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
+		data[0] = MIPI_DCS_CABC_CONTROL_WR;
+		data[1] = CABC_VIDEO_MODE;
+		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
+
+		if (count == 2)
+			port = PORT_C;
+	} while (--count > 0);
+
+	intel_panel_actually_set_backlight(connector, panel->backlight.level);
+}
+
 #if IS_ENABLED(CONFIG_BACKLIGHT_CLASS_DEVICE)
 static int intel_backlight_device_update_status(struct backlight_device *bd)
 {
@@ -1362,6 +1533,33 @@  static int vlv_setup_backlight(struct intel_connector *connector, enum pipe pipe
 	return 0;
 }
 
+static int cabc_setup_backlight(struct intel_connector *connector,
+					enum pipe unused)
+{
+	struct drm_device *dev = connector->base.dev;
+	struct intel_dsi *intel_dsi = NULL;
+	struct drm_crtc *crtc = NULL;
+	struct intel_encoder *encoder = NULL;
+	struct intel_panel *panel = &connector->panel;
+	struct mipi_dsi_device *dsi_device;
+	u8 level;
+
+	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+		for_each_encoder_on_crtc(dev, crtc, encoder) {
+			if (encoder->type == INTEL_OUTPUT_DSI)
+				intel_dsi = enc_to_intel_dsi(&encoder->base);
+		}
+	}
+	dsi_device = intel_dsi->dsi_hosts[0]->device;
+	panel->backlight.max = 0xFF;
+	panel->backlight.level = 0xFF;
+
+	level = 255;
+	panel->backlight.enabled =
+		(level & 0x02) && panel->backlight.level != 0;
+	return 0;
+}
+
 static int
 bxt_setup_backlight(struct intel_connector *connector, enum pipe unused)
 {
@@ -1438,11 +1636,29 @@  void intel_panel_init_backlight_funcs(struct drm_device *dev)
 	struct drm_i915_private *dev_priv = dev->dev_private;
 
 	if (IS_BROXTON(dev)) {
-		dev_priv->display.setup_backlight = bxt_setup_backlight;
-		dev_priv->display.enable_backlight = bxt_enable_backlight;
-		dev_priv->display.disable_backlight = bxt_disable_backlight;
-		dev_priv->display.set_backlight = bxt_set_backlight;
-		dev_priv->display.get_backlight = bxt_get_backlight;
+		if (dev_priv->vbt.dsi.config->cabc_supported) {
+			dev_priv->display.setup_backlight =
+					cabc_setup_backlight;
+			dev_priv->display.enable_backlight =
+					cabc_enable_backlight;
+			dev_priv->display.disable_backlight =
+					cabc_disable_backlight;
+			dev_priv->display.set_backlight =
+					cabc_set_backlight;
+			dev_priv->display.get_backlight =
+					cabc_get_backlight;
+		} else {
+			dev_priv->display.setup_backlight =
+					bxt_setup_backlight;
+			dev_priv->display.enable_backlight =
+					bxt_enable_backlight;
+			dev_priv->display.disable_backlight =
+					bxt_disable_backlight;
+			dev_priv->display.set_backlight =
+					bxt_set_backlight;
+			dev_priv->display.get_backlight =
+					bxt_get_backlight;
+		}
 	} else if (IS_BROADWELL(dev) || IS_SKYLAKE(dev)) {
 		dev_priv->display.setup_backlight = bdw_setup_backlight;
 		dev_priv->display.enable_backlight = bdw_enable_backlight;
diff --git a/include/video/mipi_display.h b/include/video/mipi_display.h
index ddcc8ca..5b8eeec 100644
--- a/include/video/mipi_display.h
+++ b/include/video/mipi_display.h
@@ -117,6 +117,14 @@  enum {
 	MIPI_DCS_GET_SCANLINE		= 0x45,
 	MIPI_DCS_READ_DDB_START		= 0xA1,
 	MIPI_DCS_READ_DDB_CONTINUE	= 0xA8,
+	MIPI_DCS_CABC_LEVEL_RD          = 0x52,
+	MIPI_DCS_CABC_MIN_BRIGHTNESS_RD = 0x5F,
+	MIPI_DCS_CABC_CONTROL_RD        = 0x56,
+	MIPI_DCS_CABC_CONTROL_BRIGHT_RD = 0x54,
+	MIPI_DCS_CABC_LEVEL_WR          = 0x51,
+	MIPI_DCS_CABC_MIN_BRIGHTNESS_WR = 0x5E,
+	MIPI_DCS_CABC_CONTROL_WR        = 0x55,
+	MIPI_DCS_CABC_CONTROL_BRIGHT_WR = 0x53,
 };
 
 /* MIPI DCS pixel formats */