|
@@ -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
|