@@ -2004,8 +2004,11 @@ static int acpi_video_bus_put_one_device(struct acpi_video_device *device)
status = acpi_remove_notify_handler(device->dev->handle,
ACPI_DEVICE_NOTIFY,
acpi_video_device_notify);
- sysfs_remove_link(&device->backlight->dev.kobj, "device");
- backlight_device_unregister(device->backlight);
+ if (device->backlight) {
+ sysfs_remove_link(&device->backlight->dev.kobj, "device");
+ backlight_device_unregister(device->backlight);
+ device->backlight = NULL;
+ }
if (device->cdev) {
sysfs_remove_link(&device->dev->dev.kobj,
"thermal_cooling");
@@ -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 = 0;
+module_param_named(powersave, i915_powersave, int, 0400);
+
static struct drm_driver driver;
static struct pci_device_id pciidlist[] = {
@@ -437,6 +437,13 @@ 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 */
+ bool lvds_lowfreq_avail;
+ bool render_lowfreq_avail;
+ struct work_struct idle_work;
+ struct timer_list idle_timer;
+ bool busy;
} drm_i915_private_t;
/** driver private structure attached to each drm_gem_object */
@@ -566,6 +573,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 +901,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
@@ -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,
@@ -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)
@@ -1569,6 +1569,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)
@@ -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_displayclock(struct drm_crtc *crtc);
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 10000
#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,12 @@ 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;
+ intel_increase_displayclock(crtc);
i915_gem_object_unpin(intel_fb->obj);
}
+ intel_increase_displayclock(crtc);
+
mutex_unlock(&dev->struct_mutex);
if (!dev->primary->master)
@@ -1855,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;
@@ -1906,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;
@@ -1921,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",
@@ -1994,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);
@@ -2029,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,
@@ -2063,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;
@@ -2143,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/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 +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;
@@ -2202,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:
@@ -2344,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 (is_lvds && has_reduced_clock && i915_powersave) {
+ I915_WRITE(fp_reg + 4, fp2);
+ dev_priv->lvds_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);
+ dev_priv->lvds_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. */
@@ -2854,6 +2957,227 @@ 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)
+{
+ drm_i915_private_t *dev_priv = dev->dev_private;
+
+ if (IS_G4X(dev) || IS_I9XX(dev)) {
+ u16 gcfgc;
+
+ /* Adjust render clock... */
+ pci_read_config_word(dev->pdev, GCFGC, &gcfgc);
+
+ /* Up to maximum... */
+ gcfgc &= ~0xf;
+ if (IS_G4X(dev))
+ gcfgc |= 0xd;
+ else if (IS_I9XX(dev))
+ gcfgc |= GC_CLOCK_166_250;
+
+ 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_166_250;
+
+ pci_write_config_word(dev->pdev, HPLLCC, hpllcc);
+ }
+ DRM_DEBUG("increasing render clock frequency\n");
+
+ /* 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) || IS_I9XX(dev)) {
+ u16 gcfgc;
+
+ /* Adjust render clock... */
+ pci_read_config_word(dev->pdev, GCFGC, &gcfgc);
+
+ /* Up to maximum... */
+ gcfgc &= ~0xf;
+ gcfgc |= GC_DISPLAY_CLOCK_190_200_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");
+}
+
+#define CRTC_IDLE_TIMEOUT 1000 /* 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_displayclock(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);
+
+ if (!HAS_PIPE_CXSR(dev) && (dpll & DISPLAY_RATE_SELECT_FPA1)) {
+ DRM_DEBUG("upclocking LVDS\n");
+
+ 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");
+ }
+
+ /* Schedule downclock */
+ mod_timer(&intel_crtc->idle_timer, jiffies +
+ msecs_to_jiffies(CRTC_IDLE_TIMEOUT));
+}
+
+static void intel_decrease_displayclock(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) && dev_priv->lvds_lowfreq_avail) {
+ DRM_DEBUG("downclocking LVDS\n");
+
+ 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");
+ }
+
+}
+
+/**
+ * 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);
+
+ 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_increase_displayclock(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;
+ bool schedule_display_idle = false;
+
+ dev_priv->busy = true;
+ intel_increase_renderclock(dev);
+
+ 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) {
+ 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 +3233,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 +3502,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;
@@ -3213,10 +3542,27 @@ void intel_modeset_init(struct drm_device *dev)
}
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);
+ }
+
drm_mode_config_cleanup(dev);
}
@@ -98,6 +98,8 @@ 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;
};
#define to_intel_crtc(x) container_of(x, struct intel_crtc, base)
@@ -116,6 +118,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