]> git.kernelconcepts.de Git - karo-tx-linux.git/blobdiff - drivers/gpu/vga/vga_switcheroo.c
gpu/vga_switcheroo: add driver control power feature. (v3)
[karo-tx-linux.git] / drivers / gpu / vga / vga_switcheroo.c
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);