I finally got back to debugging the HDMI audio interactions that stopped me last time,
This adds switcheroo + nouveau + hda_intel support for turning off the secondary GPU in optimus laptops, saving a lot of power.
The GPU should come back on for things like lspci on the devices or for DRI_PRIME= or X starting up.
Signed-off-by: Dave Airlie airlied@redhat.com
From: Dave Airlie airlied@dhcp-40-90.bne.redhat.com
For optimus and powerxpress muxless we really want the GPU driver deciding when to power up/down the GPU, not userspace.
This adds the ability for a driver to dynamically power up/down the GPU and remove the switcheroo from controlling it, the switcheroo reports the dynamic state to userspace also.
It also adds 2 power domains, one for machine where the power switch is controlled outside the GPU D3 state, so the powerdown ordering is done correctly, and the second for the hdmi audio device to make sure it can resume for PCI config space accesses.
v1.1: fix build with switcheroo off
v2: add power domain support for radeon and v1 nvidia dsms v2.1: fix typo in off case
v3: add audio power domain for hdmi audio + misc audio fixes
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/i915/i915_dma.c | 2 +- drivers/gpu/drm/nouveau/nouveau_vga.c | 2 +- drivers/gpu/drm/radeon/radeon_device.c | 2 +- drivers/gpu/vga/vga_switcheroo.c | 147 +++++++++++++++++++++++++++++++-- include/linux/vga_switcheroo.h | 13 ++- 5 files changed, 156 insertions(+), 10 deletions(-)
diff --git a/drivers/gpu/drm/i915/i915_dma.c b/drivers/gpu/drm/i915/i915_dma.c index b152068..5f930a1 100644 --- a/drivers/gpu/drm/i915/i915_dma.c +++ b/drivers/gpu/drm/i915/i915_dma.c @@ -1293,7 +1293,7 @@ static int i915_load_modeset_init(struct drm_device *dev)
intel_register_dsm_handler();
- ret = vga_switcheroo_register_client(dev->pdev, &i915_switcheroo_ops); + ret = vga_switcheroo_register_client(dev->pdev, &i915_switcheroo_ops, false); if (ret) goto cleanup_vga_client;
diff --git a/drivers/gpu/drm/nouveau/nouveau_vga.c b/drivers/gpu/drm/nouveau/nouveau_vga.c index 25d3495..40a09f1 100644 --- a/drivers/gpu/drm/nouveau/nouveau_vga.c +++ b/drivers/gpu/drm/nouveau/nouveau_vga.c @@ -79,7 +79,7 @@ nouveau_vga_init(struct nouveau_drm *drm) { struct drm_device *dev = drm->dev; vga_client_register(dev->pdev, dev, NULL, nouveau_vga_set_decode); - vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops); + vga_switcheroo_register_client(dev->pdev, &nouveau_switcheroo_ops, false); }
void diff --git a/drivers/gpu/drm/radeon/radeon_device.c b/drivers/gpu/drm/radeon/radeon_device.c index 82335e3..0610ca4 100644 --- a/drivers/gpu/drm/radeon/radeon_device.c +++ b/drivers/gpu/drm/radeon/radeon_device.c @@ -1269,7 +1269,7 @@ int radeon_device_init(struct radeon_device *rdev, /* this will fail for cards that aren't VGA class devices, just * ignore it */ vga_client_register(rdev->pdev, rdev, NULL, radeon_vga_set_decode); - vga_switcheroo_register_client(rdev->pdev, &radeon_switcheroo_ops); + vga_switcheroo_register_client(rdev->pdev, &radeon_switcheroo_ops, false);
r = radeon_init(rdev); if (r) diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index cf787e1..afb3956 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -27,6 +27,7 @@ #include <linux/pci.h> #include <linux/console.h> #include <linux/vga_switcheroo.h> +#include <linux/pm_runtime.h>
#include <linux/vgaarb.h>
@@ -37,6 +38,7 @@ struct vga_switcheroo_client { const struct vga_switcheroo_client_ops *ops; int id; bool active; + bool driver_power_control; struct list_head list; };
@@ -132,7 +134,7 @@ EXPORT_SYMBOL(vga_switcheroo_unregister_handler);
static int register_client(struct pci_dev *pdev, const struct vga_switcheroo_client_ops *ops, - int id, bool active) + int id, bool active, bool driver_power_control) { struct vga_switcheroo_client *client;
@@ -145,6 +147,7 @@ static int register_client(struct pci_dev *pdev, client->ops = ops; client->id = id; client->active = active; + client->driver_power_control = driver_power_control;
mutex_lock(&vgasr_mutex); list_add_tail(&client->list, &vgasr_priv.clients); @@ -160,10 +163,11 @@ static int register_client(struct pci_dev *pdev, }
int vga_switcheroo_register_client(struct pci_dev *pdev, - const struct vga_switcheroo_client_ops *ops) + const struct vga_switcheroo_client_ops *ops, + bool driver_power_control) { return register_client(pdev, ops, -1, - pdev == vga_default_device()); + pdev == vga_default_device(), driver_power_control); } EXPORT_SYMBOL(vga_switcheroo_register_client);
@@ -171,7 +175,7 @@ int vga_switcheroo_register_audio_client(struct pci_dev *pdev, const struct vga_switcheroo_client_ops *ops, int id, bool active) { - return register_client(pdev, ops, id | ID_BIT_AUDIO, active); + return register_client(pdev, ops, id | ID_BIT_AUDIO, active, false); } EXPORT_SYMBOL(vga_switcheroo_register_audio_client);
@@ -258,10 +262,11 @@ static int vga_switcheroo_show(struct seq_file *m, void *v) int i = 0; mutex_lock(&vgasr_mutex); list_for_each_entry(client, &vgasr_priv.clients, list) { - seq_printf(m, "%d:%s%s:%c:%s:%s\n", i, + seq_printf(m, "%d:%s%s:%c:%s%s:%s\n", i, client_id(client) == VGA_SWITCHEROO_DIS ? "DIS" : "IGD", client_is_vga(client) ? "" : "-Audio", client->active ? '+' : ' ', + client->driver_power_control ? "Dyn" : "", client->pwr_state ? "Pwr" : "Off", pci_name(client->pdev)); i++; @@ -277,6 +282,8 @@ static int vga_switcheroo_debugfs_open(struct inode *inode, struct file *file)
static int vga_switchon(struct vga_switcheroo_client *client) { + if (client->driver_power_control) + return 0; if (vgasr_priv.handler->power_state) vgasr_priv.handler->power_state(client->id, VGA_SWITCHEROO_ON); /* call the driver callback to turn on device */ @@ -287,6 +294,8 @@ static int vga_switchon(struct vga_switcheroo_client *client)
static int vga_switchoff(struct vga_switcheroo_client *client) { + if (client->driver_power_control) + return 0; /* call the driver callback to turn off device */ client->ops->set_gpu_state(client->pdev, VGA_SWITCHEROO_OFF); if (vgasr_priv.handler->power_state) @@ -402,6 +411,8 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, list_for_each_entry(client, &vgasr_priv.clients, list) { if (client->active || client_is_audio(client)) continue; + if (client->driver_power_control) + continue; set_audio_state(client->id, VGA_SWITCHEROO_OFF); if (client->pwr_state == VGA_SWITCHEROO_ON) vga_switchoff(client); @@ -413,6 +424,8 @@ vga_switcheroo_debugfs_write(struct file *filp, const char __user *ubuf, list_for_each_entry(client, &vgasr_priv.clients, list) { if (client->active || client_is_audio(client)) continue; + if (client->driver_power_control) + continue; if (client->pwr_state == VGA_SWITCHEROO_OFF) vga_switchon(client); set_audio_state(client->id, VGA_SWITCHEROO_ON); @@ -565,3 +578,127 @@ err: return err; } EXPORT_SYMBOL(vga_switcheroo_process_delayed_switch); + +static void vga_switcheroo_power_switch(struct pci_dev *pdev, enum vga_switcheroo_state state) +{ + struct vga_switcheroo_client *client; + + if (!vgasr_priv.handler->power_state) + return; + + client = find_client_from_pci(&vgasr_priv.clients, pdev); + if (!client) + return; + + if (!client->driver_power_control) + return; + + vgasr_priv.handler->power_state(client->id, state); +} + +/* force a PCI device to a certain state - mainly to turn off audio clients */ + +void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo_state dynamic) +{ + struct vga_switcheroo_client *client; + + client = find_client_from_pci(&vgasr_priv.clients, pdev); + if (!client) + return; + + if (!client->driver_power_control) + return; + + client->pwr_state = dynamic; + set_audio_state(client->id, dynamic); +} +EXPORT_SYMBOL(vga_switcheroo_set_dynamic_switch); + +/* switcheroo power domain */ +static int vga_switcheroo_runtime_suspend(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + int ret; + + ret = dev->bus->pm->runtime_suspend(dev); + if (ret) + return ret; + + vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_OFF); + return 0; +} + +static int vga_switcheroo_runtime_resume(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + int ret; + + vga_switcheroo_power_switch(pdev, VGA_SWITCHEROO_ON); + ret = dev->bus->pm->runtime_resume(dev); + if (ret) + return ret; + + return 0; +} + +/* this version is for the case where the power switch is separate + to the device being powered down. */ +int vga_switcheroo_init_domain_pm_ops(struct device *dev, struct dev_pm_domain *domain) +{ + /* copy over all the bus versions */ + if (dev->bus && dev->bus->pm) { + domain->ops = *dev->bus->pm; + domain->ops.runtime_suspend = vga_switcheroo_runtime_suspend; + domain->ops.runtime_resume = vga_switcheroo_runtime_resume; + + dev->pm_domain = domain; + return 0; + } + dev->pm_domain = NULL; + return -EINVAL; +} +EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_ops); + +static int vga_switcheroo_runtime_resume_hdmi_audio(struct device *dev) +{ + struct pci_dev *pdev = to_pci_dev(dev); + int ret; + struct vga_switcheroo_client *client, *found = NULL; + + /* we need to check if we have to switch back on the video + device so the audio device can come back */ + list_for_each_entry(client, &vgasr_priv.clients, list) { + if ((client->pdev->devfn & ~(0x7)) == (pdev->devfn & ~(0x7)) && client_is_vga(client)) { + found = client; + ret = pm_runtime_get_sync(&client->pdev->dev); + if (ret) { + if (ret != 1) + return ret; + } + break; + } + } + ret = dev->bus->pm->runtime_resume(dev); + if (ret) + return ret; + + /* put the reference for the gpu */ + if (found) + pm_runtime_put_autosuspend(&found->pdev->dev); + return 0; +} + +int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct dev_pm_domain *domain) +{ + /* copy over all the bus versions */ + if (dev->bus && dev->bus->pm) { + domain->ops = *dev->bus->pm; + domain->ops.runtime_resume = vga_switcheroo_runtime_resume_hdmi_audio; + + dev->pm_domain = domain; + return 0; + } + dev->pm_domain = NULL; + return -EINVAL; +} +EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_optimus_hdmi_audio); diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index ddb419c..502073a 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -45,7 +45,8 @@ struct vga_switcheroo_client_ops { #if defined(CONFIG_VGA_SWITCHEROO) void vga_switcheroo_unregister_client(struct pci_dev *dev); int vga_switcheroo_register_client(struct pci_dev *dev, - const struct vga_switcheroo_client_ops *ops); + const struct vga_switcheroo_client_ops *ops, + bool driver_power_control); int vga_switcheroo_register_audio_client(struct pci_dev *pdev, const struct vga_switcheroo_client_ops *ops, int id, bool active); @@ -60,11 +61,15 @@ int vga_switcheroo_process_delayed_switch(void);
int vga_switcheroo_get_client_state(struct pci_dev *dev);
+void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo_state dynamic); + +int vga_switcheroo_init_domain_pm_ops(struct device *dev, struct dev_pm_domain *domain); +int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct dev_pm_domain *domain); #else
static inline void vga_switcheroo_unregister_client(struct pci_dev *dev) {} static inline int vga_switcheroo_register_client(struct pci_dev *dev, - const struct vga_switcheroo_client_ops *ops) { return 0; } + const struct vga_switcheroo_client_ops *ops, bool driver_power_control) { return 0; } static inline void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info) {} static inline int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler) { return 0; } static inline int vga_switcheroo_register_audio_client(struct pci_dev *pdev, @@ -74,6 +79,10 @@ static inline void vga_switcheroo_unregister_handler(void) {} static inline int vga_switcheroo_process_delayed_switch(void) { return 0; } static inline int vga_switcheroo_get_client_state(struct pci_dev *dev) { return VGA_SWITCHEROO_ON; }
+static inline void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo_state dynamic) {} + +static inline int vga_switcheroo_init_domain_pm_ops(struct device *dev, struct dev_pm_domain *domain) { return -EINVAL; } +static inline int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct dev_pm_domain *domain) { return -EINVAL; }
#endif #endif /* _LINUX_VGA_SWITCHEROO_H_ */
Hi Dave,
I don't know anything of PM domains, but there was one minor thing I wanted to mention, see below.
On Monday 29 July 2013 16:06:56 Dave Airlie wrote:
From: Dave Airlie airlied@dhcp-40-90.bne.redhat.com
Something went wrong here I guess?
[..]
+static int vga_switcheroo_runtime_resume_hdmi_audio(struct device *dev) +{
- struct pci_dev *pdev = to_pci_dev(dev);
- int ret;
- struct vga_switcheroo_client *client, *found = NULL;
- /* we need to check if we have to switch back on the video
device so the audio device can come back */
- list_for_each_entry(client, &vgasr_priv.clients, list) {
if ((client->pdev->devfn & ~(0x7)) == (pdev->devfn & ~(0x7)) &&
client_is_vga(client)) {
Less obfuscated:
PCI_SLOT(client->pdev->devfn) == PCI_SLOT(pdev->devfn)
found = client;
ret = pm_runtime_get_sync(&client->pdev->dev);
if (ret) {
if (ret != 1)
return ret;
}
break;
}
- }
- ret = dev->bus->pm->runtime_resume(dev);
- if (ret)
return ret;
- /* put the reference for the gpu */
- if (found)
pm_runtime_put_autosuspend(&found->pdev->dev);
- return 0;
+} [..]
Regards, Peter
From: Dave Airlie airlied@redhat.com
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/drm_fops.c | 2 +- include/drm/drmP.h | 1 + 2 files changed, 2 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/drm/drm_fops.c b/drivers/gpu/drm/drm_fops.c index 3a24385..d5429ee 100644 --- a/drivers/gpu/drm/drm_fops.c +++ b/drivers/gpu/drm/drm_fops.c @@ -257,7 +257,7 @@ static int drm_open_helper(struct inode *inode, struct file *filp, return -EBUSY; /* No exclusive opens */ if (!drm_cpu_valid()) return -EINVAL; - if (dev->switch_power_state != DRM_SWITCH_POWER_ON) + if (dev->switch_power_state != DRM_SWITCH_POWER_ON && dev->switch_power_state != DRM_SWITCH_POWER_DYNAMIC_OFF) return -EINVAL;
DRM_DEBUG("pid = %d, minor = %d\n", task_pid_nr(current), minor_id); diff --git a/include/drm/drmP.h b/include/drm/drmP.h index 12083dc..7f8acaf 100644 --- a/include/drm/drmP.h +++ b/include/drm/drmP.h @@ -1223,6 +1223,7 @@ struct drm_device { #define DRM_SWITCH_POWER_ON 0 #define DRM_SWITCH_POWER_OFF 1 #define DRM_SWITCH_POWER_CHANGING 2 +#define DRM_SWITCH_POWER_DYNAMIC_OFF 3
static __inline__ int drm_core_check_feature(struct drm_device *dev, int feature)
From: Dave Airlie airlied@redhat.com
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.
Signed-off-by: Dave Airlie airlied@redhat.com --- drivers/gpu/drm/nouveau/core/core/printk.c | 19 ++ drivers/gpu/drm/nouveau/core/include/core/printk.h | 13 ++ drivers/gpu/drm/nouveau/core/subdev/bios/init.c | 2 +- drivers/gpu/drm/nouveau/core/subdev/mc/base.c | 5 + drivers/gpu/drm/nouveau/dispnv04/crtc.c | 49 +++- drivers/gpu/drm/nouveau/nouveau_acpi.c | 42 +++- drivers/gpu/drm/nouveau/nouveau_connector.c | 27 ++- drivers/gpu/drm/nouveau/nouveau_display.c | 12 +- drivers/gpu/drm/nouveau/nouveau_display.h | 2 + drivers/gpu/drm/nouveau/nouveau_drm.c | 247 +++++++++++++++++++-- drivers/gpu/drm/nouveau/nouveau_drm.h | 9 + drivers/gpu/drm/nouveau/nouveau_vga.c | 14 +- 12 files changed, 399 insertions(+), 42 deletions(-)
diff --git a/drivers/gpu/drm/nouveau/core/core/printk.c b/drivers/gpu/drm/nouveau/core/core/printk.c index 6161eaf..52fb2aa 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 febed2e..d87836e 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 0687e64..2e11ea0 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 1c0330b..b6afd98 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,7 @@ nouveau_mc_intr(int irq, void *arg) nv_error(pmc, "unknown intr 0x%08x\n", stat); }
+ 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 0782bd2..6413552 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 d97f200..dd7d2e1 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 4da776f..c5b36f9 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 907d20e..448022e 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 1ea3e47..229ac3f 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, @@ -73,6 +74,7 @@ int nouveau_display_dumb_destroy(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 6197266..1565684 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_autosuspend(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 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, .fasync = drm_fasync, @@ -753,6 +868,87 @@ 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; + u32 val; + + 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 */ + val = nv_rd32(device, 0x88488); + val |= (1 << 25); + nv_wr32(device, 0x88488, val); + + 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) + 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; + } + } + return 0; +} + static const struct dev_pm_ops nouveau_pm_ops = { .suspend = nouveau_pmops_suspend, .resume = nouveau_pmops_resume, @@ -760,6 +956,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 41ff7e0..994fd6e 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 40a09f1..81638d7 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
Add support for HDMI audio device on VGA cards that powerdown to D3cold using non-standard ACPI/PCI infrastructure (optimus).
This does a couple of things to make it work:
a) add a set of power ops for the hdmi domain, and enables them via vga_switcheroo when we are a switcheroo controlled card. This just replaces the runtime resume operation so that when the card is in D3cold the userspace pci config space access via sysfs, the vga switcheroon runtime resume gets called first and it calls the GPU resume callback before calling the sound card runtime resume.
b) standard ACPI/PCI stacks won't put a device into D3cold without an ACPI handle, but since the hdmi audio devices on gpus don't have an ACPI handle, we need to manually force the device into D3cold after suspend from the switcheroo path only.
c) don't try and do runtime s/r when the GPU is off.
d) call runtime suspend/resume during switcheroo suspend/resume this is to make sure the runtime stack knows to try and resume the hdmi audio device for pci config space access.
Signed-off-by: Dave Airlie airlied@redhat.com --- sound/pci/hda/hda_intel.c | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-)
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 8860dd5..4b4d05b 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -555,6 +555,9 @@ struct azx { #ifdef CONFIG_SND_HDA_DSP_LOADER struct azx_dev saved_azx_dev; #endif + + /* secondary power domain for hdmi audio under vga device */ + struct dev_pm_domain hdmi_pm_domain; };
#define CREATE_TRACE_POINTS @@ -2898,7 +2901,7 @@ static int param_set_xint(const char *val, const struct kernel_param *kp) /* * power management */ -static int azx_suspend(struct device *dev) +static int azx_do_suspend(struct device *dev, pci_power_t state) { struct pci_dev *pci = to_pci_dev(dev); struct snd_card *card = dev_get_drvdata(dev); @@ -2920,16 +2923,30 @@ static int azx_suspend(struct device *dev) free_irq(chip->irq, chip); chip->irq = -1; } + + /* + * for vga switcheroo suspend we need to + * force runtime suspend so lspci works. + */ + if (state == PCI_D3cold) + pm_runtime_suspend(&pci->dev); + if (chip->msi) pci_disable_msi(chip->pci); pci_disable_device(pci); pci_save_state(pci); - pci_set_power_state(pci, PCI_D3hot); + + pci_set_power_state(pci, state); if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) hda_display_power(false); return 0; }
+static int azx_suspend(struct device *dev) +{ + return azx_do_suspend(dev, PCI_D3hot); +} + static int azx_resume(struct device *dev) { struct pci_dev *pci = to_pci_dev(dev); @@ -2971,6 +2988,9 @@ static int azx_runtime_suspend(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data;
+ if (chip->disabled) + return 0; + azx_stop_chip(chip); azx_enter_link_reset(chip); azx_clear_irq_pending(chip); @@ -2984,6 +3004,9 @@ static int azx_runtime_resume(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data;
+ if (chip->disabled) + return 0; + if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) hda_display_power(true); azx_init_pci(chip); @@ -2996,6 +3019,9 @@ static int azx_runtime_idle(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data;
+ if (chip->disabled) + return 0; + if (!power_save_controller || !(chip->driver_caps & AZX_DCAPS_PM_RUNTIME)) return -EBUSY; @@ -3078,7 +3104,11 @@ static void azx_vs_set_state(struct pci_dev *pci, "%s: %s via VGA-switcheroo\n", pci_name(chip->pci), disabled ? "Disabling" : "Enabling"); if (disabled) { - azx_suspend(&pci->dev); + azx_do_suspend(&pci->dev, PCI_D3cold); + /* when we get suspended by vga switcheroo we end up in D3cold, + * however we have no ACPI handle, so pci/acpi can't put us there, + * put ourselves there */ + pci->current_state = PCI_D3cold; chip->disabled = true; if (snd_hda_lock_devices(chip->bus)) snd_printk(KERN_WARNING SFX "%s: Cannot lock devices!\n", @@ -3087,6 +3117,7 @@ static void azx_vs_set_state(struct pci_dev *pci, snd_hda_unlock_devices(chip->bus); chip->disabled = false; azx_resume(&pci->dev); + pm_runtime_suspend(&pci->dev); } } } @@ -3139,6 +3170,9 @@ static int register_vga_switcheroo(struct azx *chip) if (err < 0) return err; chip->vga_switcheroo_registered = 1; + + /* register as an optimus hdmi audio power domain */ + vga_switcheroo_init_domain_pm_optimus_hdmi_audio(&chip->pci->dev, &chip->hdmi_pm_domain); return 0; } #else
At Mon, 29 Jul 2013 16:06:59 +1000, Dave Airlie wrote:
Add support for HDMI audio device on VGA cards that powerdown to D3cold using non-standard ACPI/PCI infrastructure (optimus).
This does a couple of things to make it work:
a) add a set of power ops for the hdmi domain, and enables them via vga_switcheroo when we are a switcheroo controlled card. This just replaces the runtime resume operation so that when the card is in D3cold the userspace pci config space access via sysfs, the vga switcheroon runtime resume gets called first and it calls the GPU resume callback before calling the sound card runtime resume.
b) standard ACPI/PCI stacks won't put a device into D3cold without an ACPI handle, but since the hdmi audio devices on gpus don't have an ACPI handle, we need to manually force the device into D3cold after suspend from the switcheroo path only.
c) don't try and do runtime s/r when the GPU is off.
d) call runtime suspend/resume during switcheroo suspend/resume this is to make sure the runtime stack knows to try and resume the hdmi audio device for pci config space access.
Signed-off-by: Dave Airlie airlied@redhat.com
sound/pci/hda/hda_intel.c | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-)
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 8860dd5..4b4d05b 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -555,6 +555,9 @@ struct azx { #ifdef CONFIG_SND_HDA_DSP_LOADER struct azx_dev saved_azx_dev; #endif
- /* secondary power domain for hdmi audio under vga device */
- struct dev_pm_domain hdmi_pm_domain;
};
#define CREATE_TRACE_POINTS @@ -2898,7 +2901,7 @@ static int param_set_xint(const char *val, const struct kernel_param *kp) /*
- power management
*/ -static int azx_suspend(struct device *dev) +static int azx_do_suspend(struct device *dev, pci_power_t state) { struct pci_dev *pci = to_pci_dev(dev); struct snd_card *card = dev_get_drvdata(dev); @@ -2920,16 +2923,30 @@ static int azx_suspend(struct device *dev) free_irq(chip->irq, chip); chip->irq = -1; }
- /*
* for vga switcheroo suspend we need to
* force runtime suspend so lspci works.
*/
- if (state == PCI_D3cold)
pm_runtime_suspend(&pci->dev);
- if (chip->msi) pci_disable_msi(chip->pci); pci_disable_device(pci); pci_save_state(pci);
- pci_set_power_state(pci, PCI_D3hot);
- pci_set_power_state(pci, state); if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) hda_display_power(false); return 0;
}
+static int azx_suspend(struct device *dev) +{
- return azx_do_suspend(dev, PCI_D3hot);
+}
static int azx_resume(struct device *dev) { struct pci_dev *pci = to_pci_dev(dev); @@ -2971,6 +2988,9 @@ static int azx_runtime_suspend(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data;
- if (chip->disabled)
return 0;
- azx_stop_chip(chip); azx_enter_link_reset(chip); azx_clear_irq_pending(chip);
@@ -2984,6 +3004,9 @@ static int azx_runtime_resume(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data;
- if (chip->disabled)
return 0;
- if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) hda_display_power(true); azx_init_pci(chip);
@@ -2996,6 +3019,9 @@ static int azx_runtime_idle(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data;
- if (chip->disabled)
return 0;
- if (!power_save_controller || !(chip->driver_caps & AZX_DCAPS_PM_RUNTIME)) return -EBUSY;
@@ -3078,7 +3104,11 @@ static void azx_vs_set_state(struct pci_dev *pci, "%s: %s via VGA-switcheroo\n", pci_name(chip->pci), disabled ? "Disabling" : "Enabling"); if (disabled) {
azx_suspend(&pci->dev);
azx_do_suspend(&pci->dev, PCI_D3cold);
/* when we get suspended by vga switcheroo we end up in D3cold,
* however we have no ACPI handle, so pci/acpi can't put us there,
* put ourselves there */
pci->current_state = PCI_D3cold; chip->disabled = true; if (snd_hda_lock_devices(chip->bus)) snd_printk(KERN_WARNING SFX "%s: Cannot lock devices!\n",
@@ -3087,6 +3117,7 @@ static void azx_vs_set_state(struct pci_dev *pci, snd_hda_unlock_devices(chip->bus); chip->disabled = false; azx_resume(&pci->dev);
pm_runtime_suspend(&pci->dev);
Is this meant as pm_runtime_resume()?
thanks,
Takashi
}
} } @@ -3139,6 +3170,9 @@ static int register_vga_switcheroo(struct azx *chip) if (err < 0) return err; chip->vga_switcheroo_registered = 1;
- /* register as an optimus hdmi audio power domain */
- vga_switcheroo_init_domain_pm_optimus_hdmi_audio(&chip->pci->dev, &chip->hdmi_pm_domain); return 0;
}
#else
1.8.2.1
On Mon, Jul 29, 2013 at 10:46 PM, Takashi Iwai tiwai@suse.de wrote:
At Mon, 29 Jul 2013 16:06:59 +1000, Dave Airlie wrote:
Add support for HDMI audio device on VGA cards that powerdown to D3cold using non-standard ACPI/PCI infrastructure (optimus).
This does a couple of things to make it work:
a) add a set of power ops for the hdmi domain, and enables them via vga_switcheroo when we are a switcheroo controlled card. This just replaces the runtime resume operation so that when the card is in D3cold the userspace pci config space access via sysfs, the vga switcheroon runtime resume gets called first and it calls the GPU resume callback before calling the sound card runtime resume.
b) standard ACPI/PCI stacks won't put a device into D3cold without an ACPI handle, but since the hdmi audio devices on gpus don't have an ACPI handle, we need to manually force the device into D3cold after suspend from the switcheroo path only.
c) don't try and do runtime s/r when the GPU is off.
d) call runtime suspend/resume during switcheroo suspend/resume this is to make sure the runtime stack knows to try and resume the hdmi audio device for pci config space access.
Signed-off-by: Dave Airlie airlied@redhat.com
sound/pci/hda/hda_intel.c | 40 +++++++++++++++++++++++++++++++++++++--- 1 file changed, 37 insertions(+), 3 deletions(-)
diff --git a/sound/pci/hda/hda_intel.c b/sound/pci/hda/hda_intel.c index 8860dd5..4b4d05b 100644 --- a/sound/pci/hda/hda_intel.c +++ b/sound/pci/hda/hda_intel.c @@ -555,6 +555,9 @@ struct azx { #ifdef CONFIG_SND_HDA_DSP_LOADER struct azx_dev saved_azx_dev; #endif
/* secondary power domain for hdmi audio under vga device */
struct dev_pm_domain hdmi_pm_domain;
};
#define CREATE_TRACE_POINTS @@ -2898,7 +2901,7 @@ static int param_set_xint(const char *val, const struct kernel_param *kp) /*
- power management
*/ -static int azx_suspend(struct device *dev) +static int azx_do_suspend(struct device *dev, pci_power_t state) { struct pci_dev *pci = to_pci_dev(dev); struct snd_card *card = dev_get_drvdata(dev); @@ -2920,16 +2923,30 @@ static int azx_suspend(struct device *dev) free_irq(chip->irq, chip); chip->irq = -1; }
/*
* for vga switcheroo suspend we need to
* force runtime suspend so lspci works.
*/
if (state == PCI_D3cold)
pm_runtime_suspend(&pci->dev);
if (chip->msi) pci_disable_msi(chip->pci); pci_disable_device(pci); pci_save_state(pci);
pci_set_power_state(pci, PCI_D3hot);
pci_set_power_state(pci, state); if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) hda_display_power(false); return 0;
}
+static int azx_suspend(struct device *dev) +{
return azx_do_suspend(dev, PCI_D3hot);
+}
static int azx_resume(struct device *dev) { struct pci_dev *pci = to_pci_dev(dev); @@ -2971,6 +2988,9 @@ static int azx_runtime_suspend(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data;
if (chip->disabled)
return 0;
azx_stop_chip(chip); azx_enter_link_reset(chip); azx_clear_irq_pending(chip);
@@ -2984,6 +3004,9 @@ static int azx_runtime_resume(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data;
if (chip->disabled)
return 0;
if (chip->driver_caps & AZX_DCAPS_I915_POWERWELL) hda_display_power(true); azx_init_pci(chip);
@@ -2996,6 +3019,9 @@ static int azx_runtime_idle(struct device *dev) struct snd_card *card = dev_get_drvdata(dev); struct azx *chip = card->private_data;
if (chip->disabled)
return 0;
if (!power_save_controller || !(chip->driver_caps & AZX_DCAPS_PM_RUNTIME)) return -EBUSY;
@@ -3078,7 +3104,11 @@ static void azx_vs_set_state(struct pci_dev *pci, "%s: %s via VGA-switcheroo\n", pci_name(chip->pci), disabled ? "Disabling" : "Enabling"); if (disabled) {
azx_suspend(&pci->dev);
azx_do_suspend(&pci->dev, PCI_D3cold);
/* when we get suspended by vga switcheroo we end up in D3cold,
* however we have no ACPI handle, so pci/acpi can't put us there,
* put ourselves there */
pci->current_state = PCI_D3cold; chip->disabled = true; if (snd_hda_lock_devices(chip->bus)) snd_printk(KERN_WARNING SFX "%s: Cannot lock devices!\n",
@@ -3087,6 +3117,7 @@ static void azx_vs_set_state(struct pci_dev *pci, snd_hda_unlock_devices(chip->bus); chip->disabled = false; azx_resume(&pci->dev);
pm_runtime_suspend(&pci->dev);
Is this meant as pm_runtime_resume()?
Oh probably that seems likely alright!
Dave.
dri-devel@lists.freedesktop.org