| From 8b8040b69f7c13d44347e614bc29ca404411c7e8 Mon Sep 17 00:00:00 2001 |
| From: Dave Airlie <airlied@dhcp-40-90.bne.redhat.com> |
| Date: Mon, 10 Sep 2012 12:28:36 +1000 |
| Subject: gpu/vga_switcheroo: add driver control power feature. (v3) |
| |
| 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> |
| (cherry picked from commit 0d69704ae348c03bc216b01e32a0e9a2372be419) |
| Signed-off-by: Darren Hart <dvhart@linux.intel.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(-) |
| |
| --- a/drivers/gpu/drm/i915/i915_dma.c |
| +++ b/drivers/gpu/drm/i915/i915_dma.c |
| @@ -1304,7 +1304,7 @@ static int i915_load_modeset_init(struct |
| |
| 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; |
| |
| --- 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 |
| --- a/drivers/gpu/drm/radeon/radeon_device.c |
| +++ b/drivers/gpu/drm/radeon/radeon_device.c |
| @@ -1169,7 +1169,7 @@ int radeon_device_init(struct radeon_dev |
| /* 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) |
| --- 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_ |
| |
| 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_de |
| 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_de |
| } |
| |
| 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 |
| 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 se |
| 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(s |
| |
| 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_switc |
| |
| 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 |
| 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 |
| 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); |
| --- 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_switc |
| |
| 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_unregi |
| 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_ */ |