| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039 |
- // SPDX-License-Identifier: GPL-2.0-only
- /*
- * Gmux driver for Apple laptops
- *
- * Copyright (C) Canonical Ltd. <seth.forshee@canonical.com>
- * Copyright (C) 2010-2012 Andreas Heider <andreas@meetr.de>
- * Copyright (C) 2015 Lukas Wunner <lukas@wunner.de>
- * Copyright (C) 2023 Orlando Chamberlain <orlandoch.dev@gmail.com>
- */
- #define pr_fmt(fmt) KBUILD_MODNAME ": " 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 {
- u8 __iomem *iomem_base;
- unsigned long iostart;
- unsigned long iolen;
- const struct apple_gmux_config *config;
- struct mutex index_lock;
- struct backlight_device *bdev;
- /* 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;
- enum vga_switcheroo_state power_state;
- struct completion powerchange_done;
- /* debugfs data */
- u8 selected_port;
- struct dentry *debug_dentry;
- };
- static struct apple_gmux_data *apple_gmux_data;
- struct apple_gmux_config {
- u8 (*read8)(struct apple_gmux_data *gmux_data, int port);
- void (*write8)(struct apple_gmux_data *gmux_data, int port, u8 val);
- u32 (*read32)(struct apple_gmux_data *gmux_data, int port);
- void (*write32)(struct apple_gmux_data *gmux_data, int port, u32 val);
- const struct vga_switcheroo_handler *gmux_handler;
- enum vga_switcheroo_handler_flags_t handler_flags;
- unsigned long resource_type;
- bool read_version_as_u32;
- char *name;
- };
- #define GMUX_INTERRUPT_ENABLE 0xff
- #define GMUX_INTERRUPT_DISABLE 0x00
- #define GMUX_INTERRUPT_STATUS_ACTIVE 0
- #define GMUX_INTERRUPT_STATUS_DISPLAY (1 << 0)
- #define GMUX_INTERRUPT_STATUS_POWER (1 << 2)
- #define GMUX_INTERRUPT_STATUS_HOTPLUG (1 << 3)
- #define GMUX_BRIGHTNESS_MASK 0x00ffffff
- #define GMUX_MAX_BRIGHTNESS GMUX_BRIGHTNESS_MASK
- # define MMIO_GMUX_MAX_BRIGHTNESS 0xffff
- static u8 gmux_pio_read8(struct apple_gmux_data *gmux_data, int port)
- {
- return inb(gmux_data->iostart + port);
- }
- static void gmux_pio_write8(struct apple_gmux_data *gmux_data, int port,
- u8 val)
- {
- outb(val, gmux_data->iostart + port);
- }
- static u32 gmux_pio_read32(struct apple_gmux_data *gmux_data, int port)
- {
- return inl(gmux_data->iostart + port);
- }
- static void gmux_pio_write32(struct apple_gmux_data *gmux_data, int port,
- u32 val)
- {
- int i;
- u8 tmpval;
- for (i = 0; i < 4; i++) {
- tmpval = (val >> (i * 8)) & 0xff;
- outb(tmpval, gmux_data->iostart + port + i);
- }
- }
- static int gmux_index_wait_ready(struct apple_gmux_data *gmux_data)
- {
- int i = 200;
- u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
- while (i && (gwr & 0x01)) {
- inb(gmux_data->iostart + GMUX_PORT_READ);
- gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
- udelay(100);
- i--;
- }
- return !!i;
- }
- static int gmux_index_wait_complete(struct apple_gmux_data *gmux_data)
- {
- int i = 200;
- u8 gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
- while (i && !(gwr & 0x01)) {
- gwr = inb(gmux_data->iostart + GMUX_PORT_WRITE);
- udelay(100);
- i--;
- }
- if (gwr & 0x01)
- inb(gmux_data->iostart + GMUX_PORT_READ);
- return !!i;
- }
- static u8 gmux_index_read8(struct apple_gmux_data *gmux_data, int port)
- {
- u8 val;
- mutex_lock(&gmux_data->index_lock);
- gmux_index_wait_ready(gmux_data);
- outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ);
- gmux_index_wait_complete(gmux_data);
- val = inb(gmux_data->iostart + GMUX_PORT_VALUE);
- mutex_unlock(&gmux_data->index_lock);
- return val;
- }
- static void gmux_index_write8(struct apple_gmux_data *gmux_data, int port,
- u8 val)
- {
- mutex_lock(&gmux_data->index_lock);
- outb(val, gmux_data->iostart + GMUX_PORT_VALUE);
- gmux_index_wait_ready(gmux_data);
- outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE);
- gmux_index_wait_complete(gmux_data);
- mutex_unlock(&gmux_data->index_lock);
- }
- static u32 gmux_index_read32(struct apple_gmux_data *gmux_data, int port)
- {
- u32 val;
- mutex_lock(&gmux_data->index_lock);
- gmux_index_wait_ready(gmux_data);
- outb((port & 0xff), gmux_data->iostart + GMUX_PORT_READ);
- gmux_index_wait_complete(gmux_data);
- val = inl(gmux_data->iostart + GMUX_PORT_VALUE);
- mutex_unlock(&gmux_data->index_lock);
- return val;
- }
- static void gmux_index_write32(struct apple_gmux_data *gmux_data, int port,
- u32 val)
- {
- int i;
- u8 tmpval;
- mutex_lock(&gmux_data->index_lock);
- for (i = 0; i < 4; i++) {
- tmpval = (val >> (i * 8)) & 0xff;
- outb(tmpval, gmux_data->iostart + GMUX_PORT_VALUE + i);
- }
- gmux_index_wait_ready(gmux_data);
- outb(port & 0xff, gmux_data->iostart + GMUX_PORT_WRITE);
- gmux_index_wait_complete(gmux_data);
- mutex_unlock(&gmux_data->index_lock);
- }
- static int gmux_mmio_wait(struct apple_gmux_data *gmux_data)
- {
- int i = 200;
- u8 gwr = ioread8(gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
- while (i && gwr) {
- gwr = ioread8(gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
- udelay(100);
- i--;
- }
- return !!i;
- }
- static u8 gmux_mmio_read8(struct apple_gmux_data *gmux_data, int port)
- {
- u8 val;
- mutex_lock(&gmux_data->index_lock);
- gmux_mmio_wait(gmux_data);
- iowrite8((port & 0xff), gmux_data->iomem_base + GMUX_MMIO_PORT_SELECT);
- iowrite8(GMUX_MMIO_READ | sizeof(val),
- gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
- gmux_mmio_wait(gmux_data);
- val = ioread8(gmux_data->iomem_base);
- mutex_unlock(&gmux_data->index_lock);
- return val;
- }
- static void gmux_mmio_write8(struct apple_gmux_data *gmux_data, int port,
- u8 val)
- {
- mutex_lock(&gmux_data->index_lock);
- gmux_mmio_wait(gmux_data);
- iowrite8(val, gmux_data->iomem_base);
- iowrite8(port & 0xff, gmux_data->iomem_base + GMUX_MMIO_PORT_SELECT);
- iowrite8(GMUX_MMIO_WRITE | sizeof(val),
- gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
- gmux_mmio_wait(gmux_data);
- mutex_unlock(&gmux_data->index_lock);
- }
- static u32 gmux_mmio_read32(struct apple_gmux_data *gmux_data, int port)
- {
- u32 val;
- mutex_lock(&gmux_data->index_lock);
- gmux_mmio_wait(gmux_data);
- iowrite8((port & 0xff), gmux_data->iomem_base + GMUX_MMIO_PORT_SELECT);
- iowrite8(GMUX_MMIO_READ | sizeof(val),
- gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
- gmux_mmio_wait(gmux_data);
- val = ioread32be(gmux_data->iomem_base);
- mutex_unlock(&gmux_data->index_lock);
- return val;
- }
- static void gmux_mmio_write32(struct apple_gmux_data *gmux_data, int port,
- u32 val)
- {
- mutex_lock(&gmux_data->index_lock);
- iowrite32be(val, gmux_data->iomem_base);
- iowrite8(port & 0xff, gmux_data->iomem_base + GMUX_MMIO_PORT_SELECT);
- iowrite8(GMUX_MMIO_WRITE | sizeof(val),
- gmux_data->iomem_base + GMUX_MMIO_COMMAND_SEND);
- gmux_mmio_wait(gmux_data);
- mutex_unlock(&gmux_data->index_lock);
- }
- static u8 gmux_read8(struct apple_gmux_data *gmux_data, int port)
- {
- return gmux_data->config->read8(gmux_data, port);
- }
- static void gmux_write8(struct apple_gmux_data *gmux_data, int port, u8 val)
- {
- return gmux_data->config->write8(gmux_data, port, val);
- }
- static u32 gmux_read32(struct apple_gmux_data *gmux_data, int port)
- {
- return gmux_data->config->read32(gmux_data, port);
- }
- static void gmux_write32(struct apple_gmux_data *gmux_data, int port,
- u32 val)
- {
- return gmux_data->config->write32(gmux_data, port, 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)
- {
- struct apple_gmux_data *gmux_data = bl_get_data(bd);
- return gmux_read32(gmux_data, GMUX_PORT_BRIGHTNESS) &
- GMUX_BRIGHTNESS_MASK;
- }
- static int gmux_update_status(struct backlight_device *bd)
- {
- struct apple_gmux_data *gmux_data = bl_get_data(bd);
- u32 brightness = backlight_get_brightness(bd);
- gmux_write32(gmux_data, GMUX_PORT_BRIGHTNESS, brightness);
- return 0;
- }
- static const struct backlight_ops gmux_bl_ops = {
- .options = BL_CORE_SUSPENDRESUME,
- .get_brightness = gmux_get_brightness,
- .update_status = gmux_update_status,
- };
- /**
- * 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)
- {
- if (gmux_read8(gmux_data, GMUX_PORT_SWITCH_DDC) == 1)
- gmux_data->switch_state_ddc = VGA_SWITCHEROO_IGD;
- else
- gmux_data->switch_state_ddc = VGA_SWITCHEROO_DIS;
- if (gmux_read8(gmux_data, GMUX_PORT_SWITCH_DISPLAY) & 1)
- gmux_data->switch_state_display = VGA_SWITCHEROO_DIS;
- else
- gmux_data->switch_state_display = VGA_SWITCHEROO_IGD;
- if (gmux_read8(gmux_data, GMUX_PORT_SWITCH_EXTERNAL) == 2)
- gmux_data->switch_state_external = VGA_SWITCHEROO_IGD;
- else
- gmux_data->switch_state_external = VGA_SWITCHEROO_DIS;
- }
- static void gmux_write_switch_state(struct apple_gmux_data *gmux_data)
- {
- if (gmux_data->switch_state_ddc == VGA_SWITCHEROO_IGD)
- gmux_write8(gmux_data, GMUX_PORT_SWITCH_DDC, 1);
- else
- gmux_write8(gmux_data, GMUX_PORT_SWITCH_DDC, 2);
- if (gmux_data->switch_state_display == VGA_SWITCHEROO_IGD)
- gmux_write8(gmux_data, GMUX_PORT_SWITCH_DISPLAY, 2);
- else
- gmux_write8(gmux_data, GMUX_PORT_SWITCH_DISPLAY, 3);
- if (gmux_data->switch_state_external == VGA_SWITCHEROO_IGD)
- gmux_write8(gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 2);
- else
- gmux_write8(gmux_data, GMUX_PORT_SWITCH_EXTERNAL, 3);
- }
- static int gmux_switchto(enum vga_switcheroo_client_id id)
- {
- apple_gmux_data->switch_state_ddc = id;
- apple_gmux_data->switch_state_display = id;
- if (apple_gmux_data->external_switchable)
- apple_gmux_data->switch_state_external = id;
- gmux_write_switch_state(apple_gmux_data);
- return 0;
- }
- static int gmux_switch_ddc(enum vga_switcheroo_client_id id)
- {
- enum vga_switcheroo_client_id old_ddc_owner =
- apple_gmux_data->switch_state_ddc;
- if (id == old_ddc_owner)
- return id;
- pr_debug("Switching DDC from %d to %d\n", old_ddc_owner, id);
- apple_gmux_data->switch_state_ddc = 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 old_ddc_owner;
- }
- /**
- * 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)
- {
- reinit_completion(&gmux_data->powerchange_done);
- if (state == VGA_SWITCHEROO_ON) {
- gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 1);
- gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 3);
- pr_debug("Discrete card powered up\n");
- } else {
- gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 1);
- gmux_write8(gmux_data, GMUX_PORT_DISCRETE_POWER, 0);
- pr_debug("Discrete card powered down\n");
- }
- gmux_data->power_state = state;
- if (gmux_data->gpe >= 0 &&
- !wait_for_completion_interruptible_timeout(&gmux_data->powerchange_done,
- msecs_to_jiffies(200)))
- pr_warn("Timeout waiting for gmux switch to complete\n");
- return 0;
- }
- static int gmux_set_power_state(enum vga_switcheroo_client_id id,
- enum vga_switcheroo_state state)
- {
- if (id == VGA_SWITCHEROO_IGD)
- return 0;
- return gmux_set_discrete_state(apple_gmux_data, state);
- }
- static enum vga_switcheroo_client_id gmux_get_client_id(struct pci_dev *pdev)
- {
- /*
- * Early Macbook Pros with switchable graphics use nvidia
- * integrated graphics. Hardcode that the 9400M is integrated.
- */
- if (pdev->vendor == PCI_VENDOR_ID_INTEL)
- return VGA_SWITCHEROO_IGD;
- else if (pdev->vendor == PCI_VENDOR_ID_NVIDIA &&
- pdev->device == 0x0863)
- return VGA_SWITCHEROO_IGD;
- else
- return VGA_SWITCHEROO_DIS;
- }
- static const struct vga_switcheroo_handler gmux_handler_no_ddc = {
- .switchto = gmux_switchto,
- .power_state = gmux_set_power_state,
- .get_client_id = gmux_get_client_id,
- };
- static const struct vga_switcheroo_handler gmux_handler_ddc = {
- .switchto = gmux_switchto,
- .switch_ddc = gmux_switch_ddc,
- .power_state = gmux_set_power_state,
- .get_client_id = gmux_get_client_id,
- };
- static const struct apple_gmux_config apple_gmux_pio = {
- .read8 = &gmux_pio_read8,
- .write8 = &gmux_pio_write8,
- .read32 = &gmux_pio_read32,
- .write32 = &gmux_pio_write32,
- .gmux_handler = &gmux_handler_ddc,
- .handler_flags = VGA_SWITCHEROO_CAN_SWITCH_DDC,
- .resource_type = IORESOURCE_IO,
- .read_version_as_u32 = false,
- .name = "classic"
- };
- static const struct apple_gmux_config apple_gmux_index = {
- .read8 = &gmux_index_read8,
- .write8 = &gmux_index_write8,
- .read32 = &gmux_index_read32,
- .write32 = &gmux_index_write32,
- .gmux_handler = &gmux_handler_no_ddc,
- .handler_flags = VGA_SWITCHEROO_NEEDS_EDP_CONFIG,
- .resource_type = IORESOURCE_IO,
- .read_version_as_u32 = true,
- .name = "indexed"
- };
- static const struct apple_gmux_config apple_gmux_mmio = {
- .read8 = &gmux_mmio_read8,
- .write8 = &gmux_mmio_write8,
- .read32 = &gmux_mmio_read32,
- .write32 = &gmux_mmio_write32,
- .gmux_handler = &gmux_handler_no_ddc,
- .handler_flags = VGA_SWITCHEROO_NEEDS_EDP_CONFIG,
- .resource_type = IORESOURCE_MEM,
- .read_version_as_u32 = true,
- .name = "T2"
- };
- /**
- * 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)
- {
- gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE,
- GMUX_INTERRUPT_DISABLE);
- }
- static inline void gmux_enable_interrupts(struct apple_gmux_data *gmux_data)
- {
- gmux_write8(gmux_data, GMUX_PORT_INTERRUPT_ENABLE,
- GMUX_INTERRUPT_ENABLE);
- }
- static inline u8 gmux_interrupt_get_status(struct apple_gmux_data *gmux_data)
- {
- return gmux_read8(gmux_data, GMUX_PORT_INTERRUPT_STATUS);
- }
- static void gmux_clear_interrupts(struct apple_gmux_data *gmux_data)
- {
- u8 status;
- /* 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_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);
- 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);
- if (status & GMUX_INTERRUPT_STATUS_POWER)
- complete(&gmux_data->powerchange_done);
- }
- /**
- * 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)
- {
- struct apple_gmux_data *gmux_data = file->private_data;
- if (*ppos)
- return -EINVAL;
- if (count == 1) {
- u8 data;
- if (copy_from_user(&data, userbuf, 1))
- return -EFAULT;
- gmux_write8(gmux_data, gmux_data->selected_port, data);
- } else if (count == 4) {
- u32 data;
- if (copy_from_user(&data, userbuf, 4))
- return -EFAULT;
- gmux_write32(gmux_data, gmux_data->selected_port, data);
- } else
- return -EINVAL;
- return count;
- }
- static ssize_t gmux_selected_port_data_read(struct file *file,
- char __user *userbuf, size_t count, loff_t *ppos)
- {
- struct apple_gmux_data *gmux_data = file->private_data;
- u32 data;
- data = gmux_read32(gmux_data, gmux_data->selected_port);
- return simple_read_from_buffer(userbuf, count, ppos, &data, sizeof(data));
- }
- static const struct file_operations gmux_port_data_ops = {
- .open = simple_open,
- .write = gmux_selected_port_data_write,
- .read = gmux_selected_port_data_read
- };
- static void gmux_init_debugfs(struct apple_gmux_data *gmux_data)
- {
- gmux_data->debug_dentry = debugfs_create_dir(KBUILD_MODNAME, NULL);
- debugfs_create_u8("selected_port", 0644, gmux_data->debug_dentry,
- &gmux_data->selected_port);
- debugfs_create_file("selected_port_data", 0644, gmux_data->debug_dentry,
- gmux_data, &gmux_port_data_ops);
- }
- static void gmux_fini_debugfs(struct apple_gmux_data *gmux_data)
- {
- debugfs_remove_recursive(gmux_data->debug_dentry);
- }
- static int gmux_suspend(struct device *dev)
- {
- struct pnp_dev *pnp = to_pnp_dev(dev);
- struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
- gmux_disable_interrupts(gmux_data);
- return 0;
- }
- 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_write_switch_state(gmux_data);
- if (gmux_data->power_state == VGA_SWITCHEROO_OFF)
- gmux_set_discrete_state(gmux_data, gmux_data->power_state);
- return 0;
- }
- 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;
- struct resource *res;
- struct backlight_properties props;
- struct backlight_device *bdev = NULL;
- u8 ver_major, ver_minor, ver_release;
- bool register_bdev = true;
- int ret = -ENXIO;
- acpi_status status;
- unsigned long long gpe;
- enum apple_gmux_type type;
- u32 version;
- if (apple_gmux_data)
- return -EBUSY;
- if (!apple_gmux_detect(pnp, &type)) {
- pr_info("gmux device not present\n");
- return -ENODEV;
- }
- gmux_data = kzalloc_obj(*gmux_data);
- if (!gmux_data)
- return -ENOMEM;
- pnp_set_drvdata(pnp, gmux_data);
- switch (type) {
- case APPLE_GMUX_TYPE_MMIO:
- gmux_data->config = &apple_gmux_mmio;
- mutex_init(&gmux_data->index_lock);
- res = pnp_get_resource(pnp, IORESOURCE_MEM, 0);
- gmux_data->iostart = res->start;
- /* Although the ACPI table only allocates 8 bytes, we need 16. */
- gmux_data->iolen = 16;
- if (!request_mem_region(gmux_data->iostart, gmux_data->iolen,
- "Apple gmux")) {
- pr_err("gmux I/O already in use\n");
- goto err_free;
- }
- gmux_data->iomem_base = ioremap(gmux_data->iostart, gmux_data->iolen);
- if (!gmux_data->iomem_base) {
- pr_err("couldn't remap gmux mmio region");
- goto err_release;
- }
- goto get_version;
- case APPLE_GMUX_TYPE_INDEXED:
- gmux_data->config = &apple_gmux_index;
- mutex_init(&gmux_data->index_lock);
- break;
- case APPLE_GMUX_TYPE_PIO:
- gmux_data->config = &apple_gmux_pio;
- break;
- }
- res = pnp_get_resource(pnp, IORESOURCE_IO, 0);
- gmux_data->iostart = res->start;
- gmux_data->iolen = resource_size(res);
- if (!request_region(gmux_data->iostart, gmux_data->iolen,
- "Apple gmux")) {
- pr_err("gmux I/O already in use\n");
- goto err_free;
- }
- get_version:
- if (gmux_data->config->read_version_as_u32) {
- version = gmux_read32(gmux_data, GMUX_PORT_VERSION_MAJOR);
- ver_major = (version >> 24) & 0xff;
- ver_minor = (version >> 16) & 0xff;
- ver_release = (version >> 8) & 0xff;
- } else {
- ver_major = gmux_read8(gmux_data, GMUX_PORT_VERSION_MAJOR);
- ver_minor = gmux_read8(gmux_data, GMUX_PORT_VERSION_MINOR);
- ver_release = gmux_read8(gmux_data, GMUX_PORT_VERSION_RELEASE);
- }
- pr_info("Found gmux version %d.%d.%d [%s]\n", ver_major, ver_minor,
- ver_release, gmux_data->config->name);
- memset(&props, 0, sizeof(props));
- props.type = BACKLIGHT_PLATFORM;
- /*
- * All MMIO gmux's have 0xffff as max brightness, but some iMacs incorrectly
- * report 0x03ff, despite the firmware being happy to set 0xffff as the brightness
- * at boot. Force 0xffff for all MMIO gmux's so they all have the correct brightness
- * range.
- */
- if (type == APPLE_GMUX_TYPE_MMIO)
- props.max_brightness = MMIO_GMUX_MAX_BRIGHTNESS;
- else
- props.max_brightness = gmux_read32(gmux_data, GMUX_PORT_MAX_BRIGHTNESS);
- #if IS_REACHABLE(CONFIG_ACPI_VIDEO)
- register_bdev = acpi_video_get_backlight_type() == acpi_backlight_apple_gmux;
- #endif
- if (register_bdev) {
- /*
- * Currently it's assumed that the maximum brightness is less than
- * 2^24 for compatibility with old gmux versions. Cap the max
- * brightness at this value, but print a warning if the hardware
- * reports something higher so that it can be fixed.
- */
- if (WARN_ON(props.max_brightness > GMUX_MAX_BRIGHTNESS))
- props.max_brightness = GMUX_MAX_BRIGHTNESS;
- bdev = backlight_device_register("gmux_backlight", &pnp->dev,
- gmux_data, &gmux_bl_ops, &props);
- if (IS_ERR(bdev)) {
- ret = PTR_ERR(bdev);
- goto err_unmap;
- }
- gmux_data->bdev = bdev;
- bdev->props.brightness = gmux_get_brightness(bdev);
- backlight_update_status(bdev);
- }
- gmux_data->power_state = VGA_SWITCHEROO_ON;
- gmux_data->dhandle = ACPI_HANDLE(&pnp->dev);
- if (!gmux_data->dhandle) {
- pr_err("Cannot find acpi handle for pnp device %s\n",
- dev_name(&pnp->dev));
- ret = -ENODEV;
- goto err_notify;
- }
- status = acpi_evaluate_integer(gmux_data->dhandle, "GMGP", NULL, &gpe);
- if (ACPI_SUCCESS(status)) {
- gmux_data->gpe = (int)gpe;
- status = acpi_install_notify_handler(gmux_data->dhandle,
- ACPI_DEVICE_NOTIFY,
- &gmux_notify_handler, pnp);
- if (ACPI_FAILURE(status)) {
- pr_err("Install notify handler failed: %s\n",
- acpi_format_exception(status));
- ret = -ENODEV;
- goto err_notify;
- }
- status = acpi_enable_gpe(NULL, gmux_data->gpe);
- if (ACPI_FAILURE(status)) {
- pr_err("Cannot enable gpe: %s\n",
- acpi_format_exception(status));
- goto err_enable_gpe;
- }
- } else {
- pr_warn("No GPE found for gmux\n");
- 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);
- gmux_read_switch_state(gmux_data);
- /*
- * Retina MacBook Pros cannot switch the panel's AUX separately
- * and need eDP pre-calibration. They are distinguishable from
- * pre-retinas by having an "indexed" or "T2" gmux.
- *
- * Pre-retina MacBook Pros can switch the panel's DDC separately.
- */
- ret = vga_switcheroo_register_handler(gmux_data->config->gmux_handler,
- gmux_data->config->handler_flags);
- if (ret) {
- pr_err("Failed to register vga_switcheroo handler\n");
- goto err_register_handler;
- }
- gmux_init_debugfs(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:
- if (gmux_data->gpe >= 0)
- acpi_remove_notify_handler(gmux_data->dhandle,
- ACPI_DEVICE_NOTIFY,
- &gmux_notify_handler);
- err_notify:
- backlight_device_unregister(bdev);
- err_unmap:
- if (gmux_data->iomem_base)
- iounmap(gmux_data->iomem_base);
- err_release:
- if (gmux_data->config->resource_type == IORESOURCE_MEM)
- release_mem_region(gmux_data->iostart, gmux_data->iolen);
- else
- release_region(gmux_data->iostart, gmux_data->iolen);
- err_free:
- kfree(gmux_data);
- return ret;
- }
- static void gmux_remove(struct pnp_dev *pnp)
- {
- struct apple_gmux_data *gmux_data = pnp_get_drvdata(pnp);
- gmux_fini_debugfs(gmux_data);
- vga_switcheroo_unregister_handler();
- gmux_disable_interrupts(gmux_data);
- if (gmux_data->gpe >= 0) {
- acpi_disable_gpe(NULL, gmux_data->gpe);
- acpi_remove_notify_handler(gmux_data->dhandle,
- ACPI_DEVICE_NOTIFY,
- &gmux_notify_handler);
- }
- backlight_device_unregister(gmux_data->bdev);
- if (gmux_data->iomem_base) {
- iounmap(gmux_data->iomem_base);
- release_mem_region(gmux_data->iostart, gmux_data->iolen);
- } else
- release_region(gmux_data->iostart, gmux_data->iolen);
- apple_gmux_data = NULL;
- kfree(gmux_data);
- }
- static const struct pnp_device_id gmux_device_ids[] = {
- {GMUX_ACPI_HID, 0},
- {"", 0}
- };
- static const struct dev_pm_ops gmux_dev_pm_ops = {
- .suspend = gmux_suspend,
- .resume = gmux_resume,
- };
- static struct pnp_driver gmux_pnp_driver = {
- .name = "apple-gmux",
- .probe = gmux_probe,
- .remove = gmux_remove,
- .id_table = gmux_device_ids,
- .driver = {
- .pm = &gmux_dev_pm_ops,
- },
- };
- module_pnp_driver(gmux_pnp_driver);
- MODULE_AUTHOR("Seth Forshee <seth.forshee@canonical.com>");
- MODULE_DESCRIPTION("Apple Gmux Driver");
- MODULE_LICENSE("GPL");
- MODULE_DEVICE_TABLE(pnp, gmux_device_ids);
|