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