// SPDX-License-Identifier: GPL-2.0-only /* * Gmux driver for Apple laptops * * Copyright (C) Canonical Ltd. <[email protected]> * Copyright (C) 2010-2012 Andreas Heider <[email protected]> * Copyright (C) 2015 Lukas Wunner <[email protected]> * Copyright (C) 2023 Orlando Chamberlain <[email protected]> */ #define pr_fmt(fmt) … #include <linux/module.h> #include <linux/kernel.h> #include <linux/init.h> #include <linux/backlight.h> #include <linux/acpi.h> #include <linux/pnp.h> #include <linux/apple-gmux.h> #include <linux/slab.h> #include <linux/delay.h> #include <linux/pci.h> #include <linux/vga_switcheroo.h> #include <linux/debugfs.h> #include <acpi/video.h> #include <asm/io.h> /** * DOC: Overview * * gmux is a microcontroller built into the MacBook Pro to support dual GPUs: * A `Lattice XP2`_ on pre-retinas, a `Renesas R4F2113`_ on pre-T2 retinas. * * On T2 Macbooks, the gmux is part of the T2 Coprocessor's SMC. The SMC has * an I2C connection to a `NXP PCAL6524` GPIO expander, which enables/disables * the voltage regulators of the discrete GPU, drives the display panel power, * and has a GPIO to switch the eDP mux. The Intel CPU can interact with * gmux through MMIO, similar to how the main SMC interface is controlled. * * (The MacPro6,1 2013 also has a gmux, however it is unclear why since it has * dual GPUs but no built-in display.) * * gmux is connected to the LPC bus of the southbridge. Its I/O ports are * accessed differently depending on the microcontroller: Driver functions * to access a pre-retina gmux are infixed ``_pio_``, those for a pre-T2 * retina gmux are infixed ``_index_``, and those on T2 Macs are infixed * with ``_mmio_``. * * .. _Lattice XP2: * http://www.latticesemi.com/en/Products/FPGAandCPLD/LatticeXP2.aspx * .. _Renesas R4F2113: * http://www.renesas.com/products/mpumcu/h8s/h8s2100/h8s2113/index.jsp * .. _NXP PCAL6524: * https://www.nxp.com/docs/en/data-sheet/PCAL6524.pdf */ struct apple_gmux_config; struct apple_gmux_data { … }; static struct apple_gmux_data *apple_gmux_data; struct apple_gmux_config { … }; #define GMUX_INTERRUPT_ENABLE … #define GMUX_INTERRUPT_DISABLE … #define GMUX_INTERRUPT_STATUS_ACTIVE … #define GMUX_INTERRUPT_STATUS_DISPLAY … #define GMUX_INTERRUPT_STATUS_POWER … #define GMUX_INTERRUPT_STATUS_HOTPLUG … #define GMUX_BRIGHTNESS_MASK … #define GMUX_MAX_BRIGHTNESS … #define MMIO_GMUX_MAX_BRIGHTNESS … static u8 gmux_pio_read8(struct apple_gmux_data *gmux_data, int port) { … } static void gmux_pio_write8(struct apple_gmux_data *gmux_data, int port, u8 val) { … } static u32 gmux_pio_read32(struct apple_gmux_data *gmux_data, int port) { … } static void gmux_pio_write32(struct apple_gmux_data *gmux_data, int port, u32 val) { … } static int gmux_index_wait_ready(struct apple_gmux_data *gmux_data) { … } static int gmux_index_wait_complete(struct apple_gmux_data *gmux_data) { … } static u8 gmux_index_read8(struct apple_gmux_data *gmux_data, int port) { … } static void gmux_index_write8(struct apple_gmux_data *gmux_data, int port, u8 val) { … } static u32 gmux_index_read32(struct apple_gmux_data *gmux_data, int port) { … } static void gmux_index_write32(struct apple_gmux_data *gmux_data, int port, u32 val) { … } static int gmux_mmio_wait(struct apple_gmux_data *gmux_data) { … } static u8 gmux_mmio_read8(struct apple_gmux_data *gmux_data, int port) { … } static void gmux_mmio_write8(struct apple_gmux_data *gmux_data, int port, u8 val) { … } static u32 gmux_mmio_read32(struct apple_gmux_data *gmux_data, int port) { … } static void gmux_mmio_write32(struct apple_gmux_data *gmux_data, int port, u32 val) { … } static u8 gmux_read8(struct apple_gmux_data *gmux_data, int port) { … } static void gmux_write8(struct apple_gmux_data *gmux_data, int port, u8 val) { … } static u32 gmux_read32(struct apple_gmux_data *gmux_data, int port) { … } static void gmux_write32(struct apple_gmux_data *gmux_data, int port, u32 val) { … } /** * DOC: Backlight control * * On single GPU MacBooks, the PWM signal for the backlight is generated by * the GPU. On dual GPU MacBook Pros by contrast, either GPU may be suspended * to conserve energy. Hence the PWM signal needs to be generated by a separate * backlight driver which is controlled by gmux. The earliest generation * MBP5 2008/09 uses a `TI LP8543`_ backlight driver. Newer models * use a `TI LP8545`_ or a TI LP8548. * * .. _TI LP8543: https://www.ti.com/lit/ds/symlink/lp8543.pdf * .. _TI LP8545: https://www.ti.com/lit/ds/symlink/lp8545.pdf */ static int gmux_get_brightness(struct backlight_device *bd) { … } static int gmux_update_status(struct backlight_device *bd) { … } static const struct backlight_ops gmux_bl_ops = …; /** * DOC: Graphics mux * * On pre-retinas, the LVDS outputs of both GPUs feed into gmux which muxes * either of them to the panel. One of the tricks gmux has up its sleeve is * to lengthen the blanking interval of its output during a switch to * synchronize it with the GPU switched to. This allows for a flicker-free * switch that is imperceptible by the user (`US 8,687,007 B2`_). * * On retinas, muxing is no longer done by gmux itself, but by a separate * chip which is controlled by gmux. The chip is triple sourced, it is * either an `NXP CBTL06142`_, `TI HD3SS212`_ or `Pericom PI3VDP12412`_. * The panel is driven with eDP instead of LVDS since the pixel clock * required for retina resolution exceeds LVDS' limits. * * Pre-retinas are able to switch the panel's DDC pins separately. * This is handled by a `TI SN74LV4066A`_ which is controlled by gmux. * The inactive GPU can thus probe the panel's EDID without switching over * the entire panel. Retinas lack this functionality as the chips used for * eDP muxing are incapable of switching the AUX channel separately (see * the linked data sheets, Pericom would be capable but this is unused). * However the retina panel has the NO_AUX_HANDSHAKE_LINK_TRAINING bit set * in its DPCD, allowing the inactive GPU to skip the AUX handshake and * set up the output with link parameters pre-calibrated by the active GPU. * * The external DP port is only fully switchable on the first two unibody * MacBook Pro generations, MBP5 2008/09 and MBP6 2010. This is done by an * `NXP CBTL06141`_ which is controlled by gmux. It's the predecessor of the * eDP mux on retinas, the difference being support for 2.7 versus 5.4 Gbit/s. * * The following MacBook Pro generations replaced the external DP port with a * combined DP/Thunderbolt port and lost the ability to switch it between GPUs, * connecting it either to the discrete GPU or the Thunderbolt controller. * Oddly enough, while the full port is no longer switchable, AUX and HPD * are still switchable by way of an `NXP CBTL03062`_ (on pre-retinas * MBP8 2011 and MBP9 2012) or two `TI TS3DS10224`_ (on pre-t2 retinas) under * the control of gmux. Since the integrated GPU is missing the main link, * external displays appear to it as phantoms which fail to link-train. * * gmux receives the HPD signal of all display connectors and sends an * interrupt on hotplug. On generations which cannot switch external ports, * 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. * * 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 * switch the panel and the external DP connector and allocates a framebuffer * for the selected GPU. * * .. _US 8,687,007 B2: https://pimg-fpiw.uspto.gov/fdd/07/870/086/0.pdf * .. _NXP CBTL06141: https://www.nxp.com/documents/data_sheet/CBTL06141.pdf * .. _NXP CBTL06142: https://www.nxp.com/documents/data_sheet/CBTL06141.pdf * .. _TI HD3SS212: https://www.ti.com/lit/ds/symlink/hd3ss212.pdf * .. _Pericom PI3VDP12412: https://www.pericom.com/assets/Datasheets/PI3VDP12412.pdf * .. _TI SN74LV4066A: https://www.ti.com/lit/ds/symlink/sn74lv4066a.pdf * .. _NXP CBTL03062: http://pdf.datasheetarchive.com/indexerfiles/Datasheets-SW16/DSASW00308511.pdf * .. _TI TS3DS10224: https://www.ti.com/lit/ds/symlink/ts3ds10224.pdf */ static void gmux_read_switch_state(struct apple_gmux_data *gmux_data) { … } static void gmux_write_switch_state(struct apple_gmux_data *gmux_data) { … } static int gmux_switchto(enum vga_switcheroo_client_id id) { … } static int gmux_switch_ddc(enum vga_switcheroo_client_id id) { … } /** * DOC: Power control * * gmux is able to cut power to the discrete GPU. It automatically takes care * of the correct sequence to tear down and bring up the power rails for * core voltage, VRAM and PCIe. */ static int gmux_set_discrete_state(struct apple_gmux_data *gmux_data, enum vga_switcheroo_state state) { … } static int gmux_set_power_state(enum vga_switcheroo_client_id id, enum vga_switcheroo_state state) { … } static enum vga_switcheroo_client_id gmux_get_client_id(struct pci_dev *pdev) { … } static const struct vga_switcheroo_handler gmux_handler_no_ddc = …; static const struct vga_switcheroo_handler gmux_handler_ddc = …; static const struct apple_gmux_config apple_gmux_pio = …; static const struct apple_gmux_config apple_gmux_index = …; static const struct apple_gmux_config apple_gmux_mmio = …; /** * DOC: Interrupt * * gmux is also connected to a GPIO pin of the southbridge and thereby is able * to trigger an ACPI GPE. ACPI name GMGP holds this GPIO pin's number. On the * MBP5 2008/09 it's GPIO pin 22 of the Nvidia MCP79, on following generations * it's GPIO pin 6 of the Intel PCH, on MMIO gmux's it's pin 21. * * The GPE merely signals that an interrupt occurred, the actual type of event * is identified by reading a gmux register. * * In addition to the GMGP name, gmux's ACPI device also has two methods GMSP * and GMLV. GMLV likely means "GMUX Level", and reads the value of the GPIO, * while GMSP likely means "GMUX Set Polarity", and seems to write to the GPIO's * value. On newer Macbooks (This was introduced with or sometime before the * MacBookPro14,3), the ACPI GPE method differentiates between the OS type: On * Darwin, only a notification is signaled, whereas on other OSes, the GPIO's * 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. */ static inline void gmux_disable_interrupts(struct apple_gmux_data *gmux_data) { … } static inline void gmux_enable_interrupts(struct apple_gmux_data *gmux_data) { … } static inline u8 gmux_interrupt_get_status(struct apple_gmux_data *gmux_data) { … } static void gmux_clear_interrupts(struct apple_gmux_data *gmux_data) { … } static void gmux_notify_handler(acpi_handle device, u32 value, void *context) { … } /** * DOC: Debugfs Interface * * gmux ports can be accessed from userspace as a debugfs interface. For example: * * # echo 4 > /sys/kernel/debug/apple_gmux/selected_port * # cat /sys/kernel/debug/apple_gmux/selected_port_data | xxd -p * 00000005 * * Reads 4 bytes from port 4 (GMUX_PORT_VERSION_MAJOR). * * 1 and 4 byte writes are also allowed. */ static ssize_t gmux_selected_port_data_write(struct file *file, const char __user *userbuf, size_t count, loff_t *ppos) { … } static ssize_t gmux_selected_port_data_read(struct file *file, char __user *userbuf, size_t count, loff_t *ppos) { … } static const struct file_operations gmux_port_data_ops = …; static void gmux_init_debugfs(struct apple_gmux_data *gmux_data) { … } static void gmux_fini_debugfs(struct apple_gmux_data *gmux_data) { … } static int gmux_suspend(struct device *dev) { … } static int gmux_resume(struct device *dev) { … } static int is_thunderbolt(struct device *dev, void *data) { … } static int gmux_probe(struct pnp_dev *pnp, const struct pnp_device_id *id) { … } static void gmux_remove(struct pnp_dev *pnp) { … } static const struct pnp_device_id gmux_device_ids[] = …; static const struct dev_pm_ops gmux_dev_pm_ops = …; static struct pnp_driver gmux_pnp_driver = …; module_pnp_driver(…); MODULE_AUTHOR(…) …; MODULE_DESCRIPTION(…) …; MODULE_LICENSE(…) …; MODULE_DEVICE_TABLE(pnp, gmux_device_ids);