The Apple GMUX switcheroo code doesn't really work that well - there's no straightforward way to ensure that drivers are loaded in the correct order, and no mechanism to probe displays without performing a full switch. This patchset adds infrastructural support to switcheroo and functionality to GMUX in order to fix things up. This won't actually *work* in its current form - it needs additional patches to the GPU drivers, which are currently in a somewhat hacky state.
From: Seth Forshee seth.forshee@canonical.com
During graphics driver initialization its useful to be able to mux only the DDC to the inactive client in order to read the EDID. Add a switch_ddc callback to allow capable handlers to provide this functionality, and add vga_switcheroo_switch_ddc() to allow DRM to mux only the DDC.
Signed-off-by: Seth Forshee seth.forshee@canonical.com --- drivers/gpu/vga/vga_switcheroo.c | 38 +++++++++++++++++++++++++++++++++++++- include/linux/vga_switcheroo.h | 4 ++++ 2 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index ec0ae2d..dd1d587 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -256,6 +256,28 @@ void vga_switcheroo_client_fb_set(struct pci_dev *pdev, } EXPORT_SYMBOL(vga_switcheroo_client_fb_set);
+int vga_switcheroo_switch_ddc(struct pci_dev *pdev) +{ + int ret = 0; + int id; + + mutex_lock(&vgasr_mutex); + + if (!vgasr_priv.handler) { + ret = -ENODEV; + goto out; + } + + if (vgasr_priv.handler->switch_ddc) { + id = vgasr_priv.handler->get_client_id(pdev); + ret = vgasr_priv.handler->switch_ddc(id); + } +out: + mutex_unlock(&vgasr_mutex); + return ret; +} +EXPORT_SYMBOL(vga_switcheroo_switch_ddc); + static int vga_switcheroo_show(struct seq_file *m, void *v) { struct vga_switcheroo_client *client; @@ -353,9 +375,15 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) console_unlock(); }
+ if (vgasr_priv.handler->switch_ddc) { + ret = vgasr_priv.handler->switch_ddc(new_client->id); + if (ret) + return ret; + } + ret = vgasr_priv.handler->switchto(new_client->id); if (ret) - return ret; + goto restore_ddc;
if (new_client->ops->reprobe) new_client->ops->reprobe(new_client->pdev); @@ -367,6 +395,14 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client)
new_client->active = true; return 0; + +restore_ddc: + if (vgasr_priv.handler->switch_ddc) { + int id = (new_client->id == VGA_SWITCHEROO_IGD) ? + VGA_SWITCHEROO_DIS : VGA_SWITCHEROO_IGD; + vgasr_priv.handler->switch_ddc(id); + } + return ret; }
static bool check_can_switch(void) diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index 502073a..37d6850 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -29,6 +29,7 @@ enum vga_switcheroo_client_id { };
struct vga_switcheroo_handler { + int (*switch_ddc)(enum vga_switcheroo_client_id id); int (*switchto)(enum vga_switcheroo_client_id id); int (*power_state)(enum vga_switcheroo_client_id id, enum vga_switcheroo_state state); @@ -54,6 +55,8 @@ int vga_switcheroo_register_audio_client(struct pci_dev *pdev, void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info);
+int vga_switcheroo_switch_ddc(struct pci_dev *pdev); + int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler); void vga_switcheroo_unregister_handler(void);
@@ -71,6 +74,7 @@ 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, bool driver_power_control) { return 0; } static inline void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info) {} +static inline void vga_switcheroo_switch_ddc(struct pci_dev *pdev) { return NULL; } 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, const struct vga_switcheroo_client_ops *ops,
On Sun, 01 Jun 2014, Matthew Garrett matthew.garrett@nebula.com wrote:
From: Seth Forshee seth.forshee@canonical.com
During graphics driver initialization its useful to be able to mux only the DDC to the inactive client in order to read the EDID. Add a switch_ddc callback to allow capable handlers to provide this functionality, and add vga_switcheroo_switch_ddc() to allow DRM to mux only the DDC.
Signed-off-by: Seth Forshee seth.forshee@canonical.com
drivers/gpu/vga/vga_switcheroo.c | 38 +++++++++++++++++++++++++++++++++++++- include/linux/vga_switcheroo.h | 4 ++++ 2 files changed, 41 insertions(+), 1 deletion(-)
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index ec0ae2d..dd1d587 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -256,6 +256,28 @@ void vga_switcheroo_client_fb_set(struct pci_dev *pdev, } EXPORT_SYMBOL(vga_switcheroo_client_fb_set);
+int vga_switcheroo_switch_ddc(struct pci_dev *pdev) +{
- int ret = 0;
- int id;
- mutex_lock(&vgasr_mutex);
- if (!vgasr_priv.handler) {
ret = -ENODEV;
goto out;
- }
- if (vgasr_priv.handler->switch_ddc) {
id = vgasr_priv.handler->get_client_id(pdev);
ret = vgasr_priv.handler->switch_ddc(id);
- }
+out:
- mutex_unlock(&vgasr_mutex);
- return ret;
+} +EXPORT_SYMBOL(vga_switcheroo_switch_ddc);
static int vga_switcheroo_show(struct seq_file *m, void *v) { struct vga_switcheroo_client *client; @@ -353,9 +375,15 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) console_unlock(); }
- if (vgasr_priv.handler->switch_ddc) {
ret = vgasr_priv.handler->switch_ddc(new_client->id);
if (ret)
return ret;
- }
- ret = vgasr_priv.handler->switchto(new_client->id); if (ret)
return ret;
goto restore_ddc;
if (new_client->ops->reprobe) new_client->ops->reprobe(new_client->pdev);
@@ -367,6 +395,14 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client)
new_client->active = true; return 0;
+restore_ddc:
- if (vgasr_priv.handler->switch_ddc) {
int id = (new_client->id == VGA_SWITCHEROO_IGD) ?
VGA_SWITCHEROO_DIS : VGA_SWITCHEROO_IGD;
vgasr_priv.handler->switch_ddc(id);
- }
- return ret;
}
static bool check_can_switch(void) diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index 502073a..37d6850 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -29,6 +29,7 @@ enum vga_switcheroo_client_id { };
struct vga_switcheroo_handler {
- int (*switch_ddc)(enum vga_switcheroo_client_id id); int (*switchto)(enum vga_switcheroo_client_id id); int (*power_state)(enum vga_switcheroo_client_id id, enum vga_switcheroo_state state);
@@ -54,6 +55,8 @@ int vga_switcheroo_register_audio_client(struct pci_dev *pdev, void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info);
+int vga_switcheroo_switch_ddc(struct pci_dev *pdev);
int vga_switcheroo_register_handler(struct vga_switcheroo_handler *handler); void vga_switcheroo_unregister_handler(void);
@@ -71,6 +74,7 @@ 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, bool driver_power_control) { return 0; } static inline void vga_switcheroo_client_fb_set(struct pci_dev *dev, struct fb_info *info) {} +static inline void vga_switcheroo_switch_ddc(struct pci_dev *pdev) { return NULL; }
Not really reviewing, but this three-way contradiction of return types caught my eye.
BR, Jani.
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, const struct vga_switcheroo_client_ops *ops, -- 1.8.5.3
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
On 2 June 2014 02:38, Matthew Garrett matthew.garrett@nebula.com wrote:
From: Seth Forshee seth.forshee@canonical.com
During graphics driver initialization its useful to be able to mux only the DDC to the inactive client in order to read the EDID. Add a switch_ddc callback to allow capable handlers to provide this functionality, and add vga_switcheroo_switch_ddc() to allow DRM to mux only the DDC.
I can't figure out why I didn't like this, but I rewrote this way back to lock/unlock the ddc lines, bits are contained in this WIP mess. http://cgit.freedesktop.org/~airlied/linux/commit/?h=switchy-wip&id=aead...
I think I'd prefer something like that otherwise the interface got really ugly.
Dave.
If display MUXing is handled by an external driver, the handler may not initialise until some time after the graphics drivers originally bound. If the driver is unable to detect some drivers unless appropriately MUXed, this will cause problems. This patch adds a callback to allow switcheroo to retrigger the driver's output probing.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com --- drivers/gpu/vga/vga_switcheroo.c | 10 ++++++++++ include/linux/vga_switcheroo.h | 1 + 2 files changed, 11 insertions(+)
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index dd1d587..c06ad5f 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -96,6 +96,16 @@ static void vga_switcheroo_enable(void) return;
client->id = ret; + + if (!client->active && client->ops->reprobe_connectors) { + int old_id = (client->id == VGA_SWITCHEROO_IGD) ? + VGA_SWITCHEROO_DIS : VGA_SWITCHEROO_IGD; + if (vgasr_priv.handler->switch_ddc) + vgasr_priv.handler->switch_ddc(client->id); + client->ops->reprobe_connectors(client->pdev); + if (vgasr_priv.handler->switch_ddc) + vgasr_priv.handler->switch_ddc(old_id); + } } vga_switcheroo_debugfs_init(&vgasr_priv); vgasr_priv.active = true; diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index 37d6850..bae62fd 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -40,6 +40,7 @@ struct vga_switcheroo_handler { struct vga_switcheroo_client_ops { void (*set_gpu_state)(struct pci_dev *dev, enum vga_switcheroo_state); void (*reprobe)(struct pci_dev *dev); + void (*reprobe_connectors)(struct pci_dev *dev); bool (*can_switch)(struct pci_dev *dev); };
Add a command line option in order to allow the user to provide a perferred GPU at boot time.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com --- Documentation/kernel-parameters.txt | 5 +++++ drivers/gpu/vga/vga_switcheroo.c | 29 +++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+)
diff --git a/Documentation/kernel-parameters.txt b/Documentation/kernel-parameters.txt index 30a8ad0d..6e6d505 100644 --- a/Documentation/kernel-parameters.txt +++ b/Documentation/kernel-parameters.txt @@ -3486,6 +3486,11 @@ bytes respectively. Such letter suffixes can also be entirely omitted. This is actually a boot loader parameter; the value is passed to the kernel using a special protocol.
+ vga_switcheroo= [HW] Switches the enabled GPU at boot time. + Format: { IGD | DIS } + IGD -- enable the Integrated GPU + DIS -- enable the Discrete GPU + vmalloc=nn[KMG] [KNL,BOOT] Forces the vmalloc area to have an exact size of <nn>. This can be used to increase the minimum size (128MB on x86). It can also be used to diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index c06ad5f..6d95626 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -67,6 +67,11 @@ struct vgasr_priv { static int vga_switcheroo_debugfs_init(struct vgasr_priv *priv); static void vga_switcheroo_debugfs_fini(struct vgasr_priv *priv);
+static enum vga_switcheroo_client_id vga_switcheroo_default = -1; + +static int vga_switchto_stage1(struct vga_switcheroo_client *new_client); +static int vga_switchto_stage2(struct vga_switcheroo_client *new_client); + /* only one switcheroo per system */ static struct vgasr_priv vgasr_priv = { .clients = LIST_HEAD_INIT(vgasr_priv.clients), @@ -107,6 +112,18 @@ static void vga_switcheroo_enable(void) vgasr_priv.handler->switch_ddc(old_id); } } + + list_for_each_entry(client, &vgasr_priv.clients, list) { + if (!client->active && client->id == vga_switcheroo_default) { + ret = vga_switchto_stage1(client); + if (ret) + printk(KERN_ERR "vga_switcheroo: switching failed stage 1 %d\n", ret); + + ret = vga_switchto_stage2(client); + if (ret) + printk(KERN_ERR "vga_switcheroo: switching failed stage 2 %d\n", ret); + } + } vga_switcheroo_debugfs_init(&vgasr_priv); vgasr_priv.active = true; } @@ -748,3 +765,15 @@ int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct return -EINVAL; } EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_optimus_hdmi_audio); + +static int __init vga_switcheroo_setup(char *str) +{ + if (!strcmp(str, "IGD")) + vga_switcheroo_default = VGA_SWITCHEROO_IGD; + else if (!strcmp(str, "DIS")) + vga_switcheroo_default = VGA_SWITCHEROO_DIS; + + return 1; +} + +__setup("vga_switcheroo=", vga_switcheroo_setup);
Not all MUXes allow us to connect the panel data channel to a GPU without handing over the entire panel and triggering additional flickering during boot. We only need to do this in order to probe for data that the first GPU driver has already identified, so add some functions for stashing that data in vga_switcheroo where it can be retrieved by the other driver later.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com --- drivers/gpu/vga/vga_switcheroo.c | 59 ++++++++++++++++++++++++++++++++++++++++ include/linux/vga_switcheroo.h | 12 ++++++++ 2 files changed, 71 insertions(+)
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index 6d95626..1a80b93 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -17,6 +17,8 @@ - switch_check - check if the device is in a position to switch now */
+#include <drm/drm_edid.h> + #include <linux/module.h> #include <linux/seq_file.h> #include <linux/uaccess.h> @@ -39,6 +41,7 @@ struct vga_switcheroo_client { int id; bool active; bool driver_power_control; + bool use_panel; struct list_head list; };
@@ -56,6 +59,9 @@ struct vgasr_priv { int registered_clients; struct list_head clients;
+ struct edid *edid; + u8 *dpcd; + struct vga_switcheroo_handler *handler; };
@@ -107,7 +113,9 @@ static void vga_switcheroo_enable(void) VGA_SWITCHEROO_DIS : VGA_SWITCHEROO_IGD; if (vgasr_priv.handler->switch_ddc) vgasr_priv.handler->switch_ddc(client->id); + client->use_panel = true; client->ops->reprobe_connectors(client->pdev); + client->use_panel = false; if (vgasr_priv.handler->switch_ddc) vgasr_priv.handler->switch_ddc(old_id); } @@ -412,6 +420,9 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) if (ret) goto restore_ddc;
+ new_client->use_panel = true; + active->use_panel = false; + if (new_client->ops->reprobe) new_client->ops->reprobe(new_client->pdev);
@@ -766,6 +777,54 @@ int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct } EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_optimus_hdmi_audio);
+int vga_switcheroo_set_dpcd(u8 *dpcd) +{ + if (vgasr_priv.dpcd) + return -EEXIST; + + vgasr_priv.dpcd = kmalloc(8, GFP_KERNEL); + memcpy(vgasr_priv.dpcd, dpcd, 8); + return 0; +} +EXPORT_SYMBOL(vga_switcheroo_set_dpcd); + +u8 *vga_switcheroo_get_dpcd(struct pci_dev *pdev) +{ + struct vga_switcheroo_client *client; + client = find_client_from_pci(&vgasr_priv.clients, pdev); + + if (!client || !client->use_panel) + return NULL; + + return vgasr_priv.dpcd; +} +EXPORT_SYMBOL(vga_switcheroo_get_dpcd); + +int vga_switcheroo_set_edid(struct edid *edid) +{ + int size = EDID_LENGTH * (1 + edid->extensions); + + if (vgasr_priv.edid) + return -EEXIST; + + vgasr_priv.edid = kmalloc(size, GFP_KERNEL); + memcpy(vgasr_priv.edid, edid, size); + return 0; +} +EXPORT_SYMBOL(vga_switcheroo_set_edid); + +struct edid *vga_switcheroo_get_edid(struct pci_dev *pdev) +{ + struct vga_switcheroo_client *client; + client = find_client_from_pci(&vgasr_priv.clients, pdev); + + if (!client || !client->use_panel) + return NULL; + + return vgasr_priv.edid; +} +EXPORT_SYMBOL(vga_switcheroo_get_edid); + static int __init vga_switcheroo_setup(char *str) { if (!strcmp(str, "IGD")) diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index bae62fd..07b07fc 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -10,6 +10,7 @@ #ifndef _LINUX_VGA_SWITCHEROO_H_ #define _LINUX_VGA_SWITCHEROO_H_
+#include <drm/drm_edid.h> #include <linux/fb.h>
struct pci_dev; @@ -69,6 +70,12 @@ void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo
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); + +int vga_switcheroo_set_dpcd(u8 *dpcd); +u8 *vga_switcheroo_get_dpcd(struct pci_dev *pdev); +int vga_switcheroo_set_edid(struct edid *edid); +struct edid *vga_switcheroo_get_edid(struct pci_dev *pdev); + #else
static inline void vga_switcheroo_unregister_client(struct pci_dev *dev) {} @@ -89,5 +96,10 @@ static inline void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum 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; }
+static inline int vga_switcheroo_set_dpcd(u8 *dpcd) { return 0 }; +static inline u8 *vga_switcheroo_get_dpcd(struct pci_dev *pdev) { return NULL }; +static inline int vga_switcheroo_set_edid(struct edid *edid) { return 0 }; +static inline struct edid *vga_switcheroo_get_edid(struct pci_dev *pdev) { return NULL }; + #endif #endif /* _LINUX_VGA_SWITCHEROO_H_ */
On Sun, 01 Jun 2014, Matthew Garrett matthew.garrett@nebula.com wrote:
Not all MUXes allow us to connect the panel data channel to a GPU without handing over the entire panel and triggering additional flickering during boot. We only need to do this in order to probe for data that the first GPU driver has already identified, so add some functions for stashing that data in vga_switcheroo where it can be retrieved by the other driver later.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com
drivers/gpu/vga/vga_switcheroo.c | 59 ++++++++++++++++++++++++++++++++++++++++ include/linux/vga_switcheroo.h | 12 ++++++++ 2 files changed, 71 insertions(+)
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index 6d95626..1a80b93 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -17,6 +17,8 @@
- switch_check - check if the device is in a position to switch now
*/
+#include <drm/drm_edid.h>
#include <linux/module.h> #include <linux/seq_file.h> #include <linux/uaccess.h> @@ -39,6 +41,7 @@ struct vga_switcheroo_client { int id; bool active; bool driver_power_control;
- bool use_panel; struct list_head list;
};
@@ -56,6 +59,9 @@ struct vgasr_priv { int registered_clients; struct list_head clients;
- struct edid *edid;
- u8 *dpcd;
- struct vga_switcheroo_handler *handler;
};
@@ -107,7 +113,9 @@ static void vga_switcheroo_enable(void) VGA_SWITCHEROO_DIS : VGA_SWITCHEROO_IGD; if (vgasr_priv.handler->switch_ddc) vgasr_priv.handler->switch_ddc(client->id);
client->use_panel = true; client->ops->reprobe_connectors(client->pdev);
}client->use_panel = false; if (vgasr_priv.handler->switch_ddc) vgasr_priv.handler->switch_ddc(old_id);
@@ -412,6 +420,9 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) if (ret) goto restore_ddc;
- new_client->use_panel = true;
- active->use_panel = false;
- if (new_client->ops->reprobe) new_client->ops->reprobe(new_client->pdev);
@@ -766,6 +777,54 @@ int vga_switcheroo_init_domain_pm_optimus_hdmi_audio(struct device *dev, struct } EXPORT_SYMBOL(vga_switcheroo_init_domain_pm_optimus_hdmi_audio);
+int vga_switcheroo_set_dpcd(u8 *dpcd)
Quite a few DPCD related things in drm_dp_helper.h expect DP_RECEIVER_CAP_SIZE bytes of DPCD. Might be helpful to conform to that.
Can also be made const u8 *.
+{
- if (vgasr_priv.dpcd)
return -EEXIST;
- vgasr_priv.dpcd = kmalloc(8, GFP_KERNEL);
- memcpy(vgasr_priv.dpcd, dpcd, 8);
kmemdup.
- return 0;
+} +EXPORT_SYMBOL(vga_switcheroo_set_dpcd);
+u8 *vga_switcheroo_get_dpcd(struct pci_dev *pdev) +{
- struct vga_switcheroo_client *client;
- client = find_client_from_pci(&vgasr_priv.clients, pdev);
- if (!client || !client->use_panel)
return NULL;
- return vgasr_priv.dpcd;
+} +EXPORT_SYMBOL(vga_switcheroo_get_dpcd);
+int vga_switcheroo_set_edid(struct edid *edid) +{
- int size = EDID_LENGTH * (1 + edid->extensions);
- if (vgasr_priv.edid)
return -EEXIST;
- vgasr_priv.edid = kmalloc(size, GFP_KERNEL);
- memcpy(vgasr_priv.edid, edid, size);
kmemdup.
BR, Jani.
- return 0;
+} +EXPORT_SYMBOL(vga_switcheroo_set_edid);
+struct edid *vga_switcheroo_get_edid(struct pci_dev *pdev) +{
- struct vga_switcheroo_client *client;
- client = find_client_from_pci(&vgasr_priv.clients, pdev);
- if (!client || !client->use_panel)
return NULL;
- return vgasr_priv.edid;
+} +EXPORT_SYMBOL(vga_switcheroo_get_edid);
static int __init vga_switcheroo_setup(char *str) { if (!strcmp(str, "IGD")) diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index bae62fd..07b07fc 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -10,6 +10,7 @@ #ifndef _LINUX_VGA_SWITCHEROO_H_ #define _LINUX_VGA_SWITCHEROO_H_
+#include <drm/drm_edid.h> #include <linux/fb.h>
struct pci_dev; @@ -69,6 +70,12 @@ void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum vga_switcheroo
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);
+int vga_switcheroo_set_dpcd(u8 *dpcd); +u8 *vga_switcheroo_get_dpcd(struct pci_dev *pdev); +int vga_switcheroo_set_edid(struct edid *edid); +struct edid *vga_switcheroo_get_edid(struct pci_dev *pdev);
#else
static inline void vga_switcheroo_unregister_client(struct pci_dev *dev) {} @@ -89,5 +96,10 @@ static inline void vga_switcheroo_set_dynamic_switch(struct pci_dev *pdev, enum 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; }
+static inline int vga_switcheroo_set_dpcd(u8 *dpcd) { return 0 }; +static inline u8 *vga_switcheroo_get_dpcd(struct pci_dev *pdev) { return NULL }; +static inline int vga_switcheroo_set_edid(struct edid *edid) { return 0 }; +static inline struct edid *vga_switcheroo_get_edid(struct pci_dev *pdev) { return NULL };
#endif
#endif /* _LINUX_VGA_SWITCHEROO_H_ */
1.8.5.3
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Most switcheroo setups attach power management to one of the GPUs. This is not always the case, so provide a mechanism for handlers to declare that they can change the power state of GPUs and permit drivers to obtain this information.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com --- drivers/gpu/vga/vga_switcheroo.c | 8 ++++++++ include/linux/vga_switcheroo.h | 5 +++++ 2 files changed, 13 insertions(+)
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index 1a80b93..c5e97d4 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -825,6 +825,14 @@ struct edid *vga_switcheroo_get_edid(struct pci_dev *pdev) } EXPORT_SYMBOL(vga_switcheroo_get_edid);
+bool vga_switcheroo_handler_pm(void) { + if (!vgasr_priv.handler) + return false; + + return vgasr_priv.handler->handler_pm; +} +EXPORT_SYMBOL(vga_switcheroo_handler_pm); + static int __init vga_switcheroo_setup(char *str) { if (!strcmp(str, "IGD")) diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index 07b07fc..4bba934 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -36,6 +36,7 @@ struct vga_switcheroo_handler { enum vga_switcheroo_state state); int (*init)(void); int (*get_client_id)(struct pci_dev *pdev); + bool handler_pm; };
struct vga_switcheroo_client_ops { @@ -76,6 +77,8 @@ u8 *vga_switcheroo_get_dpcd(struct pci_dev *pdev); int vga_switcheroo_set_edid(struct edid *edid); struct edid *vga_switcheroo_get_edid(struct pci_dev *pdev);
+bool vga_switcheroo_handler_pm(void); + #else
static inline void vga_switcheroo_unregister_client(struct pci_dev *dev) {} @@ -101,5 +104,7 @@ static inline u8 *vga_switcheroo_get_dpcd(struct pci_dev *pdev) { return NULL }; static inline int vga_switcheroo_set_edid(struct edid *edid) { return 0 }; static inline struct edid *vga_switcheroo_get_edid(struct pci_dev *pdev) { return NULL };
+static inline bool vga_switcheroo_handler_pm(void) { return false; }; + #endif #endif /* _LINUX_VGA_SWITCHEROO_H_ */
We may not know whether the platform supports dynamic PM of GPUs until the switcheroo handler is registered. Add an enable() callback for clients and another entry point to switcheroo in order to permit them to set dynamic PM appropriately.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com --- drivers/gpu/vga/vga_switcheroo.c | 17 +++++++++++++++++ include/linux/vga_switcheroo.h | 3 +++ 2 files changed, 20 insertions(+)
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index c5e97d4..199a111 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -108,6 +108,9 @@ static void vga_switcheroo_enable(void)
client->id = ret;
+ if (client->ops->enable) + client->ops->enable(client->pdev); + if (!client->active && client->ops->reprobe_connectors) { int old_id = (client->id == VGA_SWITCHEROO_IGD) ? VGA_SWITCHEROO_DIS : VGA_SWITCHEROO_IGD; @@ -670,6 +673,20 @@ static void vga_switcheroo_power_switch(struct pci_dev *pdev, enum vga_switchero vgasr_priv.handler->power_state(client->id, state); }
+void vga_switcheroo_set_dynamic_support(struct pci_dev *pdev, bool dynamic) +{ + struct vga_switcheroo_client *client; + + client = find_client_from_pci(&vgasr_priv.clients, pdev); + if (!client) + return; + + client->driver_power_control = dynamic; + + return; +} +EXPORT_SYMBOL(vga_switcheroo_set_dynamic_support); + /* 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) diff --git a/include/linux/vga_switcheroo.h b/include/linux/vga_switcheroo.h index 4bba934..5a483c6 100644 --- a/include/linux/vga_switcheroo.h +++ b/include/linux/vga_switcheroo.h @@ -44,6 +44,7 @@ struct vga_switcheroo_client_ops { void (*reprobe)(struct pci_dev *dev); void (*reprobe_connectors)(struct pci_dev *dev); bool (*can_switch)(struct pci_dev *dev); + void (*enable)(struct pci_dev *dev); };
#if defined(CONFIG_VGA_SWITCHEROO) @@ -67,6 +68,7 @@ int vga_switcheroo_process_delayed_switch(void);
int vga_switcheroo_get_client_state(struct pci_dev *dev);
+void vga_switcheroo_set_dynamic_support(struct pci_dev *pdev, bool dynamic); 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); @@ -94,6 +96,7 @@ 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_support(struct pci_dev *pdev, bool dynamic) {} 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; }
We need to force the previously active device to reprobe its connectors when we switch in order to allow it to give up connectors that are no longer in use.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com --- drivers/gpu/vga/vga_switcheroo.c | 3 +++ 1 file changed, 3 insertions(+)
diff --git a/drivers/gpu/vga/vga_switcheroo.c b/drivers/gpu/vga/vga_switcheroo.c index 199a111..d380158 100644 --- a/drivers/gpu/vga/vga_switcheroo.c +++ b/drivers/gpu/vga/vga_switcheroo.c @@ -429,6 +429,9 @@ static int vga_switchto_stage2(struct vga_switcheroo_client *new_client) if (new_client->ops->reprobe) new_client->ops->reprobe(new_client->pdev);
+ if (active->ops->reprobe) + active->ops->reprobe(active->pdev); + if (active->pwr_state == VGA_SWITCHEROO_ON) vga_switchoff(active);
We can switch DDC pins in a way that ought (with luck) to work for LVDS. This isn't sufficient for eDP, which is addressed in later patches.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com --- drivers/platform/x86/apple-gmux.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index b9429fb..5594cbc 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -271,6 +271,16 @@ static const struct backlight_ops gmux_bl_ops = { .update_status = gmux_update_status, };
+static int gmux_switch_ddc(enum vga_switcheroo_client_id id) +{ + if (id == VGA_SWITCHEROO_IGD) + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 1); + else + gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 2); + + return 0; +} + static int gmux_switchto(enum vga_switcheroo_client_id id) { if (id == VGA_SWITCHEROO_IGD) { @@ -346,6 +356,7 @@ gmux_active_client(struct apple_gmux_data *gmux_data)
static struct vga_switcheroo_handler gmux_handler = { .switchto = gmux_switchto, + .switch_ddc = gmux_switch_ddc, .power_state = gmux_set_power_state, .get_client_id = gmux_get_client_id, };
On Sun, Jun 1, 2014 at 12:38 PM, Matthew Garrett matthew.garrett@nebula.com wrote:
We can switch DDC pins in a way that ought (with luck) to work for LVDS. This isn't sufficient for eDP, which is addressed in later patches.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com
FWIW, on AMD muxed PX systems, there are separate muxes for ddc/hpd and data lines so it would be trivial to implement there as well.
Alex
drivers/platform/x86/apple-gmux.c | 11 +++++++++++ 1 file changed, 11 insertions(+)
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index b9429fb..5594cbc 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -271,6 +271,16 @@ static const struct backlight_ops gmux_bl_ops = { .update_status = gmux_update_status, };
+static int gmux_switch_ddc(enum vga_switcheroo_client_id id) +{
if (id == VGA_SWITCHEROO_IGD)
gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 1);
else
gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 2);
return 0;
+}
static int gmux_switchto(enum vga_switcheroo_client_id id) { if (id == VGA_SWITCHEROO_IGD) { @@ -346,6 +356,7 @@ gmux_active_client(struct apple_gmux_data *gmux_data)
static struct vga_switcheroo_handler gmux_handler = { .switchto = gmux_switchto,
.switch_ddc = gmux_switch_ddc, .power_state = gmux_set_power_state, .get_client_id = gmux_get_client_id,
};
1.8.5.3
dri-devel mailing list dri-devel@lists.freedesktop.org http://lists.freedesktop.org/mailman/listinfo/dri-devel
Registering the handler after both GPUs will trigger a DDC switch for connector reprobing. This will oops if apple_gmux_data hasn't already been assigned. Reorder the code to do that.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com --- drivers/platform/x86/apple-gmux.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-)
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index 5594cbc..e9b6d77 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -563,18 +563,20 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) gmux_data->gpe = -1; }
+ apple_gmux_data = gmux_data; + init_completion(&gmux_data->powerchange_done); + gmux_enable_interrupts(gmux_data); + if (vga_switcheroo_register_handler(&gmux_handler)) { ret = -ENODEV; goto err_register_handler; }
- init_completion(&gmux_data->powerchange_done); - apple_gmux_data = gmux_data; - gmux_enable_interrupts(gmux_data); - return 0;
err_register_handler: + gmux_disable_interrupts(gmux_data); + apple_gmux_data = NULL; if (gmux_data->gpe >= 0) acpi_disable_gpe(NULL, gmux_data->gpe); err_enable_gpe:
The Apple GMUX can cut power to the discrete GPU, so should declare this to the vga_switcheroo core.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com --- drivers/platform/x86/apple-gmux.c | 1 + 1 file changed, 1 insertion(+)
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index e9b6d77..17f906d 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -359,6 +359,7 @@ static struct vga_switcheroo_handler gmux_handler = { .switch_ddc = gmux_switch_ddc, .power_state = gmux_set_power_state, .get_client_id = gmux_get_client_id, + .handler_pm = true, };
static inline void gmux_disable_interrupts(struct apple_gmux_data *gmux_data)
The GMUX doesn't appear to switch instantly, which can trigger problems in panel detection and setup. Wait for an interrupt or 200msec, whichever comes first.
Signed-off-by: Matthew Garrett matthew.garrett@nebula.com --- drivers/platform/x86/apple-gmux.c | 12 ++++++++++++ 1 file changed, 12 insertions(+)
diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index 17f906d..6826ede 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -38,6 +38,7 @@ struct apple_gmux_data { int gpe; enum vga_switcheroo_client_id resume_client_id; enum vga_switcheroo_state power_state; + struct completion switch_done; struct completion powerchange_done; };
@@ -283,6 +284,8 @@ static int gmux_switch_ddc(enum vga_switcheroo_client_id id)
static int gmux_switchto(enum vga_switcheroo_client_id id) { + reinit_completion(&apple_gmux_data->switch_done); + if (id == VGA_SWITCHEROO_IGD) { gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DDC, 1); gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_DISPLAY, 2); @@ -293,6 +296,11 @@ static int gmux_switchto(enum vga_switcheroo_client_id id) gmux_write8(apple_gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 3); }
+ if (apple_gmux_data->gpe >= 0 && + !wait_for_completion_interruptible_timeout(&apple_gmux_data->switch_done, + msecs_to_jiffies(200))) + pr_warn("Timeout waiting for gmux GPU switch to complete\n"); + return 0; }
@@ -401,6 +409,9 @@ static void gmux_notify_handler(acpi_handle device, u32 value, void *context) gmux_clear_interrupts(gmux_data); gmux_enable_interrupts(gmux_data);
+ if (status & GMUX_INTERRUPT_STATUS_DISPLAY) + complete(&gmux_data->switch_done); + if (status & GMUX_INTERRUPT_STATUS_POWER) complete(&gmux_data->powerchange_done); } @@ -565,6 +576,7 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) }
apple_gmux_data = gmux_data; + init_completion(&gmux_data->switch_done); init_completion(&gmux_data->powerchange_done); gmux_enable_interrupts(gmux_data);
dri-devel@lists.freedesktop.org