linux/drivers/platform/x86/apple-gmux.c

// 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);