[MIPI,CABC,2/2] drm/i915: CABC support for backlight control
diff mbox

Message ID 1447705088-2395-3-git-send-email-m.deepak@intel.com
State New
Headers show

Commit Message

Deepak M Nov. 16, 2015, 8:18 p.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.

Cc: Jani Nikula <jani.nikula@intel.com>
Cc: Daniel Vetter <daniel.vetter@intel.com>
Cc: Yetunde Adebisi <yetundex.adebisi@intel.com>
Signed-off-by: Deepak M <m.deepak@intel.com>
---
Addressed the review comments from Jani which were mentioned in
the below patch
http://lists.freedesktop.org/archives/intel-gfx/2015-September/075819.html

Went with the name CABC because the commands which are used
here are mainly for CABC and many of the panels have the same
commands for the CABC operation. For the cases where PWM is
directly from panel PWM, DCS commands may be different and
may have to add new functions to support it.

 drivers/gpu/drm/i915/intel_dsi.c   |  14 +++-
 drivers/gpu/drm/i915/intel_dsi.h   |  24 ++++++
 drivers/gpu/drm/i915/intel_panel.c | 145 +++++++++++++++++++++++++++++++++++--
 3 files changed, 177 insertions(+), 6 deletions(-)

Comments

Jani Nikula Nov. 25, 2015, 12:26 p.m. UTC | #1
On Mon, 16 Nov 2015, Deepak M <m.deepak@intel.com> 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.

I'd like you to move the DSI DCS backlight stuff from intel_panel.c to a
new file, say, intel_dsi_dcs_backlight.c, similar to what I told Yetunde
to do with DP DPCD backlight [1].

[1] http://patchwork.freedesktop.org/patch/msgid/1447698547-9027-1-git-send-email-yetundex.adebisi@intel.com

> Cc: Jani Nikula <jani.nikula@intel.com>
> Cc: Daniel Vetter <daniel.vetter@intel.com>
> Cc: Yetunde Adebisi <yetundex.adebisi@intel.com>
> Signed-off-by: Deepak M <m.deepak@intel.com>
> ---
> Addressed the review comments from Jani which were mentioned in
> the below patch
> http://lists.freedesktop.org/archives/intel-gfx/2015-September/075819.html
>
> Went with the name CABC because the commands which are used
> here are mainly for CABC and many of the panels have the same
> commands for the CABC operation. For the cases where PWM is
> directly from panel PWM, DCS commands may be different and
> may have to add new functions to support it.
>
>  drivers/gpu/drm/i915/intel_dsi.c   |  14 +++-
>  drivers/gpu/drm/i915/intel_dsi.h   |  24 ++++++
>  drivers/gpu/drm/i915/intel_panel.c | 145 +++++++++++++++++++++++++++++++++++--
>  3 files changed, 177 insertions(+), 6 deletions(-)
>
> diff --git a/drivers/gpu/drm/i915/intel_dsi.c b/drivers/gpu/drm/i915/intel_dsi.c
> index 170ae6f..5d1ba35 100644
> --- a/drivers/gpu/drm/i915/intel_dsi.c
> +++ b/drivers/gpu/drm/i915/intel_dsi.c
> @@ -1175,8 +1175,20 @@ void intel_dsi_init(struct drm_device *dev)
>  		intel_dsi->ports = (1 << PORT_C);
>  	}
>  
> -	if (dev_priv->vbt.dsi.config->dual_link)
> +	if (dev_priv->vbt.dsi.config->dual_link) {
>  		intel_dsi->ports = ((1 << PORT_A) | (1 << PORT_C));
> +		switch (dev_priv->vbt.dsi.config->dl_cabc_port) {
> +		case CABC_PORT_A:
> +			intel_dsi->bkl_dcs_ports = (1 << PORT_A);
> +		case CABC_PORT_C:
> +			intel_dsi->bkl_dcs_ports = (1 << PORT_C);
> +		case CABC_PORT_A_AND_C:
> +			intel_dsi->bkl_dcs_ports = intel_dsi->ports;
> +		default:
> +			DRM_ERROR("Unknown MIPI ports for sending DCS\n");

"break;" is useful in making the switch case do what you want.

> +		}
> +	} else
> +		intel_dsi->bkl_dcs_ports = intel_dsi->ports;
>  
>  	/* Create a DSI host (and a device) for each port. */
>  	for_each_dsi_port(port, intel_dsi->ports) {
> diff --git a/drivers/gpu/drm/i915/intel_dsi.h b/drivers/gpu/drm/i915/intel_dsi.h
> index 4fde83b..4bcee40 100644
> --- a/drivers/gpu/drm/i915/intel_dsi.h
> +++ b/drivers/gpu/drm/i915/intel_dsi.h
> @@ -34,6 +34,30 @@
>  #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
> +
> +#define CABC_MAX_VALUE			0xFF
> +
> +#define MIPI_DCS_CABC_LEVEL_RD		0x52
> +#define MIPI_DCS_CABC_MIN_BRIGHTNESS_RD	0x5F
> +#define MIPI_DCS_CABC_CONTROL_RD	0x56
> +#define MIPI_DCS_CABC_CONTROL_BRIGHT_RD	0x54
> +#define MIPI_DCS_CABC_LEVEL_WR		0x51
> +#define MIPI_DCS_CABC_MIN_BRIGHTNESS_WR	0x5E
> +#define MIPI_DCS_CABC_CONTROL_WR	0x55
> +#define MIPI_DCS_CABC_CONTROL_BRIGHT_WR	0x53
> +

Put these in the new .c file. The rest of the code shouldn't need to know.

>  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 a24df35..085d9a6 100644
> --- a/drivers/gpu/drm/i915/intel_panel.c
> +++ b/drivers/gpu/drm/i915/intel_panel.c
> @@ -34,6 +34,7 @@
>  #include <linux/moduleparam.h>
>  #include <linux/pwm.h>
>  #include "intel_drv.h"
> +#include "intel_dsi.h"
>  
>  #define CRC_PMIC_PWM_PERIOD_NS	21333
>  
> @@ -533,6 +534,30 @@ 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 intel_dsi *intel_dsi = NULL;
> +	struct intel_encoder *encoder = NULL;
> +	struct mipi_dsi_device *dsi_device;
> +	u8 data[2] = {0};
> +	enum port port;
> +
> +	encoder = connector->encoder;
> +	if (encoder->type == INTEL_OUTPUT_DSI)
> +		intel_dsi = enc_to_intel_dsi(&encoder->base);
> +	else {
> +		DRM_ERROR("Use DSI encoder for CABC\n");
> +		return -EINVAL;
> +	}

You need to check this once when you setup the CABC backlight, and not
afterwards. In the function that sets up the CABC backlight hooks, not
the setup hook itself.

> +
> +	for_each_dsi_port(port, intel_dsi->bkl_dcs_ports) {
> +		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;
> @@ -631,6 +656,30 @@ 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 intel_dsi *intel_dsi = NULL;
> +	struct intel_encoder *encoder = NULL;
> +	struct mipi_dsi_device *dsi_device;
> +	u8 data[2] = {0};
> +	enum port port;
> +
> +	encoder = connector->encoder;
> +	if (encoder->type == INTEL_OUTPUT_DSI)
> +		intel_dsi = enc_to_intel_dsi(&encoder->base);
> +	else {
> +		DRM_ERROR("Use DSI encoder for CABC\n");
> +		return;
> +	}

Same as above.

> +
> +	for_each_dsi_port(port, intel_dsi->bkl_dcs_ports) {
> +		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);
> +	}
> +}
> +
>  static void bxt_set_backlight(struct intel_connector *connector, u32 level)
>  {
>  	struct drm_device *dev = connector->base.dev;
> @@ -798,6 +847,34 @@ 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 intel_dsi *intel_dsi = NULL;
> +	struct intel_encoder *encoder = NULL;
> +	struct mipi_dsi_device *dsi_device;
> +	enum port port;
> +	u8 data[2] = {0};
> +
> +	encoder = connector->encoder;
> +	if (encoder->type == INTEL_OUTPUT_DSI)
> +		intel_dsi = enc_to_intel_dsi(&encoder->base);
> +	else {
> +		DRM_ERROR("Use DSI encoder for CABC\n");
> +		return;
> +	}

Same as above.

> +
> +	intel_panel_actually_set_backlight(connector, 0);
> +
> +	for_each_dsi_port(port, intel_dsi->bkl_dcs_ports) {
> +		dsi_device = intel_dsi->dsi_hosts[port]->device;
> +		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);
> +	}
> +}
> +
>  static void bxt_disable_backlight(struct intel_connector *connector)
>  {
>  	struct drm_device *dev = connector->base.dev;
> @@ -1133,6 +1210,36 @@ 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 intel_dsi *intel_dsi = NULL;
> +	struct intel_encoder *encoder = NULL;
> +	struct intel_panel *panel = &connector->panel;
> +	struct mipi_dsi_device *dsi_device;
> +	enum port port;
> +	u8 data[2] = {0};
> +
> +	encoder = connector->encoder;
> +	if (encoder->type == INTEL_OUTPUT_DSI)
> +		intel_dsi = enc_to_intel_dsi(&encoder->base);
> +	else {
> +		DRM_ERROR("Use DSI encoder for the CABC\n");
> +		return;
> +	}


Same as above.

> +
> +	for_each_dsi_port(port, intel_dsi->bkl_dcs_ports) {
> +		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_STILL_PICTURE;
> +		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
> +	}
> +
> +	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)
>  {
> @@ -1601,6 +1708,26 @@ 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 drm_i915_private *dev_priv = dev->dev_private;
> +	struct intel_panel *panel = &connector->panel;
> +
> +	if (dev_priv->vbt.backlight.present)
> +		panel->backlight.present = true;
> +	else {
> +		DRM_ERROR("no backlight present per VBT\n");
> +		return 0;
> +	}

Same as above.

> +
> +	panel->backlight.max = CABC_MAX_VALUE;
> +	panel->backlight.level = CABC_MAX_VALUE;
> +
> +	return 0;
> +}
> +
>  static int
>  bxt_setup_backlight(struct intel_connector *connector, enum pipe unused)
>  {
> @@ -1745,11 +1872,19 @@ intel_panel_init_backlight_funcs(struct intel_panel *panel)
>  	struct drm_i915_private *dev_priv = dev->dev_private;
>  
>  	if (IS_BROXTON(dev)) {
> -		panel->backlight.setup = bxt_setup_backlight;
> -		panel->backlight.enable = bxt_enable_backlight;
> -		panel->backlight.disable = bxt_disable_backlight;
> -		panel->backlight.set = bxt_set_backlight;
> -		panel->backlight.get = bxt_get_backlight;
> +		if (dev_priv->vbt.dsi.config->cabc_supported) {
> +			panel->backlight.setup = cabc_setup_backlight;
> +			panel->backlight.enable = cabc_enable_backlight;
> +			panel->backlight.disable = cabc_disable_backlight;
> +			panel->backlight.set = cabc_set_backlight;
> +			panel->backlight.get = cabc_get_backlight;
> +		} else {
> +			panel->backlight.setup = bxt_setup_backlight;
> +			panel->backlight.enable = bxt_enable_backlight;
> +			panel->backlight.disable = bxt_disable_backlight;
> +			panel->backlight.set = bxt_set_backlight;
> +			panel->backlight.get = bxt_get_backlight;
> +		}

Why is this Broxton specific? It's DSI specific, so please do this
similar to what I told Yetunde to do with DP AUX backlight.


>  	} else if (HAS_PCH_LPT(dev) || HAS_PCH_SPT(dev)) {
>  		panel->backlight.setup = lpt_setup_backlight;
>  		panel->backlight.enable = lpt_enable_backlight;

Patch
diff mbox

diff --git a/drivers/gpu/drm/i915/intel_dsi.c b/drivers/gpu/drm/i915/intel_dsi.c
index 170ae6f..5d1ba35 100644
--- a/drivers/gpu/drm/i915/intel_dsi.c
+++ b/drivers/gpu/drm/i915/intel_dsi.c
@@ -1175,8 +1175,20 @@  void intel_dsi_init(struct drm_device *dev)
 		intel_dsi->ports = (1 << PORT_C);
 	}
 
-	if (dev_priv->vbt.dsi.config->dual_link)
+	if (dev_priv->vbt.dsi.config->dual_link) {
 		intel_dsi->ports = ((1 << PORT_A) | (1 << PORT_C));
+		switch (dev_priv->vbt.dsi.config->dl_cabc_port) {
+		case CABC_PORT_A:
+			intel_dsi->bkl_dcs_ports = (1 << PORT_A);
+		case CABC_PORT_C:
+			intel_dsi->bkl_dcs_ports = (1 << PORT_C);
+		case CABC_PORT_A_AND_C:
+			intel_dsi->bkl_dcs_ports = intel_dsi->ports;
+		default:
+			DRM_ERROR("Unknown MIPI ports for sending DCS\n");
+		}
+	} else
+		intel_dsi->bkl_dcs_ports = intel_dsi->ports;
 
 	/* Create a DSI host (and a device) for each port. */
 	for_each_dsi_port(port, intel_dsi->ports) {
diff --git a/drivers/gpu/drm/i915/intel_dsi.h b/drivers/gpu/drm/i915/intel_dsi.h
index 4fde83b..4bcee40 100644
--- a/drivers/gpu/drm/i915/intel_dsi.h
+++ b/drivers/gpu/drm/i915/intel_dsi.h
@@ -34,6 +34,30 @@ 
 #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
+
+#define CABC_MAX_VALUE			0xFF
+
+#define MIPI_DCS_CABC_LEVEL_RD		0x52
+#define MIPI_DCS_CABC_MIN_BRIGHTNESS_RD	0x5F
+#define MIPI_DCS_CABC_CONTROL_RD	0x56
+#define MIPI_DCS_CABC_CONTROL_BRIGHT_RD	0x54
+#define MIPI_DCS_CABC_LEVEL_WR		0x51
+#define MIPI_DCS_CABC_MIN_BRIGHTNESS_WR	0x5E
+#define MIPI_DCS_CABC_CONTROL_WR	0x55
+#define MIPI_DCS_CABC_CONTROL_BRIGHT_WR	0x53
+
 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 a24df35..085d9a6 100644
--- a/drivers/gpu/drm/i915/intel_panel.c
+++ b/drivers/gpu/drm/i915/intel_panel.c
@@ -34,6 +34,7 @@ 
 #include <linux/moduleparam.h>
 #include <linux/pwm.h>
 #include "intel_drv.h"
+#include "intel_dsi.h"
 
 #define CRC_PMIC_PWM_PERIOD_NS	21333
 
@@ -533,6 +534,30 @@  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 intel_dsi *intel_dsi = NULL;
+	struct intel_encoder *encoder = NULL;
+	struct mipi_dsi_device *dsi_device;
+	u8 data[2] = {0};
+	enum port port;
+
+	encoder = connector->encoder;
+	if (encoder->type == INTEL_OUTPUT_DSI)
+		intel_dsi = enc_to_intel_dsi(&encoder->base);
+	else {
+		DRM_ERROR("Use DSI encoder for CABC\n");
+		return -EINVAL;
+	}
+
+	for_each_dsi_port(port, intel_dsi->bkl_dcs_ports) {
+		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;
@@ -631,6 +656,30 @@  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 intel_dsi *intel_dsi = NULL;
+	struct intel_encoder *encoder = NULL;
+	struct mipi_dsi_device *dsi_device;
+	u8 data[2] = {0};
+	enum port port;
+
+	encoder = connector->encoder;
+	if (encoder->type == INTEL_OUTPUT_DSI)
+		intel_dsi = enc_to_intel_dsi(&encoder->base);
+	else {
+		DRM_ERROR("Use DSI encoder for CABC\n");
+		return;
+	}
+
+	for_each_dsi_port(port, intel_dsi->bkl_dcs_ports) {
+		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);
+	}
+}
+
 static void bxt_set_backlight(struct intel_connector *connector, u32 level)
 {
 	struct drm_device *dev = connector->base.dev;
@@ -798,6 +847,34 @@  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 intel_dsi *intel_dsi = NULL;
+	struct intel_encoder *encoder = NULL;
+	struct mipi_dsi_device *dsi_device;
+	enum port port;
+	u8 data[2] = {0};
+
+	encoder = connector->encoder;
+	if (encoder->type == INTEL_OUTPUT_DSI)
+		intel_dsi = enc_to_intel_dsi(&encoder->base);
+	else {
+		DRM_ERROR("Use DSI encoder for CABC\n");
+		return;
+	}
+
+	intel_panel_actually_set_backlight(connector, 0);
+
+	for_each_dsi_port(port, intel_dsi->bkl_dcs_ports) {
+		dsi_device = intel_dsi->dsi_hosts[port]->device;
+		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);
+	}
+}
+
 static void bxt_disable_backlight(struct intel_connector *connector)
 {
 	struct drm_device *dev = connector->base.dev;
@@ -1133,6 +1210,36 @@  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 intel_dsi *intel_dsi = NULL;
+	struct intel_encoder *encoder = NULL;
+	struct intel_panel *panel = &connector->panel;
+	struct mipi_dsi_device *dsi_device;
+	enum port port;
+	u8 data[2] = {0};
+
+	encoder = connector->encoder;
+	if (encoder->type == INTEL_OUTPUT_DSI)
+		intel_dsi = enc_to_intel_dsi(&encoder->base);
+	else {
+		DRM_ERROR("Use DSI encoder for the CABC\n");
+		return;
+	}
+
+	for_each_dsi_port(port, intel_dsi->bkl_dcs_ports) {
+		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_STILL_PICTURE;
+		mipi_dsi_dcs_write_buffer(dsi_device, data, 2);
+	}
+
+	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)
 {
@@ -1601,6 +1708,26 @@  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 drm_i915_private *dev_priv = dev->dev_private;
+	struct intel_panel *panel = &connector->panel;
+
+	if (dev_priv->vbt.backlight.present)
+		panel->backlight.present = true;
+	else {
+		DRM_ERROR("no backlight present per VBT\n");
+		return 0;
+	}
+
+	panel->backlight.max = CABC_MAX_VALUE;
+	panel->backlight.level = CABC_MAX_VALUE;
+
+	return 0;
+}
+
 static int
 bxt_setup_backlight(struct intel_connector *connector, enum pipe unused)
 {
@@ -1745,11 +1872,19 @@  intel_panel_init_backlight_funcs(struct intel_panel *panel)
 	struct drm_i915_private *dev_priv = dev->dev_private;
 
 	if (IS_BROXTON(dev)) {
-		panel->backlight.setup = bxt_setup_backlight;
-		panel->backlight.enable = bxt_enable_backlight;
-		panel->backlight.disable = bxt_disable_backlight;
-		panel->backlight.set = bxt_set_backlight;
-		panel->backlight.get = bxt_get_backlight;
+		if (dev_priv->vbt.dsi.config->cabc_supported) {
+			panel->backlight.setup = cabc_setup_backlight;
+			panel->backlight.enable = cabc_enable_backlight;
+			panel->backlight.disable = cabc_disable_backlight;
+			panel->backlight.set = cabc_set_backlight;
+			panel->backlight.get = cabc_get_backlight;
+		} else {
+			panel->backlight.setup = bxt_setup_backlight;
+			panel->backlight.enable = bxt_enable_backlight;
+			panel->backlight.disable = bxt_disable_backlight;
+			panel->backlight.set = bxt_set_backlight;
+			panel->backlight.get = bxt_get_backlight;
+		}
 	} else if (HAS_PCH_LPT(dev) || HAS_PCH_SPT(dev)) {
 		panel->backlight.setup = lpt_setup_backlight;
 		panel->backlight.enable = lpt_enable_backlight;