From: Stéphane Marchesin <[email protected]>

This adds device-specific backlight support for Link, and also
enables adaptive backlight by default there.

BUG=chrome-os-partner:13276,chrome-os-partner:15248
TEST=by hand

Change-Id: I9ef546bba9f121657a653aa9cfc6a80bbde55cb0
Reviewed-on: https://gerrit.chromium.org/gerrit/36976
Reviewed-by: Daniel Erat <[email protected]>
Commit-Ready: Stéphane Marchesin <[email protected]>
Tested-by: Stéphane Marchesin <[email protected]>
[marcheu: Fixups for 3.8]
---
 drivers/gpu/drm/i915/Makefile                   |   1 +
 drivers/gpu/drm/i915/i915_drv.h                 |  17 +
 drivers/gpu/drm/i915/i915_irq.c                 |   4 +-
 drivers/gpu/drm/i915/i915_reg.h                 |  22 ++
 drivers/gpu/drm/i915/intel_adaptive_backlight.c | 401 ++++++++++++++++++++++++
 drivers/gpu/drm/i915/intel_dp.c                 |  24 ++
 drivers/gpu/drm/i915/intel_drv.h                |   3 +
 drivers/gpu/drm/i915/intel_fixedpoint.h         | 143 +++++++++
 drivers/gpu/drm/i915/intel_modes.c              |  48 +++
 drivers/gpu/drm/i915/intel_panel.c              |  38 +++
 10 files changed, 700 insertions(+), 1 deletion(-)
 create mode 100644 drivers/gpu/drm/i915/intel_adaptive_backlight.c
 create mode 100644 drivers/gpu/drm/i915/intel_fixedpoint.h

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
-- 
1.8.3.2

_______________________________________________
Intel-gfx mailing list
[email protected]
http://lists.freedesktop.org/mailman/listinfo/intel-gfx

Reply via email to