git: d7d237ce731f - main - acpi_apple: Apple platform ACPI driver,

From: Abdelkader Boudih <seuros_at_FreeBSD.org>
Date: Thu, 15 Oct 2026 13:05:12 UTC
The branch main has been updated by seuros:

URL: https://cgit.FreeBSD.org/src/commit/?id=d7d237ce731f0897107ca0f9301b4cef1846a182

commit d7d237ce731f0897107ca0f9301b4cef1846a182
Author:     Abdelkader Boudih <seuros@FreeBSD.org>
AuthorDate: 2026-10-15 13:03:22 +0000
Commit:     Abdelkader Boudih <seuros@FreeBSD.org>
CommitDate: 2026-10-15 13:03:22 +0000

    acpi_apple: Apple platform ACPI driver,
    
    handles wake-on-LAN arming and lid state on the PCH
    PMC/GMUX platform instance, and panel backlight
    control on the PNLF instance, registering with
    backlight(9).
    
    Reviewed by:    ziaee, adrian
    Differential Revision:  https://reviews.freebsd.org/D58867
---
 share/man/man4/Makefile              |   2 +
 share/man/man4/acpi_apple.4          |  74 ++++
 sys/conf/files                       |   3 +
 sys/dev/apple_acpi/apple_acpi.c      | 120 ++++++
 sys/dev/apple_acpi/apple_acpi.h      | 149 +++++++
 sys/dev/apple_acpi/apple_backlight.c | 771 +++++++++++++++++++++++++++++++++++
 sys/dev/apple_acpi/apple_platform.c  | 123 ++++++
 sys/modules/acpi/Makefile            |   5 +
 sys/modules/acpi/acpi_apple/Makefile |   8 +
 9 files changed, 1255 insertions(+)

diff --git a/share/man/man4/Makefile b/share/man/man4/Makefile
index f00a0400e7b0..2ec20f925018 100644
--- a/share/man/man4/Makefile
+++ b/share/man/man4/Makefile
@@ -14,6 +14,7 @@ MANPACKAGE=	kernel-man
 MAN=	aac.4 \
 	aacraid.4 \
 	acpi.4 \
+	${_acpi_apple.4} \
 	${_acpi_asus.4} \
 	${_acpi_asus_wmi.4} \
 	${_acpi_dock.4} \
@@ -956,6 +957,7 @@ _xnb.4=		xnb.4
 .endif
 
 .if ${MACHINE_CPUARCH} == "amd64"
+_acpi_apple.4=	acpi_apple.4
 _ioat.4=	ioat.4
 _iwx.4=		iwx.4
 _nvdimm.4=	nvdimm.4
diff --git a/share/man/man4/acpi_apple.4 b/share/man/man4/acpi_apple.4
new file mode 100644
index 000000000000..437500a11425
--- /dev/null
+++ b/share/man/man4/acpi_apple.4
@@ -0,0 +1,74 @@
+.\"
+.\" Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
+.\"
+.\" SPDX-License-Identifier: BSD-2-Clause
+.\"
+.Dd August 16, 2026
+.Dt ACPI_APPLE 4 amd64
+.Os
+.Sh NAME
+.Nm acpi_apple
+.Nd Apple platform ACPI driver
+.Sh SYNOPSIS
+.Cd device acpi_apple
+.Cd device backlight
+.Sh DESCRIPTION
+The
+.Nm
+driver attaches to Apple platform ACPI devices found on Intel-based
+Macs and provides two independent functions through a single driver,
+selected by the ACPI hardware ID it matches:
+.Bl -tag -width indent
+.It APP9876 , APP000B
+Platform instance.
+Attaches to the PCH PMC companion
+.Pq T2 Macs, 2018 and later
+or the GMUX GPU multiplexer companion
+.Pq Intel Macs with discrete graphics, 2006-2020 .
+Arms Wake-on-LAN on shutdown for any interface with wake enabled
+.Pq e.g. Va dev.bge.N.wake=1 ,
+and follows the ACPI lid switch, blanking the backlight when the lid is
+closed and restoring it when the lid is reopened.
+.It APP0002
+Backlight instance.
+Controls the panel backlight found on all Intel Macs with an internal
+display, and registers with the
+.Xr backlight 9
+framework.
+The correct control method is selected automatically.
+.El
+.Pp
+.Dv APP0001
+.Pq SMC companion
+is intentionally not claimed by this driver; it is owned by
+.Xr asmc 4 .
+.Sh HARDWARE
+The
+.Nm
+driver supports the Apple ACPI devices found in the DSDT of Intel-based
+Macs:
+.Pp
+.Bl -bullet -compact
+.It
+APP9876, the PCH PMC companion, present on T2 Macs
+.Pq 2018 and later .
+.It
+APP000B, the GMUX GPU multiplexer, present on Intel Macs with discrete
+graphics
+.Pq 2006-2020 ,
+including single-GPU Mac minis.
+.It
+APP0002, the panel backlight, present on all Intel Macs with a built-in
+display.
+.El
+.Sh SEE ALSO
+.Xr acpi 4 ,
+.Xr asmc 4 ,
+.Xr backlight 9
+.Sh HISTORY
+The
+.Nm
+driver first appeared in
+.Fx 16.0 .
+.Sh AUTHORS
+.An Abdelkader Boudih Aq Mt seuros@FreeBSD.org
diff --git a/sys/conf/files b/sys/conf/files
index ee56f70e706b..6e8c80807124 100644
--- a/sys/conf/files
+++ b/sys/conf/files
@@ -848,6 +848,9 @@ dev/ale/if_ale.c		optional ale pci
 dev/alpm/alpm.c			optional alpm pci
 dev/amdpm/amdpm.c		optional amdpm pci | nfpm pci
 dev/amdsmb/amdsmb.c		optional amdsmb pci
+dev/apple_acpi/apple_acpi.c	optional acpi_apple acpi pci backlight
+dev/apple_acpi/apple_backlight.c optional acpi_apple acpi pci backlight
+dev/apple_acpi/apple_platform.c	optional acpi_apple acpi pci backlight
 #
 dev/aq/aq_main.c		optional aq
 dev/aq/aq_media.c		optional aq
diff --git a/sys/dev/apple_acpi/apple_acpi.c b/sys/dev/apple_acpi/apple_acpi.c
new file mode 100644
index 000000000000..5e979309d4c2
--- /dev/null
+++ b/sys/dev/apple_acpi/apple_acpi.c
@@ -0,0 +1,120 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * acpi_apple(4) - FreeBSD driver for Apple platform ACPI devices.
+ *
+ * Handles multiple Apple ACPI devices with a single driver:
+ *
+ *   APP000B (GMUX) / APP9876 (PCH PMC) - platform instance:
+ *     - Wake-on-LAN: walk ACPI namespace on shutdown, call _PSW(1) on
+ *       any device with ACPI_FLAG_WAKE_ENABLED (dev.X.Y.wake=1).
+ *     - Lid state: follow acpi_lid_event to blank and restore the
+ *       backlight.
+ *
+ *   APP0002 (PNLF) - backlight instance:
+ *     - Panel backlight control via GPU PCI BAR MMIO registers.
+ *     - Supports Intel PCH/CPU, Nvidia NV40/NV50, AMD R600+ families.
+ *     - Registers with backlight(9) framework.
+ *
+ * OSDW() gates on OSYS==0x2710 ("Darwin"), installed via
+ * hw.acpi.apple_darwin_osi=1.  _PSW on GIGE only fires if the bge(4)
+ * instance has ACPI_FLAG_WAKE_ENABLED (dev.bge.X.wake=1).
+ */
+
+#include <sys/cdefs.h>
+#include "opt_acpi.h"
+#include "apple_acpi.h"
+
+#include "backlight_if.h"
+
+#define _COMPONENT	ACPI_OEM
+ACPI_MODULE_NAME("Apple")
+
+/*
+ * Attach to APP9876 (PCH PMC, T2 Macs), APP000B (GMUX, discrete GPU
+ * Macs), or APP0002 (panel backlight, all Intel Macs with display).
+ * APP0001 (SMC) is owned by asmc(4).
+ *
+ * BUS_PROBE_LOW_PRIORITY so a future apple_gmux driver can claim
+ * APP000B at default priority.
+ */
+static char *acpi_apple_ids[] = { "APP9876", "APP000B", "APP0002", NULL };
+
+static int
+acpi_apple_probe(device_t dev)
+{
+	int rv;
+
+	if (acpi_disabled("apple"))
+		return (ENXIO);
+	rv = ACPI_ID_PROBE(device_get_parent(dev), dev, acpi_apple_ids, NULL);
+	if (rv > 0)
+		return (rv);
+
+	if (acpi_MatchHid(acpi_get_handle(dev), "APP0002"))
+		device_set_desc(dev, "Apple Panel Backlight");
+	else
+		device_set_desc(dev, "Apple platform controller");
+
+	return (BUS_PROBE_LOW_PRIORITY);
+}
+
+static int
+acpi_apple_attach(device_t dev)
+{
+	struct acpi_apple_softc *sc;
+
+	sc = device_get_softc(dev);
+	sc->dev = dev;
+	sc->handle = acpi_get_handle(dev);
+
+	if (acpi_MatchHid(sc->handle, "APP0002"))
+		sc->mode = ACPI_APPLE_BACKLIGHT;
+	else
+		sc->mode = ACPI_APPLE_PLATFORM;
+
+	if (sc->mode == ACPI_APPLE_PLATFORM)
+		return (acpi_apple_platform_attach(sc));
+	else
+		return (acpi_apple_backlight_attach(sc));
+}
+
+static int
+acpi_apple_detach(device_t dev)
+{
+	struct acpi_apple_softc *sc;
+
+	sc = device_get_softc(dev);
+	if (sc->mode == ACPI_APPLE_PLATFORM)
+		acpi_apple_platform_detach(sc);
+	else
+		acpi_apple_backlight_detach(sc);
+	return (0);
+}
+
+static device_method_t acpi_apple_methods[] = {
+	DEVMETHOD(device_probe,			acpi_apple_probe),
+	DEVMETHOD(device_attach,		acpi_apple_attach),
+	DEVMETHOD(device_detach,		acpi_apple_detach),
+
+	DEVMETHOD(backlight_update_status,	acpi_apple_bl_update_status),
+	DEVMETHOD(backlight_get_status,		acpi_apple_bl_get_status),
+	DEVMETHOD(backlight_get_info,		acpi_apple_bl_get_info),
+
+	DEVMETHOD_END
+};
+
+static driver_t acpi_apple_driver = {
+	"acpi_apple",
+	acpi_apple_methods,
+	sizeof(struct acpi_apple_softc),
+};
+
+DRIVER_MODULE(acpi_apple, acpi, acpi_apple_driver, 0, 0);
+MODULE_DEPEND(acpi_apple, acpi, 1, 1, 1);
+MODULE_DEPEND(acpi_apple, backlight, 1, 1, 1);
+MODULE_VERSION(acpi_apple, 1);
diff --git a/sys/dev/apple_acpi/apple_acpi.h b/sys/dev/apple_acpi/apple_acpi.h
new file mode 100644
index 000000000000..de5416de6eb0
--- /dev/null
+++ b/sys/dev/apple_acpi/apple_acpi.h
@@ -0,0 +1,149 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef	_APPLE_ACPI_H_
+#define	_APPLE_ACPI_H_
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/eventhandler.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/module.h>
+#include <sys/mutex.h>
+#include <sys/sysctl.h>
+
+#include <contrib/dev/acpica/include/acpi.h>
+
+#include <dev/acpica/acpivar.h>
+#include <dev/backlight/backlight.h>
+
+#define	ACPI_APPLE_PLATFORM	0x01	/* WoL + lid (APP000B/APP9876) */
+#define	ACPI_APPLE_BACKLIGHT	0x02	/* panel backlight (APP0002) */
+
+/*
+ * Apple GMUX (GPU multiplexer) registers.
+ * On dual-GPU MacBook Pros, the GMUX owns the backlight PWM.
+ * Port offsets are relative to the GMUX resource base, consumed from
+ * the APP000B device's _CRS.
+ *
+ * Three I/O protocols exist:
+ *   Classic (PIO)  - pre-Retina Macs: direct inb/outb at base+port.
+ *   Indexed        - Retina Macs (2012-2015): write port number to
+ *                    GMUX_PORT_READ/WRITE, data at GMUX_PORT_VALUE.
+ *   MMIO           - T2 Macs: memory-mapped, select port+direction via
+ *                    GMUX_MMIO_PORT_SELECT, trigger with
+ *                    GMUX_MMIO_COMMAND_SEND, data at the resource base
+ *                    as a big-endian 32-bit value.
+ */
+#define	GMUX_PORT_VERSION_MAJOR		0x04
+#define	GMUX_PORT_VERSION_MINOR		0x05
+#define	GMUX_PORT_VERSION_RELEASE	0x06
+#define	GMUX_PORT_MAX_BRIGHTNESS	0x70
+#define	GMUX_PORT_BRIGHTNESS		0x74
+#define	GMUX_BRIGHTNESS_MASK		0x00ffffff
+
+/* A version read returns major, minor and release packed into 32 bits. */
+#define	GMUX_VERSION_MAJOR(v)		(((v) >> 24) & 0xff)
+#define	GMUX_VERSION_MINOR(v)		(((v) >> 16) & 0xff)
+#define	GMUX_VERSION_RELEASE(v)		(((v) >> 8) & 0xff)
+
+/*
+ * An absent GMUX floats the bus high, so a read of all ones means the
+ * protocol under test is not the one this machine speaks.
+ */
+#define	GMUX_FLOATING			0xff
+
+/* Handshake polling: microseconds per iteration, and iteration limits. */
+#define	GMUX_POLL_DELAY			100
+#define	GMUX_INDEXED_RETRIES		100
+#define	GMUX_MMIO_RETRIES		200
+
+/* Indexed GMUX registers */
+#define	GMUX_PORT_VALUE			0xc2
+#define	GMUX_PORT_READ			0xd0
+#define	GMUX_PORT_WRITE			0xd4
+
+/* Bit 0 of GMUX_PORT_WRITE: command outstanding, cleared once complete. */
+#define	GMUX_INDEXED_BUSY		0x01
+
+/*
+ * Indexed-GMUX detection: write a fixed 3-byte unlock sequence to
+ * PORT_INDEXED_{MAGIC1,MAGIC2,MAGIC3}, then read the same two ports
+ * back and expect them to echo GMUX_INDEXED_SIGNATURE.
+ */
+#define	GMUX_PORT_INDEXED_MAGIC1	0xcc
+#define	GMUX_PORT_INDEXED_MAGIC2	0xcd
+#define	GMUX_PORT_INDEXED_MAGIC3	0xce
+#define	GMUX_INDEXED_UNLOCK1		0xaa
+#define	GMUX_INDEXED_UNLOCK2		0x55
+#define	GMUX_INDEXED_UNLOCK3		0x00
+#define	GMUX_INDEXED_SIGNATURE		0x55aa
+
+/* MMIO GMUX registers */
+#define	GMUX_MMIO_PORT_SELECT		0x0e
+#define	GMUX_MMIO_COMMAND_SEND		0x0f
+#define	GMUX_MMIO_READ			0x00
+#define	GMUX_MMIO_WRITE			0x40
+
+/* Minimum resource length needed to reach GMUX_PORT_BRIGHTNESS+4. */
+#define	GMUX_MIN_IO_LEN			(GMUX_PORT_BRIGHTNESS + 4)
+
+#define	GMUX_TYPE_PIO			0
+#define	GMUX_TYPE_INDEXED		1
+#define	GMUX_TYPE_MMIO			2
+
+struct apple_bl_reg {
+	uint32_t	offset;
+	uint32_t	mask;
+	uint8_t		shift;
+};
+
+struct apple_bl_hw {
+	const char		*name;
+	int			bar;
+	vm_size_t		mapsize;
+	struct apple_bl_reg	ctrl;
+	struct apple_bl_reg	enable;
+	uint32_t		enable_val;
+	struct apple_bl_reg	max_reg;
+	uint32_t		max;
+};
+
+struct acpi_apple_softc {
+	device_t		dev;
+	ACPI_HANDLE		handle;
+	int			mode;
+
+	/* Backlight */
+	struct mtx		bl_mtx;		/* protects hw access + cache */
+	struct cdev		*bl_cdev;
+	const struct apple_bl_hw *bl_hw;
+	void			*bl_mmio;	/* GPU BAR MMIO (non-GMUX) */
+	uint16_t		bl_gmux_base;	/* GMUX I/O port base, 0=none */
+	void			*bl_gmux_mmio;	/* GMUX MMIO base, NULL=none */
+	uint8_t			bl_gmux_type;	/* GMUX_TYPE_* */
+	uint32_t		bl_max;
+	uint32_t		bl_brightness;	/* cached percent 0..100 */
+};
+
+/* apple_platform.c */
+int	acpi_apple_platform_attach(struct acpi_apple_softc *sc);
+void	acpi_apple_platform_detach(struct acpi_apple_softc *sc);
+
+/* apple_backlight.c */
+int	acpi_apple_backlight_attach(struct acpi_apple_softc *sc);
+void	acpi_apple_backlight_detach(struct acpi_apple_softc *sc);
+void	acpi_apple_backlight_restore(void);
+void	acpi_apple_backlight_off(void);
+int	acpi_apple_bl_update_status(device_t dev,
+	    struct backlight_props *props);
+int	acpi_apple_bl_get_status(device_t dev,
+	    struct backlight_props *props);
+int	acpi_apple_bl_get_info(device_t dev,
+	    struct backlight_info *info);
+
+#endif /* _APPLE_ACPI_H_ */
diff --git a/sys/dev/apple_acpi/apple_backlight.c b/sys/dev/apple_acpi/apple_backlight.c
new file mode 100644
index 000000000000..5b945689f5fe
--- /dev/null
+++ b/sys/dev/apple_acpi/apple_backlight.c
@@ -0,0 +1,771 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <seuros@FreeBSD.org>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * Apple panel backlight control.
+ *
+ * Two backends: GMUX (dual-GPU MacBook Pros, base+0x70/0x74 over PIO,
+ * indexed I/O, or MMIO depending on platform) and direct GPU PCI BAR
+ * MMIO (single-GPU Macs).  GMUX is preferred whenever APP000B is
+ * present and its _CRS yields a working protocol.
+ */
+
+#include <sys/cdefs.h>
+#include "apple_acpi.h"
+
+#include <sys/endian.h>
+#include <sys/systm.h>
+
+#include <dev/backlight/backlight.h>
+#include <dev/pci/pcireg.h>
+#include <dev/pci/pcivar.h>
+#include <machine/cpufunc.h>
+#include <vm/vm.h>
+#include <vm/pmap.h>
+
+#include "backlight_if.h"
+
+ACPI_SERIAL_DECL(apple, "Apple ACPI");
+static struct acpi_apple_softc *apple_backlight_sc;
+
+static const struct apple_bl_hw apple_bl_intel_pch = {
+	.name		= "intel_pch",
+	.bar		= 0,
+	.mapsize	= 0xc8258,
+	.ctrl		= { 0xc8254, 0x0000FFFF, 0 },
+	.enable		= { 0xc8250, 0xC0000000, 0 },
+	.enable_val	= 0xC0000000,
+	.max_reg	= { 0xc8254, 0xFFFF0000, 16 },
+	.max		= 0xFFFF,
+};
+
+static const struct apple_bl_hw apple_bl_intel_cpu = {
+	.name		= "intel_cpu",
+	.bar		= 0,
+	.mapsize	= 0xc8258,
+	.ctrl		= { 0x48254, 0x0000FFFF, 0 },
+	.enable		= { 0x48250, 0xC0000000, 0 },
+	.enable_val	= 0xC0000000,
+	.max_reg	= { 0xc8254, 0xFFFF0000, 16 },
+	.max		= 0xFFFF,
+};
+
+static const struct apple_bl_hw apple_bl_nvidia_nv40 = {
+	.name		= "nvidia_nv40",
+	.bar		= 0,
+	.mapsize	= 0x15f4,
+	.ctrl		= { 0x15f0, 0x001F0000, 16 },
+	.max		= 31,
+};
+
+static const struct apple_bl_hw apple_bl_nvidia_nv50 = {
+	.name		= "nvidia_nv50",
+	.bar		= 0,
+	.mapsize	= 0x61c088,
+	.ctrl		= { 0x61c084, 0x000007FF, 0 },
+	.enable		= { 0x61c084, 0x80000000, 0 },
+	.enable_val	= 0x80000000,
+	.max		= 0x7FF,
+};
+
+static const struct apple_bl_hw apple_bl_amd_r600 = {
+	.name		= "amd_r600",
+	.bar		= 2,
+	.mapsize	= 0x1730,
+	.ctrl		= { 0x172c, 0x0000FF00, 8 },
+	.max		= 255,
+};
+
+/* DCE10 (Tonga/Fiji, e.g. R9 M395/M395X, R9 Nano). */
+static const struct apple_bl_hw apple_bl_amd_dce10 = {
+	.name		= "amd_dce10",
+	.bar		= 2,
+	.mapsize	= 0x1208c,
+	.ctrl		= { 0x12080, 0x0000FFFF, 0 },
+	.enable		= { 0x12080, 0x80000000, 0 },
+	.enable_val	= 0x80000000,
+	.max_reg	= { 0x12088, 0x0000FFFF, 0 },
+	.max		= 0xFFFF,
+};
+
+struct apple_bl_match {
+	uint16_t		vendor;
+	uint16_t		dev_lo;
+	uint16_t		dev_hi;
+	const struct apple_bl_hw *hw;
+};
+
+static const struct apple_bl_match apple_bl_matches[] = {
+	{ 0x8086, 0x0100, 0x013f, &apple_bl_intel_cpu },
+	{ 0x8086, 0x0140, 0x017f, &apple_bl_intel_cpu },
+	{ 0x8086, 0, 0, &apple_bl_intel_pch },
+	{ 0x10de, 0x0040, 0x03ff, &apple_bl_nvidia_nv40 },
+	{ 0x10de, 0, 0, &apple_bl_nvidia_nv50 },
+	{ 0x1002, 0x6920, 0x6939, &apple_bl_amd_dce10 },
+	{ 0x1002, 0, 0, &apple_bl_amd_r600 },
+	{ 0, 0, 0, NULL },
+};
+
+/* Classic (PIO) GMUX - pre-Retina Macs. */
+static uint32_t
+gmux_pio_read32(uint16_t base, uint8_t port)
+{
+	return (inl(base + port));
+}
+
+/* A 32-bit GMUX write goes out as four byte writes, low byte first. */
+static void
+gmux_pio_write32_at(uint16_t addr, uint32_t val)
+{
+
+	for (u_int i = 0; i < sizeof(val); i++)
+		outb(addr + i, (uint8_t)(val >> (i * NBBY)));
+}
+
+static void
+gmux_pio_write32(uint16_t base, uint8_t port, uint32_t val)
+{
+	gmux_pio_write32_at(base + port, val);
+}
+
+/* Indexed GMUX - Retina Macs (2012-2015). */
+static int
+gmux_index_wait_ready(uint16_t base)
+{
+	uint8_t gwr;
+
+	for (int i = 0; i < GMUX_INDEXED_RETRIES; i++) {
+		gwr = inb(base + GMUX_PORT_WRITE);
+		if ((gwr & GMUX_INDEXED_BUSY) == 0)
+			return (0);
+		inb(base + GMUX_PORT_READ);
+		DELAY(GMUX_POLL_DELAY);
+	}
+	return (ETIMEDOUT);
+}
+
+static int
+gmux_index_wait_complete(uint16_t base)
+{
+	uint8_t gwr;
+
+	for (int i = 0; i < GMUX_INDEXED_RETRIES; i++) {
+		gwr = inb(base + GMUX_PORT_WRITE);
+		if ((gwr & GMUX_INDEXED_BUSY) != 0) {
+			/* Acknowledge completion. */
+			inb(base + GMUX_PORT_READ);
+			return (0);
+		}
+		DELAY(GMUX_POLL_DELAY);
+	}
+	/* Clear stale status. */
+	inb(base + GMUX_PORT_READ);
+	return (ETIMEDOUT);
+}
+
+static uint32_t
+gmux_index_read32(uint16_t base, uint8_t port)
+{
+	uint32_t val;
+
+	gmux_index_wait_ready(base);
+	outb(base + GMUX_PORT_READ, port);
+	gmux_index_wait_complete(base);
+	val = inl(base + GMUX_PORT_VALUE);
+	return (val);
+}
+
+static void
+gmux_index_write32(uint16_t base, uint8_t port, uint32_t val)
+{
+	gmux_pio_write32_at(base + GMUX_PORT_VALUE, val);
+	gmux_index_wait_ready(base);
+	outb(base + GMUX_PORT_WRITE, port);
+	gmux_index_wait_complete(base);
+}
+
+/* MMIO GMUX - T2 Macs. */
+static inline uint8_t
+gmux_mmio_read8(void *mmio, uint8_t port)
+{
+	return (*(volatile uint8_t *)((char *)mmio + port));
+}
+
+static inline void
+gmux_mmio_write8(void *mmio, uint8_t port, uint8_t val)
+{
+	*(volatile uint8_t *)((char *)mmio + port) = val;
+}
+
+static int
+gmux_mmio_wait(void *mmio)
+{
+
+	for (int i = 0; i < GMUX_MMIO_RETRIES; i++) {
+		if (gmux_mmio_read8(mmio, GMUX_MMIO_COMMAND_SEND) == 0)
+			return (0);
+		DELAY(GMUX_POLL_DELAY);
+	}
+	return (ETIMEDOUT);
+}
+
+static uint32_t
+gmux_mmio_read32(void *mmio, uint8_t port)
+{
+	uint32_t val;
+
+	gmux_mmio_wait(mmio);
+	gmux_mmio_write8(mmio, GMUX_MMIO_PORT_SELECT, port);
+	gmux_mmio_write8(mmio, GMUX_MMIO_COMMAND_SEND,
+	    GMUX_MMIO_READ | sizeof(val));
+	gmux_mmio_wait(mmio);
+	val = be32toh(*(volatile uint32_t *)mmio);
+	return (val);
+}
+
+static void
+gmux_mmio_write32(void *mmio, uint8_t port, uint32_t val)
+{
+	gmux_mmio_wait(mmio);
+	*(volatile uint32_t *)mmio = htobe32(val);
+	gmux_mmio_write8(mmio, GMUX_MMIO_PORT_SELECT, port);
+	gmux_mmio_write8(mmio, GMUX_MMIO_COMMAND_SEND,
+	    GMUX_MMIO_WRITE | sizeof(val));
+	gmux_mmio_wait(mmio);
+}
+
+static uint32_t
+gmux_read32(struct acpi_apple_softc *sc, uint8_t port)
+{
+	if (sc->bl_gmux_type == GMUX_TYPE_MMIO)
+		return (gmux_mmio_read32(sc->bl_gmux_mmio, port));
+	if (sc->bl_gmux_type == GMUX_TYPE_INDEXED)
+		return (gmux_index_read32(sc->bl_gmux_base, port));
+	return (gmux_pio_read32(sc->bl_gmux_base, port));
+}
+
+static void
+gmux_write32(struct acpi_apple_softc *sc, uint8_t port, uint32_t val)
+{
+	if (sc->bl_gmux_type == GMUX_TYPE_MMIO)
+		gmux_mmio_write32(sc->bl_gmux_mmio, port, val);
+	else if (sc->bl_gmux_type == GMUX_TYPE_INDEXED)
+		gmux_index_write32(sc->bl_gmux_base, port, val);
+	else
+		gmux_pio_write32(sc->bl_gmux_base, port, val);
+}
+
+static inline bool
+apple_bl_gmux_active(struct acpi_apple_softc *sc)
+{
+	return (sc->bl_gmux_base != 0 || sc->bl_gmux_mmio != NULL);
+}
+
+static void
+apple_bl_gmux_teardown(struct acpi_apple_softc *sc)
+{
+	if (sc->bl_gmux_mmio != NULL) {
+		pmap_unmapdev(sc->bl_gmux_mmio, GMUX_MIN_IO_LEN);
+		sc->bl_gmux_mmio = NULL;
+	}
+	sc->bl_gmux_base = 0;
+}
+
+static inline uint32_t
+apple_bl_read(struct acpi_apple_softc *sc, uint32_t offset)
+{
+	return (*(volatile uint32_t *)((char *)sc->bl_mmio + offset));
+}
+
+static inline void
+apple_bl_write(struct acpi_apple_softc *sc, uint32_t offset, uint32_t val)
+{
+	*(volatile uint32_t *)((char *)sc->bl_mmio + offset) = val;
+}
+
+static int
+apple_bl_hw_get(struct acpi_apple_softc *sc)
+{
+	if (apple_bl_gmux_active(sc))
+		return (gmux_read32(sc,
+		    GMUX_PORT_BRIGHTNESS) & GMUX_BRIGHTNESS_MASK);
+
+	return ((apple_bl_read(sc, sc->bl_hw->ctrl.offset) &
+	    sc->bl_hw->ctrl.mask) >> sc->bl_hw->ctrl.shift);
+}
+
+/* Resolve max brightness, enable the PWM if needed, cache brightness. */
+static void
+apple_bl_mmio_init(struct acpi_apple_softc *sc, const struct apple_bl_hw *hw)
+{
+	uint32_t val;
+
+	if (hw->max_reg.offset != 0) {
+		sc->bl_max = (apple_bl_read(sc, hw->max_reg.offset) &
+		    hw->max_reg.mask) >> hw->max_reg.shift;
+		if (sc->bl_max == 0)
+			sc->bl_max = hw->max;
+	} else {
+		sc->bl_max = hw->max;
+	}
+
+	if (hw->enable.offset != 0) {
+		val = apple_bl_read(sc, hw->enable.offset);
+		if ((val & hw->enable_val) != hw->enable_val) {
+			val |= hw->enable_val;
+			apple_bl_write(sc, hw->enable.offset, val);
+		}
+	}
+
+	if (sc->bl_max > 0)
+		sc->bl_brightness = apple_bl_hw_get(sc) * 100 / sc->bl_max;
+}
+
+static void
+apple_bl_hw_set(struct acpi_apple_softc *sc, int level)
+{
+	uint32_t val;
+
+	if (apple_bl_gmux_active(sc)) {
+		gmux_write32(sc, GMUX_PORT_BRIGHTNESS,
+		    level & GMUX_BRIGHTNESS_MASK);
+		return;
+	}
+
+	val = apple_bl_read(sc, sc->bl_hw->ctrl.offset);
+	val = (val & ~sc->bl_hw->ctrl.mask) |
+	    ((level << sc->bl_hw->ctrl.shift) & sc->bl_hw->ctrl.mask);
+	apple_bl_write(sc, sc->bl_hw->ctrl.offset, val);
+}
+
+static const struct apple_bl_hw *
+apple_bl_find_gpu(device_t *gpu_devp)
+{
+	const struct apple_bl_match *m;
+	device_t gpu;
+	uint16_t vendor, device;
+
+	gpu = pci_find_class(PCIC_DISPLAY, PCIS_DISPLAY_VGA);
+	if (gpu == NULL)
+		return (NULL);
+
+	vendor = pci_get_vendor(gpu);
+	device = pci_get_device(gpu);
+
+	for (m = apple_bl_matches; m->vendor != 0; m++) {
+		if (vendor != m->vendor)
+			continue;
+		if (m->dev_lo != 0 &&
+		    (device < m->dev_lo || device > m->dev_hi))
+			continue;
+		*gpu_devp = gpu;
+		return (m->hw);
+	}
+
+	return (NULL);
+}
+
+static void *
+apple_bl_map_bar(device_t dev, device_t gpu, const struct apple_bl_hw *hw)
+{
+	uint64_t bar_addr;
+	uint32_t bar_lo, bar_hi;
+
+	bar_lo = pci_read_config(gpu, PCIR_BAR(hw->bar), 4);
+
+	if (PCI_BAR_IO(bar_lo)) {
+		device_printf(dev, "BAR%d is I/O, expected memory\n", hw->bar);
+		return (NULL);
+	}
+
+	if ((bar_lo & PCIM_BAR_MEM_TYPE) == PCIM_BAR_MEM_64) {
+		bar_hi = pci_read_config(gpu, PCIR_BAR(hw->bar) + 4, 4);
+		bar_addr = ((uint64_t)bar_hi << 32) |
+		    (bar_lo & PCIM_BAR_MEM_BASE);
+	} else {
+		bar_addr = bar_lo & PCIM_BAR_MEM_BASE;
+	}
+
+	if (bar_addr == 0) {
+		device_printf(dev, "BAR%d address is zero\n", hw->bar);
+		return (NULL);
+	}
+
+	return (pmap_mapdev(bar_addr, hw->mapsize));
+}
+
+/*
+ * Probe for GMUX.  Locate APP000B in the ACPI namespace by HID, consume
+ * its _CRS resource, and detect which I/O protocol the hardware speaks.
+ */
+#define	GMUX_ACPI_HID	"APP000B"
+
+struct apple_bl_gmux_crs {
+	uint16_t	io_base;
+	uint32_t	io_len;
+	uint64_t	mem_base;
+};
+
+static ACPI_STATUS
+apple_bl_gmux_found(ACPI_HANDLE handle, UINT32 level __unused, void *ctx,
+    void **retval __unused)
+{
+	*(ACPI_HANDLE *)ctx = handle;
+	return (AE_CTRL_TERMINATE);
+}
+
+static ACPI_STATUS
+apple_bl_gmux_crs_cb(ACPI_RESOURCE *res, void *ctx)
+{
+	struct apple_bl_gmux_crs *crs = ctx;
+
+	switch (res->Type) {
+	case ACPI_RESOURCE_TYPE_IO:
+		crs->io_base = res->Data.Io.Minimum;
+		crs->io_len = res->Data.Io.AddressLength;
+		break;
+	case ACPI_RESOURCE_TYPE_FIXED_IO:
+		crs->io_base = res->Data.FixedIo.Address;
+		crs->io_len = res->Data.FixedIo.AddressLength;
+		break;
+	case ACPI_RESOURCE_TYPE_MEMORY32:
+		crs->mem_base = res->Data.Memory32.Minimum;
+		break;
+	case ACPI_RESOURCE_TYPE_FIXED_MEMORY32:
+		crs->mem_base = res->Data.FixedMemory32.Address;
+		break;
+	default:
+		break;
+	}
+	return (AE_OK);
+}
+
+static bool
+apple_bl_is_indexed(uint16_t base)
+{
+	uint16_t val;
+
+	outb(base + GMUX_PORT_INDEXED_MAGIC1, GMUX_INDEXED_UNLOCK1);
+	outb(base + GMUX_PORT_INDEXED_MAGIC2, GMUX_INDEXED_UNLOCK2);
+	outb(base + GMUX_PORT_INDEXED_MAGIC3, GMUX_INDEXED_UNLOCK3);
+
+	val = inb(base + GMUX_PORT_INDEXED_MAGIC1) |
+	    ((uint16_t)inb(base + GMUX_PORT_INDEXED_MAGIC2) << 8);
+	return (val == GMUX_INDEXED_SIGNATURE);
+}
+
+static bool
+apple_bl_is_mmio(void *mmio)
+{
+	return (gmux_mmio_read8(mmio, GMUX_MMIO_COMMAND_SEND) !=
+	    GMUX_FLOATING);
+}
+
+static bool
+apple_bl_find_gmux(struct acpi_apple_softc *sc)
+{
+	ACPI_HANDLE gmux_handle;
+	struct apple_bl_gmux_crs crs;
+	uint16_t base;
+	uint8_t ver_major, ver_minor, ver_release;
+	uint32_t version;
+
+	gmux_handle = NULL;
+	AcpiGetDevices(GMUX_ACPI_HID, apple_bl_gmux_found, &gmux_handle, NULL);
+	if (gmux_handle == NULL)
+		return (false);
+
+	bzero(&crs, sizeof(crs));
+	if (ACPI_FAILURE(AcpiWalkResources(gmux_handle, "_CRS",
+	    apple_bl_gmux_crs_cb, &crs)))
+		return (false);
+
+	if (crs.io_base != 0 && crs.io_len >= GMUX_MIN_IO_LEN) {
+		base = crs.io_base;
+
+		/* Try direct PIO first (classic/pre-Retina GMUX). */
+		ver_major = inb(base + GMUX_PORT_VERSION_MAJOR);
+		ver_minor = inb(base + GMUX_PORT_VERSION_MINOR);
+		ver_release = inb(base + GMUX_PORT_VERSION_RELEASE);
+
+		if (ver_major != GMUX_FLOATING ||
+		    ver_minor != GMUX_FLOATING) {
+			sc->bl_gmux_base = base;
+			sc->bl_gmux_type = GMUX_TYPE_PIO;
+			device_printf(sc->dev,
+			    "GMUX v%d.%d.%d [classic] at 0x%x\n",
+			    ver_major, ver_minor, ver_release, base);
+			return (true);
+		}
+
+		/* The bus floated high - check for indexed GMUX. */
+		if (apple_bl_is_indexed(base)) {
+			sc->bl_gmux_base = base;
+			sc->bl_gmux_type = GMUX_TYPE_INDEXED;
+
+			version = gmux_index_read32(base,
+			    GMUX_PORT_VERSION_MAJOR);
+			ver_major = GMUX_VERSION_MAJOR(version);
+			ver_minor = GMUX_VERSION_MINOR(version);
+			ver_release = GMUX_VERSION_RELEASE(version);
+
+			device_printf(sc->dev,
+			    "GMUX v%d.%d.%d [indexed] at 0x%x\n",
+			    ver_major, ver_minor, ver_release, base);
+			return (true);
+		}
+	}
+
+	/* No usable I/O resource - try the MMIO protocol (T2 GMUX). */
+	if (crs.mem_base == 0)
+		return (false);
+
+	sc->bl_gmux_mmio = pmap_mapdev(crs.mem_base, GMUX_MIN_IO_LEN);
+	if (sc->bl_gmux_mmio == NULL)
+		return (false);
+
+	if (!apple_bl_is_mmio(sc->bl_gmux_mmio)) {
+		pmap_unmapdev(sc->bl_gmux_mmio, GMUX_MIN_IO_LEN);
+		sc->bl_gmux_mmio = NULL;
+		return (false);
+	}
+
+	sc->bl_gmux_type = GMUX_TYPE_MMIO;
+
+	version = gmux_mmio_read32(sc->bl_gmux_mmio, GMUX_PORT_VERSION_MAJOR);
+	ver_major = GMUX_VERSION_MAJOR(version);
+	ver_minor = GMUX_VERSION_MINOR(version);
+	ver_release = GMUX_VERSION_RELEASE(version);
+
+	device_printf(sc->dev, "GMUX v%d.%d.%d [mmio] at %#jx\n",
+	    ver_major, ver_minor, ver_release, (uintmax_t)crs.mem_base);
+	return (true);
+}
+
+static int
+acpi_apple_backlight_attach_gmux(struct acpi_apple_softc *sc)
+{
+	uint32_t raw_max;
+
+	mtx_init(&sc->bl_mtx, "apple_bl", NULL, MTX_DEF);
+
+	raw_max = gmux_read32(sc, GMUX_PORT_MAX_BRIGHTNESS) &
*** 375 LINES SKIPPED ***