on MMIO systems, we need check to for failure in interrupts, and we have implimented display hotplug to wake up the gpu.
Co-developed-by: Andre Eikmeyer <[email protected]> Signed-off-by: Andre Eikmeyer <[email protected]> Signed-off-by: Atharva Tiwari <[email protected]> --- drivers/platform/x86/apple-gmux.c | 96 +++++++++++++++++++++++++++---- 1 file changed, 84 insertions(+), 12 deletions(-) diff --git a/drivers/platform/x86/apple-gmux.c b/drivers/platform/x86/apple-gmux.c index 076ed7ba6..bef87a57a 100644 --- a/drivers/platform/x86/apple-gmux.c +++ b/drivers/platform/x86/apple-gmux.c @@ -6,6 +6,8 @@ * Copyright (C) 2010-2012 Andreas Heider <[email protected]> * Copyright (C) 2015 Lukas Wunner <[email protected]> * Copyright (C) 2023 Orlando Chamberlain <[email protected]> + * Copyright (C) 2026 Atharva Tiwari <[email protected]> + * Copyright (C) 2026 Andre Eikmeyer <[email protected]> */ #define pr_fmt(fmt) KBUILD_MODNAME ": " fmt @@ -23,6 +25,8 @@ #include <linux/vga_switcheroo.h> #include <linux/debugfs.h> #include <linux/dmi.h> +#include <linux/workqueue.h> +#include <linux/pm_runtime.h> #include <acpi/video.h> #include <asm/io.h> @@ -66,6 +70,10 @@ struct apple_gmux_data { struct backlight_device *bdev; + /* interrupt handling data */ + bool external_state; + struct work_struct interrupt_work; + /* power handling data */ struct pci_dev *discrete_pdev; bool uses_pwrd; @@ -105,6 +113,7 @@ struct apple_gmux_config { #define GMUX_INTERRUPT_STATUS_ACTIVE 0 #define GMUX_INTERRUPT_STATUS_DISPLAY (1 << 0) +#define GMUX_INTERRUPT_STATUS_FAILED (1 << 1) #define GMUX_INTERRUPT_STATUS_POWER (1 << 2) #define GMUX_INTERRUPT_STATUS_HOTPLUG (1 << 3) @@ -417,8 +426,8 @@ static const struct backlight_ops gmux_bl_ops = { * the discrete GPU can then be woken to drive the newly connected display. * The ability to switch AUX on these generations could be used to improve * reliability of hotplug detection by having the integrated GPU poll the - * ports while the discrete GPU is asleep, but currently we do not make use - * of this feature. + * ports while the discrete GPU is asleep, but currently we only make use of + * this feature on MMIO/T2 Macs. * * 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 @@ -796,9 +805,10 @@ static const struct apple_gmux_config apple_gmux_mmio = { * value is read and then inverted. * * Because Linux masquerades as Darwin, it ends up in the notification-only code - * path. On MMIO gmux's, this seems to lead to us being unable to clear interrupts, - * unless we call GMSP(0). Without this, there is a flood of status=0 interrupts - * that can't be cleared. This issue seems to be unique to MMIO gmux's. + * path. On MMIO gmux's, The Polarity is initally left in a undefined state leading + * to lots of interrupts, that we can't clear, to fix this we set the polarity to 0 + * everytime we enable interrupts, On MMIO gmux's, GMLV reads the same GPIO pin as + * GMSP, and setting the polarity to 1, stops the Interrupts completely. */ static inline void gmux_disable_interrupts(struct apple_gmux_data *gmux_data) @@ -807,8 +817,11 @@ static inline void gmux_disable_interrupts(struct apple_gmux_data *gmux_data) GMUX_INTERRUPT_DISABLE); } -static inline void gmux_enable_interrupts(struct apple_gmux_data *gmux_data) +static inline void gmux_enable_interrupts(struct apple_gmux_data *gmux_data, bool do_gmsp) { + if (gmux_data->type == APPLE_GMUX_TYPE_MMIO && do_gmsp) + acpi_execute_simple_method(gmux_data->dhandle, "GMSP", 0); + gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE, GMUX_INTERRUPT_ENABLE); } @@ -825,23 +838,78 @@ static void gmux_clear_interrupts(struct apple_gmux_data *gmux_data) /* to clear interrupts write back current status */ status = gmux_interrupt_get_status(gmux_data); gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_STATUS, status); - /* Prevent flood of status=0 interrupts */ - if (gmux_data->config == &apple_gmux_mmio) - acpi_execute_simple_method(gmux_data->dhandle, "GMSP", 0); } +static void gmux_notify_work(struct work_struct *work) +{ + struct apple_gmux_data *gmux_data = + container_of(work, struct apple_gmux_data, interrupt_work); + u8 status; + bool turn_on_gpu = false; + + status = gmux_interrupt_get_status(gmux_data); + if (!status) + return; + + gmux_disable_interrupts(gmux_data); + pr_debug("Notify handler called: status %d\n", status); + acpi_execute_simple_method(gmux_data->dhandle, "GMSP", 0); + + status = gmux_interrupt_get_status(gmux_data); + while (status) { + gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_STATUS, status); + if (status & GMUX_INTERRUPT_STATUS_FAILED) { + pr_warn_ratelimited("interrupt failed: status: 0x%x\n", status); + status = gmux_interrupt_get_status(gmux_data); + continue; + } + if (status & GMUX_INTERRUPT_STATUS_POWER) + complete(&gmux_data->powerchange_done); + + if (status & GMUX_INTERRUPT_STATUS_HOTPLUG) { + /* + * We get an interrupt every time a display connects or disconnects, but + * the GET_DISPLAY port only tells us if a display is connected or not, + * so we resume every time a display is connected or disconnected. + */ + bool external_state = !!gmux_read8(gmux_data, + GMUX_PORT_SWITCH_GET_DISPLAY); + if (external_state != gmux_data->external_state) { + gmux_data->external_state = external_state; + if (external_state) + pr_debug("A display has been connected\n"); + else + pr_debug("All displays has been disconnected\n"); + } + if (!gmux_data->external_switchable && gmux_data->discrete_pdev) + turn_on_gpu = true; + } + status = gmux_interrupt_get_status(gmux_data); + } + + gmux_enable_interrupts(gmux_data, 0); + + if (turn_on_gpu) + pm_runtime_resume(&gmux_data->discrete_pdev->dev); + +} static void gmux_notify_handler(acpi_handle device, u32 value, void *context) { u8 status; struct pnp_dev *pnp = (struct pnp_dev *)context; struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); + if (gmux_data->type == APPLE_GMUX_TYPE_MMIO) { + schedule_work(&gmux_data->interrupt_work); + return; + } + status = gmux_interrupt_get_status(gmux_data); gmux_disable_interrupts(gmux_data); pr_debug("Notify handler called: status %d\n", status); gmux_clear_interrupts(gmux_data); - gmux_enable_interrupts(gmux_data); + gmux_enable_interrupts(gmux_data, 0); if (status & GMUX_INTERRUPT_STATUS_POWER) complete(&gmux_data->powerchange_done); @@ -935,7 +1003,7 @@ static int gmux_resume(struct device *dev) struct pnp_dev *pnp = to_pnp_dev(dev); struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp); - gmux_enable_interrupts(gmux_data); + gmux_enable_interrupts(gmux_data, 1); gmux_write_switch_state(gmux_data); if (gmux_data->power_state == VGA_SWITCHEROO_OFF) gmux_set_discrete_state(gmux_data, gmux_data->power_state); @@ -996,6 +1064,7 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) pr_err("couldn't remap gmux mmio region"); goto err_release; } + INIT_WORK(&gmux_data->interrupt_work, gmux_notify_work); goto get_version; case APPLE_GMUX_TYPE_INDEXED: gmux_data->config = &apple_gmux_index; @@ -1115,7 +1184,7 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) apple_gmux_data = gmux_data; init_completion(&gmux_data->powerchange_done); - gmux_enable_interrupts(gmux_data); + gmux_enable_interrupts(gmux_data, 1); gmux_read_switch_state(gmux_data); /* @@ -1150,6 +1219,8 @@ static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) err_unmap: if (gmux_data->iomem_base) iounmap(gmux_data->iomem_base); + if (gmux_data->type == APPLE_GMUX_TYPE_MMIO) + cancel_work_sync(&gmux_data->interrupt_work); err_release: if (gmux_data->config->resource_type == IORESOURCE_MEM) release_mem_region(gmux_data->iostart, gmux_data->iolen); @@ -1168,6 +1239,7 @@ static void gmux_remove(struct pnp_dev *pnp) vga_switcheroo_unregister_handler(); pci_dev_put(gmux_data->discrete_pdev); gmux_disable_interrupts(gmux_data); + cancel_work_sync(&gmux_data->interrupt_work); if (gmux_data->gpe >= 0) { acpi_disable_gpe(NULL, gmux_data->gpe); acpi_remove_notify_handler(gmux_data->dhandle, -- 2.55.0
