drm/i915: Add no-lvds quirk for Supermicro X7SPA-H
[linux-2.6.git] / drivers / gpu / drm / i915 / intel_lvds.c
index a5d61d3..802fec2 100644 (file)
@@ -30,6 +30,7 @@
 #include <acpi/button.h>
 #include <linux/dmi.h>
 #include <linux/i2c.h>
+#include <linux/slab.h>
 #include "drmP.h"
 #include "drm.h"
 #include "drm_crtc.h"
 #include <linux/acpi.h>
 
 /* Private structure for the integrated LVDS support */
-struct intel_lvds_priv {
+struct intel_lvds {
+       struct intel_encoder base;
+
+       struct edid *edid;
+
        int fitting_mode;
        u32 pfit_control;
        u32 pfit_pgm_ratios;
+       bool pfit_dirty;
+
+       struct drm_display_mode *fixed_mode;
 };
 
-/**
- * Sets the backlight level.
- *
- * \param level backlight level, from 0 to intel_lvds_get_max_backlight().
- */
-static void intel_lvds_set_backlight(struct drm_device *dev, int level)
+static struct intel_lvds *to_intel_lvds(struct drm_encoder *encoder)
 {
-       struct drm_i915_private *dev_priv = dev->dev_private;
-       u32 blc_pwm_ctl, reg;
-
-       if (HAS_PCH_SPLIT(dev))
-               reg = BLC_PWM_CPU_CTL;
-       else
-               reg = BLC_PWM_CTL;
-
-       blc_pwm_ctl = I915_READ(reg) & ~BACKLIGHT_DUTY_CYCLE_MASK;
-       I915_WRITE(reg, (blc_pwm_ctl |
-                                (level << BACKLIGHT_DUTY_CYCLE_SHIFT)));
+       return container_of(encoder, struct intel_lvds, base.base);
 }
 
-/**
- * Returns the maximum level of the backlight duty cycle field.
- */
-static u32 intel_lvds_get_max_backlight(struct drm_device *dev)
+static struct intel_lvds *intel_attached_lvds(struct drm_connector *connector)
 {
-       struct drm_i915_private *dev_priv = dev->dev_private;
-       u32 reg;
-
-       if (HAS_PCH_SPLIT(dev))
-               reg = BLC_PWM_PCH_CTL2;
-       else
-               reg = BLC_PWM_CTL;
-
-       return ((I915_READ(reg) & BACKLIGHT_MODULATION_FREQ_MASK) >>
-               BACKLIGHT_MODULATION_FREQ_SHIFT) * 2;
+       return container_of(intel_attached_encoder(connector),
+                           struct intel_lvds, base);
 }
 
 /**
  * Sets the power state for the panel.
  */
-static void intel_lvds_set_power(struct drm_device *dev, bool on)
+static void intel_lvds_enable(struct intel_lvds *intel_lvds)
 {
+       struct drm_device *dev = intel_lvds->base.base.dev;
        struct drm_i915_private *dev_priv = dev->dev_private;
-       u32 pp_status, ctl_reg, status_reg, lvds_reg;
+       u32 ctl_reg, lvds_reg, stat_reg;
 
        if (HAS_PCH_SPLIT(dev)) {
                ctl_reg = PCH_PP_CONTROL;
-               status_reg = PCH_PP_STATUS;
                lvds_reg = PCH_LVDS;
+               stat_reg = PCH_PP_STATUS;
        } else {
                ctl_reg = PP_CONTROL;
-               status_reg = PP_STATUS;
                lvds_reg = LVDS;
+               stat_reg = PP_STATUS;
        }
 
-       if (on) {
-               I915_WRITE(lvds_reg, I915_READ(lvds_reg) | LVDS_PORT_EN);
-               POSTING_READ(lvds_reg);
+       I915_WRITE(lvds_reg, I915_READ(lvds_reg) | LVDS_PORT_EN);
 
-               I915_WRITE(ctl_reg, I915_READ(ctl_reg) |
-                          POWER_TARGET_ON);
-               do {
-                       pp_status = I915_READ(status_reg);
-               } while ((pp_status & PP_ON) == 0);
+       if (intel_lvds->pfit_dirty) {
+               /*
+                * Enable automatic panel scaling so that non-native modes
+                * fill the screen.  The panel fitter should only be
+                * adjusted whilst the pipe is disabled, according to
+                * register description and PRM.
+                */
+               DRM_DEBUG_KMS("applying panel-fitter: %x, %x\n",
+                             intel_lvds->pfit_control,
+                             intel_lvds->pfit_pgm_ratios);
+
+               I915_WRITE(PFIT_PGM_RATIOS, intel_lvds->pfit_pgm_ratios);
+               I915_WRITE(PFIT_CONTROL, intel_lvds->pfit_control);
+               intel_lvds->pfit_dirty = false;
+       }
+
+       I915_WRITE(ctl_reg, I915_READ(ctl_reg) | POWER_TARGET_ON);
+       POSTING_READ(lvds_reg);
+       if (wait_for((I915_READ(stat_reg) & PP_ON) != 0, 1000))
+               DRM_ERROR("timed out waiting for panel to power on\n");
 
-               intel_lvds_set_backlight(dev, dev_priv->backlight_duty_cycle);
+       intel_panel_enable_backlight(dev);
+}
+
+static void intel_lvds_disable(struct intel_lvds *intel_lvds)
+{
+       struct drm_device *dev = intel_lvds->base.base.dev;
+       struct drm_i915_private *dev_priv = dev->dev_private;
+       u32 ctl_reg, lvds_reg, stat_reg;
+
+       if (HAS_PCH_SPLIT(dev)) {
+               ctl_reg = PCH_PP_CONTROL;
+               lvds_reg = PCH_LVDS;
+               stat_reg = PCH_PP_STATUS;
        } else {
-               intel_lvds_set_backlight(dev, 0);
+               ctl_reg = PP_CONTROL;
+               lvds_reg = LVDS;
+               stat_reg = PP_STATUS;
+       }
+
+       intel_panel_disable_backlight(dev);
 
-               I915_WRITE(ctl_reg, I915_READ(ctl_reg) &
-                          ~POWER_TARGET_ON);
-               do {
-                       pp_status = I915_READ(status_reg);
-               } while (pp_status & PP_ON);
+       I915_WRITE(ctl_reg, I915_READ(ctl_reg) & ~POWER_TARGET_ON);
+       if (wait_for((I915_READ(stat_reg) & PP_ON) == 0, 1000))
+               DRM_ERROR("timed out waiting for panel to power off\n");
 
-               I915_WRITE(lvds_reg, I915_READ(lvds_reg) & ~LVDS_PORT_EN);
-               POSTING_READ(lvds_reg);
+       if (intel_lvds->pfit_control) {
+               I915_WRITE(PFIT_CONTROL, 0);
+               intel_lvds->pfit_dirty = true;
        }
+
+       I915_WRITE(lvds_reg, I915_READ(lvds_reg) & ~LVDS_PORT_EN);
+       POSTING_READ(lvds_reg);
 }
 
 static void intel_lvds_dpms(struct drm_encoder *encoder, int mode)
 {
-       struct drm_device *dev = encoder->dev;
+       struct intel_lvds *intel_lvds = to_intel_lvds(encoder);
 
        if (mode == DRM_MODE_DPMS_ON)
-               intel_lvds_set_power(dev, true);
+               intel_lvds_enable(intel_lvds);
        else
-               intel_lvds_set_power(dev, false);
+               intel_lvds_disable(intel_lvds);
 
        /* XXX: We never power down the LVDS pairs. */
 }
@@ -141,48 +156,91 @@ static void intel_lvds_dpms(struct drm_encoder *encoder, int mode)
 static int intel_lvds_mode_valid(struct drm_connector *connector,
                                 struct drm_display_mode *mode)
 {
-       struct drm_device *dev = connector->dev;
-       struct drm_i915_private *dev_priv = dev->dev_private;
-       struct drm_display_mode *fixed_mode = dev_priv->panel_fixed_mode;
+       struct intel_lvds *intel_lvds = intel_attached_lvds(connector);
+       struct drm_display_mode *fixed_mode = intel_lvds->fixed_mode;
 
-       if (fixed_mode) {
-               if (mode->hdisplay > fixed_mode->hdisplay)
-                       return MODE_PANEL;
-               if (mode->vdisplay > fixed_mode->vdisplay)
-                       return MODE_PANEL;
-       }
+       if (mode->hdisplay > fixed_mode->hdisplay)
+               return MODE_PANEL;
+       if (mode->vdisplay > fixed_mode->vdisplay)
+               return MODE_PANEL;
 
        return MODE_OK;
 }
 
+static void
+centre_horizontally(struct drm_display_mode *mode,
+                   int width)
+{
+       u32 border, sync_pos, blank_width, sync_width;
+
+       /* keep the hsync and hblank widths constant */
+       sync_width = mode->crtc_hsync_end - mode->crtc_hsync_start;
+       blank_width = mode->crtc_hblank_end - mode->crtc_hblank_start;
+       sync_pos = (blank_width - sync_width + 1) / 2;
+
+       border = (mode->hdisplay - width + 1) / 2;
+       border += border & 1; /* make the border even */
+
+       mode->crtc_hdisplay = width;
+       mode->crtc_hblank_start = width + border;
+       mode->crtc_hblank_end = mode->crtc_hblank_start + blank_width;
+
+       mode->crtc_hsync_start = mode->crtc_hblank_start + sync_pos;
+       mode->crtc_hsync_end = mode->crtc_hsync_start + sync_width;
+
+       mode->private_flags |= INTEL_MODE_CRTC_TIMINGS_SET;
+}
+
+static void
+centre_vertically(struct drm_display_mode *mode,
+                 int height)
+{
+       u32 border, sync_pos, blank_width, sync_width;
+
+       /* keep the vsync and vblank widths constant */
+       sync_width = mode->crtc_vsync_end - mode->crtc_vsync_start;
+       blank_width = mode->crtc_vblank_end - mode->crtc_vblank_start;
+       sync_pos = (blank_width - sync_width + 1) / 2;
+
+       border = (mode->vdisplay - height + 1) / 2;
+
+       mode->crtc_vdisplay = height;
+       mode->crtc_vblank_start = height + border;
+       mode->crtc_vblank_end = mode->crtc_vblank_start + blank_width;
+
+       mode->crtc_vsync_start = mode->crtc_vblank_start + sync_pos;
+       mode->crtc_vsync_end = mode->crtc_vsync_start + sync_width;
+
+       mode->private_flags |= INTEL_MODE_CRTC_TIMINGS_SET;
+}
+
+static inline u32 panel_fitter_scaling(u32 source, u32 target)
+{
+       /*
+        * Floating point operation is not supported. So the FACTOR
+        * is defined, which can avoid the floating point computation
+        * when calculating the panel ratio.
+        */
+#define ACCURACY 12
+#define FACTOR (1 << ACCURACY)
+       u32 ratio = source * FACTOR / target;
+       return (FACTOR * ratio + FACTOR/2) / FACTOR;
+}
+
 static bool intel_lvds_mode_fixup(struct drm_encoder *encoder,
                                  struct drm_display_mode *mode,
                                  struct drm_display_mode *adjusted_mode)
 {
-       /*
-        * float point operation is not supported . So the PANEL_RATIO_FACTOR
-        * is defined, which can avoid the float point computation when
-        * calculating the panel ratio.
-        */
-#define PANEL_RATIO_FACTOR 8192
        struct drm_device *dev = encoder->dev;
        struct drm_i915_private *dev_priv = dev->dev_private;
        struct intel_crtc *intel_crtc = to_intel_crtc(encoder->crtc);
+       struct intel_lvds *intel_lvds = to_intel_lvds(encoder);
        struct drm_encoder *tmp_encoder;
-       struct intel_encoder *intel_encoder = enc_to_intel_encoder(encoder);
-       struct intel_lvds_priv *lvds_priv = intel_encoder->dev_priv;
-       u32 pfit_control = 0, pfit_pgm_ratios = 0;
-       int left_border = 0, right_border = 0, top_border = 0;
-       int bottom_border = 0;
-       bool border = 0;
-       int panel_ratio, desired_ratio, vert_scale, horiz_scale;
-       int horiz_ratio, vert_ratio;
-       u32 hsync_width, vsync_width;
-       u32 hblank_width, vblank_width;
-       u32 hsync_pos, vsync_pos;
+       u32 pfit_control = 0, pfit_pgm_ratios = 0, border = 0;
+       int pipe;
 
        /* Should never happen!! */
-       if (!IS_I965G(dev) && intel_crtc->pipe == 0) {
+       if (INTEL_INFO(dev)->gen < 4 && intel_crtc->pipe == 0) {
                DRM_ERROR("Can't support LVDS on pipe A\n");
                return false;
        }
@@ -195,252 +253,104 @@ static bool intel_lvds_mode_fixup(struct drm_encoder *encoder,
                        return false;
                }
        }
-       /* If we don't have a panel mode, there is nothing we can do */
-       if (dev_priv->panel_fixed_mode == NULL)
-               return true;
+
        /*
-        * If we have timings from the BIOS for the panel, put them in
+        * We have timings from the BIOS for the panel, put them in
         * to the adjusted mode.  The CRTC will be set up for this mode,
         * with the panel scaling set up to source from the H/VDisplay
         * of the original mode.
         */
-       if (dev_priv->panel_fixed_mode != NULL) {
-               adjusted_mode->hdisplay = dev_priv->panel_fixed_mode->hdisplay;
-               adjusted_mode->hsync_start =
-                       dev_priv->panel_fixed_mode->hsync_start;
-               adjusted_mode->hsync_end =
-                       dev_priv->panel_fixed_mode->hsync_end;
-               adjusted_mode->htotal = dev_priv->panel_fixed_mode->htotal;
-               adjusted_mode->vdisplay = dev_priv->panel_fixed_mode->vdisplay;
-               adjusted_mode->vsync_start =
-                       dev_priv->panel_fixed_mode->vsync_start;
-               adjusted_mode->vsync_end =
-                       dev_priv->panel_fixed_mode->vsync_end;
-               adjusted_mode->vtotal = dev_priv->panel_fixed_mode->vtotal;
-               adjusted_mode->clock = dev_priv->panel_fixed_mode->clock;
-               drm_mode_set_crtcinfo(adjusted_mode, CRTC_INTERLACE_HALVE_V);
-       }
+       intel_fixed_panel_mode(intel_lvds->fixed_mode, adjusted_mode);
 
-       /* Make sure pre-965s set dither correctly */
-       if (!IS_I965G(dev)) {
-               if (dev_priv->panel_wants_dither || dev_priv->lvds_dither)
-                       pfit_control |= PANEL_8TO6_DITHER_ENABLE;
+       if (HAS_PCH_SPLIT(dev)) {
+               intel_pch_panel_fitting(dev, intel_lvds->fitting_mode,
+                                       mode, adjusted_mode);
+               return true;
        }
 
        /* Native modes don't need fitting */
        if (adjusted_mode->hdisplay == mode->hdisplay &&
-                       adjusted_mode->vdisplay == mode->vdisplay) {
-               pfit_pgm_ratios = 0;
-               border = 0;
-               goto out;
-       }
-
-       /* full screen scale for now */
-       if (HAS_PCH_SPLIT(dev))
+           adjusted_mode->vdisplay == mode->vdisplay)
                goto out;
 
        /* 965+ wants fuzzy fitting */
-       if (IS_I965G(dev))
-               pfit_control |= (intel_crtc->pipe << PFIT_PIPE_SHIFT) |
-                                       PFIT_FILTER_FUZZY;
-
-       hsync_width = adjusted_mode->crtc_hsync_end -
-                                       adjusted_mode->crtc_hsync_start;
-       vsync_width = adjusted_mode->crtc_vsync_end -
-                                       adjusted_mode->crtc_vsync_start;
-       hblank_width = adjusted_mode->crtc_hblank_end -
-                                       adjusted_mode->crtc_hblank_start;
-       vblank_width = adjusted_mode->crtc_vblank_end -
-                                       adjusted_mode->crtc_vblank_start;
-       /*
-        * Deal with panel fitting options. Figure out how to stretch the
-        * image based on its aspect ratio & the current panel fitting mode.
-        */
-       panel_ratio = adjusted_mode->hdisplay * PANEL_RATIO_FACTOR /
-                               adjusted_mode->vdisplay;
-       desired_ratio = mode->hdisplay * PANEL_RATIO_FACTOR /
-                               mode->vdisplay;
+       if (INTEL_INFO(dev)->gen >= 4)
+               pfit_control |= ((intel_crtc->pipe << PFIT_PIPE_SHIFT) |
+                                PFIT_FILTER_FUZZY);
+
        /*
         * Enable automatic panel scaling for non-native modes so that they fill
         * the screen.  Should be enabled before the pipe is enabled, according
         * to register description and PRM.
         * Change the value here to see the borders for debugging
         */
-       if (!HAS_PCH_SPLIT(dev)) {
-               I915_WRITE(BCLRPAT_A, 0);
-               I915_WRITE(BCLRPAT_B, 0);
-       }
+       for_each_pipe(pipe)
+               I915_WRITE(BCLRPAT(pipe), 0);
 
-       switch (lvds_priv->fitting_mode) {
+       drm_mode_set_crtcinfo(adjusted_mode, 0);
+
+       switch (intel_lvds->fitting_mode) {
        case DRM_MODE_SCALE_CENTER:
                /*
                 * For centered modes, we have to calculate border widths &
                 * heights and modify the values programmed into the CRTC.
                 */
-               left_border = (adjusted_mode->hdisplay - mode->hdisplay) / 2;
-               right_border = left_border;
-               if (mode->hdisplay & 1)
-                       right_border++;
-               top_border = (adjusted_mode->vdisplay - mode->vdisplay) / 2;
-               bottom_border = top_border;
-               if (mode->vdisplay & 1)
-                       bottom_border++;
-               /* Set active & border values */
-               adjusted_mode->crtc_hdisplay = mode->hdisplay;
-               /* Keep the boder be even */
-               if (right_border & 1)
-                       right_border++;
-               /* use the border directly instead of border minuse one */
-               adjusted_mode->crtc_hblank_start = mode->hdisplay +
-                                               right_border;
-               /* keep the blank width constant */
-               adjusted_mode->crtc_hblank_end =
-                       adjusted_mode->crtc_hblank_start + hblank_width;
-               /* get the hsync pos relative to hblank start */
-               hsync_pos = (hblank_width - hsync_width) / 2;
-               /* keep the hsync pos be even */
-               if (hsync_pos & 1)
-                       hsync_pos++;
-               adjusted_mode->crtc_hsync_start =
-                               adjusted_mode->crtc_hblank_start + hsync_pos;
-               /* keep the hsync width constant */
-               adjusted_mode->crtc_hsync_end =
-                               adjusted_mode->crtc_hsync_start + hsync_width;
-               adjusted_mode->crtc_vdisplay = mode->vdisplay;
-               /* use the border instead of border minus one */
-               adjusted_mode->crtc_vblank_start = mode->vdisplay +
-                                               bottom_border;
-               /* keep the vblank width constant */
-               adjusted_mode->crtc_vblank_end =
-                               adjusted_mode->crtc_vblank_start + vblank_width;
-               /* get the vsync start postion relative to vblank start */
-               vsync_pos = (vblank_width - vsync_width) / 2;
-               adjusted_mode->crtc_vsync_start =
-                               adjusted_mode->crtc_vblank_start + vsync_pos;
-               /* keep the vsync width constant */
-               adjusted_mode->crtc_vsync_end =
-                               adjusted_mode->crtc_vsync_start + vsync_width;
-               border = 1;
+               centre_horizontally(adjusted_mode, mode->hdisplay);
+               centre_vertically(adjusted_mode, mode->vdisplay);
+               border = LVDS_BORDER_ENABLE;
                break;
+
        case DRM_MODE_SCALE_ASPECT:
-               /* Scale but preserve the spect ratio */
-               pfit_control |= PFIT_ENABLE;
-               if (IS_I965G(dev)) {
+               /* Scale but preserve the aspect ratio */
+               if (INTEL_INFO(dev)->gen >= 4) {
+                       u32 scaled_width = adjusted_mode->hdisplay * mode->vdisplay;
+                       u32 scaled_height = mode->hdisplay * adjusted_mode->vdisplay;
+
                        /* 965+ is easy, it does everything in hw */
-                       if (panel_ratio > desired_ratio)
-                               pfit_control |= PFIT_SCALING_PILLAR;
-                       else if (panel_ratio < desired_ratio)
-                               pfit_control |= PFIT_SCALING_LETTER;
-                       else
-                               pfit_control |= PFIT_SCALING_AUTO;
+                       if (scaled_width > scaled_height)
+                               pfit_control |= PFIT_ENABLE | PFIT_SCALING_PILLAR;
+                       else if (scaled_width < scaled_height)
+                               pfit_control |= PFIT_ENABLE | PFIT_SCALING_LETTER;
+                       else if (adjusted_mode->hdisplay != mode->hdisplay)
+                               pfit_control |= PFIT_ENABLE | PFIT_SCALING_AUTO;
                } else {
+                       u32 scaled_width = adjusted_mode->hdisplay * mode->vdisplay;
+                       u32 scaled_height = mode->hdisplay * adjusted_mode->vdisplay;
                        /*
                         * For earlier chips we have to calculate the scaling
                         * ratio by hand and program it into the
                         * PFIT_PGM_RATIO register
                         */
-                       u32 horiz_bits, vert_bits, bits = 12;
-                       horiz_ratio = mode->hdisplay * PANEL_RATIO_FACTOR/
-                                               adjusted_mode->hdisplay;
-                       vert_ratio = mode->vdisplay * PANEL_RATIO_FACTOR/
-                                               adjusted_mode->vdisplay;
-                       horiz_scale = adjusted_mode->hdisplay *
-                                       PANEL_RATIO_FACTOR / mode->hdisplay;
-                       vert_scale = adjusted_mode->vdisplay *
-                                       PANEL_RATIO_FACTOR / mode->vdisplay;
-
-                       /* retain aspect ratio */
-                       if (panel_ratio > desired_ratio) { /* Pillar */
-                               u32 scaled_width;
-                               scaled_width = mode->hdisplay * vert_scale /
-                                               PANEL_RATIO_FACTOR;
-                               horiz_ratio = vert_ratio;
-                               pfit_control |= (VERT_AUTO_SCALE |
+                       if (scaled_width > scaled_height) { /* pillar */
+                               centre_horizontally(adjusted_mode, scaled_height / mode->vdisplay);
+
+                               border = LVDS_BORDER_ENABLE;
+                               if (mode->vdisplay != adjusted_mode->vdisplay) {
+                                       u32 bits = panel_fitter_scaling(mode->vdisplay, adjusted_mode->vdisplay);
+                                       pfit_pgm_ratios |= (bits << PFIT_HORIZ_SCALE_SHIFT |
+                                                           bits << PFIT_VERT_SCALE_SHIFT);
+                                       pfit_control |= (PFIT_ENABLE |
+                                                        VERT_INTERP_BILINEAR |
+                                                        HORIZ_INTERP_BILINEAR);
+                               }
+                       } else if (scaled_width < scaled_height) { /* letter */
+                               centre_vertically(adjusted_mode, scaled_width / mode->hdisplay);
+
+                               border = LVDS_BORDER_ENABLE;
+                               if (mode->hdisplay != adjusted_mode->hdisplay) {
+                                       u32 bits = panel_fitter_scaling(mode->hdisplay, adjusted_mode->hdisplay);
+                                       pfit_pgm_ratios |= (bits << PFIT_HORIZ_SCALE_SHIFT |
+                                                           bits << PFIT_VERT_SCALE_SHIFT);
+                                       pfit_control |= (PFIT_ENABLE |
+                                                        VERT_INTERP_BILINEAR |
+                                                        HORIZ_INTERP_BILINEAR);
+                               }
+                       } else
+                               /* Aspects match, Let hw scale both directions */
+                               pfit_control |= (PFIT_ENABLE |
+                                                VERT_AUTO_SCALE | HORIZ_AUTO_SCALE |
                                                 VERT_INTERP_BILINEAR |
                                                 HORIZ_INTERP_BILINEAR);
-                               /* Pillar will have left/right borders */
-                               left_border = (adjusted_mode->hdisplay -
-                                               scaled_width) / 2;
-                               right_border = left_border;
-                               if (mode->hdisplay & 1) /* odd resolutions */
-                                       right_border++;
-                               /* keep the border be even */
-                               if (right_border & 1)
-                                       right_border++;
-                               adjusted_mode->crtc_hdisplay = scaled_width;
-                               /* use border instead of border minus one */
-                               adjusted_mode->crtc_hblank_start =
-                                       scaled_width + right_border;
-                               /* keep the hblank width constant */
-                               adjusted_mode->crtc_hblank_end =
-                                       adjusted_mode->crtc_hblank_start +
-                                                       hblank_width;
-                               /*
-                                * get the hsync start pos relative to
-                                * hblank start
-                                */
-                               hsync_pos = (hblank_width - hsync_width) / 2;
-                               /* keep the hsync_pos be even */
-                               if (hsync_pos & 1)
-                                       hsync_pos++;
-                               adjusted_mode->crtc_hsync_start =
-                                       adjusted_mode->crtc_hblank_start +
-                                                       hsync_pos;
-                               /* keept hsync width constant */
-                               adjusted_mode->crtc_hsync_end =
-                                       adjusted_mode->crtc_hsync_start +
-                                                       hsync_width;
-                               border = 1;
-                       } else if (panel_ratio < desired_ratio) { /* letter */
-                               u32 scaled_height = mode->vdisplay *
-                                       horiz_scale / PANEL_RATIO_FACTOR;
-                               vert_ratio = horiz_ratio;
-                               pfit_control |= (HORIZ_AUTO_SCALE |
-                                                VERT_INTERP_BILINEAR |
-                                                HORIZ_INTERP_BILINEAR);
-                               /* Letterbox will have top/bottom border */
-                               top_border = (adjusted_mode->vdisplay -
-                                       scaled_height) / 2;
-                               bottom_border = top_border;
-                               if (mode->vdisplay & 1)
-                                       bottom_border++;
-                               adjusted_mode->crtc_vdisplay = scaled_height;
-                               /* use border instead of border minus one */
-                               adjusted_mode->crtc_vblank_start =
-                                       scaled_height + bottom_border;
-                               /* keep the vblank width constant */
-                               adjusted_mode->crtc_vblank_end =
-                                       adjusted_mode->crtc_vblank_start +
-                                                       vblank_width;
-                               /*
-                                * get the vsync start pos relative to
-                                * vblank start
-                                */
-                               vsync_pos = (vblank_width - vsync_width) / 2;
-                               adjusted_mode->crtc_vsync_start =
-                                       adjusted_mode->crtc_vblank_start +
-                                                       vsync_pos;
-                               /* keep the vsync width constant */
-                               adjusted_mode->crtc_vsync_end =
-                                       adjusted_mode->crtc_vsync_start +
-                                                       vsync_width;
-                               border = 1;
-                       } else {
-                       /* Aspects match, Let hw scale both directions */
-                               pfit_control |= (VERT_AUTO_SCALE |
-                                                HORIZ_AUTO_SCALE |
-                                                VERT_INTERP_BILINEAR |
-                                                HORIZ_INTERP_BILINEAR);
-                       }
-                       horiz_bits = (1 << bits) * horiz_ratio /
-                                       PANEL_RATIO_FACTOR;
-                       vert_bits = (1 << bits) * vert_ratio /
-                                       PANEL_RATIO_FACTOR;
-                       pfit_pgm_ratios =
-                               ((vert_bits << PFIT_VERT_SCALE_SHIFT) &
-                                               PFIT_VERT_SCALE_MASK) |
-                               ((horiz_bits << PFIT_HORIZ_SCALE_SHIFT) &
-                                               PFIT_HORIZ_SCALE_MASK);
                }
                break;
 
@@ -449,29 +359,42 @@ static bool intel_lvds_mode_fixup(struct drm_encoder *encoder,
                 * Full scaling, even if it changes the aspect ratio.
                 * Fortunately this is all done for us in hw.
                 */
-               pfit_control |= PFIT_ENABLE;
-               if (IS_I965G(dev))
-                       pfit_control |= PFIT_SCALING_AUTO;
-               else
-                       pfit_control |= (VERT_AUTO_SCALE | HORIZ_AUTO_SCALE |
-                                        VERT_INTERP_BILINEAR |
-                                        HORIZ_INTERP_BILINEAR);
+               if (mode->vdisplay != adjusted_mode->vdisplay ||
+                   mode->hdisplay != adjusted_mode->hdisplay) {
+                       pfit_control |= PFIT_ENABLE;
+                       if (INTEL_INFO(dev)->gen >= 4)
+                               pfit_control |= PFIT_SCALING_AUTO;
+                       else
+                               pfit_control |= (VERT_AUTO_SCALE |
+                                                VERT_INTERP_BILINEAR |
+                                                HORIZ_AUTO_SCALE |
+                                                HORIZ_INTERP_BILINEAR);
+               }
                break;
+
        default:
                break;
        }
 
 out:
-       lvds_priv->pfit_control = pfit_control;
-       lvds_priv->pfit_pgm_ratios = pfit_pgm_ratios;
-       /*
-        * When there exists the border, it means that the LVDS_BORDR
-        * should be enabled.
-        */
-       if (border)
-               dev_priv->lvds_border_bits |= LVDS_BORDER_ENABLE;
-       else
-               dev_priv->lvds_border_bits &= ~(LVDS_BORDER_ENABLE);
+       /* If not enabling scaling, be consistent and always use 0. */
+       if ((pfit_control & PFIT_ENABLE) == 0) {
+               pfit_control = 0;
+               pfit_pgm_ratios = 0;
+       }
+
+       /* Make sure pre-965 set dither correctly */
+       if (INTEL_INFO(dev)->gen < 4 && dev_priv->lvds_dither)
+               pfit_control |= PANEL_8TO6_DITHER_ENABLE;
+
+       if (pfit_control != intel_lvds->pfit_control ||
+           pfit_pgm_ratios != intel_lvds->pfit_pgm_ratios) {
+               intel_lvds->pfit_control = pfit_control;
+               intel_lvds->pfit_pgm_ratios = pfit_pgm_ratios;
+               intel_lvds->pfit_dirty = true;
+       }
+       dev_priv->lvds_border_bits = border;
+
        /*
         * XXX: It would be nice to support lower refresh rates on the
         * panels to reduce power consumption, and perhaps match the
@@ -483,59 +406,36 @@ out:
 
 static void intel_lvds_prepare(struct drm_encoder *encoder)
 {
-       struct drm_device *dev = encoder->dev;
-       struct drm_i915_private *dev_priv = dev->dev_private;
-       u32 reg;
-
-       if (HAS_PCH_SPLIT(dev))
-               reg = BLC_PWM_CPU_CTL;
-       else
-               reg = BLC_PWM_CTL;
-
-       dev_priv->saveBLC_PWM_CTL = I915_READ(reg);
-       dev_priv->backlight_duty_cycle = (dev_priv->saveBLC_PWM_CTL &
-                                      BACKLIGHT_DUTY_CYCLE_MASK);
+       struct intel_lvds *intel_lvds = to_intel_lvds(encoder);
 
-       intel_lvds_set_power(dev, false);
+       /*
+        * Prior to Ironlake, we must disable the pipe if we want to adjust
+        * the panel fitter. However at all other times we can just reset
+        * the registers regardless.
+        */
+       if (!HAS_PCH_SPLIT(encoder->dev) && intel_lvds->pfit_dirty)
+               intel_lvds_disable(intel_lvds);
 }
 
-static void intel_lvds_commit( struct drm_encoder *encoder)
+static void intel_lvds_commit(struct drm_encoder *encoder)
 {
-       struct drm_device *dev = encoder->dev;
-       struct drm_i915_private *dev_priv = dev->dev_private;
-
-       if (dev_priv->backlight_duty_cycle == 0)
-               dev_priv->backlight_duty_cycle =
-                       intel_lvds_get_max_backlight(dev);
+       struct intel_lvds *intel_lvds = to_intel_lvds(encoder);
 
-       intel_lvds_set_power(dev, true);
+       /* Always do a full power on as we do not know what state
+        * we were left in.
+        */
+       intel_lvds_enable(intel_lvds);
 }
 
 static void intel_lvds_mode_set(struct drm_encoder *encoder,
                                struct drm_display_mode *mode,
                                struct drm_display_mode *adjusted_mode)
 {
-       struct drm_device *dev = encoder->dev;
-       struct drm_i915_private *dev_priv = dev->dev_private;
-       struct intel_encoder *intel_encoder = enc_to_intel_encoder(encoder);
-       struct intel_lvds_priv *lvds_priv = intel_encoder->dev_priv;
-
        /*
         * The LVDS pin pair will already have been turned on in the
         * intel_crtc_mode_set since it has a large impact on the DPLL
         * settings.
         */
-
-       if (HAS_PCH_SPLIT(dev))
-               return;
-
-       /*
-        * Enable automatic panel scaling so that non-native modes fill the
-        * screen.  Should be enabled before the pipe is enabled, according to
-        * register description and PRM.
-        */
-       I915_WRITE(PFIT_PGM_RATIOS, lvds_priv->pfit_pgm_ratios);
-       I915_WRITE(PFIT_CONTROL, lvds_priv->pfit_control);
 }
 
 /**
@@ -545,18 +445,17 @@ static void intel_lvds_mode_set(struct drm_encoder *encoder,
  * connected and closed means disconnected.  We also send hotplug events as
  * needed, using lid status notification from the input layer.
  */
-static enum drm_connector_status intel_lvds_detect(struct drm_connector *connector)
+static enum drm_connector_status
+intel_lvds_detect(struct drm_connector *connector, bool force)
 {
        struct drm_device *dev = connector->dev;
-       enum drm_connector_status status = connector_status_connected;
+       enum drm_connector_status status;
 
-       /* ACPI lid methods were generally unreliable in this generation, so
-        * don't even bother.
-        */
-       if (IS_GEN2(dev) || IS_GEN3(dev))
-               return connector_status_connected;
+       status = intel_panel_detect(dev);
+       if (status != connector_status_unknown)
+               return status;
 
-       return status;
+       return connector_status_connected;
 }
 
 /**
@@ -564,39 +463,40 @@ static enum drm_connector_status intel_lvds_detect(struct drm_connector *connect
  */
 static int intel_lvds_get_modes(struct drm_connector *connector)
 {
+       struct intel_lvds *intel_lvds = intel_attached_lvds(connector);
        struct drm_device *dev = connector->dev;
-       struct drm_encoder *encoder = intel_attached_encoder(connector);
-       struct intel_encoder *intel_encoder = enc_to_intel_encoder(encoder);
-       struct drm_i915_private *dev_priv = dev->dev_private;
-       int ret = 0;
-
-       if (dev_priv->lvds_edid_good) {
-               ret = intel_ddc_get_modes(connector, intel_encoder->ddc_bus);
+       struct drm_display_mode *mode;
 
-               if (ret)
-                       return ret;
-       }
+       if (intel_lvds->edid)
+               return drm_add_edid_modes(connector, intel_lvds->edid);
 
-       /* Didn't get an EDID, so
-        * Set wide sync ranges so we get all modes
-        * handed to valid_mode for checking
-        */
-       connector->display_info.min_vfreq = 0;
-       connector->display_info.max_vfreq = 200;
-       connector->display_info.min_hfreq = 0;
-       connector->display_info.max_hfreq = 200;
+       mode = drm_mode_duplicate(dev, intel_lvds->fixed_mode);
+       if (mode == NULL)
+               return 0;
 
-       if (dev_priv->panel_fixed_mode != NULL) {
-               struct drm_display_mode *mode;
+       drm_mode_probed_add(connector, mode);
+       return 1;
+}
 
-               mode = drm_mode_duplicate(dev, dev_priv->panel_fixed_mode);
-               drm_mode_probed_add(connector, mode);
+static int intel_no_modeset_on_lid_dmi_callback(const struct dmi_system_id *id)
+{
+       DRM_DEBUG_KMS("Skipping forced modeset for %s\n", id->ident);
+       return 1;
+}
 
-               return 1;
-       }
+/* The GPU hangs up on these systems if modeset is performed on LID open */
+static const struct dmi_system_id intel_no_modeset_on_lid[] = {
+       {
+               .callback = intel_no_modeset_on_lid_dmi_callback,
+               .ident = "Toshiba Tecra A11",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "TOSHIBA"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "TECRA A11"),
+               },
+       },
 
-       return 0;
-}
+       { }     /* terminating entry */
+};
 
 /*
  * Lid events. Note the use of 'modeset_on_lid':
@@ -615,12 +515,20 @@ static int intel_lid_notify(struct notifier_block *nb, unsigned long val,
        struct drm_device *dev = dev_priv->dev;
        struct drm_connector *connector = dev_priv->int_lvds_connector;
 
+       if (dev->switch_power_state != DRM_SWITCH_POWER_ON)
+               return NOTIFY_OK;
+
        /*
         * check and update the status of LVDS connector after receiving
         * the LID nofication event.
         */
        if (connector)
-               connector->status = connector->funcs->detect(connector);
+               connector->status = connector->funcs->detect(connector,
+                                                            false);
+
+       /* Don't force modeset on machines where it causes a GPU lockup */
+       if (dmi_check_system(intel_no_modeset_on_lid))
+               return NOTIFY_OK;
        if (!acpi_lid_open()) {
                dev_priv->modeset_on_lid = 1;
                return NOTIFY_OK;
@@ -650,6 +558,8 @@ static void intel_lvds_destroy(struct drm_connector *connector)
        struct drm_device *dev = connector->dev;
        struct drm_i915_private *dev_priv = dev->dev_private;
 
+       intel_panel_destroy_backlight(dev);
+
        if (dev_priv->lid_notifier.notifier_call)
                acpi_lid_notifier_unregister(&dev_priv->lid_notifier);
        drm_sysfs_connector_remove(connector);
@@ -661,24 +571,22 @@ static int intel_lvds_set_property(struct drm_connector *connector,
                                   struct drm_property *property,
                                   uint64_t value)
 {
+       struct intel_lvds *intel_lvds = intel_attached_lvds(connector);
        struct drm_device *dev = connector->dev;
 
-       if (property == dev->mode_config.scaling_mode_property &&
-                               connector->encoder) {
-               struct drm_crtc *crtc = connector->encoder->crtc;
-               struct drm_encoder *encoder = connector->encoder;
-               struct intel_encoder *intel_encoder = enc_to_intel_encoder(encoder);
-               struct intel_lvds_priv *lvds_priv = intel_encoder->dev_priv;
+       if (property == dev->mode_config.scaling_mode_property) {
+               struct drm_crtc *crtc = intel_lvds->base.base.crtc;
 
                if (value == DRM_MODE_SCALE_NONE) {
                        DRM_DEBUG_KMS("no scaling not supported\n");
-                       return 0;
+                       return -EINVAL;
                }
-               if (lvds_priv->fitting_mode == value) {
+
+               if (intel_lvds->fitting_mode == value) {
                        /* the LVDS scaling property is not changed */
                        return 0;
                }
-               lvds_priv->fitting_mode = value;
+               intel_lvds->fitting_mode = value;
                if (crtc && crtc->enabled) {
                        /*
                         * If the CRTC is enabled, the display will be changed
@@ -703,7 +611,7 @@ static const struct drm_encoder_helper_funcs intel_lvds_helper_funcs = {
 static const struct drm_connector_helper_funcs intel_lvds_connector_helper_funcs = {
        .get_modes = intel_lvds_get_modes,
        .mode_valid = intel_lvds_mode_valid,
-       .best_encoder = intel_attached_encoder,
+       .best_encoder = intel_best_encoder,
 };
 
 static const struct drm_connector_funcs intel_lvds_connector_funcs = {
@@ -714,19 +622,8 @@ static const struct drm_connector_funcs intel_lvds_connector_funcs = {
        .destroy = intel_lvds_destroy,
 };
 
-
-static void intel_lvds_enc_destroy(struct drm_encoder *encoder)
-{
-       struct intel_encoder *intel_encoder = enc_to_intel_encoder(encoder);
-
-       if (intel_encoder->ddc_bus)
-               intel_i2c_destroy(intel_encoder->ddc_bus);
-       drm_encoder_cleanup(encoder);
-       kfree(intel_encoder);
-}
-
 static const struct drm_encoder_funcs intel_lvds_enc_funcs = {
-       .destroy = intel_lvds_enc_destroy,
+       .destroy = intel_encoder_destroy,
 };
 
 static int __init intel_no_lvds_dmi_callback(const struct dmi_system_id *id)
@@ -771,6 +668,14 @@ static const struct dmi_system_id intel_no_lvds[] = {
        },
        {
                .callback = intel_no_lvds_dmi_callback,
+               .ident = "Dell OptiPlex FX170",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Dell Inc."),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "OptiPlex FX170"),
+               },
+       },
+       {
+               .callback = intel_no_lvds_dmi_callback,
                .ident = "AOpen Mini PC",
                .matches = {
                        DMI_MATCH(DMI_SYS_VENDOR, "AOpen"),
@@ -787,6 +692,22 @@ static const struct dmi_system_id intel_no_lvds[] = {
        },
        {
                .callback = intel_no_lvds_dmi_callback,
+               .ident = "AOpen i915GMm-HFS",
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "AOpen"),
+                       DMI_MATCH(DMI_BOARD_NAME, "i915GMm-HFS"),
+               },
+       },
+       {
+               .callback = intel_no_lvds_dmi_callback,
+                .ident = "AOpen i45GMx-I",
+                .matches = {
+                        DMI_MATCH(DMI_BOARD_VENDOR, "AOpen"),
+                        DMI_MATCH(DMI_BOARD_NAME, "i45GMx-I"),
+                },
+        },
+       {
+               .callback = intel_no_lvds_dmi_callback,
                .ident = "Aopen i945GTt-VFA",
                .matches = {
                        DMI_MATCH(DMI_PRODUCT_VERSION, "AO00001JW"),
@@ -800,6 +721,86 @@ static const struct dmi_system_id intel_no_lvds[] = {
                        DMI_MATCH(DMI_PRODUCT_NAME, "U800"),
                },
        },
+       {
+                .callback = intel_no_lvds_dmi_callback,
+                .ident = "Clientron E830",
+                .matches = {
+                        DMI_MATCH(DMI_SYS_VENDOR, "Clientron"),
+                        DMI_MATCH(DMI_PRODUCT_NAME, "E830"),
+                },
+        },
+        {
+               .callback = intel_no_lvds_dmi_callback,
+               .ident = "Asus EeeBox PC EB1007",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "ASUSTeK Computer INC."),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "EB1007"),
+               },
+       },
+       {
+               .callback = intel_no_lvds_dmi_callback,
+               .ident = "Asus AT5NM10T-I",
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ASUSTeK Computer INC."),
+                       DMI_MATCH(DMI_BOARD_NAME, "AT5NM10T-I"),
+               },
+       },
+       {
+               .callback = intel_no_lvds_dmi_callback,
+               .ident = "Hewlett-Packard HP t5740e Thin Client",
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "HP t5740e Thin Client"),
+               },
+       },
+       {
+               .callback = intel_no_lvds_dmi_callback,
+               .ident = "Hewlett-Packard t5745",
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "hp t5745"),
+               },
+       },
+       {
+               .callback = intel_no_lvds_dmi_callback,
+               .ident = "Hewlett-Packard st5747",
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "Hewlett-Packard"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "hp st5747"),
+               },
+       },
+       {
+               .callback = intel_no_lvds_dmi_callback,
+               .ident = "MSI Wind Box DC500",
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "MICRO-STAR INTERNATIONAL CO., LTD"),
+                       DMI_MATCH(DMI_BOARD_NAME, "MS-7469"),
+               },
+       },
+       {
+               .callback = intel_no_lvds_dmi_callback,
+               .ident = "ZOTAC ZBOXSD-ID12/ID13",
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "ZOTAC"),
+                       DMI_MATCH(DMI_BOARD_NAME, "ZBOXSD-ID12/ID13"),
+               },
+       },
+       {
+               .callback = intel_no_lvds_dmi_callback,
+               .ident = "Gigabyte GA-D525TUD",
+               .matches = {
+                       DMI_MATCH(DMI_BOARD_VENDOR, "Gigabyte Technology Co., Ltd."),
+                       DMI_MATCH(DMI_BOARD_NAME, "D525TUD"),
+               },
+       },
+       {
+               .callback = intel_no_lvds_dmi_callback,
+               .ident = "Supermicro X7SPA-H",
+               .matches = {
+                       DMI_MATCH(DMI_SYS_VENDOR, "Supermicro"),
+                       DMI_MATCH(DMI_PRODUCT_NAME, "X7SPA-H"),
+               },
+       },
 
        { }     /* terminating entry */
 };
@@ -812,16 +813,14 @@ static const struct dmi_system_id intel_no_lvds[] = {
  * Find the reduced downclock for LVDS in EDID.
  */
 static void intel_find_lvds_downclock(struct drm_device *dev,
-                               struct drm_connector *connector)
+                                     struct drm_display_mode *fixed_mode,
+                                     struct drm_connector *connector)
 {
        struct drm_i915_private *dev_priv = dev->dev_private;
-       struct drm_display_mode *scan, *panel_fixed_mode;
+       struct drm_display_mode *scan;
        int temp_downclock;
 
-       panel_fixed_mode = dev_priv->panel_fixed_mode;
-       temp_downclock = panel_fixed_mode->clock;
-
-       mutex_lock(&dev->mode_config.mutex);
+       temp_downclock = fixed_mode->clock;
        list_for_each_entry(scan, &connector->probed_modes, head) {
                /*
                 * If one mode has the same resolution with the fixed_panel
@@ -830,14 +829,14 @@ static void intel_find_lvds_downclock(struct drm_device *dev,
                 * case we can set the different FPx0/1 to dynamically select
                 * between low and high frequency.
                 */
-               if (scan->hdisplay == panel_fixed_mode->hdisplay &&
-                       scan->hsync_start == panel_fixed_mode->hsync_start &&
-                       scan->hsync_end == panel_fixed_mode->hsync_end &&
-                       scan->htotal == panel_fixed_mode->htotal &&
-                       scan->vdisplay == panel_fixed_mode->vdisplay &&
-                       scan->vsync_start == panel_fixed_mode->vsync_start &&
-                       scan->vsync_end == panel_fixed_mode->vsync_end &&
-                       scan->vtotal == panel_fixed_mode->vtotal) {
+               if (scan->hdisplay == fixed_mode->hdisplay &&
+                   scan->hsync_start == fixed_mode->hsync_start &&
+                   scan->hsync_end == fixed_mode->hsync_end &&
+                   scan->htotal == fixed_mode->htotal &&
+                   scan->vdisplay == fixed_mode->vdisplay &&
+                   scan->vsync_start == fixed_mode->vsync_start &&
+                   scan->vsync_end == fixed_mode->vsync_end &&
+                   scan->vtotal == fixed_mode->vtotal) {
                        if (scan->clock < temp_downclock) {
                                /*
                                 * The downclock is already found. But we
@@ -847,17 +846,14 @@ static void intel_find_lvds_downclock(struct drm_device *dev,
                        }
                }
        }
-       mutex_unlock(&dev->mode_config.mutex);
-       if (temp_downclock < panel_fixed_mode->clock &&
-           i915_lvds_downclock) {
+       if (temp_downclock < fixed_mode->clock && i915_lvds_downclock) {
                /* We found the downclock for LVDS. */
                dev_priv->lvds_downclock_avail = 1;
                dev_priv->lvds_downclock = temp_downclock;
                DRM_DEBUG_KMS("LVDS downclock is found in EDID. "
-                               "Normal clock %dKhz, downclock %dKhz\n",
-                               panel_fixed_mode->clock, temp_downclock);
+                             "Normal clock %dKhz, downclock %dKhz\n",
+                             fixed_mode->clock, temp_downclock);
        }
-       return;
 }
 
 /*
@@ -866,38 +862,60 @@ static void intel_find_lvds_downclock(struct drm_device *dev,
  * If it is present, return 1.
  * If it is not present, return false.
  * If no child dev is parsed from VBT, it assumes that the LVDS is present.
- * Note: The addin_offset should also be checked for LVDS panel.
- * Only when it is non-zero, it is assumed that it is present.
  */
-static int lvds_is_present_in_vbt(struct drm_device *dev)
+static bool lvds_is_present_in_vbt(struct drm_device *dev,
+                                  u8 *i2c_pin)
 {
        struct drm_i915_private *dev_priv = dev->dev_private;
-       struct child_device_config *p_child;
-       int i, ret;
+       int i;
 
        if (!dev_priv->child_dev_num)
-               return 1;
+               return true;
 
-       ret = 0;
        for (i = 0; i < dev_priv->child_dev_num; i++) {
-               p_child = dev_priv->child_dev + i;
-               /*
-                * If the device type is not LFP, continue.
-                * If the device type is 0x22, it is also regarded as LFP.
+               struct child_device_config *child = dev_priv->child_dev + i;
+
+               /* If the device type is not LFP, continue.
+                * We have to check both the new identifiers as well as the
+                * old for compatibility with some BIOSes.
                 */
-               if (p_child->device_type != DEVICE_TYPE_INT_LFP &&
-                       p_child->device_type != DEVICE_TYPE_LFP)
+               if (child->device_type != DEVICE_TYPE_INT_LFP &&
+                   child->device_type != DEVICE_TYPE_LFP)
                        continue;
 
-               /* The addin_offset should be checked. Only when it is
-                * non-zero, it is regarded as present.
+               if (child->i2c_pin)
+                   *i2c_pin = child->i2c_pin;
+
+               /* However, we cannot trust the BIOS writers to populate
+                * the VBT correctly.  Since LVDS requires additional
+                * information from AIM blocks, a non-zero addin offset is
+                * a good indicator that the LVDS is actually present.
                 */
-               if (p_child->addin_offset) {
-                       ret = 1;
-                       break;
-               }
+               if (child->addin_offset)
+                       return true;
+
+               /* But even then some BIOS writers perform some black magic
+                * and instantiate the device without reference to any
+                * additional data.  Trust that if the VBT was written into
+                * the OpRegion then they have validated the LVDS's existence.
+                */
+               if (dev_priv->opregion.vbt)
+                       return true;
        }
-       return ret;
+
+       return false;
+}
+
+static bool intel_lvds_supported(struct drm_device *dev)
+{
+       /* With the introduction of the PCH we gained a dedicated
+        * LVDS presence pin, use it. */
+       if (HAS_PCH_SPLIT(dev))
+               return true;
+
+       /* Otherwise LVDS was only attached to mobile products,
+        * except for the inglorious 830gm */
+       return IS_MOBILE(dev) && !IS_I830(dev);
 }
 
 /**
@@ -907,73 +925,81 @@ static int lvds_is_present_in_vbt(struct drm_device *dev)
  * Create the connector, register the LVDS DDC bus, and try to figure out what
  * modes we can display on the LVDS panel (if present).
  */
-void intel_lvds_init(struct drm_device *dev)
+bool intel_lvds_init(struct drm_device *dev)
 {
        struct drm_i915_private *dev_priv = dev->dev_private;
+       struct intel_lvds *intel_lvds;
        struct intel_encoder *intel_encoder;
        struct intel_connector *intel_connector;
        struct drm_connector *connector;
        struct drm_encoder *encoder;
        struct drm_display_mode *scan; /* *modes, *bios_mode; */
        struct drm_crtc *crtc;
-       struct intel_lvds_priv *lvds_priv;
        u32 lvds;
-       int pipe, gpio = GPIOC;
+       int pipe;
+       u8 pin;
+
+       if (!intel_lvds_supported(dev))
+               return false;
 
        /* Skip init on machines we know falsely report LVDS */
        if (dmi_check_system(intel_no_lvds))
-               return;
+               return false;
 
-       if (!lvds_is_present_in_vbt(dev)) {
+       pin = GMBUS_PORT_PANEL;
+       if (!lvds_is_present_in_vbt(dev, &pin)) {
                DRM_DEBUG_KMS("LVDS is not present in VBT\n");
-               return;
+               return false;
        }
 
        if (HAS_PCH_SPLIT(dev)) {
                if ((I915_READ(PCH_LVDS) & LVDS_DETECTED) == 0)
-                       return;
-               if (dev_priv->edp_support) {
+                       return false;
+               if (dev_priv->edp.support) {
                        DRM_DEBUG_KMS("disable LVDS for eDP support\n");
-                       return;
+                       return false;
                }
-               gpio = PCH_GPIOC;
        }
 
-       intel_encoder = kzalloc(sizeof(struct intel_encoder) +
-                               sizeof(struct intel_lvds_priv), GFP_KERNEL);
-       if (!intel_encoder) {
-               return;
+       intel_lvds = kzalloc(sizeof(struct intel_lvds), GFP_KERNEL);
+       if (!intel_lvds) {
+               return false;
        }
 
        intel_connector = kzalloc(sizeof(struct intel_connector), GFP_KERNEL);
        if (!intel_connector) {
-               kfree(intel_encoder);
-               return;
+               kfree(intel_lvds);
+               return false;
        }
 
+       if (!HAS_PCH_SPLIT(dev)) {
+               intel_lvds->pfit_control = I915_READ(PFIT_CONTROL);
+       }
+
+       intel_encoder = &intel_lvds->base;
+       encoder = &intel_encoder->base;
        connector = &intel_connector->base;
-       encoder = &intel_encoder->enc;
        drm_connector_init(dev, &intel_connector->base, &intel_lvds_connector_funcs,
                           DRM_MODE_CONNECTOR_LVDS);
 
-       drm_encoder_init(dev, &intel_encoder->enc, &intel_lvds_enc_funcs,
+       drm_encoder_init(dev, &intel_encoder->base, &intel_lvds_enc_funcs,
                         DRM_MODE_ENCODER_LVDS);
 
-       drm_mode_connector_attach_encoder(&intel_connector->base, &intel_encoder->enc);
+       intel_connector_attach_encoder(intel_connector, intel_encoder);
        intel_encoder->type = INTEL_OUTPUT_LVDS;
 
        intel_encoder->clone_mask = (1 << INTEL_LVDS_CLONE_BIT);
-       intel_encoder->crtc_mask = (1 << 1);
-       if (IS_I965G(dev))
-               intel_encoder->crtc_mask |= (1 << 0);
+       if (HAS_PCH_SPLIT(dev))
+               intel_encoder->crtc_mask = (1 << 0) | (1 << 1) | (1 << 2);
+       else
+               intel_encoder->crtc_mask = (1 << 1);
+
        drm_encoder_helper_add(encoder, &intel_lvds_helper_funcs);
        drm_connector_helper_add(connector, &intel_lvds_connector_helper_funcs);
        connector->display_info.subpixel_order = SubPixelHorizontalRGB;
        connector->interlace_allowed = false;
        connector->doublescan_allowed = false;
 
-       lvds_priv = (struct intel_lvds_priv *)(intel_encoder + 1);
-       intel_encoder->dev_priv = lvds_priv;
        /* create the scaling mode property */
        drm_mode_create_scaling_mode_property(dev);
        /*
@@ -982,8 +1008,8 @@ void intel_lvds_init(struct drm_device *dev)
 
        drm_connector_attach_property(&intel_connector->base,
                                      dev->mode_config.scaling_mode_property,
-                                     DRM_MODE_SCALE_FULLSCREEN);
-       lvds_priv->fitting_mode = DRM_MODE_SCALE_FULLSCREEN;
+                                     DRM_MODE_SCALE_ASPECT);
+       intel_lvds->fitting_mode = DRM_MODE_SCALE_ASPECT;
        /*
         * LVDS discovery:
         * 1) check for EDID on DDC
@@ -994,43 +1020,50 @@ void intel_lvds_init(struct drm_device *dev)
         *    if closed, act like it's not there for now
         */
 
-       /* Set up the DDC bus. */
-       intel_encoder->ddc_bus = intel_i2c_create(dev, gpio, "LVDSDDC_C");
-       if (!intel_encoder->ddc_bus) {
-               dev_printk(KERN_ERR, &dev->pdev->dev, "DDC bus registration "
-                          "failed.\n");
-               goto failed;
-       }
-
        /*
         * Attempt to get the fixed panel mode from DDC.  Assume that the
         * preferred mode is the right one.
         */
-       dev_priv->lvds_edid_good = true;
-
-       if (!intel_ddc_get_modes(connector, intel_encoder->ddc_bus))
-               dev_priv->lvds_edid_good = false;
+       intel_lvds->edid = drm_get_edid(connector,
+                                       &dev_priv->gmbus[pin].adapter);
+       if (intel_lvds->edid) {
+               if (drm_add_edid_modes(connector,
+                                      intel_lvds->edid)) {
+                       drm_mode_connector_update_edid_property(connector,
+                                                               intel_lvds->edid);
+               } else {
+                       kfree(intel_lvds->edid);
+                       intel_lvds->edid = NULL;
+               }
+       }
+       if (!intel_lvds->edid) {
+               /* Didn't get an EDID, so
+                * Set wide sync ranges so we get all modes
+                * handed to valid_mode for checking
+                */
+               connector->display_info.min_vfreq = 0;
+               connector->display_info.max_vfreq = 200;
+               connector->display_info.min_hfreq = 0;
+               connector->display_info.max_hfreq = 200;
+       }
 
        list_for_each_entry(scan, &connector->probed_modes, head) {
-               mutex_lock(&dev->mode_config.mutex);
                if (scan->type & DRM_MODE_TYPE_PREFERRED) {
-                       dev_priv->panel_fixed_mode =
+                       intel_lvds->fixed_mode =
                                drm_mode_duplicate(dev, scan);
-                       mutex_unlock(&dev->mode_config.mutex);
-                       intel_find_lvds_downclock(dev, connector);
+                       intel_find_lvds_downclock(dev,
+                                                 intel_lvds->fixed_mode,
+                                                 connector);
                        goto out;
                }
-               mutex_unlock(&dev->mode_config.mutex);
        }
 
        /* Failed to get EDID, what about VBT? */
        if (dev_priv->lfp_lvds_vbt_mode) {
-               mutex_lock(&dev->mode_config.mutex);
-               dev_priv->panel_fixed_mode =
+               intel_lvds->fixed_mode =
                        drm_mode_duplicate(dev, dev_priv->lfp_lvds_vbt_mode);
-               mutex_unlock(&dev->mode_config.mutex);
-               if (dev_priv->panel_fixed_mode) {
-                       dev_priv->panel_fixed_mode->type |=
+               if (intel_lvds->fixed_mode) {
+                       intel_lvds->fixed_mode->type |=
                                DRM_MODE_TYPE_PREFERRED;
                        goto out;
                }
@@ -1048,32 +1081,53 @@ void intel_lvds_init(struct drm_device *dev)
 
        lvds = I915_READ(LVDS);
        pipe = (lvds & LVDS_PIPEB_SELECT) ? 1 : 0;
-       crtc = intel_get_crtc_from_pipe(dev, pipe);
+       crtc = intel_get_crtc_for_pipe(dev, pipe);
 
        if (crtc && (lvds & LVDS_PORT_EN)) {
-               dev_priv->panel_fixed_mode = intel_crtc_mode_get(dev, crtc);
-               if (dev_priv->panel_fixed_mode) {
-                       dev_priv->panel_fixed_mode->type |=
+               intel_lvds->fixed_mode = intel_crtc_mode_get(dev, crtc);
+               if (intel_lvds->fixed_mode) {
+                       intel_lvds->fixed_mode->type |=
                                DRM_MODE_TYPE_PREFERRED;
                        goto out;
                }
        }
 
        /* If we still don't have a mode after all that, give up. */
-       if (!dev_priv->panel_fixed_mode)
+       if (!intel_lvds->fixed_mode)
                goto failed;
 
 out:
        if (HAS_PCH_SPLIT(dev)) {
                u32 pwm;
-               /* make sure PWM is enabled */
+
+               pipe = (I915_READ(PCH_LVDS) & LVDS_PIPEB_SELECT) ? 1 : 0;
+
+               /* make sure PWM is enabled and locked to the LVDS pipe */
                pwm = I915_READ(BLC_PWM_CPU_CTL2);
-               pwm |= (PWM_ENABLE | PWM_PIPE_B);
-               I915_WRITE(BLC_PWM_CPU_CTL2, pwm);
+               if (pipe == 0 && (pwm & PWM_PIPE_B))
+                       I915_WRITE(BLC_PWM_CPU_CTL2, pwm & ~PWM_ENABLE);
+               if (pipe)
+                       pwm |= PWM_PIPE_B;
+               else
+                       pwm &= ~PWM_PIPE_B;
+               I915_WRITE(BLC_PWM_CPU_CTL2, pwm | PWM_ENABLE);
 
                pwm = I915_READ(BLC_PWM_PCH_CTL1);
                pwm |= PWM_PCH_ENABLE;
                I915_WRITE(BLC_PWM_PCH_CTL1, pwm);
+               /*
+                * Unlock registers and just
+                * leave them unlocked
+                */
+               I915_WRITE(PCH_PP_CONTROL,
+                          I915_READ(PCH_PP_CONTROL) | PANEL_UNLOCK_REGS);
+       } else {
+               /*
+                * Unlock registers and just
+                * leave them unlocked
+                */
+               I915_WRITE(PP_CONTROL,
+                          I915_READ(PP_CONTROL) | PANEL_UNLOCK_REGS);
        }
        dev_priv->lid_notifier.notifier_call = intel_lid_notify;
        if (acpi_lid_notifier_register(&dev_priv->lid_notifier)) {
@@ -1083,14 +1137,16 @@ out:
        /* keep the LVDS connector */
        dev_priv->int_lvds_connector = connector;
        drm_sysfs_connector_add(connector);
-       return;
+
+       intel_panel_setup_backlight(dev);
+
+       return true;
 
 failed:
        DRM_DEBUG_KMS("No LVDS modes found, disabling.\n");
-       if (intel_encoder->ddc_bus)
-               intel_i2c_destroy(intel_encoder->ddc_bus);
        drm_connector_cleanup(connector);
        drm_encoder_cleanup(encoder);
-       kfree(intel_encoder);
+       kfree(intel_lvds);
        kfree(intel_connector);
+       return false;
 }