apple-gmux: Don't switch external DP port on 2011+ MacBook Pros
authorLukas Wunner <lukas@wunner.de>
Fri, 10 Mar 2017 20:23:45 +0000 (21:23 +0100)
committerLukas Wunner <lukas@wunner.de>
Thu, 30 Mar 2017 20:42:30 +0000 (22:42 +0200)
On MacBook Pros introduced 2011 and onward, external DP ports are
combined DP/Thunderbolt ports that are no longer fully switchable
between GPUs, they can only be driven by the discrete GPU.

More specifically, the Main Link pins (which transport the actual video
and audio streams) are soldered to the discrete GPU, whereas the AUX
Channel pins are switchable. Because the integrated GPU is missing the
Main Link, external displays appear to it as phantoms which fail to
link-train.

Force the AUX channel to the discrete GPU on these models to avoid any
confusion. Document the switching policy implemented by this commit.

Acked-by: Andy Shevchenko <andy.shevchenko@gmail.com>
Signed-off-by: Lukas Wunner <lukas@wunner.de>
Link: http://patchwork.freedesktop.org/patch/msgid/4d1fcc92d1960049e2cff997fbd2d74e45e84e49.1489145162.git.lukas@wunner.de
drivers/platform/x86/apple-gmux.c

index a66be137324c096a3a6b0f1e5fba18a3c4993f1b..623d322447a2192fcfba9e0041ddbe76080b30bb 100644 (file)
@@ -60,6 +60,7 @@ struct apple_gmux_data {
        /* switcheroo data */
        acpi_handle dhandle;
        int gpe;
+       bool external_switchable;
        enum vga_switcheroo_client_id switch_state_display;
        enum vga_switcheroo_client_id switch_state_ddc;
        enum vga_switcheroo_client_id switch_state_external;
@@ -358,6 +359,19 @@ static const struct backlight_ops gmux_bl_ops = {
  * ports while the discrete GPU is asleep, but currently we do not make use
  * of this feature.
  *
+ * Our switching policy for the external port is that on those generations
+ * which are able to switch it fully, the port is switched together with the
+ * panel when IGD / DIS commands are issued to vga_switcheroo. It is thus
+ * possible to drive e.g. a beamer on battery power with the integrated GPU.
+ * The user may manually switch to the discrete GPU if more performance is
+ * needed.
+ *
+ * On all newer generations, the external port can only be driven by the
+ * discrete GPU. If a display is plugged in while the panel is switched to
+ * the integrated GPU, *both* GPUs will be in use for maximum performance.
+ * To decrease power consumption, the user may manually switch to the
+ * discrete GPU, thereby suspending the integrated GPU.
+ *
  * gmux' initial switch state on bootup is user configurable via the EFI
  * variable ``gpu-power-prefs-fa4ce28d-b62f-4c99-9cc3-6815686e30f9`` (5th byte,
  * 1 = IGD, 0 = DIS). Based on this setting, the EFI firmware tells gmux to
@@ -414,7 +428,8 @@ static int gmux_switchto(enum vga_switcheroo_client_id id)
 {
        apple_gmux_data->switch_state_ddc = id;
        apple_gmux_data->switch_state_display = id;
-       apple_gmux_data->switch_state_external = id;
+       if (apple_gmux_data->external_switchable)
+               apple_gmux_data->switch_state_external = id;
 
        gmux_write_switch_state(apple_gmux_data);
 
@@ -601,6 +616,11 @@ static struct pci_dev *gmux_get_io_pdev(void)
        return NULL;
 }
 
+static int is_thunderbolt(struct device *dev, void *data)
+{
+       return to_pci_dev(dev)->is_thunderbolt;
+}
+
 static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
 {
        struct apple_gmux_data *gmux_data;
@@ -755,6 +775,15 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id)
                gmux_data->gpe = -1;
        }
 
+       /*
+        * If Thunderbolt is present, the external DP port is not fully
+        * switchable. Force its AUX channel to the discrete GPU.
+        */
+       gmux_data->external_switchable =
+               !bus_for_each_dev(&pci_bus_type, NULL, NULL, is_thunderbolt);
+       if (!gmux_data->external_switchable)
+               gmux_write8(gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 3);
+
        apple_gmux_data = gmux_data;
        init_completion(&gmux_data->powerchange_done);
        gmux_enable_interrupts(gmux_data);