From patchwork Mon Jul 27 16:55:36 2009 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Matthew Garrett X-Patchwork-Id: 37587 Received: from gabe.freedesktop.org (gabe.freedesktop.org [131.252.210.177]) by demeter.kernel.org (8.14.2/8.14.2) with ESMTP id n6RGtiow014518 for ; Mon, 27 Jul 2009 16:55:44 GMT Received: from gabe.freedesktop.org (localhost [127.0.0.1]) by gabe.freedesktop.org (Postfix) with ESMTP id A48F79ED30; Mon, 27 Jul 2009 09:55:43 -0700 (PDT) X-Original-To: intel-gfx@lists.freedesktop.org Delivered-To: intel-gfx@lists.freedesktop.org Received: from cavan.codon.org.uk (cavan.codon.org.uk [93.93.128.6]) by gabe.freedesktop.org (Postfix) with ESMTP id E13EE9EB2A for ; Mon, 27 Jul 2009 09:55:40 -0700 (PDT) Received: from mjg59 by cavan.codon.org.uk with local (Exim 4.69) (envelope-from ) id 1MVTU8-0002fO-A3; Mon, 27 Jul 2009 17:55:36 +0100 Date: Mon, 27 Jul 2009 17:55:36 +0100 From: Matthew Garrett To: Jesse Barnes Message-ID: <20090727165536.GA10181@srcf.ucam.org> References: <20090722121607.29042660@jbarnes-g45> MIME-Version: 1.0 Content-Disposition: inline In-Reply-To: <20090722121607.29042660@jbarnes-g45> User-Agent: Mutt/1.5.18 (2008-05-17) X-SA-Exim-Connect-IP: X-SA-Exim-Mail-From: mjg59@cavan.codon.org.uk X-SA-Exim-Scanned: No (on cavan.codon.org.uk); SAEximRunCond expanded to false Cc: intel-gfx@lists.freedesktop.org Subject: Re: [Intel-gfx] [RFC] drm/i915: downclock support X-BeenThere: intel-gfx@lists.freedesktop.org X-Mailman-Version: 2.1.9 Precedence: list List-Id: Intel graphics driver community testing & development List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Sender: intel-gfx-bounces@lists.freedesktop.org Errors-To: intel-gfx-bounces@lists.freedesktop.org Here's my current version - tested working on 945 and 965. commit 914cbeb9dee6a75140a79b617206a6f7b253593e Author: Matthew Garrett Date: Sun Jul 26 19:40:21 2009 +0100 drm: Add dynamic power management to Intel 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 TFTs, 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, and so there should be no user visible graphical changes. Signed-off-by: Jesse Barnes Signed-off-by: Matthew Garrett 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 b05b44d..5cd915b 100644 --- a/drivers/gpu/drm/i915/i915_drv.h +++ b/drivers/gpu/drm/i915/i915_drv.h @@ -437,6 +437,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 */ @@ -566,6 +572,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); @@ -893,6 +900,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 #include @@ -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..9db90b4 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 */ @@ -1569,6 +1588,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/intel_display.c b/drivers/gpu/drm/i915/intel_display.c index a58bfad..f9984fe 100644 --- a/drivers/gpu/drm/i915/intel_display.c +++ b/drivers/gpu/drm/i915/intel_display.c @@ -36,6 +36,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, int schedule); typedef struct { /* given values */ @@ -65,6 +66,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 @@ -190,7 +193,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 @@ -259,6 +262,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 @@ -281,6 +287,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 = { @@ -295,6 +302,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 = { @@ -309,6 +317,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 = { @@ -326,6 +335,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*/ @@ -343,6 +353,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 = { @@ -359,6 +370,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 = { @@ -383,6 +395,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 = { @@ -407,6 +420,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 = { @@ -444,6 +458,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 = { @@ -459,6 +474,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 = { @@ -666,15 +682,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); @@ -695,6 +712,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) @@ -725,7 +782,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; @@ -735,8 +792,8 @@ intel_g4x_find_best_PLL(const intel_limit_t *limit, struct drm_crtc *crtc, int this_err; intel_clock(dev, refclk, &clock); - if (!intel_PLL_is_valid(crtc, &clock)) - continue; + if (!intel_PLL_is_valid(crtc, &clock)) + continue; this_err = abs(clock.dot - target) ; if (this_err < err_most) { *best_clock = clock; @@ -778,15 +835,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); @@ -976,8 +1032,11 @@ intel_pipe_set_base(struct drm_crtc *crtc, int x, int y, if (old_fb) { intel_fb = to_intel_framebuffer(old_fb); + obj_priv = intel_fb->obj->driver_private; i915_gem_object_unpin(intel_fb->obj); } + intel_increase_pllclock(crtc, true); + mutex_unlock(&dev->struct_mutex); if (!dev->primary->master) @@ -1855,6 +1914,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; @@ -1906,7 +1977,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; @@ -1921,8 +1993,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", @@ -1994,9 +2065,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); @@ -2029,7 +2097,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, @@ -2063,9 +2133,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; @@ -2143,6 +2213,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) { @@ -2170,10 +2248,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; @@ -2202,6 +2287,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: @@ -2344,6 +2431,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. */ @@ -2548,6 +2650,8 @@ fail_locked: return ret; } +#define CRTC_IDLE_TIMEOUT 50 /* ms */ + static int intel_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) { struct drm_device *dev = crtc->dev; @@ -2557,6 +2661,12 @@ static int intel_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) uint32_t temp = 0; uint32_t adder; + if (!intel_crtc->busy) + intel_increase_pllclock(crtc, true); + intel_crtc->busy = true; + mod_timer(&intel_crtc->idle_timer, jiffies + + msecs_to_jiffies(CRTC_IDLE_TIMEOUT)); + if (x < 0) { temp |= CURSOR_POS_SIGN << CURSOR_X_SHIFT; x = -x; @@ -2854,6 +2964,271 @@ 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, int schedule) +{ + drm_i915_private_t *dev_priv = dev->dev_private; + + if (IS_G4X(dev) || IS_I9XX(dev)) { + /* Up to maximum... */ + 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"); + + if (!schedule) + return; + + /* Schedule downclock */ + 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) || IS_I915GM(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); + } +} + +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, int 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"); + + I915_WRITE(PP_CONTROL, I915_READ(PP_CONTROL) | (0xabcd << 16)); + I915_WRITE(dpll_reg, dpll & ~DISPLAY_RATE_SELECT_FPA1); + 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"); + I915_WRITE(PP_CONTROL, I915_READ(PP_CONTROL) & 0x3); + } + + if (!schedule) + return; + + /* Schedule downclock */ + 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"); + 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"); + 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) + intel_increase_pllclock(crtc, true); + intel_crtc->busy = true; + 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); @@ -2909,6 +3284,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,6 +3553,7 @@ static const struct drm_mode_config_funcs intel_mode_funcs = { void intel_modeset_init(struct drm_device *dev) { + struct drm_i915_private *dev_priv = dev->dev_private; int num_pipe; int i; @@ -3208,15 +3588,38 @@ 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); + + 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; + + /* Clean up up/down clocking */ + if (!HAS_PIPE_CXSR(dev)) { + u32 dpll_b = I915_READ(DPLL_B); + + del_timer_sync(&dev_priv->idle_timer); + dpll_b &= ~DISPLAY_RATE_SELECT_FPA1; + I915_WRITE(DPLL_B, dpll_b); + POSTING_READ(DPLL_B); + intel_wait_for_vblank(dev); + } + + intel_increase_renderclock(dev, false); 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