git: d7d237ce731f - main - acpi_apple: Apple platform ACPI driver,
- Go to: [ bottom of page ] [ top of archives ] [ this month ]
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 ***