diff options
-rw-r--r-- | drivers/gpu/drm/i915/i915_dma.c | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/nouveau/nouveau_vga.c | 2 | ||||
-rw-r--r-- | drivers/gpu/drm/radeon/radeon_device.c | 2 | ||||
-rw-r--r-- | drivers/gpu/vga/vga_switcheroo.c | 147 | ||||
-rw-r--r-- | 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 0adfe40..54f8624 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..ec0ae2d 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 (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); 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_ */ |