diff options
author | Dave Airlie <airlied@redhat.com> | 2012-09-10 14:20:51 +1000 |
---|---|---|
committer | Dave Airlie <airlied@redhat.com> | 2013-08-29 13:30:46 +1000 |
commit | 5addcf0a5f0fadceba6bd562d0616a1c5d4c1a4d (patch) | |
tree | 5162dd3bf9dd94b2b59b01a320fc019deda052bd /drivers/gpu/drm/nouveau | |
parent | 13bb9cc8726c716a7f271fc2c760ba15e1fdd38c (diff) |
nouveau: add runtime PM support (v0.9)
This hooks nouveau up to the runtime PM system to enable
dynamic power management for secondary GPUs in switchable
and optimus laptops.
a) rewrite suspend/resume printks to hide them during dynamic s/r
to avoid cluttering logs
b) add runtime pm suspend to irq handler, crtc display, ioctl handler,
connector status,
c) handle hdmi audio dynamic power on/off using magic register.
v0.5:
make sure we hit D3 properly
fix fbdev_set_suspend locking interaction, we only will poweroff if we have no
active crtcs/fbcon anyways.
add reference for active crtcs.
sprinkle mark last busy for autosuspend timeout
v0.6:
allow more flexible debugging - to avoid log spam
add option to enable/disable dynpm
got to D3Cold
v0.7:
add hdmi audio support.
v0.8:
call autosuspend from idle, so pci config space access doesn't go straight
back to sleep, this makes starting X faster.
only signal usage if we actually handle the irq, otherwise usb keeps us awake.
fix nv50 display active powerdown
v0.9:
use masking function to enable hdmi audio
set busy when we fail to suspend
Signed-off-by: Dave Airlie <airlied@redhat.com>
Diffstat (limited to 'drivers/gpu/drm/nouveau')
-rw-r--r-- | drivers/gpu/drm/nouveau/core/core/printk.c | 19 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/include/core/printk.h | 13 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/bios/init.c | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/core/subdev/mc/base.c | 6 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/dispnv04/crtc.c | 49 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_acpi.c | 42 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_connector.c | 27 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_display.c | 12 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_display.h | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_drm.c | 250 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_drm.h | 9 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_vga.c | 14 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nv50_display.c | 2 |
13 files changed, 404 insertions, 43 deletions
diff --git a/drivers/gpu/drm/nouveau/core/core/printk.c b/drivers/gpu/drm/nouveau/core/core/printk.c index 6161eaf5447..52fb2aa129e 100644 --- a/drivers/gpu/drm/nouveau/core/core/printk.c +++ b/drivers/gpu/drm/nouveau/core/core/printk.c @@ -27,6 +27,8 @@ #include <core/subdev.h> #include <core/printk.h> +int nv_printk_suspend_level = NV_DBG_DEBUG; + void nv_printk_(struct nouveau_object *object, const char *pfx, int level, const char *fmt, ...) @@ -72,3 +74,20 @@ nv_printk_(struct nouveau_object *object, const char *pfx, int level, vprintk(mfmt, args); va_end(args); } + +#define CONV_LEVEL(x) case NV_DBG_##x: return NV_PRINTK_##x + +const char *nv_printk_level_to_pfx(int level) +{ + switch (level) { + CONV_LEVEL(FATAL); + CONV_LEVEL(ERROR); + CONV_LEVEL(WARN); + CONV_LEVEL(INFO); + CONV_LEVEL(DEBUG); + CONV_LEVEL(PARANOIA); + CONV_LEVEL(TRACE); + CONV_LEVEL(SPAM); + } + return NV_PRINTK_DEBUG; +} diff --git a/drivers/gpu/drm/nouveau/core/include/core/printk.h b/drivers/gpu/drm/nouveau/core/include/core/printk.h index febed2ea5c8..d87836e3a70 100644 --- a/drivers/gpu/drm/nouveau/core/include/core/printk.h +++ b/drivers/gpu/drm/nouveau/core/include/core/printk.h @@ -15,6 +15,12 @@ struct nouveau_object; #define NV_PRINTK_TRACE KERN_DEBUG #define NV_PRINTK_SPAM KERN_DEBUG +extern int nv_printk_suspend_level; + +#define NV_DBG_SUSPEND (nv_printk_suspend_level) +#define NV_PRINTK_SUSPEND (nv_printk_level_to_pfx(nv_printk_suspend_level)) + +const char *nv_printk_level_to_pfx(int level); void __printf(4, 5) nv_printk_(struct nouveau_object *, const char *, int, const char *, ...); @@ -31,6 +37,13 @@ nv_printk_(struct nouveau_object *, const char *, int, const char *, ...); #define nv_trace(o,f,a...) nv_printk((o), TRACE, f, ##a) #define nv_spam(o,f,a...) nv_printk((o), SPAM, f, ##a) +#define nv_suspend(o,f,a...) nv_printk((o), SUSPEND, f, ##a) + +static inline void nv_suspend_set_printk_level(int level) +{ + nv_printk_suspend_level = level; +} + #define nv_assert(f,a...) do { \ if (NV_DBG_FATAL <= CONFIG_NOUVEAU_DEBUG) \ nv_printk_(NULL, NV_PRINTK_FATAL, NV_DBG_FATAL, f "\n", ##a); \ diff --git a/drivers/gpu/drm/nouveau/core/subdev/bios/init.c b/drivers/gpu/drm/nouveau/core/subdev/bios/init.c index 0687e648143..2e11ea02cf8 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/bios/init.c +++ b/drivers/gpu/drm/nouveau/core/subdev/bios/init.c @@ -2165,7 +2165,7 @@ nvbios_init(struct nouveau_subdev *subdev, bool execute) u16 data; if (execute) - nv_info(bios, "running init tables\n"); + nv_suspend(bios, "running init tables\n"); while (!ret && (data = (init_script(bios, ++i)))) { struct nvbios_init init = { .subdev = subdev, diff --git a/drivers/gpu/drm/nouveau/core/subdev/mc/base.c b/drivers/gpu/drm/nouveau/core/subdev/mc/base.c index 1c0330b8c9a..2e7c5fd3de3 100644 --- a/drivers/gpu/drm/nouveau/core/subdev/mc/base.c +++ b/drivers/gpu/drm/nouveau/core/subdev/mc/base.c @@ -23,16 +23,20 @@ */ #include <subdev/mc.h> +#include <linux/pm_runtime.h> static irqreturn_t nouveau_mc_intr(int irq, void *arg) { struct nouveau_mc *pmc = arg; const struct nouveau_mc_intr *map = pmc->intr_map; + struct nouveau_device *device = nv_device(pmc); struct nouveau_subdev *unit; u32 stat, intr; intr = stat = nv_rd32(pmc, 0x000100); + if (intr == 0xffffffff) + return IRQ_NONE; while (stat && map->stat) { if (stat & map->stat) { unit = nouveau_subdev(pmc, map->unit); @@ -47,6 +51,8 @@ nouveau_mc_intr(int irq, void *arg) nv_error(pmc, "unknown intr 0x%08x\n", stat); } + if (stat == IRQ_HANDLED) + pm_runtime_mark_last_busy(&device->pdev->dev); return stat ? IRQ_HANDLED : IRQ_NONE; } diff --git a/drivers/gpu/drm/nouveau/dispnv04/crtc.c b/drivers/gpu/drm/nouveau/dispnv04/crtc.c index 0782bd2f1e0..6413552df21 100644 --- a/drivers/gpu/drm/nouveau/dispnv04/crtc.c +++ b/drivers/gpu/drm/nouveau/dispnv04/crtc.c @@ -22,6 +22,7 @@ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER * DEALINGS IN THE SOFTWARE. */ +#include <linux/pm_runtime.h> #include <drm/drmP.h> #include <drm/drm_crtc_helper.h> @@ -1007,13 +1008,59 @@ nv04_crtc_cursor_move(struct drm_crtc *crtc, int x, int y) return 0; } +int +nouveau_crtc_set_config(struct drm_mode_set *set) +{ + struct drm_device *dev; + struct nouveau_drm *drm; + int ret; + struct drm_crtc *crtc; + bool active = false; + if (!set || !set->crtc) + return -EINVAL; + + dev = set->crtc->dev; + + /* get a pm reference here */ + ret = pm_runtime_get_sync(dev->dev); + if (ret < 0) + return ret; + + ret = drm_crtc_helper_set_config(set); + + drm = nouveau_drm(dev); + + /* if we get here with no crtcs active then we can drop a reference */ + list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { + if (crtc->enabled) + active = true; + } + + pm_runtime_mark_last_busy(dev->dev); + /* if we have active crtcs and we don't have a power ref, + take the current one */ + if (active && !drm->have_disp_power_ref) { + drm->have_disp_power_ref = true; + return ret; + } + /* if we have no active crtcs, then drop the power ref + we got before */ + if (!active && drm->have_disp_power_ref) { + pm_runtime_put_autosuspend(dev->dev); + drm->have_disp_power_ref = false; + } + /* drop the power reference we got coming in here */ + pm_runtime_put_autosuspend(dev->dev); + return ret; +} + static const struct drm_crtc_funcs nv04_crtc_funcs = { .save = nv_crtc_save, .restore = nv_crtc_restore, .cursor_set = nv04_crtc_cursor_set, .cursor_move = nv04_crtc_cursor_move, .gamma_set = nv_crtc_gamma_set, - .set_config = drm_crtc_helper_set_config, + .set_config = nouveau_crtc_set_config, .page_flip = nouveau_crtc_page_flip, .destroy = nv_crtc_destroy, }; diff --git a/drivers/gpu/drm/nouveau/nouveau_acpi.c b/drivers/gpu/drm/nouveau/nouveau_acpi.c index d97f20069d3..dd7d2e18271 100644 --- a/drivers/gpu/drm/nouveau/nouveau_acpi.c +++ b/drivers/gpu/drm/nouveau/nouveau_acpi.c @@ -25,8 +25,27 @@ #define NOUVEAU_DSM_POWER_SPEED 0x01 #define NOUVEAU_DSM_POWER_STAMINA 0x02 -#define NOUVEAU_DSM_OPTIMUS_FN 0x1A -#define NOUVEAU_DSM_OPTIMUS_ARGS 0x03000001 +#define NOUVEAU_DSM_OPTIMUS_CAPS 0x1A +#define NOUVEAU_DSM_OPTIMUS_FLAGS 0x1B + +#define NOUVEAU_DSM_OPTIMUS_POWERDOWN_PS3 (3 << 24) +#define NOUVEAU_DSM_OPTIMUS_NO_POWERDOWN_PS3 (2 << 24) +#define NOUVEAU_DSM_OPTIMUS_FLAGS_CHANGED (1) + +#define NOUVEAU_DSM_OPTIMUS_SET_POWERDOWN (NOUVEAU_DSM_OPTIMUS_POWERDOWN_PS3 | NOUVEAU_DSM_OPTIMUS_FLAGS_CHANGED) + +/* result of the optimus caps function */ +#define OPTIMUS_ENABLED (1 << 0) +#define OPTIMUS_STATUS_MASK (3 << 3) +#define OPTIMUS_STATUS_OFF (0 << 3) +#define OPTIMUS_STATUS_ON_ENABLED (1 << 3) +#define OPTIMUS_STATUS_PWR_STABLE (3 << 3) +#define OPTIMUS_DISPLAY_HOTPLUG (1 << 6) +#define OPTIMUS_CAPS_MASK (7 << 24) +#define OPTIMUS_DYNAMIC_PWR_CAP (1 << 24) + +#define OPTIMUS_AUDIO_CAPS_MASK (3 << 27) +#define OPTIMUS_HDA_CODEC_MASK (2 << 27) /* hda bios control */ static struct nouveau_dsm_priv { bool dsm_detected; @@ -251,9 +270,18 @@ static int nouveau_dsm_pci_probe(struct pci_dev *pdev) retval |= NOUVEAU_DSM_HAS_MUX; if (nouveau_test_dsm(dhandle, nouveau_optimus_dsm, - NOUVEAU_DSM_OPTIMUS_FN)) + NOUVEAU_DSM_OPTIMUS_CAPS)) retval |= NOUVEAU_DSM_HAS_OPT; + if (retval & NOUVEAU_DSM_HAS_OPT) { + uint32_t result; + nouveau_optimus_dsm(dhandle, NOUVEAU_DSM_OPTIMUS_CAPS, 0, + &result); + dev_info(&pdev->dev, "optimus capabilities: %s, status %s%s\n", + (result & OPTIMUS_ENABLED) ? "enabled" : "disabled", + (result & OPTIMUS_DYNAMIC_PWR_CAP) ? "dynamic power, " : "", + (result & OPTIMUS_HDA_CODEC_MASK) ? "hda bios codec supported" : ""); + } if (retval) nouveau_dsm_priv.dhandle = dhandle; @@ -328,8 +356,12 @@ void nouveau_switcheroo_optimus_dsm(void) if (!nouveau_dsm_priv.optimus_detected) return; - nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_FN, - NOUVEAU_DSM_OPTIMUS_ARGS, &result); + nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_FLAGS, + 0x3, &result); + + nouveau_optimus_dsm(nouveau_dsm_priv.dhandle, NOUVEAU_DSM_OPTIMUS_CAPS, + NOUVEAU_DSM_OPTIMUS_SET_POWERDOWN, &result); + } void nouveau_unregister_dsm_handler(void) diff --git a/drivers/gpu/drm/nouveau/nouveau_connector.c b/drivers/gpu/drm/nouveau/nouveau_connector.c index 4da776f344d..c5b36f9e9a1 100644 --- a/drivers/gpu/drm/nouveau/nouveau_connector.c +++ b/drivers/gpu/drm/nouveau/nouveau_connector.c @@ -26,6 +26,8 @@ #include <acpi/button.h> +#include <linux/pm_runtime.h> + #include <drm/drmP.h> #include <drm/drm_edid.h> #include <drm/drm_crtc_helper.h> @@ -240,6 +242,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) struct nouveau_encoder *nv_partner; struct nouveau_i2c_port *i2c; int type; + int ret; + enum drm_connector_status conn_status = connector_status_disconnected; /* Cleanup the previous EDID block. */ if (nv_connector->edid) { @@ -248,6 +252,10 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) nv_connector->edid = NULL; } + ret = pm_runtime_get_sync(connector->dev->dev); + if (ret < 0) + return conn_status; + i2c = nouveau_connector_ddc_detect(connector, &nv_encoder); if (i2c) { nv_connector->edid = drm_get_edid(connector, &i2c->adapter); @@ -263,7 +271,8 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) !nouveau_dp_detect(to_drm_encoder(nv_encoder))) { NV_ERROR(drm, "Detected %s, but failed init\n", drm_get_connector_name(connector)); - return connector_status_disconnected; + conn_status = connector_status_disconnected; + goto out; } /* Override encoder type for DVI-I based on whether EDID @@ -290,13 +299,15 @@ nouveau_connector_detect(struct drm_connector *connector, bool force) } nouveau_connector_set_encoder(connector, nv_encoder); - return connector_status_connected; + conn_status = connector_status_connected; + goto out; } nv_encoder = nouveau_connector_of_detect(connector); if (nv_encoder) { nouveau_connector_set_encoder(connector, nv_encoder); - return connector_status_connected; + conn_status = connector_status_connected; + goto out; } detect_analog: @@ -311,12 +322,18 @@ detect_analog: if (helper->detect(encoder, connector) == connector_status_connected) { nouveau_connector_set_encoder(connector, nv_encoder); - return connector_status_connected; + conn_status = connector_status_connected; + goto out; } } - return connector_status_disconnected; + out: + + pm_runtime_mark_last_busy(connector->dev->dev); + pm_runtime_put_autosuspend(connector->dev->dev); + + return conn_status; } static enum drm_connector_status diff --git a/drivers/gpu/drm/nouveau/nouveau_display.c b/drivers/gpu/drm/nouveau/nouveau_display.c index 78637afb9b9..dbcf10681ab 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.c +++ b/drivers/gpu/drm/nouveau/nouveau_display.c @@ -394,7 +394,7 @@ nouveau_display_suspend(struct drm_device *dev) nouveau_display_fini(dev); - NV_INFO(drm, "unpinning framebuffer(s)...\n"); + NV_SUSPEND(drm, "unpinning framebuffer(s)...\n"); list_for_each_entry(crtc, &dev->mode_config.crtc_list, head) { struct nouveau_framebuffer *nouveau_fb; @@ -416,7 +416,7 @@ nouveau_display_suspend(struct drm_device *dev) } void -nouveau_display_resume(struct drm_device *dev) +nouveau_display_repin(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); struct drm_crtc *crtc; @@ -441,10 +441,12 @@ nouveau_display_resume(struct drm_device *dev) if (ret) NV_ERROR(drm, "Could not pin/map cursor.\n"); } +} - nouveau_fbcon_set_suspend(dev, 0); - nouveau_fbcon_zfill_all(dev); - +void +nouveau_display_resume(struct drm_device *dev) +{ + struct drm_crtc *crtc; nouveau_display_init(dev); /* Force CLUT to get re-loaded during modeset */ diff --git a/drivers/gpu/drm/nouveau/nouveau_display.h b/drivers/gpu/drm/nouveau/nouveau_display.h index 185e74132a6..da84f1f40ec 100644 --- a/drivers/gpu/drm/nouveau/nouveau_display.h +++ b/drivers/gpu/drm/nouveau/nouveau_display.h @@ -57,6 +57,7 @@ void nouveau_display_destroy(struct drm_device *dev); int nouveau_display_init(struct drm_device *dev); void nouveau_display_fini(struct drm_device *dev); int nouveau_display_suspend(struct drm_device *dev); +void nouveau_display_repin(struct drm_device *dev); void nouveau_display_resume(struct drm_device *dev); int nouveau_crtc_page_flip(struct drm_crtc *crtc, struct drm_framebuffer *fb, @@ -71,6 +72,7 @@ int nouveau_display_dumb_map_offset(struct drm_file *, struct drm_device *, void nouveau_hdmi_mode_set(struct drm_encoder *, struct drm_display_mode *); +int nouveau_crtc_set_config(struct drm_mode_set *set); #ifdef CONFIG_DRM_NOUVEAU_BACKLIGHT extern int nouveau_backlight_init(struct drm_device *); extern void nouveau_backlight_exit(struct drm_device *); diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.c b/drivers/gpu/drm/nouveau/nouveau_drm.c index b29d04b822a..62c6118e94c 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.c +++ b/drivers/gpu/drm/nouveau/nouveau_drm.c @@ -25,7 +25,10 @@ #include <linux/console.h> #include <linux/module.h> #include <linux/pci.h> - +#include <linux/pm_runtime.h> +#include <linux/vga_switcheroo.h> +#include "drmP.h" +#include "drm_crtc_helper.h" #include <core/device.h> #include <core/client.h> #include <core/gpuobj.h> @@ -69,6 +72,10 @@ MODULE_PARM_DESC(modeset, "enable driver (default: auto, " int nouveau_modeset = -1; module_param_named(modeset, nouveau_modeset, int, 0400); +MODULE_PARM_DESC(runpm, "disable (0), force enable (1), optimus only default (-1)"); +int nouveau_runtime_pm = -1; +module_param_named(runpm, nouveau_runtime_pm, int, 0400); + static struct drm_driver driver; static int @@ -296,6 +303,31 @@ static int nouveau_drm_probe(struct pci_dev *pdev, return 0; } +#define PCI_CLASS_MULTIMEDIA_HD_AUDIO 0x0403 + +static void +nouveau_get_hdmi_dev(struct drm_device *dev) +{ + struct nouveau_drm *drm = dev->dev_private; + struct pci_dev *pdev = dev->pdev; + + /* subfunction one is a hdmi audio device? */ + drm->hdmi_device = pci_get_bus_and_slot((unsigned int)pdev->bus->number, + PCI_DEVFN(PCI_SLOT(pdev->devfn), 1)); + + if (!drm->hdmi_device) { + DRM_INFO("hdmi device not found %d %d %d\n", pdev->bus->number, PCI_SLOT(pdev->devfn), 1); + return; + } + + if ((drm->hdmi_device->class >> 8) != PCI_CLASS_MULTIMEDIA_HD_AUDIO) { + DRM_INFO("possible hdmi device not audio %d\n", drm->hdmi_device->class); + pci_dev_put(drm->hdmi_device); + drm->hdmi_device = NULL; + return; + } +} + static int nouveau_drm_load(struct drm_device *dev, unsigned long flags) { @@ -314,6 +346,8 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags) INIT_LIST_HEAD(&drm->clients); spin_lock_init(&drm->tile.lock); + nouveau_get_hdmi_dev(dev); + /* make sure AGP controller is in a consistent state before we * (possibly) execute vbios init tables (see nouveau_agp.h) */ @@ -388,6 +422,15 @@ nouveau_drm_load(struct drm_device *dev, unsigned long flags) nouveau_accel_init(drm); nouveau_fbcon_init(dev); + + if (nouveau_runtime_pm != 0) { + pm_runtime_use_autosuspend(dev->dev); + pm_runtime_set_autosuspend_delay(dev->dev, 5000); + pm_runtime_set_active(dev->dev); + pm_runtime_allow(dev->dev); + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put(dev->dev); + } return 0; fail_dispinit: @@ -409,6 +452,7 @@ nouveau_drm_unload(struct drm_device *dev) { struct nouveau_drm *drm = nouveau_drm(dev); + pm_runtime_get_sync(dev->dev); nouveau_fbcon_fini(dev); nouveau_accel_fini(drm); @@ -424,6 +468,8 @@ nouveau_drm_unload(struct drm_device *dev) nouveau_agp_fini(drm); nouveau_vga_fini(drm); + if (drm->hdmi_device) + pci_dev_put(drm->hdmi_device); nouveau_cli_destroy(&drm->client); return 0; } @@ -450,19 +496,16 @@ nouveau_do_suspend(struct drm_device *dev) int ret; if (dev->mode_config.num_crtc) { - NV_INFO(drm, "suspending fbcon...\n"); - nouveau_fbcon_set_suspend(dev, 1); - - NV_INFO(drm, "suspending display...\n"); + NV_SUSPEND(drm, "suspending display...\n"); ret = nouveau_display_suspend(dev); if (ret) return ret; } - NV_INFO(drm, "evicting buffers...\n"); + NV_SUSPEND(drm, "evicting buffers...\n"); ttm_bo_evict_mm(&drm->ttm.bdev, TTM_PL_VRAM); - NV_INFO(drm, "waiting for kernel channels to go idle...\n"); + NV_SUSPEND(drm, "waiting for kernel channels to go idle...\n"); if (drm->cechan) { ret = nouveau_channel_idle(drm->cechan); if (ret) @@ -475,7 +518,7 @@ nouveau_do_suspend(struct drm_device *dev) return ret; } - NV_INFO(drm, "suspending client object trees...\n"); + NV_SUSPEND(drm, "suspending client object trees...\n"); if (drm->fence && nouveau_fence(drm)->suspend) { if (!nouveau_fence(drm)->suspend(drm)) return -ENOMEM; @@ -487,7 +530,7 @@ nouveau_do_suspend(struct drm_device *dev) goto fail_client; } - NV_INFO(drm, "suspending kernel object tree...\n"); + NV_SUSPEND(drm, "suspending kernel object tree...\n"); ret = nouveau_client_fini(&drm->client.base, true); if (ret) goto fail_client; @@ -501,7 +544,7 @@ fail_client: } if (dev->mode_config.num_crtc) { - NV_INFO(drm, "resuming display...\n"); + NV_SUSPEND(drm, "resuming display...\n"); nouveau_display_resume(dev); } return ret; @@ -513,9 +556,14 @@ int nouveau_pmops_suspend(struct device *dev) struct drm_device *drm_dev = pci_get_drvdata(pdev); int ret; - if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF) + if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF || + drm_dev->switch_power_state == DRM_SWITCH_POWER_DYNAMIC_OFF) return 0; + if (drm_dev->mode_config.num_crtc) + nouveau_fbcon_set_suspend(drm_dev, 1); + + nv_suspend_set_printk_level(NV_DBG_INFO); ret = nouveau_do_suspend(drm_dev); if (ret) return ret; @@ -523,6 +571,7 @@ int nouveau_pmops_suspend(struct device *dev) pci_save_state(pdev); pci_disable_device(pdev); pci_set_power_state(pdev, PCI_D3hot); + nv_suspend_set_printk_level(NV_DBG_DEBUG); return 0; } @@ -533,15 +582,15 @@ nouveau_do_resume(struct drm_device *dev) struct nouveau_drm *drm = nouveau_drm(dev); struct nouveau_cli *cli; - NV_INFO(drm, "re-enabling device...\n"); + NV_SUSPEND(drm, "re-enabling device...\n"); nouveau_agp_reset(drm); - NV_INFO(drm, "resuming kernel object tree...\n"); + NV_SUSPEND(drm, "resuming kernel object tree...\n"); nouveau_client_init(&drm->client.base); nouveau_agp_init(drm); - NV_INFO(drm, "resuming client object trees...\n"); + NV_SUSPEND(drm, "resuming client object trees...\n"); if (drm->fence && nouveau_fence(drm)->resume) nouveau_fence(drm)->resume(drm); @@ -553,9 +602,10 @@ nouveau_do_resume(struct drm_device *dev) nouveau_pm_resume(dev); if (dev->mode_config.num_crtc) { - NV_INFO(drm, "resuming display...\n"); - nouveau_display_resume(dev); + NV_SUSPEND(drm, "resuming display...\n"); + nouveau_display_repin(dev); } + return 0; } @@ -565,7 +615,8 @@ int nouveau_pmops_resume(struct device *dev) struct drm_device *drm_dev = pci_get_drvdata(pdev); int ret; - if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF) + if (drm_dev->switch_power_state == DRM_SWITCH_POWER_OFF || + drm_dev->switch_power_state == DRM_SWITCH_POWER_DYNAMIC_OFF) return 0; pci_set_power_state(pdev, PCI_D0); @@ -575,23 +626,54 @@ int nouveau_pmops_resume(struct device *dev) return ret; pci_set_master(pdev); - return nouveau_do_resume(drm_dev); + nv_suspend_set_printk_level(NV_DBG_INFO); + ret = nouveau_do_resume(drm_dev); + if (ret) { + nv_suspend_set_printk_level(NV_DBG_DEBUG); + return ret; + } + if (drm_dev->mode_config.num_crtc) + nouveau_fbcon_set_suspend(drm_dev, 0); + + nouveau_fbcon_zfill_all(drm_dev); + nouveau_display_resume(drm_dev); + nv_suspend_set_printk_level(NV_DBG_DEBUG); + return 0; } static int nouveau_pmops_freeze(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); struct drm_device *drm_dev = pci_get_drvdata(pdev); + int ret; + + nv_suspend_set_printk_level(NV_DBG_INFO); + if (drm_dev->mode_config.num_crtc) + nouveau_fbcon_set_suspend(drm_dev, 1); - return nouveau_do_suspend(drm_dev); + ret = nouveau_do_suspend(drm_dev); + nv_suspend_set_printk_level(NV_DBG_DEBUG); + return ret; } static int nouveau_pmops_thaw(struct device *dev) { struct pci_dev *pdev = to_pci_dev(dev); struct drm_device *drm_dev = pci_get_drvdata(pdev); + int ret; - return nouveau_do_resume(drm_dev); + nv_suspend_set_printk_level(NV_DBG_INFO); + ret = nouveau_do_resume(drm_dev); + if (ret) { + nv_suspend_set_printk_level(NV_DBG_DEBUG); + return ret; + } + if (drm_dev->mode_config.num_crtc) + nouveau_fbcon_set_suspend(drm_dev, 0); + nouveau_fbcon_zfill_all(drm_dev); + nouveau_display_resume(drm_dev); + nv_suspend_set_printk_level(NV_DBG_DEBUG); + return 0; } @@ -604,19 +686,24 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv) char name[32], tmpname[TASK_COMM_LEN]; int ret; + /* need to bring up power immediately if opening device */ + ret = pm_runtime_get_sync(dev->dev); + if (ret < 0) + return ret; + get_task_comm(tmpname, current); snprintf(name, sizeof(name), "%s[%d]", tmpname, pid_nr(fpriv->pid)); ret = nouveau_cli_create(pdev, name, sizeof(*cli), (void **)&cli); if (ret) - return ret; + goto out_suspend; if (nv_device(drm->device)->card_type >= NV_50) { ret = nouveau_vm_new(nv_device(drm->device), 0, (1ULL << 40), 0x1000, &cli->base.vm); if (ret) { nouveau_cli_destroy(cli); - return ret; + goto out_suspend; } } @@ -625,7 +712,12 @@ nouveau_drm_open(struct drm_device *dev, struct drm_file *fpriv) mutex_lock(&drm->client.mutex); list_add(&cli->head, &drm->clients); mutex_unlock(&drm->client.mutex); - return 0; + +out_suspend: + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); + + return ret; } static void @@ -634,12 +726,15 @@ nouveau_drm_preclose(struct drm_device *dev, struct drm_file *fpriv) struct nouveau_cli *cli = nouveau_cli(fpriv); struct nouveau_drm *drm = nouveau_drm(dev); + pm_runtime_get_sync(dev->dev); + if (cli->abi16) nouveau_abi16_fini(cli->abi16); mutex_lock(&drm->client.mutex); list_del(&cli->head); mutex_unlock(&drm->client.mutex); + } static void @@ -647,6 +742,8 @@ nouveau_drm_postclose(struct drm_device *dev, struct drm_file *fpriv) { struct nouveau_cli *cli = nouveau_cli(fpriv); nouveau_cli_destroy(cli); + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); } static const struct drm_ioctl_desc @@ -665,12 +762,30 @@ nouveau_ioctls[] = { DRM_IOCTL_DEF_DRV(NOUVEAU_GEM_INFO, nouveau_gem_ioctl_info, DRM_UNLOCKED|DRM_AUTH), }; +long nouveau_drm_ioctl(struct file *filp, + unsigned int cmd, unsigned long arg) +{ + struct drm_file *file_priv = filp->private_data; + struct drm_device *dev; + long ret; + dev = file_priv->minor->dev; + + ret = pm_runtime_get_sync(dev->dev); + if (ret < 0) + return ret; + + ret = drm_ioctl(filp, cmd, arg); + + pm_runtime_mark_last_busy(dev->dev); + pm_runtime_put_autosuspend(dev->dev); + return ret; +} static const struct file_operations nouveau_driver_fops = { .owner = THIS_MODULE, .open = drm_open, .release = drm_release, - .unlocked_ioctl = drm_ioctl, + .unlocked_ioctl = nouveau_drm_ioctl, .mmap = nouveau_ttm_mmap, .poll = drm_poll, .read = drm_read, @@ -753,6 +868,90 @@ nouveau_drm_pci_table[] = { {} }; +static int nouveau_pmops_runtime_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + int ret; + + if (nouveau_runtime_pm == 0) + return -EINVAL; + + drm_kms_helper_poll_disable(drm_dev); + vga_switcheroo_set_dynamic_switch(pdev, VGA_SWITCHEROO_OFF); + nouveau_switcheroo_optimus_dsm(); + ret = nouveau_do_suspend(drm_dev); + pci_save_state(pdev); + pci_disable_device(pdev); + pci_set_power_state(pdev, PCI_D3cold); + drm_dev->switch_power_state = DRM_SWITCH_POWER_DYNAMIC_OFF; + return ret; +} + +static int nouveau_pmops_runtime_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + struct nouveau_device *device = nouveau_dev(drm_dev); + int ret; + + if (nouveau_runtime_pm == 0) + return -EINVAL; + + pci_set_power_state(pdev, PCI_D0); + pci_restore_state(pdev); + ret = pci_enable_device(pdev); + if (ret) + return ret; + pci_set_master(pdev); + + ret = nouveau_do_resume(drm_dev); + nouveau_display_resume(drm_dev); + drm_kms_helper_poll_enable(drm_dev); + /* do magic */ + nv_mask(device, 0x88488, (1 << 25), (1 << 25)); + vga_switcheroo_set_dynamic_switch(pdev, VGA_SWITCHEROO_ON); + drm_dev->switch_power_state = DRM_SWITCH_POWER_ON; + return ret; +} + +static int nouveau_pmops_runtime_idle(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + struct drm_device *drm_dev = pci_get_drvdata(pdev); + struct nouveau_drm *drm = nouveau_drm(drm_dev); + struct drm_crtc *crtc; + + if (nouveau_runtime_pm == 0) + return -EBUSY; + + /* are we optimus enabled? */ + if (nouveau_runtime_pm == -1 && !nouveau_is_optimus() && !nouveau_is_v1_dsm()) { + DRM_DEBUG_DRIVER("failing to power off - not optimus\n"); + return -EBUSY; + } + + /* if we have a hdmi audio device - make sure it has a driver loaded */ + if (drm->hdmi_device) { + if (!drm->hdmi_device->driver) { + DRM_DEBUG_DRIVER("failing to power off - no HDMI audio driver loaded\n"); + pm_runtime_mark_last_busy(dev); + return -EBUSY; + } + } + + list_for_each_entry(crtc, &drm->dev->mode_config.crtc_list, head) { + if (crtc->enabled) { + DRM_DEBUG_DRIVER("failing to power off - crtc active\n"); + return -EBUSY; + } + } + pm_runtime_mark_last_busy(dev); + pm_runtime_autosuspend(dev); + /* we don't want the main rpm_idle to call suspend - we want to autosuspend */ + return 1; +} + static const struct dev_pm_ops nouveau_pm_ops = { .suspend = nouveau_pmops_suspend, .resume = nouveau_pmops_resume, @@ -760,6 +959,9 @@ static const struct dev_pm_ops nouveau_pm_ops = { .thaw = nouveau_pmops_thaw, .poweroff = nouveau_pmops_freeze, .restore = nouveau_pmops_resume, + .runtime_suspend = nouveau_pmops_runtime_suspend, + .runtime_resume = nouveau_pmops_runtime_resume, + .runtime_idle = nouveau_pmops_runtime_idle, }; static struct pci_driver diff --git a/drivers/gpu/drm/nouveau/nouveau_drm.h b/drivers/gpu/drm/nouveau/nouveau_drm.h index 41ff7e0d403..994fd6ec373 100644 --- a/drivers/gpu/drm/nouveau/nouveau_drm.h +++ b/drivers/gpu/drm/nouveau/nouveau_drm.h @@ -70,6 +70,8 @@ nouveau_cli(struct drm_file *fpriv) return fpriv ? fpriv->driver_priv : NULL; } +extern int nouveau_runtime_pm; + struct nouveau_drm { struct nouveau_cli client; struct drm_device *dev; @@ -129,6 +131,12 @@ struct nouveau_drm { /* power management */ struct nouveau_pm *pm; + + /* display power reference */ + bool have_disp_power_ref; + + struct dev_pm_domain vga_pm_domain; + struct pci_dev *hdmi_device; }; static inline struct nouveau_drm * @@ -146,6 +154,7 @@ nouveau_dev(struct drm_device *dev) int nouveau_pmops_suspend(struct device *); int nouveau_pmops_resume(struct device *); +#define NV_SUSPEND(cli, fmt, args...) nv_suspend((cli), fmt, ##args) #define NV_FATAL(cli, fmt, args...) nv_fatal((cli), fmt, ##args) #define NV_ERROR(cli, fmt, args...) nv_error((cli), fmt, ##args) #define NV_WARN(cli, fmt, args...) nv_warn((cli), fmt, ##args) diff --git a/drivers/gpu/drm/nouveau/nouveau_vga.c b/drivers/gpu/drm/nouveau/nouveau_vga.c index 40a09f11a60..81638d7f2ef 100644 --- a/drivers/gpu/drm/nouveau/nouveau_vga.c +++ b/drivers/gpu/drm/nouveau/nouveau_vga.c @@ -32,6 +32,9 @@ nouveau_switcheroo_set_state(struct pci_dev *pdev, { struct drm_device *dev = pci_get_drvdata(pdev); + if ((nouveau_is_optimus() || nouveau_is_v1_dsm()) && state == VGA_SWITCHEROO_OFF) + return; + if (state == VGA_SWITCHEROO_ON) { printk(KERN_ERR "VGA switcheroo: switched nouveau on\n"); dev->switch_power_state = DRM_SWITCH_POWER_CHANGING; @@ -78,8 +81,17 @@ void nouveau_vga_init(struct nouveau_drm *drm) { struct drm_device *dev = drm->dev; + bool runtime = false; vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode); - vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops, false); + + if (nouveau_runtime_pm == 1) + runtime = true; + if ((nouveau_runtime_pm == -1) && (nouveau_is_optimus() || nouveau_is_v1_dsm())) + runtime = true; + vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops, runtime); + + if (runtime && nouveau_is_v1_dsm() && !nouveau_is_optimus()) + vga_switcheroo_init_domain_pm_ops(drm->dev->dev, &drm->vga_pm_domain); } void diff --git a/drivers/gpu/drm/nouveau/nv50_display.c b/drivers/gpu/drm/nouveau/nv50_display.c index 8b40a36c1b5..9d2092a5ed3 100644 --- a/drivers/gpu/drm/nouveau/nv50_display.c +++ b/drivers/gpu/drm/nouveau/nv50_display.c @@ -1326,7 +1326,7 @@ static const struct drm_crtc_funcs nv50_crtc_func = { .cursor_set = nv50_crtc_cursor_set, .cursor_move = nv50_crtc_cursor_move, .gamma_set = nv50_crtc_gamma_set, - .set_config = drm_crtc_helper_set_config, + .set_config = nouveau_crtc_set_config, .destroy = nv50_crtc_destroy, .page_flip = nouveau_crtc_page_flip, }; |