diff --git a/drivers/gpu/drm/i915/Makefile b/drivers/gpu/drm/i915/Makefile
index 0f2c549..1c8613d 100644
--- a/drivers/gpu/drm/i915/Makefile
+++ b/drivers/gpu/drm/i915/Makefile
@@ -16,6 +16,7 @@ i915-y := i915_drv.o i915_dma.o i915_irq.o \
 	  i915_gem_tiling.o \
 	  i915_sysfs.o \
 	  i915_trace_points.o \
+	  intel_adaptive_backlight.o \
 	  intel_display.o \
 	  intel_crt.o \
 	  intel_lvds.o \
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index 67932ce..646c3eb 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -711,6 +711,13 @@ typedef struct drm_i915_private {
 	void (*disable_backlight)(struct drm_device *dev);
 	void (*enable_backlight)(struct drm_device *dev, enum pipe pipe);
 
+	/* Adaptive backlight */
+	bool adaptive_backlight_enabled;
+	int backlight_correction_level;
+	int backlight_correction_count;
+	int backlight_correction_direction;
+	int adaptive_backlight_panel_gamma; /* as 16.16 fixed point */
+
 	/* Feature bits from the VBIOS */
 	unsigned int int_tv_support:1;
 	unsigned int lvds_dither:1;
@@ -922,6 +929,9 @@ typedef struct drm_i915_private {
 
 	struct drm_property *broadcast_rgb_property;
 	struct drm_property *force_audio_property;
+	struct drm_property *adaptive_backlight_property;
+	struct drm_property *panel_gamma_property;
+
 	bool hw_contexts_disabled;
 	uint32_t hw_context_size;
 
@@ -1627,6 +1637,13 @@ extern int i915_restore_state(struct drm_device *dev);
 void i915_setup_sysfs(struct drm_device *dev_priv);
 void i915_teardown_sysfs(struct drm_device *dev_priv);
 
+/* intel_adaptive_backlight.c */
+extern void intel_adaptive_backlight(struct drm_device *dev, int pipe);
+extern void intel_adaptive_backlight_enable(struct drm_i915_private *dev_priv);
+extern void intel_adaptive_backlight_disable(struct drm_i915_private *dev_priv,
+					     struct drm_connector *connector);
+extern void intel_adaptive_backlight_setup(struct drm_device *dev);
+
 /* intel_i2c.c */
 extern int intel_setup_gmbus(struct drm_device *dev);
 extern void intel_teardown_gmbus(struct drm_device *dev);
diff --git a/drivers/gpu/drm/i915/i915_irq.c b/drivers/gpu/drm/i915/i915_irq.c
index fe84338..17f6406 100644
--- a/drivers/gpu/drm/i915/i915_irq.c
+++ b/drivers/gpu/drm/i915/i915_irq.c
@@ -707,8 +707,10 @@ static irqreturn_t ivybridge_irq_handler(int irq, void *arg)
 			intel_opregion_gse_intr(dev);
 
 		for (i = 0; i < 3; i++) {
-			if (de_iir & (DE_PIPEA_VBLANK_IVB << (5 * i)))
+			if (de_iir & (DE_PIPEA_VBLANK_IVB << (5 * i))) {
 				drm_handle_vblank(dev, i);
+				intel_adaptive_backlight(dev, i);
+			}
 			if (de_iir & (DE_PLANEA_FLIP_DONE_IVB << (5 * i))) {
 				intel_prepare_page_flip(dev, i);
 				intel_finish_page_flip_plane(dev, i);
diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
index 59afb7e..157cb5d 100644
--- a/drivers/gpu/drm/i915/i915_reg.h
+++ b/drivers/gpu/drm/i915/i915_reg.h
@@ -1960,6 +1960,28 @@
 #define BLC_PWM_CPU_CTL2	0x48250
 #define BLC_PWM_CPU_CTL		0x48254
 
+#define BLM_HIST_CTL			0x48260
+#define  ENH_HIST_ENABLE		(1<<31)
+#define  ENH_MODIF_TBL_ENABLE		(1<<30)
+#define  ENH_PIPE_A_SELECT		(0<<29)
+#define  ENH_PIPE_B_SELECT		(1<<29)
+#define  ENH_PIPE(pipe) _PIPE(pipe, ENH_PIPE_A_SELECT, ENH_PIPE_B_SELECT)
+#define  HIST_MODE_YUV			(0<<24)
+#define  HIST_MODE_HSV			(1<<24)
+#define  ENH_MODE_DIRECT		(0<<13)
+#define  ENH_MODE_ADDITIVE		(1<<13)
+#define  ENH_MODE_MULTIPLICATIVE	(2<<13)
+#define  BIN_REGISTER_SET		(1<<11)
+#define  ENH_NUM_BINS			32
+
+#define BLM_HIST_ENH			0x48264
+
+#define BLM_HIST_GUARD_BAND		0x48268
+#define  BLM_HIST_INTR_ENABLE		(1<<31)
+#define  BLM_HIST_EVENT_STATUS		(1<<30)
+#define  BLM_HIST_INTR_DELAY_MASK	(0xFF<<22)
+#define  BLM_HIST_INTR_DELAY_SHIFT	22
+
 /* PCH CTL1 is totally different, all but the below bits are reserved. CTL2 is
  * like the normal CTL from gen4 and earlier. Hooray for confusing naming. */
 #define BLC_PWM_PCH_CTL1	0xc8250
diff --git a/drivers/gpu/drm/i915/intel_adaptive_backlight.c b/drivers/gpu/drm/i915/intel_adaptive_backlight.c
new file mode 100644
index 0000000..aa610c3
--- /dev/null
+++ b/drivers/gpu/drm/i915/intel_adaptive_backlight.c
@@ -0,0 +1,401 @@
+/*
+ * Copyright (C) 2012 Google, Inc.
+ *
+ * This software is licensed under the terms of the GNU General Public
+ * License version 2, as published by the Free Software Foundation, and
+ * may be copied, distributed, and modified under those terms.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ */
+
+
+#include "drmP.h"
+#include "i915_drm.h"
+#include "i915_drv.h"
+#include "i915_reg.h"
+#include "intel_drv.h"
+#include "intel_fixedpoint.h"
+
+/*
+ * Some notes about the adaptive backlight implementation:
+ * - If we let it run as designed, it will generate a lot of interrupts which
+ *   tends to wake the CPU up and waste power. This is a bad idea for a power
+ *   saving feature. Instead, we couple it to the vblank interrupt since that
+ *   means we drew something. This means that we do not react to non-vsynced
+ *   GL updates, or updates to the front buffer, and also adds a little bit of
+ *   extra latency. But it is an acceptable tradeoff to make.
+ * - Ivy bridge has a hardware issue where the color correction doesn't seem
+ *   to work. When you enable the ENH_MODIF_TBL_ENABLE bit, not only does the
+ *   correction not work, but it becomes impossible to read the levels.
+ *   Instead, as a workaround, we don't set that bit on ivy bridge and
+ *   (ab)use the gamma ramp registers to do the correction.
+ */
+
+/*
+ * This function takes a histogram of buckets as input and determines an
+ * acceptable target backlight level.
+ */
+static int histogram_find_correction_level(int *levels)
+{
+	int i, sum = 0;
+	int ratio, distortion, prev_distortion = 0, off, final_ratio, target;
+
+	for (i = 0; i < ENH_NUM_BINS; i++)
+		sum += levels[i];
+
+	/* Allow 0.33/256 distortion per pixel, on average */
+	target = sum / 3;
+
+	/* Special case where we only have less than 100 pixels
+	 * outside of the darkest bin.
+	 */
+	if (sum - levels[0] <= 100)
+		return 70;
+
+	for (ratio = ENH_NUM_BINS - 1; ratio >= 0 ; ratio--) {
+		distortion = 0;
+		for (i = ratio; i < ENH_NUM_BINS; i++) {
+			int pixel_distortion = (i - ratio)*8;
+			int num_pixels = levels[i];
+			distortion += num_pixels * pixel_distortion;
+		}
+		if (distortion > target)
+			break;
+		else
+			prev_distortion = distortion;
+	}
+
+	ratio++;
+
+	/* If we're not exactly at the border between two buckets, extrapolate
+	 * to get 3 extra bits of accuracy.
+	 */
+	if (distortion - prev_distortion)
+		off = 8 * (target - prev_distortion) /
+		      (distortion - prev_distortion);
+	else
+		off = 0;
+
+	final_ratio = ratio * 255 / 31 + off;
+
+	if (final_ratio > 255)
+		final_ratio = 255;
+
+	/* Never aim for less than 50% of the total backlight */
+	if (final_ratio < 128)
+		final_ratio = 128;
+
+	return final_ratio;
+}
+
+static void get_levels(struct drm_device *dev, int pipe, int *levels)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	int i;
+
+	for (i = 0; i < ENH_NUM_BINS; i++) {
+		u32 hist_ctl = ENH_HIST_ENABLE |
+			       ENH_MODIF_TBL_ENABLE |
+			       ENH_PIPE(pipe) |
+			       HIST_MODE_YUV |
+			       ENH_MODE_ADDITIVE |
+			       i;
+
+		/* Ivb workaround, see the explanation at the top */
+		if (INTEL_INFO(dev)->gen == 7)
+			hist_ctl &= ~ENH_MODIF_TBL_ENABLE;
+
+		I915_WRITE(BLM_HIST_CTL, hist_ctl);
+
+		levels[i] = I915_READ(BLM_HIST_ENH);
+	}
+}
+
+/* Multiplier is 16.16 fixed point */
+static void set_levels(struct drm_device *dev, int pipe, int multiplier)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	int i;
+
+	if (INTEL_INFO(dev)->gen == 7) {
+		/* Ivb workaround, see the explanation at the top */
+		for (i = 0; i < 256; i++) {
+			int v = intel_fixed_div(i, multiplier);
+			if (v > 255)
+				v = 255;
+			v = v | (v << 8) | (v << 16);
+			I915_WRITE(LGC_PALETTE(pipe) + i * 4, v);
+		}
+
+		return;
+	}
+
+	for (i = 0; i < ENH_NUM_BINS; i++) {
+		int base_value = i * 8 * 4;
+		int level = base_value -
+			    intel_fixed_mul(base_value, multiplier);
+		I915_WRITE(BLM_HIST_CTL, ENH_HIST_ENABLE |
+					 ENH_MODIF_TBL_ENABLE |
+					 ENH_PIPE(pipe) |
+					 HIST_MODE_YUV |
+					 ENH_MODE_ADDITIVE |
+					 BIN_REGISTER_SET |
+					 i);
+		I915_WRITE(BLM_HIST_ENH, level);
+	}
+}
+
+/* Compute the current step. Returns true if we need to change the levels,
+ * false otherwise.
+ */
+static bool adaptive_backlight_current_step(drm_i915_private_t *dev_priv,
+					   int correction_level)
+{
+	int delta, direction;
+
+	direction = (correction_level >
+			dev_priv->backlight_correction_level);
+
+	if (direction == dev_priv->backlight_correction_direction) {
+		dev_priv->backlight_correction_count++;
+	} else {
+		dev_priv->backlight_correction_count = 0;
+		dev_priv->backlight_correction_direction = direction;
+	}
+
+	delta = abs(correction_level -
+			dev_priv->backlight_correction_level)/4;
+
+	if (delta < 1)
+		delta = 1;
+
+	/* For increasing the brightness, we do it instantly.
+	 * For lowering the brightness, we require at least 10 frames
+	 * below the current value. This avoids ping-ponging of the
+	 * backlight level.
+	 *
+	 * We also never increase the backlight by more than 6% per
+	 * frame, and never lower it by more than 3% per frame, because
+	 * the backlight needs time to adjust and the LCD correction
+	 * would be "ahead" otherwise.
+	 */
+	if (correction_level > dev_priv->backlight_correction_level) {
+		if (delta > 15)
+			delta = 15;
+		dev_priv->backlight_correction_level += delta;
+	} else if ((dev_priv->backlight_correction_count > 10) &&
+			(correction_level < dev_priv->backlight_correction_level)) {
+		if (delta > 7)
+			delta = 7;
+		dev_priv->backlight_correction_level -= delta;
+	} else {
+		return false;
+	}
+
+	return true;
+}
+
+/*
+ * This function computes the backlight correction level for an acceptable
+ * distortion and fills up the correction bins adequately.
+ */
+static void
+adaptive_backlight_correct(struct drm_device *dev, int pipe)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	int correction_level;
+	int multiplier, one_over_gamma;
+	int levels[ENH_NUM_BINS];
+
+	get_levels(dev, pipe, levels);
+
+	/* Find the correction level for an acceptable distortion */
+	correction_level = histogram_find_correction_level(levels);
+
+	/* If we're already at our correction target, then there is
+	 * nothing to do
+	 */
+	if (dev_priv->backlight_correction_level == correction_level)
+		return;
+
+	/* Decide by how much to move this step. If we didn't move, return */
+	if (!adaptive_backlight_current_step(dev_priv, correction_level))
+		return;
+
+	dev_priv->set_backlight(dev, dev_priv->backlight_level);
+
+	/* We need to invert the gamma correction of the LCD values,
+	 * but not of the backlight which is linear.
+	 */
+	one_over_gamma = intel_fixed_div(FIXED_ONE,
+			dev_priv->adaptive_backlight_panel_gamma);
+	multiplier = intel_fixed_pow(dev_priv->backlight_correction_level * 256,
+			one_over_gamma);
+
+	set_levels(dev, pipe, multiplier);
+}
+
+void intel_adaptive_backlight(struct drm_device *dev, int pipe_vblank_event)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	int pipe;
+	struct drm_connector *connector;
+	struct intel_crtc *intel_crtc;
+	bool found = false;
+
+	if (!dev_priv->adaptive_backlight_enabled)
+		return;
+
+	/* Find the connector */
+	list_for_each_entry(connector,
+			    &dev->mode_config.connector_list,
+			    head)
+		if (connector->connector_type == DRM_MODE_CONNECTOR_eDP) {
+			found = true;
+			break;
+		}
+
+	if (!found)
+		return;
+
+	if (!connector)
+		return;
+
+	if (!connector->encoder)
+		return;
+
+	if (!connector->encoder->crtc)
+		return;
+
+	/* Find the pipe for the panel. */
+	intel_crtc = to_intel_crtc(connector->encoder->crtc);
+	pipe = intel_crtc->pipe;
+
+	/* The callback happens for both pipe A & B. Now that we know which
+	 * pipe we're doing adaptive backlight on, check that it's the right
+	 * one. Bail if it isn't.
+	 */
+	if (pipe != pipe_vblank_event)
+		return;
+
+	/* Make sure we ack the previous event. Even though we do not get the
+	 * IRQs (see above explanation), we must still ack the events otherwise
+	 * the histogram data doesn't get updated any more.
+	 */
+	I915_WRITE(BLM_HIST_GUARD_BAND, BLM_HIST_INTR_ENABLE |
+					BLM_HIST_EVENT_STATUS |
+					(1 << BLM_HIST_INTR_DELAY_SHIFT));
+
+
+	adaptive_backlight_correct(dev, pipe);
+}
+
+void intel_adaptive_backlight_enable(struct drm_i915_private *dev_priv)
+{
+	dev_priv->backlight_correction_level = 256;
+	dev_priv->backlight_correction_count = 0;
+	dev_priv->backlight_correction_direction = 0;
+	/* Default gamma is 2.2 as 16.16 fixed point */
+	if (!dev_priv->adaptive_backlight_panel_gamma)
+		dev_priv->adaptive_backlight_panel_gamma = 144179;
+
+	dev_priv->adaptive_backlight_enabled = true;
+}
+
+void intel_adaptive_backlight_disable(struct drm_i915_private *dev_priv,
+				      struct drm_connector *connector)
+{
+	struct intel_crtc *intel_crtc;
+	int pipe;
+	struct drm_device *dev = dev_priv->dev;
+
+	dev_priv->adaptive_backlight_enabled = false;
+
+	dev_priv->backlight_correction_level = 256;
+
+	dev_priv->set_backlight(dev, dev_priv->backlight_level);
+
+	/* Find the pipe */
+	if (!connector->encoder)
+		return;
+
+	if (!connector->encoder->crtc)
+		return;
+
+	intel_crtc = to_intel_crtc(connector->encoder->crtc);
+	pipe = intel_crtc->pipe;
+
+	/* Reset the levels to default */
+	set_levels(dev, pipe, FIXED_ONE);
+}
+
+static u32 intel_link_get_backlight(struct drm_device *dev)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	u32 val;
+
+	val = I915_READ(BLC_PWM_CPU_CTL) & BACKLIGHT_DUTY_CYCLE_MASK;
+
+	return (val - 2) / 4;
+}
+
+static u32 intel_link_get_max_backlight(struct drm_device *dev)
+{
+	return 255;
+}
+
+static void intel_link_set_backlight(struct drm_device *dev, u32 level)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	u32 hw_level;
+	u32 val;
+
+	dev_priv->backlight_level = level;
+
+	if (dev_priv->adaptive_backlight_enabled)
+		level = level * dev_priv->backlight_correction_level >> 8;
+
+	if (level == 0)
+		hw_level = 0;
+	else
+		hw_level = level * 4 + 2;
+
+	val = I915_READ(BLC_PWM_CPU_CTL) & ~BACKLIGHT_DUTY_CYCLE_MASK;
+	I915_WRITE(BLC_PWM_CPU_CTL, val | hw_level);
+}
+
+static void intel_link_disable_backlight(struct drm_device *dev)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+
+	dev_priv->backlight_enabled = false;
+	dev_priv->set_backlight(dev, 0);
+}
+
+static void intel_link_enable_backlight(struct drm_device *dev, enum pipe pipe)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+
+	/* Increase the level from 0 */
+	if (dev_priv->backlight_level == 0)
+		dev_priv->backlight_level = dev_priv->get_max_backlight(dev);
+
+	dev_priv->backlight_enabled = true;
+	dev_priv->set_backlight(dev, dev_priv->backlight_level);
+}
+
+void intel_adaptive_backlight_setup(struct drm_device *dev)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+
+	dev_priv->get_backlight = intel_link_get_backlight;
+	dev_priv->get_max_backlight = intel_link_get_max_backlight;
+	dev_priv->set_backlight = intel_link_set_backlight;
+	dev_priv->disable_backlight = intel_link_disable_backlight;
+	dev_priv->enable_backlight = intel_link_enable_backlight;
+
+	intel_adaptive_backlight_enable(dev_priv);
+}
diff --git a/drivers/gpu/drm/i915/intel_dp.c b/drivers/gpu/drm/i915/intel_dp.c
index d5f3105..e5d16bc 100644
--- a/drivers/gpu/drm/i915/intel_dp.c
+++ b/drivers/gpu/drm/i915/intel_dp.c
@@ -2442,6 +2442,23 @@ intel_dp_set_property(struct drm_connector *connector,
 		goto done;
 	}
 
+	if (property == dev_priv->adaptive_backlight_property) {
+		dev_priv->adaptive_backlight_enabled = !!val;
+
+		if (dev_priv->adaptive_backlight_enabled)
+			intel_adaptive_backlight_enable(dev_priv);
+		else
+			intel_adaptive_backlight_disable(dev_priv, connector);
+
+		goto done_nomodeset;
+	}
+
+	if (property == dev_priv->panel_gamma_property) {
+		dev_priv->adaptive_backlight_panel_gamma = (u32)val * 65536 / 100;
+
+		goto done_nomodeset;
+	}
+
 	return -EINVAL;
 
 done:
@@ -2451,6 +2468,7 @@ done:
 			       crtc->x, crtc->y, crtc->fb);
 	}
 
+done_nomodeset:
 	return 0;
 }
 
@@ -2575,6 +2593,12 @@ intel_dp_add_properties(struct intel_dp *intel_dp, struct drm_connector *connect
 			DRM_MODE_SCALE_ASPECT);
 		intel_connector->panel.fitting_mode = DRM_MODE_SCALE_ASPECT;
 	}
+
+	if ((INTEL_INFO(connector->dev)->gen == 7) &&
+	    (connector->connector_type == DRM_MODE_CONNECTOR_eDP)) {
+		intel_attach_adaptive_backlight_property(connector);
+		intel_attach_panel_gamma_property(connector);
+	}
 }
 
 static void
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 4f41b8a..0762970 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -423,6 +423,9 @@ int intel_ddc_get_modes(struct drm_connector *c, struct i2c_adapter *adapter);
 
 extern void intel_attach_force_audio_property(struct drm_connector *connector);
 extern void intel_attach_broadcast_rgb_property(struct drm_connector *connector);
+extern void
+intel_attach_adaptive_backlight_property(struct drm_connector *connector);
+extern void intel_attach_panel_gamma_property(struct drm_connector *connector);
 
 extern void intel_crt_init(struct drm_device *dev);
 extern void intel_hdmi_init(struct drm_device *dev,
diff --git a/drivers/gpu/drm/i915/intel_fixedpoint.h b/drivers/gpu/drm/i915/intel_fixedpoint.h
new file mode 100644
index 0000000..0e9343b
--- /dev/null
+++ b/drivers/gpu/drm/i915/intel_fixedpoint.h
@@ -0,0 +1,143 @@
+/*
+ * Copyright 2012 The Chromium OS Authors.
+ * All Rights Reserved.
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a
+ * copy of this software and associated documentation files (the
+ * "Software"), to deal in the Software without restriction, including
+ * without limitation the rights to use, copy, modify, merge, publish,
+ * distribute, sub license, and/or sell copies of the Software, and to
+ * permit persons to whom the Software is furnished to do so, subject to
+ * the following conditions:
+ *
+ * The above copyright notice and this permission notice (including the
+ * next paragraph) shall be included in all copies or substantial portions
+ * of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
+ * OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+ * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT.
+ * IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR
+ * ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT,
+ * TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE
+ * SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+ *
+ */
+
+/*
+ * The backlight is corrected in linear space. However the LCD correction is
+ * corrected in gamma space. So to be able to compute the correction value for
+ * the LCD, we have to compute the inverse gamma. To do so, we carry this
+ * small fixed point module which allows us to use pow() to compute inverse
+ * gamma.
+ *
+ * The fixed point format used here is 16.16.
+ */
+
+/* intel_fixed_exp_tbl[x*32] = exp(x) * 65536 */
+static const int intel_fixed_exp_tbl[33] = {
+0x00010000, 0x00010820, 0x00011083, 0x00011929, 0x00012216, 0x00012b4b,
+0x000134cc, 0x00013e99, 0x000148b6, 0x00015325, 0x00015de9, 0x00016905,
+0x0001747a, 0x0001804d, 0x00018c80, 0x00019916, 0x0001a613, 0x0001b378,
+0x0001c14b, 0x0001cf8e, 0x0001de45, 0x0001ed74, 0x0001fd1e, 0x00020d47,
+0x00021df4, 0x00022f28, 0x000240e8, 0x00025338, 0x0002661d, 0x0002799b,
+0x00028db8, 0x0002a278, 0x0002b7e1
+};
+
+/* intel_fixed_log_tbl[x*32] = log(x) * 65536 */
+static const int intel_fixed_log_tbl[33] = {
+0x80000000, 0xfffc88c6, 0xfffd3a38, 0xfffda204, 0xfffdebaa, 0xfffe24ca,
+0xfffe5376, 0xfffe7aed, 0xfffe9d1c, 0xfffebb43, 0xfffed63c, 0xfffeeea2,
+0xffff04e8, 0xffff1966, 0xffff2c5f, 0xffff3e08, 0xffff4e8e, 0xffff5e13,
+0xffff6cb5, 0xffff7a8c, 0xffff87ae, 0xffff942b, 0xffffa014, 0xffffab75,
+0xffffb65a, 0xffffc0ce, 0xffffcad8, 0xffffd481, 0xffffddd1, 0xffffe6cd,
+0xffffef7a, 0xfffff7df, 0xffffffff
+};
+
+/* e * 65536 */
+#define FIXED_E (intel_fixed_exp_tbl[32])
+/* 1 * 65536 */
+#define FIXED_ONE 65536
+
+static int intel_fixed_mul(int a, int b)
+{
+	int64_t p = (int64_t)a * b;
+	do_div(p, 65536);
+	return (int)p;
+}
+
+static int intel_fixed_div(int a, int b)
+{
+	int64_t p = (int64_t)a * 65536;
+	do_div(p, b);
+	return (int)p;
+}
+
+/*
+ * Approximate fixed point log function.
+ * Only works for inputs in [0,1[
+ */
+static int intel_fixed_log(int val)
+{
+	int index = val * 32 / FIXED_ONE;
+	int remainder = (val & 0x7ff) << 5;
+	int v1 = intel_fixed_log_tbl[index];
+	int v2 = intel_fixed_log_tbl[index+1];
+	int final = v1 + intel_fixed_mul(v2 - v1, remainder);
+	return final;
+}
+
+/*
+ * Approximate fixed point exp function.
+ */
+static int intel_fixed_exp(int val)
+{
+	int count = 0;
+	int index, remainder;
+	int int_part = FIXED_ONE, frac_part;
+	int i, v, v1, v2;
+
+	while (val < 0) {
+		val += FIXED_ONE;
+		count--;
+	}
+
+	while (val > FIXED_ONE) {
+		val -= FIXED_ONE;
+		count++;
+	}
+
+	index = val * 32 / FIXED_ONE;
+	remainder = (val & 0x7ff) << 5;
+
+	v1 = intel_fixed_exp_tbl[index];
+	v2 = intel_fixed_exp_tbl[index+1];
+	frac_part = v1 + intel_fixed_mul(v2 - v1, remainder);
+
+	if (count < 0) {
+		for (i = 0; i < -count; i++)
+			int_part = intel_fixed_mul(int_part, FIXED_E);
+
+		v = intel_fixed_div(frac_part, int_part);
+	} else {
+		for (i = 0; i < count; i++)
+			int_part = intel_fixed_mul(int_part, FIXED_E);
+
+		v = intel_fixed_mul(frac_part, int_part);
+	}
+	return (v >= 0) ? v : 0;
+}
+
+/*
+ * Approximate fixed point pow function.
+ * Only works for x in [0,1[
+ */
+static int intel_fixed_pow(int x, int y)
+{
+	int e, p, r;
+	e = intel_fixed_log(x);
+	p = intel_fixed_mul(e, y);
+	r = intel_fixed_exp(p);
+	return r;
+}
+
diff --git a/drivers/gpu/drm/i915/intel_modes.c b/drivers/gpu/drm/i915/intel_modes.c
index 0d9b115..57b9d52 100644
--- a/drivers/gpu/drm/i915/intel_modes.c
+++ b/drivers/gpu/drm/i915/intel_modes.c
@@ -100,6 +100,54 @@ intel_attach_force_audio_property(struct drm_connector *connector)
 	drm_object_attach_property(&connector->base, prop, 0);
 }
 
+static const struct drm_prop_enum_list adaptive_backlight_names[] = {
+	{ 0, "off" },
+	{ 1, "on" },
+};
+
+void
+intel_attach_adaptive_backlight_property(struct drm_connector *connector)
+{
+	struct drm_device *dev = connector->dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	struct drm_property *prop;
+
+	prop = dev_priv->adaptive_backlight_property;
+	if (prop == NULL) {
+		prop = drm_property_create_enum(dev, 0,
+					"Adaptive backlight",
+					adaptive_backlight_names,
+					ARRAY_SIZE(adaptive_backlight_names));
+		if (prop == NULL)
+			return;
+
+		dev_priv->adaptive_backlight_property = prop;
+	}
+	drm_object_attach_property(&connector->base, prop, 0);
+}
+
+void
+intel_attach_panel_gamma_property(struct drm_connector *connector)
+{
+	struct drm_device *dev = connector->dev;
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	struct drm_property *prop;
+
+	prop = dev_priv->panel_gamma_property;
+	if (prop == NULL) {
+		prop = drm_property_create_range(dev, 0,
+					"Panel gamma",
+					100,
+					550);
+
+		if (prop == NULL)
+			return;
+
+		dev_priv->panel_gamma_property = prop;
+	}
+	drm_object_attach_property(&connector->base, prop, 100);
+}
+
 static const struct drm_prop_enum_list broadcast_rgb_names[] = {
 	{ 0, "Full" },
 	{ 1, "Limited 16:235" },
diff --git a/drivers/gpu/drm/i915/intel_panel.c b/drivers/gpu/drm/i915/intel_panel.c
index dddd4a1..cebabb0 100644
--- a/drivers/gpu/drm/i915/intel_panel.c
+++ b/drivers/gpu/drm/i915/intel_panel.c
@@ -30,6 +30,7 @@
 
 #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
 
+#include <linux/dmi.h>
 #include <linux/moduleparam.h>
 #include "intel_drv.h"
 #include "i915_drv.h"
@@ -392,6 +393,23 @@ set_level:
 	intel_panel_actually_set_backlight(dev, dev_priv->backlight_level);
 }
 
+static int intel_link_backlight(const struct dmi_system_id *id)
+{
+	DRM_DEBUG_KMS("Using Link backlight\n");
+	return 1;
+}
+
+static const struct dmi_system_id link_dmi_table[] = {
+	{
+		.callback = intel_link_backlight,
+		.ident = "Link",
+		.matches = {
+			DMI_MATCH(DMI_PRODUCT_NAME, "Link"),
+		},
+	},
+	{ }
+};
+
 static void intel_panel_init_backlight(struct drm_device *dev)
 {
 	struct drm_i915_private *dev_priv = dev->dev_private;
@@ -404,6 +422,26 @@ static void intel_panel_init_backlight(struct drm_device *dev)
 
 	dev_priv->backlight_level = dev_priv->get_backlight(dev);
 	dev_priv->backlight_enabled = dev_priv->backlight_level != 0;
+
+	if (dmi_check_system(link_dmi_table)) {
+		struct drm_connector *connector;
+		bool found = false;
+		/* Find the connector */
+		list_for_each_entry(connector,
+				    &dev->mode_config.connector_list,
+				    head)
+			if (connector->connector_type ==
+			    DRM_MODE_CONNECTOR_eDP) {
+				found = true;
+				break;
+			}
+
+		if (found) {
+			intel_adaptive_backlight_setup(dev);
+			intel_attach_adaptive_backlight_property(connector);
+			intel_attach_panel_gamma_property(connector);
+		}
+	}
 }
 
 enum drm_connector_status
