diff mbox

drm/i915: add dynamic clock frequency control

Message ID 20090727115549.0dec3418@jbarnes-g45 (mailing list archive)
State Accepted
Headers show

Commit Message

Jesse Barnes July 27, 2009, 6:55 p.m. UTC
Arg, here's one w/o the ACPI bit.

From e9da891a3e8753c4ad49fad29a334499d5bb31a6 Mon Sep 17 00:00:00 2001
From: Matthew Garrett <mjg59@srcf.ucam.org>
Date: Mon, 27 Jul 2009 11:39:42 -0700
Subject: [PATCH] drm/i915: add dynamic clock frequency control

There are several sources of unnecessary power consumption on Intel
graphics systems. The first is the LVDS clock. TFTs don't suffer from
persistence issues like CRTs, and so we can reduce the LVDS refresh rate
when the screen is idle. It will be automatically upclocked when
userspace triggers graphical activity. Beyond that, we can enable memory
self refresh. This allows the memory to go into a lower power state when
the graphics are idle. Finally, we can drop some clocks on the gpu
itself. All of these things can be reenabled between frames when GPU
activity is triggered, and so there should be no user visible graphical
changes.

Signed-off-by: Jesse Barnes <jesse.barnes@intel.com>
Signed-off-by: Matthew Garrett <mjg@redhat.com>
---
 drivers/gpu/drm/i915/i915_drv.c      |    3 +
 drivers/gpu/drm/i915/i915_drv.h      |   12 +-
 drivers/gpu/drm/i915/i915_gem.c      |    8 +
 drivers/gpu/drm/i915/i915_reg.h      |  137 +++++++++-
 drivers/gpu/drm/i915/i915_suspend.c  |    4 +-
 drivers/gpu/drm/i915/intel_display.c |  528 +++++++++++++++++++++++++++++++--
 drivers/gpu/drm/i915/intel_drv.h     |    4 +
 drivers/gpu/drm/i915/intel_i2c.c     |    8 +-
 9 files changed, 666 insertions(+), 45 deletions(-)

Comments

Zhenyu Wang July 28, 2009, 5:22 a.m. UTC | #1
On 2009.07.27 11:55:49 -0700, Jesse Barnes wrote:
> Arg, here's one w/o the ACPI bit.
> 
> >From e9da891a3e8753c4ad49fad29a334499d5bb31a6 Mon Sep 17 00:00:00 2001
> From: Matthew Garrett <mjg59@srcf.ucam.org>
> Date: Mon, 27 Jul 2009 11:39:42 -0700
> Subject: [PATCH] drm/i915: add dynamic clock frequency control
> 
> There are several sources of unnecessary power consumption on Intel
> graphics systems. The first is the LVDS clock. TFTs don't suffer from
> persistence issues like CRTs, and so we can reduce the LVDS refresh rate
> when the screen is idle. It will be automatically upclocked when
> userspace triggers graphical activity. Beyond that, we can enable memory
> self refresh. This allows the memory to go into a lower power state when
> the graphics are idle. Finally, we can drop some clocks on the gpu
> itself. All of these things can be reenabled between frames when GPU
> activity is triggered, and so there should be no user visible graphical
> changes.
> 

Jesse, when I was testing this on IGDNG desktop chipset, I encounter 
freeze in idle with display's DPMS all off. With 'powersave=0' it didn't
meet the hang. I think some of those regs this one touches might not be
valid on it, maybe better to exclude it for now.
Jesse Barnes July 28, 2009, 4:13 p.m. UTC | #2
On Tue, 28 Jul 2009 13:22:35 +0800
Zhenyu Wang <zhenyuw@linux.intel.com> wrote:

> On 2009.07.27 11:55:49 -0700, Jesse Barnes wrote:
> > Arg, here's one w/o the ACPI bit.
> > 
> > >From e9da891a3e8753c4ad49fad29a334499d5bb31a6 Mon Sep 17 00:00:00
> > >2001
> > From: Matthew Garrett <mjg59@srcf.ucam.org>
> > Date: Mon, 27 Jul 2009 11:39:42 -0700
> > Subject: [PATCH] drm/i915: add dynamic clock frequency control
> > 
> > There are several sources of unnecessary power consumption on Intel
> > graphics systems. The first is the LVDS clock. TFTs don't suffer
> > from persistence issues like CRTs, and so we can reduce the LVDS
> > refresh rate when the screen is idle. It will be automatically
> > upclocked when userspace triggers graphical activity. Beyond that,
> > we can enable memory self refresh. This allows the memory to go
> > into a lower power state when the graphics are idle. Finally, we
> > can drop some clocks on the gpu itself. All of these things can be
> > reenabled between frames when GPU activity is triggered, and so
> > there should be no user visible graphical changes.
> > 
> 
> Jesse, when I was testing this on IGDNG desktop chipset, I encounter 
> freeze in idle with display's DPMS all off. With 'powersave=0' it
> didn't meet the hang. I think some of those regs this one touches
> might not be valid on it, maybe better to exclude it for now.

Oh, thanks for testing.  Should be easy enough to exclude IGDNG...
diff mbox

Patch

diff --git a/drivers/gpu/drm/i915/i915_drv.c b/drivers/gpu/drm/i915/i915_drv.c
index fc4b68a..ea388fb 100644
--- a/drivers/gpu/drm/i915/i915_drv.c
+++ b/drivers/gpu/drm/i915/i915_drv.c
@@ -43,6 +43,9 @@  module_param_named(modeset, i915_modeset, int, 0400);
 unsigned int i915_fbpercrtc = 0;
 module_param_named(fbpercrtc, i915_fbpercrtc, int, 0400);
 
+unsigned int i915_powersave = 1;
+module_param_named(powersave, i915_powersave, int, 0400);
+
 static struct drm_driver driver;
 
 static struct pci_device_id pciidlist[] = {
diff --git a/drivers/gpu/drm/i915/i915_drv.h b/drivers/gpu/drm/i915/i915_drv.h
index 11d0e28..46a6d25 100644
--- a/drivers/gpu/drm/i915/i915_drv.h
+++ b/drivers/gpu/drm/i915/i915_drv.h
@@ -310,7 +310,7 @@  typedef struct drm_i915_private {
 	u32 saveIMR;
 	u32 saveCACHE_MODE_0;
 	u32 saveD_STATE;
-	u32 saveCG_2D_DIS;
+	u32 saveDSPCLK_GATE_D;
 	u32 saveMI_ARB_STATE;
 	u32 saveSWF0[16];
 	u32 saveSWF1[16];
@@ -439,6 +439,12 @@  typedef struct drm_i915_private {
 		struct drm_i915_gem_phys_object *phys_objs[I915_MAX_PHYS_OBJECT];
 	} mm;
 	struct sdvo_device_mapping sdvo_mappings[2];
+
+	/* Reclocking support */
+	struct work_struct idle_work;
+	struct timer_list idle_timer;
+	bool busy;
+	u16 orig_clock;
 } drm_i915_private_t;
 
 /** driver private structure attached to each drm_gem_object */
@@ -568,6 +574,7 @@  enum intel_chip_family {
 extern struct drm_ioctl_desc i915_ioctls[];
 extern int i915_max_ioctl;
 extern unsigned int i915_fbpercrtc;
+extern unsigned int i915_powersave;
 
 extern int i915_master_create(struct drm_device *dev, struct drm_master *master);
 extern void i915_master_destroy(struct drm_device *dev, struct drm_master *master);
@@ -895,6 +902,9 @@  extern int i915_wait_ring(struct drm_device * dev, int n, const char *caller);
 /* dsparb controlled by hw only */
 #define DSPARB_HWCONTROL(dev) (IS_G4X(dev) || IS_IGDNG(dev))
 
+#define HAS_FW_BLC(dev) (IS_I9XX(dev) || IS_G4X(dev) || IS_IGDNG(dev))
+#define HAS_PIPE_CXSR(dev) (IS_G4X(dev) || IS_IGDNG(dev))
+
 #define PRIMARY_RINGBUFFER_SIZE         (128*1024)
 
 #endif
diff --git a/drivers/gpu/drm/i915/i915_gem.c b/drivers/gpu/drm/i915/i915_gem.c
index 5bf4203..ce2d9b8 100644
--- a/drivers/gpu/drm/i915/i915_gem.c
+++ b/drivers/gpu/drm/i915/i915_gem.c
@@ -29,6 +29,7 @@ 
 #include "drm.h"
 #include "i915_drm.h"
 #include "i915_drv.h"
+#include "intel_drv.h"
 #include <linux/swap.h>
 #include <linux/pci.h>
 
@@ -980,6 +981,7 @@  i915_gem_set_domain_ioctl(struct drm_device *dev, void *data,
 {
 	struct drm_i915_gem_set_domain *args = data;
 	struct drm_gem_object *obj;
+	struct drm_i915_gem_object *obj_priv;
 	uint32_t read_domains = args->read_domains;
 	uint32_t write_domain = args->write_domain;
 	int ret;
@@ -1003,8 +1005,12 @@  i915_gem_set_domain_ioctl(struct drm_device *dev, void *data,
 	obj = drm_gem_object_lookup(dev, file_priv, args->handle);
 	if (obj == NULL)
 		return -EBADF;
+	obj_priv = obj->driver_private;
 
 	mutex_lock(&dev->struct_mutex);
+
+	intel_mark_busy(dev, obj);
+
 #if WATCH_BUF
 	DRM_INFO("set_domain_ioctl %p(%zd), %08x %08x\n",
 		 obj, obj->size, read_domains, write_domain);
@@ -2761,6 +2767,8 @@  i915_gem_object_set_to_gpu_domain(struct drm_gem_object *obj)
 	BUG_ON(obj->pending_read_domains & I915_GEM_DOMAIN_CPU);
 	BUG_ON(obj->pending_write_domain == I915_GEM_DOMAIN_CPU);
 
+	intel_mark_busy(dev, obj);
+
 #if WATCH_BUF
 	DRM_INFO("%s: object %p read %08x -> %08x write %08x -> %08x\n",
 		 __func__, obj,
diff --git a/drivers/gpu/drm/i915/i915_reg.h b/drivers/gpu/drm/i915/i915_reg.h
index 897a116..77b1b26 100644
--- a/drivers/gpu/drm/i915/i915_reg.h
+++ b/drivers/gpu/drm/i915/i915_reg.h
@@ -55,7 +55,7 @@ 
 /* PCI config space */
 
 #define HPLLCC	0xc0 /* 855 only */
-#define   GC_CLOCK_CONTROL_MASK		(3 << 0)
+#define   GC_CLOCK_CONTROL_MASK		(0xf << 0)
 #define   GC_CLOCK_133_200		(0 << 0)
 #define   GC_CLOCK_100_200		(1 << 0)
 #define   GC_CLOCK_100_133		(2 << 0)
@@ -65,6 +65,25 @@ 
 #define   GC_DISPLAY_CLOCK_190_200_MHZ	(0 << 4)
 #define   GC_DISPLAY_CLOCK_333_MHZ	(4 << 4)
 #define   GC_DISPLAY_CLOCK_MASK		(7 << 4)
+#define   GM45_GC_RENDER_CLOCK_MASK	(0xf << 0)
+#define   GM45_GC_RENDER_CLOCK_266_MHZ	(8 << 0)
+#define   GM45_GC_RENDER_CLOCK_320_MHZ	(9 << 0)
+#define   GM45_GC_RENDER_CLOCK_400_MHZ	(0xb << 0)
+#define   GM45_GC_RENDER_CLOCK_533_MHZ	(0xc << 0)
+#define   I965_GC_RENDER_CLOCK_MASK	(0xf << 0)
+#define   I965_GC_RENDER_CLOCK_267_MHZ	(2 << 0)
+#define   I965_GC_RENDER_CLOCK_333_MHZ	(3 << 0)
+#define   I965_GC_RENDER_CLOCK_444_MHZ	(4 << 0)
+#define   I965_GC_RENDER_CLOCK_533_MHZ	(5 << 0)
+#define   I945_GC_RENDER_CLOCK_MASK	(7 << 0)
+#define   I945_GC_RENDER_CLOCK_166_MHZ	(0 << 0)
+#define   I945_GC_RENDER_CLOCK_200_MHZ	(1 << 0)
+#define   I945_GC_RENDER_CLOCK_250_MHZ	(3 << 0)
+#define   I945_GC_RENDER_CLOCK_400_MHZ	(5 << 0)
+#define   I915_GC_RENDER_CLOCK_MASK	(7 << 0)
+#define   I915_GC_RENDER_CLOCK_166_MHZ	(0 << 0)
+#define   I915_GC_RENDER_CLOCK_200_MHZ	(1 << 0)
+#define   I915_GC_RENDER_CLOCK_333_MHZ	(4 << 0)
 #define LBB	0xf4
 
 /* VGA stuff */
@@ -553,9 +572,118 @@ 
 #define   DPLLA_TEST_M_BYPASS		(1 << 2)
 #define   DPLLA_INPUT_BUFFER_ENABLE	(1 << 0)
 #define D_STATE		0x6104
-#define CG_2D_DIS	0x6200
-#define DPCUNIT_CLOCK_GATE_DISABLE	(1 << 24)
-#define CG_3D_DIS	0x6204
+#define  DSTATE_PLL_D3_OFF			(1<<3)
+#define  DSTATE_GFX_CLOCK_GATING		(1<<1)
+#define  DSTATE_DOT_CLOCK_GATING		(1<<0)
+#define DSPCLK_GATE_D		0x6200
+# define DPUNIT_B_CLOCK_GATE_DISABLE		(1 << 30) /* 965 */
+# define VSUNIT_CLOCK_GATE_DISABLE		(1 << 29) /* 965 */
+# define VRHUNIT_CLOCK_GATE_DISABLE		(1 << 28) /* 965 */
+# define VRDUNIT_CLOCK_GATE_DISABLE		(1 << 27) /* 965 */
+# define AUDUNIT_CLOCK_GATE_DISABLE		(1 << 26) /* 965 */
+# define DPUNIT_A_CLOCK_GATE_DISABLE		(1 << 25) /* 965 */
+# define DPCUNIT_CLOCK_GATE_DISABLE		(1 << 24) /* 965 */
+# define TVRUNIT_CLOCK_GATE_DISABLE		(1 << 23) /* 915-945 */
+# define TVCUNIT_CLOCK_GATE_DISABLE		(1 << 22) /* 915-945 */
+# define TVFUNIT_CLOCK_GATE_DISABLE		(1 << 21) /* 915-945 */
+# define TVEUNIT_CLOCK_GATE_DISABLE		(1 << 20) /* 915-945 */
+# define DVSUNIT_CLOCK_GATE_DISABLE		(1 << 19) /* 915-945 */
+# define DSSUNIT_CLOCK_GATE_DISABLE		(1 << 18) /* 915-945 */
+# define DDBUNIT_CLOCK_GATE_DISABLE		(1 << 17) /* 915-945 */
+# define DPRUNIT_CLOCK_GATE_DISABLE		(1 << 16) /* 915-945 */
+# define DPFUNIT_CLOCK_GATE_DISABLE		(1 << 15) /* 915-945 */
+# define DPBMUNIT_CLOCK_GATE_DISABLE		(1 << 14) /* 915-945 */
+# define DPLSUNIT_CLOCK_GATE_DISABLE		(1 << 13) /* 915-945 */
+# define DPLUNIT_CLOCK_GATE_DISABLE		(1 << 12) /* 915-945 */
+# define DPOUNIT_CLOCK_GATE_DISABLE		(1 << 11)
+# define DPBUNIT_CLOCK_GATE_DISABLE		(1 << 10)
+# define DCUNIT_CLOCK_GATE_DISABLE		(1 << 9)
+# define DPUNIT_CLOCK_GATE_DISABLE		(1 << 8)
+# define VRUNIT_CLOCK_GATE_DISABLE		(1 << 7) /* 915+: reserved */
+# define OVHUNIT_CLOCK_GATE_DISABLE		(1 << 6) /* 830-865 */
+# define DPIOUNIT_CLOCK_GATE_DISABLE		(1 << 6) /* 915-945 */
+# define OVFUNIT_CLOCK_GATE_DISABLE		(1 << 5)
+# define OVBUNIT_CLOCK_GATE_DISABLE		(1 << 4)
+/**
+ * This bit must be set on the 830 to prevent hangs when turning off the
+ * overlay scaler.
+ */
+# define OVRUNIT_CLOCK_GATE_DISABLE		(1 << 3)
+# define OVCUNIT_CLOCK_GATE_DISABLE		(1 << 2)
+# define OVUUNIT_CLOCK_GATE_DISABLE		(1 << 1)
+# define ZVUNIT_CLOCK_GATE_DISABLE		(1 << 0) /* 830 */
+# define OVLUNIT_CLOCK_GATE_DISABLE		(1 << 0) /* 845,865 */
+
+#define RENCLK_GATE_D1		0x6204
+# define BLITTER_CLOCK_GATE_DISABLE		(1 << 13) /* 945GM only */
+# define MPEG_CLOCK_GATE_DISABLE		(1 << 12) /* 945GM only */
+# define PC_FE_CLOCK_GATE_DISABLE		(1 << 11)
+# define PC_BE_CLOCK_GATE_DISABLE		(1 << 10)
+# define WINDOWER_CLOCK_GATE_DISABLE		(1 << 9)
+# define INTERPOLATOR_CLOCK_GATE_DISABLE	(1 << 8)
+# define COLOR_CALCULATOR_CLOCK_GATE_DISABLE	(1 << 7)
+# define MOTION_COMP_CLOCK_GATE_DISABLE		(1 << 6)
+# define MAG_CLOCK_GATE_DISABLE			(1 << 5)
+/** This bit must be unset on 855,865 */
+# define MECI_CLOCK_GATE_DISABLE		(1 << 4)
+# define DCMP_CLOCK_GATE_DISABLE		(1 << 3)
+# define MEC_CLOCK_GATE_DISABLE			(1 << 2)
+# define MECO_CLOCK_GATE_DISABLE		(1 << 1)
+/** This bit must be set on 855,865. */
+# define SV_CLOCK_GATE_DISABLE			(1 << 0)
+# define I915_MPEG_CLOCK_GATE_DISABLE		(1 << 16)
+# define I915_VLD_IP_PR_CLOCK_GATE_DISABLE	(1 << 15)
+# define I915_MOTION_COMP_CLOCK_GATE_DISABLE	(1 << 14)
+# define I915_BD_BF_CLOCK_GATE_DISABLE		(1 << 13)
+# define I915_SF_SE_CLOCK_GATE_DISABLE		(1 << 12)
+# define I915_WM_CLOCK_GATE_DISABLE		(1 << 11)
+# define I915_IZ_CLOCK_GATE_DISABLE		(1 << 10)
+# define I915_PI_CLOCK_GATE_DISABLE		(1 << 9)
+# define I915_DI_CLOCK_GATE_DISABLE		(1 << 8)
+# define I915_SH_SV_CLOCK_GATE_DISABLE		(1 << 7)
+# define I915_PL_DG_QC_FT_CLOCK_GATE_DISABLE	(1 << 6)
+# define I915_SC_CLOCK_GATE_DISABLE		(1 << 5)
+# define I915_FL_CLOCK_GATE_DISABLE		(1 << 4)
+# define I915_DM_CLOCK_GATE_DISABLE		(1 << 3)
+# define I915_PS_CLOCK_GATE_DISABLE		(1 << 2)
+# define I915_CC_CLOCK_GATE_DISABLE		(1 << 1)
+# define I915_BY_CLOCK_GATE_DISABLE		(1 << 0)
+
+# define I965_RCZ_CLOCK_GATE_DISABLE		(1 << 30)
+/** This bit must always be set on 965G/965GM */
+# define I965_RCC_CLOCK_GATE_DISABLE		(1 << 29)
+# define I965_RCPB_CLOCK_GATE_DISABLE		(1 << 28)
+# define I965_DAP_CLOCK_GATE_DISABLE		(1 << 27)
+# define I965_ROC_CLOCK_GATE_DISABLE		(1 << 26)
+# define I965_GW_CLOCK_GATE_DISABLE		(1 << 25)
+# define I965_TD_CLOCK_GATE_DISABLE		(1 << 24)
+/** This bit must always be set on 965G */
+# define I965_ISC_CLOCK_GATE_DISABLE		(1 << 23)
+# define I965_IC_CLOCK_GATE_DISABLE		(1 << 22)
+# define I965_EU_CLOCK_GATE_DISABLE		(1 << 21)
+# define I965_IF_CLOCK_GATE_DISABLE		(1 << 20)
+# define I965_TC_CLOCK_GATE_DISABLE		(1 << 19)
+# define I965_SO_CLOCK_GATE_DISABLE		(1 << 17)
+# define I965_FBC_CLOCK_GATE_DISABLE		(1 << 16)
+# define I965_MARI_CLOCK_GATE_DISABLE		(1 << 15)
+# define I965_MASF_CLOCK_GATE_DISABLE		(1 << 14)
+# define I965_MAWB_CLOCK_GATE_DISABLE		(1 << 13)
+# define I965_EM_CLOCK_GATE_DISABLE		(1 << 12)
+# define I965_UC_CLOCK_GATE_DISABLE		(1 << 11)
+# define I965_SI_CLOCK_GATE_DISABLE		(1 << 6)
+# define I965_MT_CLOCK_GATE_DISABLE		(1 << 5)
+# define I965_PL_CLOCK_GATE_DISABLE		(1 << 4)
+# define I965_DG_CLOCK_GATE_DISABLE		(1 << 3)
+# define I965_QC_CLOCK_GATE_DISABLE		(1 << 2)
+# define I965_FT_CLOCK_GATE_DISABLE		(1 << 1)
+# define I965_DM_CLOCK_GATE_DISABLE		(1 << 0)
+
+#define RENCLK_GATE_D2		0x6208
+#define VF_UNIT_CLOCK_GATE_DISABLE		(1 << 9)
+#define GS_UNIT_CLOCK_GATE_DISABLE		(1 << 7)
+#define CL_UNIT_CLOCK_GATE_DISABLE		(1 << 6)
+#define RAMCLK_GATE_D		0x6210		/* CRL only */
+#define DEUC			0x6214          /* CRL only */
 
 /*
  * Palette regs
@@ -1569,6 +1697,7 @@ 
 #define   PIPECONF_PROGRESSIVE	(0 << 21)
 #define   PIPECONF_INTERLACE_W_FIELD_INDICATION	(6 << 21)
 #define   PIPECONF_INTERLACE_FIELD_0_ONLY		(7 << 21)
+#define   PIPECONF_CXSR_DOWNCLOCK	(1<<16)
 #define PIPEASTAT		0x70024
 #define   PIPE_FIFO_UNDERRUN_STATUS		(1UL<<31)
 #define   PIPE_CRC_ERROR_ENABLE			(1UL<<29)
diff --git a/drivers/gpu/drm/i915/i915_suspend.c b/drivers/gpu/drm/i915/i915_suspend.c
index 9e1d16e..8dfca81 100644
--- a/drivers/gpu/drm/i915/i915_suspend.c
+++ b/drivers/gpu/drm/i915/i915_suspend.c
@@ -461,7 +461,7 @@  int i915_save_state(struct drm_device *dev)
 
 	/* Clock gating state */
 	dev_priv->saveD_STATE = I915_READ(D_STATE);
-	dev_priv->saveCG_2D_DIS = I915_READ(CG_2D_DIS);
+	dev_priv->saveDSPCLK_GATE_D = I915_READ(DSPCLK_GATE_D);
 
 	/* Cache mode state */
 	dev_priv->saveCACHE_MODE_0 = I915_READ(CACHE_MODE_0);
@@ -588,7 +588,7 @@  int i915_restore_state(struct drm_device *dev)
 
 	/* Clock gating state */
 	I915_WRITE (D_STATE, dev_priv->saveD_STATE);
-	I915_WRITE (CG_2D_DIS, dev_priv->saveCG_2D_DIS);
+	I915_WRITE (DSPCLK_GATE_D, dev_priv->saveDSPCLK_GATE_D);
 
 	/* Cache mode state */
 	I915_WRITE (CACHE_MODE_0, dev_priv->saveCACHE_MODE_0 | 0xffff0000);
diff --git a/drivers/gpu/drm/i915/intel_display.c b/drivers/gpu/drm/i915/intel_display.c
index 7abbb78..b69e9e1 100644
--- a/drivers/gpu/drm/i915/intel_display.c
+++ b/drivers/gpu/drm/i915/intel_display.c
@@ -38,6 +38,7 @@ 
 
 bool intel_pipe_has_type (struct drm_crtc *crtc, int type);
 static void intel_update_watermarks(struct drm_device *dev);
+static void intel_increase_pllclock(struct drm_crtc *crtc, bool schedule);
 
 typedef struct {
     /* given values */
@@ -67,6 +68,8 @@  struct intel_limit {
     intel_p2_t	    p2;
     bool (* find_pll)(const intel_limit_t *, struct drm_crtc *,
 		      int, int, intel_clock_t *);
+    bool (* find_reduced_pll)(const intel_limit_t *, struct drm_crtc *,
+			      int, int, intel_clock_t *);
 };
 
 #define I8XX_DOT_MIN		  25000
@@ -192,7 +195,7 @@  struct intel_limit {
 #define G4X_P2_SINGLE_CHANNEL_LVDS_LIMIT          0
 
 /*The parameter is for DUAL_CHANNEL_LVDS on G4x platform*/
-#define G4X_DOT_DUAL_CHANNEL_LVDS_MIN           80000
+#define G4X_DOT_DUAL_CHANNEL_LVDS_MIN           20000
 #define G4X_DOT_DUAL_CHANNEL_LVDS_MAX           224000
 #define G4X_N_DUAL_CHANNEL_LVDS_MIN             1
 #define G4X_N_DUAL_CHANNEL_LVDS_MAX             3
@@ -261,6 +264,9 @@  static bool
 intel_find_best_PLL(const intel_limit_t *limit, struct drm_crtc *crtc,
 		    int target, int refclk, intel_clock_t *best_clock);
 static bool
+intel_find_best_reduced_PLL(const intel_limit_t *limit, struct drm_crtc *crtc,
+			    int target, int refclk, intel_clock_t *best_clock);
+static bool
 intel_g4x_find_best_PLL(const intel_limit_t *limit, struct drm_crtc *crtc,
 			int target, int refclk, intel_clock_t *best_clock);
 static bool
@@ -283,6 +289,7 @@  static const intel_limit_t intel_limits_i8xx_dvo = {
 	.p2  = { .dot_limit = I8XX_P2_SLOW_LIMIT,
 		 .p2_slow = I8XX_P2_SLOW,	.p2_fast = I8XX_P2_FAST },
 	.find_pll = intel_find_best_PLL,
+	.find_reduced_pll = intel_find_best_reduced_PLL,
 };
 
 static const intel_limit_t intel_limits_i8xx_lvds = {
@@ -297,6 +304,7 @@  static const intel_limit_t intel_limits_i8xx_lvds = {
 	.p2  = { .dot_limit = I8XX_P2_SLOW_LIMIT,
 		 .p2_slow = I8XX_P2_LVDS_SLOW,	.p2_fast = I8XX_P2_LVDS_FAST },
 	.find_pll = intel_find_best_PLL,
+	.find_reduced_pll = intel_find_best_reduced_PLL,
 };
 	
 static const intel_limit_t intel_limits_i9xx_sdvo = {
@@ -311,6 +319,7 @@  static const intel_limit_t intel_limits_i9xx_sdvo = {
 	.p2  = { .dot_limit = I9XX_P2_SDVO_DAC_SLOW_LIMIT,
 		 .p2_slow = I9XX_P2_SDVO_DAC_SLOW,	.p2_fast = I9XX_P2_SDVO_DAC_FAST },
 	.find_pll = intel_find_best_PLL,
+	.find_reduced_pll = intel_find_best_reduced_PLL,
 };
 
 static const intel_limit_t intel_limits_i9xx_lvds = {
@@ -328,6 +337,7 @@  static const intel_limit_t intel_limits_i9xx_lvds = {
 	.p2  = { .dot_limit = I9XX_P2_LVDS_SLOW_LIMIT,
 		 .p2_slow = I9XX_P2_LVDS_SLOW,	.p2_fast = I9XX_P2_LVDS_FAST },
 	.find_pll = intel_find_best_PLL,
+	.find_reduced_pll = intel_find_best_reduced_PLL,
 };
 
     /* below parameter and function is for G4X Chipset Family*/
@@ -345,6 +355,7 @@  static const intel_limit_t intel_limits_g4x_sdvo = {
 		 .p2_fast = G4X_P2_SDVO_FAST
 	},
 	.find_pll = intel_g4x_find_best_PLL,
+	.find_reduced_pll = intel_g4x_find_best_PLL,
 };
 
 static const intel_limit_t intel_limits_g4x_hdmi = {
@@ -361,6 +372,7 @@  static const intel_limit_t intel_limits_g4x_hdmi = {
 		 .p2_fast = G4X_P2_HDMI_DAC_FAST
 	},
 	.find_pll = intel_g4x_find_best_PLL,
+	.find_reduced_pll = intel_g4x_find_best_PLL,
 };
 
 static const intel_limit_t intel_limits_g4x_single_channel_lvds = {
@@ -385,6 +397,7 @@  static const intel_limit_t intel_limits_g4x_single_channel_lvds = {
 		 .p2_fast = G4X_P2_SINGLE_CHANNEL_LVDS_FAST
 	},
 	.find_pll = intel_g4x_find_best_PLL,
+	.find_reduced_pll = intel_g4x_find_best_PLL,
 };
 
 static const intel_limit_t intel_limits_g4x_dual_channel_lvds = {
@@ -409,6 +422,7 @@  static const intel_limit_t intel_limits_g4x_dual_channel_lvds = {
 		 .p2_fast = G4X_P2_DUAL_CHANNEL_LVDS_FAST
 	},
 	.find_pll = intel_g4x_find_best_PLL,
+	.find_reduced_pll = intel_g4x_find_best_PLL,
 };
 
 static const intel_limit_t intel_limits_g4x_display_port = {
@@ -446,6 +460,7 @@  static const intel_limit_t intel_limits_igd_sdvo = {
 	.p2  = { .dot_limit = I9XX_P2_SDVO_DAC_SLOW_LIMIT,
 		 .p2_slow = I9XX_P2_SDVO_DAC_SLOW,	.p2_fast = I9XX_P2_SDVO_DAC_FAST },
 	.find_pll = intel_find_best_PLL,
+	.find_reduced_pll = intel_find_best_reduced_PLL,
 };
 
 static const intel_limit_t intel_limits_igd_lvds = {
@@ -461,6 +476,7 @@  static const intel_limit_t intel_limits_igd_lvds = {
 	.p2  = { .dot_limit = I9XX_P2_LVDS_SLOW_LIMIT,
 		 .p2_slow = I9XX_P2_LVDS_SLOW,	.p2_fast = I9XX_P2_LVDS_SLOW },
 	.find_pll = intel_find_best_PLL,
+	.find_reduced_pll = intel_find_best_reduced_PLL,
 };
 
 static const intel_limit_t intel_limits_igdng_sdvo = {
@@ -668,15 +684,16 @@  intel_find_best_PLL(const intel_limit_t *limit, struct drm_crtc *crtc,
 
 	memset (best_clock, 0, sizeof (*best_clock));
 
-	for (clock.m1 = limit->m1.min; clock.m1 <= limit->m1.max; clock.m1++) {
-		for (clock.m2 = limit->m2.min; clock.m2 <= limit->m2.max; clock.m2++) {
-			/* m1 is always 0 in IGD */
-			if (clock.m2 >= clock.m1 && !IS_IGD(dev))
-				break;
-			for (clock.n = limit->n.min; clock.n <= limit->n.max;
-			     clock.n++) {
-				for (clock.p1 = limit->p1.min;
-				     clock.p1 <= limit->p1.max; clock.p1++) {
+	for (clock.p1 = limit->p1.max; clock.p1 >= limit->p1.min; clock.p1--) {
+		for (clock.m1 = limit->m1.min; clock.m1 <= limit->m1.max;
+		     clock.m1++) {
+			for (clock.m2 = limit->m2.min;
+			     clock.m2 <= limit->m2.max; clock.m2++) {
+				/* m1 is always 0 in IGD */
+				if (clock.m2 >= clock.m1 && !IS_IGD(dev))
+					break;
+				for (clock.n = limit->n.min;
+				     clock.n <= limit->n.max; clock.n++) {
 					int this_err;
 
 					intel_clock(dev, refclk, &clock);
@@ -697,6 +714,46 @@  intel_find_best_PLL(const intel_limit_t *limit, struct drm_crtc *crtc,
 	return (err != target);
 }
 
+
+static bool
+intel_find_best_reduced_PLL(const intel_limit_t *limit, struct drm_crtc *crtc,
+			    int target, int refclk, intel_clock_t *best_clock)
+
+{
+	struct drm_device *dev = crtc->dev;
+	intel_clock_t clock;
+	int err = target;
+	bool found = false;
+
+	memcpy(&clock, best_clock, sizeof(intel_clock_t));
+
+	for (clock.m1 = limit->m1.min; clock.m1 <= limit->m1.max; clock.m1++) {
+		for (clock.m2 = limit->m2.min; clock.m2 <= limit->m2.max; clock.m2++) {
+			/* m1 is always 0 in IGD */
+			if (clock.m2 >= clock.m1 && !IS_IGD(dev))
+				break;
+			for (clock.n = limit->n.min; clock.n <= limit->n.max;
+			     clock.n++) {
+				int this_err;
+
+				intel_clock(dev, refclk, &clock);
+
+				if (!intel_PLL_is_valid(crtc, &clock))
+					continue;
+
+				this_err = abs(clock.dot - target);
+				if (this_err < err) {
+					*best_clock = clock;
+					err = this_err;
+					found = true;
+				}
+			}
+		}
+	}
+
+	return found;
+}
+
 static bool
 intel_g4x_find_best_PLL(const intel_limit_t *limit, struct drm_crtc *crtc,
 			int target, int refclk, intel_clock_t *best_clock)
@@ -727,7 +784,7 @@  intel_g4x_find_best_PLL(const intel_limit_t *limit, struct drm_crtc *crtc,
 	max_n = limit->n.max;
 	/* based on hardware requriment prefer smaller n to precision */
 	for (clock.n = limit->n.min; clock.n <= max_n; clock.n++) {
-		/* based on hardware requirment prefere larger m1,m2, p1 */
+		/* based on hardware requirment prefere larger m1,m2 */
 		for (clock.m1 = limit->m1.max;
 		     clock.m1 >= limit->m1.min; clock.m1--) {
 			for (clock.m2 = limit->m2.max;
@@ -780,15 +837,14 @@  intel_igdng_find_best_PLL(const intel_limit_t *limit, struct drm_crtc *crtc,
 
 	memset(best_clock, 0, sizeof(*best_clock));
 	max_n = limit->n.max;
-	/* based on hardware requriment prefer smaller n to precision */
-	for (clock.n = limit->n.min; clock.n <= max_n; clock.n++) {
-		/* based on hardware requirment prefere larger m1,m2, p1 */
-		for (clock.m1 = limit->m1.max;
-		     clock.m1 >= limit->m1.min; clock.m1--) {
-			for (clock.m2 = limit->m2.max;
-			     clock.m2 >= limit->m2.min; clock.m2--) {
-				for (clock.p1 = limit->p1.max;
-				     clock.p1 >= limit->p1.min; clock.p1--) {
+	for (clock.p1 = limit->p1.max; clock.p1 >= limit->p1.min; clock.p1--) {
+		/* based on hardware requriment prefer smaller n to precision */
+		for (clock.n = limit->n.min; clock.n <= max_n; clock.n++) {
+			/* based on hardware requirment prefere larger m1,m2 */
+			for (clock.m1 = limit->m1.max;
+			     clock.m1 >= limit->m1.min; clock.m1--) {
+				for (clock.m2 = limit->m2.max;
+				     clock.m2 >= limit->m2.min; clock.m2--) {
 					int this_err;
 
 					intel_clock(dev, refclk, &clock);
@@ -980,6 +1036,8 @@  intel_pipe_set_base(struct drm_crtc *crtc, int x, int y,
 		intel_fb = to_intel_framebuffer(old_fb);
 		i915_gem_object_unpin(intel_fb->obj);
 	}
+	intel_increase_pllclock(crtc, true);
+
 	mutex_unlock(&dev->struct_mutex);
 
 	if (!dev->primary->master)
@@ -1857,6 +1915,18 @@  static int intel_get_fifo_size(struct drm_device *dev, int plane)
 	return size;
 }
 
+static void g4x_update_wm(struct drm_device *dev)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	u32 fw_blc_self = I915_READ(FW_BLC_SELF);
+
+	if (i915_powersave)
+		fw_blc_self |= FW_BLC_SELF_EN;
+	else
+		fw_blc_self &= ~FW_BLC_SELF_EN;
+	I915_WRITE(FW_BLC_SELF, fw_blc_self);
+}
+
 static void i965_update_wm(struct drm_device *dev)
 {
 	struct drm_i915_private *dev_priv = dev->dev_private;
@@ -1908,7 +1978,8 @@  static void i9xx_update_wm(struct drm_device *dev, int planea_clock,
 	cwm = 2;
 
 	/* Calc sr entries for one plane configs */
-	if (sr_hdisplay && (!planea_clock || !planeb_clock)) {
+	if (HAS_FW_BLC(dev) && sr_hdisplay &&
+	    (!planea_clock || !planeb_clock)) {
 		/* self-refresh has much higher latency */
 		const static int sr_latency_ns = 6000;
 
@@ -1923,8 +1994,7 @@  static void i9xx_update_wm(struct drm_device *dev, int planea_clock,
 		srwm = total_size - sr_entries;
 		if (srwm < 0)
 			srwm = 1;
-		if (IS_I9XX(dev))
-			I915_WRITE(FW_BLC_SELF, (srwm & 0x3f));
+		I915_WRITE(FW_BLC_SELF, FW_BLC_SELF_EN | (srwm & 0x3f));
 	}
 
 	DRM_DEBUG("Setting FIFO watermarks - A: %d, B: %d, C: %d, SR %d\n",
@@ -1996,9 +2066,6 @@  static void intel_update_watermarks(struct drm_device *dev)
 	unsigned long planea_clock = 0, planeb_clock = 0, sr_clock = 0;
 	int enabled = 0, pixel_size = 0;
 
-	if (DSPARB_HWCONTROL(dev))
-		return;
-
 	/* Get the clock config from both planes */
 	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
 		intel_crtc = to_intel_crtc(crtc);
@@ -2031,7 +2098,9 @@  static void intel_update_watermarks(struct drm_device *dev)
 	else if (IS_IGD(dev))
 		igd_disable_cxsr(dev);
 
-	if (IS_I965G(dev))
+	if (IS_G4X(dev))
+		g4x_update_wm(dev);
+	else if (IS_I965G(dev))
 		i965_update_wm(dev);
 	else if (IS_I9XX(dev) || IS_MOBILE(dev))
 		i9xx_update_wm(dev, planea_clock, planeb_clock, sr_hdisplay,
@@ -2065,9 +2134,9 @@  static int intel_crtc_mode_set(struct drm_crtc *crtc,
 	int dsppos_reg = (pipe == 0) ? DSPAPOS : DSPBPOS;
 	int pipesrc_reg = (pipe == 0) ? PIPEASRC : PIPEBSRC;
 	int refclk, num_outputs = 0;
-	intel_clock_t clock;
-	u32 dpll = 0, fp = 0, dspcntr, pipeconf;
-	bool ok, is_sdvo = false, is_dvo = false;
+	intel_clock_t clock, reduced_clock;
+	u32 dpll = 0, fp = 0, fp2 = 0, dspcntr, pipeconf;
+	bool ok, has_reduced_clock = false, is_sdvo = false, is_dvo = false;
 	bool is_crt = false, is_lvds = false, is_tv = false, is_dp = false;
 	struct drm_mode_config *mode_config = &dev->mode_config;
 	struct drm_connector *connector;
@@ -2145,6 +2214,14 @@  static int intel_crtc_mode_set(struct drm_crtc *crtc,
 		return -EINVAL;
 	}
 
+	if (limit->find_reduced_pll) {
+		memcpy(&reduced_clock, &clock, sizeof(intel_clock_t));
+		has_reduced_clock = limit->find_reduced_pll(limit, crtc,
+							    (adjusted_mode->clock*3/4),
+							    refclk,
+							    &reduced_clock);
+	}
+
 	/* SDVO TV has fixed PLL values depend on its clock range,
 	   this mirrors vbios setting. */
 	if (is_sdvo && is_tv) {
@@ -2172,10 +2249,17 @@  static int intel_crtc_mode_set(struct drm_crtc *crtc,
 				270000, /* lane clock */
 				&m_n);
 
-	if (IS_IGD(dev))
+	if (IS_IGD(dev)) {
 		fp = (1 << clock.n) << 16 | clock.m1 << 8 | clock.m2;
-	else
+		if (has_reduced_clock)
+			fp2 = (1 << reduced_clock.n) << 16 |
+				reduced_clock.m1 << 8 | reduced_clock.m2;
+	} else {
 		fp = clock.n << 16 | clock.m1 << 8 | clock.m2;
+		if (has_reduced_clock)
+			fp2 = reduced_clock.n << 16 | reduced_clock.m1 << 8 |
+				reduced_clock.m2;
+	}
 
 	if (!IS_IGDNG(dev))
 		dpll = DPLL_VGA_MODE_DIS;
@@ -2204,6 +2288,8 @@  static int intel_crtc_mode_set(struct drm_crtc *crtc,
 			/* also FPA1 */
 			if (IS_IGDNG(dev))
 				dpll |= (1 << (clock.p1 - 1)) << DPLL_FPA1_P1_POST_DIV_SHIFT;
+			if (IS_G4X(dev) && has_reduced_clock)
+				dpll |= (1 << (reduced_clock.p1 - 1)) << DPLL_FPA1_P1_POST_DIV_SHIFT;
 		}
 		switch (clock.p2) {
 		case 5:
@@ -2346,6 +2432,21 @@  static int intel_crtc_mode_set(struct drm_crtc *crtc,
 		intel_dp_set_m_n(crtc, mode, adjusted_mode);
 
 	I915_WRITE(fp_reg, fp);
+	if (has_reduced_clock && i915_powersave) {
+		I915_WRITE(fp_reg + 4, fp2);
+		intel_crtc->lowfreq_avail = true;
+		if (HAS_PIPE_CXSR(dev)) {
+			DRM_DEBUG("enabling CxSR downclocking\n");
+			pipeconf |= PIPECONF_CXSR_DOWNCLOCK;
+		}
+	} else {
+		I915_WRITE(fp_reg + 4, fp);
+		intel_crtc->lowfreq_avail = false;
+		if (HAS_PIPE_CXSR(dev)) {
+			DRM_DEBUG("disabling CxSR downclocking\n");
+			pipeconf &= ~PIPECONF_CXSR_DOWNCLOCK;
+		}
+	}
 	I915_WRITE(dpll_reg, dpll);
 	I915_READ(dpll_reg);
 	/* Wait for the clocks to stabilize. */
@@ -2555,10 +2656,16 @@  static int intel_crtc_cursor_move(struct drm_crtc *crtc, int x, int y)
 	struct drm_device *dev = crtc->dev;
 	struct drm_i915_private *dev_priv = dev->dev_private;
 	struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
+	struct intel_framebuffer *intel_fb;
 	int pipe = intel_crtc->pipe;
 	uint32_t temp = 0;
 	uint32_t adder;
 
+	if (crtc->fb) {
+		intel_fb = to_intel_framebuffer(crtc->fb);
+		intel_mark_busy(dev, intel_fb->obj);
+	}
+
 	if (x < 0) {
 		temp |= CURSOR_POS_SIGN << CURSOR_X_SHIFT;
 		x = -x;
@@ -2856,6 +2963,279 @@  struct drm_display_mode *intel_crtc_mode_get(struct drm_device *dev,
 	return mode;
 }
 
+#define GPU_IDLE_TIMEOUT 500 /* ms */
+
+/* When this timer fires, we've been idle for awhile */
+static void intel_gpu_idle_timer(unsigned long arg)
+{
+	struct drm_device *dev = (struct drm_device *)arg;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+
+	DRM_DEBUG("idle timer fired, downclocking\n");
+
+	dev_priv->busy = false;
+
+	schedule_work(&dev_priv->idle_work);
+}
+
+void intel_increase_renderclock(struct drm_device *dev, bool schedule)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+
+	/* Restore render clock frequency to original value */
+	if (IS_G4X(dev) || IS_I9XX(dev))
+		pci_write_config_word(dev->pdev, GCFGC, dev_priv->orig_clock);
+	else if (IS_I85X(dev))
+		pci_write_config_word(dev->pdev, HPLLCC, dev_priv->orig_clock);
+	DRM_DEBUG("increasing render clock frequency\n");
+
+	/* Schedule downclock */
+	if (schedule)
+		mod_timer(&dev_priv->idle_timer, jiffies +
+			  msecs_to_jiffies(GPU_IDLE_TIMEOUT));
+}
+
+void intel_decrease_renderclock(struct drm_device *dev)
+{
+	if (IS_G4X(dev)) {
+  		u16 gcfgc;
+
+		/* Adjust render clock... */
+		pci_read_config_word(dev->pdev, GCFGC, &gcfgc);
+
+		/* Down to minimum... */
+		gcfgc &= ~GM45_GC_RENDER_CLOCK_MASK;
+		gcfgc |= GM45_GC_RENDER_CLOCK_266_MHZ;
+
+		pci_write_config_word(dev->pdev, GCFGC, gcfgc);
+	} else if (IS_I965G(dev)) {
+		u16 gcfgc;
+
+		/* Adjust render clock... */
+		pci_read_config_word(dev->pdev, GCFGC, &gcfgc);
+
+		/* Down to minimum... */
+		gcfgc &= ~I965_GC_RENDER_CLOCK_MASK;
+		gcfgc |= I965_GC_RENDER_CLOCK_267_MHZ;
+
+		pci_write_config_word(dev->pdev, GCFGC, gcfgc);
+	} else if (IS_I945G(dev) || IS_I945GM(dev)) {
+		u16 gcfgc;
+
+		/* Adjust render clock... */
+		pci_read_config_word(dev->pdev, GCFGC, &gcfgc);
+
+		/* Down to minimum... */
+		gcfgc &= ~I945_GC_RENDER_CLOCK_MASK;
+		gcfgc |= I945_GC_RENDER_CLOCK_166_MHZ;
+
+		pci_write_config_word(dev->pdev, GCFGC, gcfgc);
+	} else if (IS_I915G(dev)) {
+		u16 gcfgc;
+
+		/* Adjust render clock... */
+		pci_read_config_word(dev->pdev, GCFGC, &gcfgc);
+
+		/* Down to minimum... */
+		gcfgc &= ~I915_GC_RENDER_CLOCK_MASK;
+		gcfgc |= I915_GC_RENDER_CLOCK_166_MHZ;
+
+  		pci_write_config_word(dev->pdev, GCFGC, gcfgc);
+	} else if (IS_I85X(dev)) {
+		u16 hpllcc;
+
+		/* Adjust render clock... */
+		pci_read_config_word(dev->pdev, HPLLCC, &hpllcc);
+
+		/* Up to maximum... */
+		hpllcc &= ~GC_CLOCK_CONTROL_MASK;
+		hpllcc |= GC_CLOCK_133_200;
+
+		pci_write_config_word(dev->pdev, HPLLCC, hpllcc);
+	}
+	DRM_DEBUG("decreasing render clock frequency\n");
+}
+
+/* Note that no increase function is needed for this - increase_renderclock()
+ *  will also rewrite these bits
+ */
+void intel_decrease_displayclock(struct drm_device *dev)
+{
+      if (IS_I945G(dev) || IS_I945GM(dev) || IS_I915G(dev) ||
+          IS_I915GM(dev)) {
+              u16 gcfgc;
+
+              /* Adjust render clock... */
+              pci_read_config_word(dev->pdev, GCFGC, &gcfgc);
+
+              /* Down to minimum... */
+              gcfgc &= ~0xf0;
+              gcfgc |= 0x80;
+
+              pci_write_config_word(dev->pdev, GCFGC, gcfgc);
+      }
+}
+
+#define CRTC_IDLE_TIMEOUT 50 /* ms */
+
+static void intel_crtc_idle_timer(unsigned long arg)
+{
+	struct intel_crtc *intel_crtc = (struct intel_crtc *)arg;
+	struct drm_crtc *crtc = &intel_crtc->base;
+	drm_i915_private_t *dev_priv = crtc->dev->dev_private;
+
+	DRM_DEBUG("idle timer fired, downclocking\n");
+
+	intel_crtc->busy = false;
+
+	schedule_work(&dev_priv->idle_work);
+}
+
+static void intel_increase_pllclock(struct drm_crtc *crtc, bool schedule)
+{
+	struct drm_device *dev = crtc->dev;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
+	int pipe = intel_crtc->pipe;
+	int dpll_reg = (pipe == 0) ? DPLL_A : DPLL_B;
+	int dpll = I915_READ(dpll_reg);
+
+	if (!HAS_PIPE_CXSR(dev) && (dpll & DISPLAY_RATE_SELECT_FPA1)) {
+		DRM_DEBUG("upclocking LVDS\n");
+
+		/* Unlock panel regs */
+		I915_WRITE(PP_CONTROL, I915_READ(PP_CONTROL) | (0xabcd << 16));
+
+		dpll &= ~DISPLAY_RATE_SELECT_FPA1;
+		I915_WRITE(dpll_reg, dpll);
+		dpll = I915_READ(dpll_reg);
+		intel_wait_for_vblank(dev);
+		dpll = I915_READ(dpll_reg);
+		if (dpll & DISPLAY_RATE_SELECT_FPA1)
+			DRM_DEBUG("failed to upclock LVDS!\n");
+
+		/* ...and lock them again */
+		I915_WRITE(PP_CONTROL, I915_READ(PP_CONTROL) & 0x3);
+	}
+
+	/* Schedule downclock */
+	if (schedule)
+		mod_timer(&intel_crtc->idle_timer, jiffies +
+			  msecs_to_jiffies(CRTC_IDLE_TIMEOUT));
+}
+
+static void intel_decrease_pllclock(struct drm_crtc *crtc)
+{
+	struct drm_device *dev = crtc->dev;
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
+	int pipe = intel_crtc->pipe;
+	int dpll_reg = (pipe == 0) ? DPLL_A : DPLL_B;
+	int dpll = I915_READ(dpll_reg);
+
+	/*
+	 * Since this is called by a timer, we should never get here in
+	 * the manual case.
+	 */
+	if (!HAS_PIPE_CXSR(dev) && intel_crtc->lowfreq_avail) {
+		DRM_DEBUG("downclocking LVDS\n");
+
+		/* Unlock panel regs */
+		I915_WRITE(PP_CONTROL, I915_READ(PP_CONTROL) | (0xabcd << 16));
+
+		dpll |= DISPLAY_RATE_SELECT_FPA1;
+		I915_WRITE(dpll_reg, dpll);
+		dpll = I915_READ(dpll_reg);
+		intel_wait_for_vblank(dev);
+		dpll = I915_READ(dpll_reg);
+		if (!(dpll & DISPLAY_RATE_SELECT_FPA1))
+			DRM_DEBUG("failed to downclock LVDS!\n");
+
+		/* ...and lock them again */
+		I915_WRITE(PP_CONTROL, I915_READ(PP_CONTROL) & 0x3);
+	}
+
+}
+
+/**
+ * intel_idle_update - adjust clocks for idleness
+ * @work: work struct
+ *
+ * Either the GPU or display (or both) went idle.  Check the busy status
+ * here and adjust the CRTC and GPU clocks as necessary.
+ */
+static void intel_idle_update(struct work_struct *work)
+{
+	drm_i915_private_t *dev_priv = container_of(work, drm_i915_private_t,
+						    idle_work);
+	struct drm_device *dev = dev_priv->dev;
+	struct drm_crtc *crtc;
+	struct intel_crtc *intel_crtc;
+
+	if (!i915_powersave)
+		return;
+
+	mutex_lock(&dev->struct_mutex);
+
+	/* GPU isn't processing, downclock it. */
+	if (!dev_priv->busy) {
+		intel_decrease_renderclock(dev);
+		intel_decrease_displayclock(dev);
+	}
+
+	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+		/* Skip inactive CRTCs */
+		if (!crtc->fb)
+			continue;
+
+		intel_crtc = to_intel_crtc(crtc);
+		if (!intel_crtc->busy)
+			intel_decrease_pllclock(crtc);
+	}
+
+	mutex_unlock(&dev->struct_mutex);
+}
+
+/**
+ * intel_mark_busy - mark the GPU and possibly the display busy
+ * @dev: drm device
+ * @obj: object we're operating on
+ *
+ * Callers can use this function to indicate that the GPU is busy processing
+ * commands.  If @obj matches one of the CRTC objects (i.e. it's a scanout
+ * buffer), we'll also mark the display as busy, so we know to increase its
+ * clock frequency.
+ */
+void intel_mark_busy(struct drm_device *dev, struct drm_gem_object *obj)
+{
+	drm_i915_private_t *dev_priv = dev->dev_private;
+	struct drm_crtc *crtc = NULL;
+	struct intel_framebuffer *intel_fb;
+	struct intel_crtc *intel_crtc;
+
+	dev_priv->busy = true;
+	intel_increase_renderclock(dev, true);
+
+	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+		if (!crtc->fb)
+			continue;
+
+		intel_crtc = to_intel_crtc(crtc);
+		intel_fb = to_intel_framebuffer(crtc->fb);
+		if (intel_fb->obj == obj) {
+			if (!intel_crtc->busy) {
+				/* Non-busy -> busy, upclock */
+				intel_increase_pllclock(crtc, true);
+				intel_crtc->busy = true;
+			} else {
+				/* Busy -> busy, put off timer */
+				mod_timer(&intel_crtc->idle_timer, jiffies +
+					  msecs_to_jiffies(CRTC_IDLE_TIMEOUT));
+			}
+		}
+	}
+}
+
 static void intel_crtc_destroy(struct drm_crtc *crtc)
 {
 	struct intel_crtc *intel_crtc = to_intel_crtc(crtc);
@@ -2911,6 +3291,10 @@  static void intel_crtc_init(struct drm_device *dev, int pipe)
 	intel_crtc->mode_set.crtc = &intel_crtc->base;
 	intel_crtc->mode_set.connectors = (struct drm_connector **)(intel_crtc + 1);
 	intel_crtc->mode_set.num_connectors = 0;
+	intel_crtc->busy = false;
+
+	setup_timer(&intel_crtc->idle_timer, intel_crtc_idle_timer,
+		    (unsigned long)intel_crtc);
 
 	if (i915_fbpercrtc) {
 
@@ -3174,8 +3558,56 @@  static const struct drm_mode_config_funcs intel_mode_funcs = {
 	.fb_changed = intelfb_probe,
 };
 
+void intel_init_clock_gating(struct drm_device *dev)
+{
+	struct drm_i915_private *dev_priv = dev->dev_private;
+
+	/*
+	 * Disable clock gating reported to work incorrectly according to the
+	 * specs, but enable as much else as we can.
+	 */
+	if (IS_G4X(dev)) {
+		uint32_t dspclk_gate;
+		I915_WRITE(RENCLK_GATE_D1, 0);
+		I915_WRITE(RENCLK_GATE_D2, VF_UNIT_CLOCK_GATE_DISABLE |
+		       GS_UNIT_CLOCK_GATE_DISABLE |
+		       CL_UNIT_CLOCK_GATE_DISABLE);
+		I915_WRITE(RAMCLK_GATE_D, 0);
+		dspclk_gate = VRHUNIT_CLOCK_GATE_DISABLE |
+			OVRUNIT_CLOCK_GATE_DISABLE |
+			OVCUNIT_CLOCK_GATE_DISABLE;
+		if (IS_GM45(dev))
+			dspclk_gate |= DSSUNIT_CLOCK_GATE_DISABLE;
+		I915_WRITE(DSPCLK_GATE_D, dspclk_gate);
+	} else if (IS_I965GM(dev)) {
+		I915_WRITE(RENCLK_GATE_D1, I965_RCC_CLOCK_GATE_DISABLE);
+		I915_WRITE(RENCLK_GATE_D2, 0);
+		I915_WRITE(DSPCLK_GATE_D, 0);
+		I915_WRITE(RAMCLK_GATE_D, 0);
+		I915_WRITE16(DEUC, 0);
+	} else if (IS_I965G(dev)) {
+		I915_WRITE(RENCLK_GATE_D1, I965_RCZ_CLOCK_GATE_DISABLE |
+		       I965_RCC_CLOCK_GATE_DISABLE |
+		       I965_RCPB_CLOCK_GATE_DISABLE |
+		       I965_ISC_CLOCK_GATE_DISABLE |
+		       I965_FBC_CLOCK_GATE_DISABLE);
+		I915_WRITE(RENCLK_GATE_D2, 0);
+	} else if (IS_I9XX(dev)) {
+		u32 dstate = I915_READ(D_STATE);
+
+		dstate |= DSTATE_PLL_D3_OFF | DSTATE_GFX_CLOCK_GATING |
+			DSTATE_DOT_CLOCK_GATING;
+		I915_WRITE(D_STATE, dstate);
+	} else if (IS_I855(dev) || IS_I865G(dev)) {
+		I915_WRITE(RENCLK_GATE_D1, SV_CLOCK_GATE_DISABLE);
+	} else if (IS_I830(dev)) {
+		I915_WRITE(DSPCLK_GATE_D, OVRUNIT_CLOCK_GATE_DISABLE);
+	}
+}
+
 void intel_modeset_init(struct drm_device *dev)
 {
+	struct drm_i915_private *dev_priv = dev->dev_private;
 	int num_pipe;
 	int i;
 
@@ -3210,15 +3642,47 @@  void intel_modeset_init(struct drm_device *dev)
 	DRM_DEBUG("%d display pipe%s available.\n",
 		  num_pipe, num_pipe > 1 ? "s" : "");
 
+	if (IS_I85X(dev))
+		pci_read_config_word(dev->pdev, HPLLCC, &dev_priv->orig_clock);
+	else if (IS_I9XX(dev) || IS_G4X(dev))
+		pci_read_config_word(dev->pdev, GCFGC, &dev_priv->orig_clock);
+
 	for (i = 0; i < num_pipe; i++) {
 		intel_crtc_init(dev, i);
 	}
 
 	intel_setup_outputs(dev);
+
+	intel_init_clock_gating(dev);
+
+	INIT_WORK(&dev_priv->idle_work, intel_idle_update);
+	setup_timer(&dev_priv->idle_timer, intel_gpu_idle_timer,
+		    (unsigned long)dev);
 }
 
 void intel_modeset_cleanup(struct drm_device *dev)
 {
+	struct drm_i915_private *dev_priv = dev->dev_private;
+	struct drm_crtc *crtc;
+	struct intel_crtc *intel_crtc;
+
+	mutex_lock(&dev->struct_mutex);
+
+	list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) {
+		/* Skip inactive CRTCs */
+		if (!crtc->fb)
+			continue;
+
+		intel_crtc = to_intel_crtc(crtc);
+		intel_increase_pllclock(crtc, false);
+		del_timer_sync(&intel_crtc->idle_timer);
+	}
+
+	intel_increase_renderclock(dev, false);
+	del_timer_sync(&dev_priv->idle_timer);
+
+	mutex_unlock(&dev->struct_mutex);
+
 	drm_mode_config_cleanup(dev);
 }
 
diff --git a/drivers/gpu/drm/i915/intel_drv.h b/drivers/gpu/drm/i915/intel_drv.h
index 004541c..44fe933 100644
--- a/drivers/gpu/drm/i915/intel_drv.h
+++ b/drivers/gpu/drm/i915/intel_drv.h
@@ -98,6 +98,9 @@  struct intel_crtc {
 	struct intel_framebuffer *fbdev_fb;
 	/* a mode_set for fbdev users on this crtc */
 	struct drm_mode_set mode_set;
+	bool busy; /* is scanout buffer being updated frequently? */
+	struct timer_list idle_timer;
+	bool lowfreq_avail;
 };
 
 #define to_intel_crtc(x) container_of(x, struct intel_crtc, base)
@@ -116,6 +119,7 @@  extern void intel_hdmi_init(struct drm_device *dev, int sdvox_reg);
 extern bool intel_sdvo_init(struct drm_device *dev, int output_device);
 extern void intel_dvo_init(struct drm_device *dev);
 extern void intel_tv_init(struct drm_device *dev);
+extern void intel_mark_busy(struct drm_device *dev, struct drm_gem_object *obj);
 extern void intel_lvds_init(struct drm_device *dev);
 extern void intel_dp_init(struct drm_device *dev, int dp_reg);
 void
diff --git a/drivers/gpu/drm/i915/intel_i2c.c b/drivers/gpu/drm/i915/intel_i2c.c
index 62b8bea..c7eab72 100644
--- a/drivers/gpu/drm/i915/intel_i2c.c
+++ b/drivers/gpu/drm/i915/intel_i2c.c
@@ -42,11 +42,11 @@  void intel_i2c_quirk_set(struct drm_device *dev, bool enable)
 	if (!IS_IGD(dev))
 		return;
 	if (enable)
-		I915_WRITE(CG_2D_DIS,
-			I915_READ(CG_2D_DIS) | DPCUNIT_CLOCK_GATE_DISABLE);
+		I915_WRITE(DSPCLK_GATE_D,
+			I915_READ(DSPCLK_GATE_D) | DPCUNIT_CLOCK_GATE_DISABLE);
 	else
-		I915_WRITE(CG_2D_DIS,
-			I915_READ(CG_2D_DIS) & (~DPCUNIT_CLOCK_GATE_DISABLE));
+		I915_WRITE(DSPCLK_GATE_D,
+			I915_READ(DSPCLK_GATE_D) & (~DPCUNIT_CLOCK_GATE_DISABLE));
 }
 
 /*