gpu/vga_switcheroo: add driver control power feature. (v3)
authorDave Airlie <airlied@dhcp-40-90.bne.redhat.com>
Mon, 10 Sep 2012 02:28:36 +0000 (12:28 +1000)
committerDave Airlie <airlied@redhat.com>
Thu, 29 Aug 2013 03:30:21 +0000 (13:30 +1000)
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

v4: use PCI_SLOT macro, drop power reference on hdmi audio resume
failure also.

Signed-off-by: Dave Airlie <airlied@redhat.com>
drivers/gpu/drm/i915/i915_dma.c
drivers/gpu/drm/nouveau/nouveau_vga.c
drivers/gpu/drm/radeon/radeon_device.c
drivers/gpu/vga/vga_switcheroo.c
include/linux/vga_switcheroo.h

index 0adfe4000871c3f81497ffacac1fd6ab79c70f15..54f86242e80ece70a0da1770534909eddf00e539 100644 (file)
@@ -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;
 
index 25d3495725ebc3c4d8b243e84b8714a929c20b12..40a09f11a600b54d315df501b7745a012ccdf4a0 100644 (file)
@@ -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
index 82335e38ec4f268e05f9d4bc1cbd875eae8523a5..0610ca4fb6a32637288fe130d2b6434a6ebb2a70 100644 (file)
@@ -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)
index cf787e1d93227f5d84e5af477b9f1a49f418c23c..ec0ae2d1686a3150c03403fa42524aca2111afa4 100644 (file)
@@ -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 (PCI_SLOT(client->pdev->devfn) == PCI_SLOT(pdev->devfn) && 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);
+
+       /* put the reference for the gpu */
+       if (found) {
+               pm_runtime_mark_last_busy(&found->pdev->dev);
+               pm_runtime_put_autosuspend(&found->pdev->dev);
+       }
+       return ret;
+}
+
+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);
index ddb419cf4530339f37d7e239d134cc4222d3d900..502073a53dd320b115925a654ef7dabdbd49872e 100644 (file)
@@ -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_ */