From nobody Thu Oct 15 13:05:12 2026 X-Original-To: dev-commits-src-main@mlmmj.nyi.freebsd.org Received: from mx1.freebsd.org (mx1.freebsd.org [IPv6:2610:1c1:1:606c::19:1]) by mlmmj.nyi.freebsd.org (Postfix) with ESMTP id 4j57bD4293z6vyMD for ; Thu, 15 Oct 2026 13:05:12 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from mxrelay.nyi.freebsd.org (mxrelay.nyi.freebsd.org [IPv6:2610:1c1:1:606c::19:3]) (using TLSv1.3 with cipher TLS_AES_256_GCM_SHA384 (256/256 bits) key-exchange x25519 server-signature RSA-PSS (4096 bits) server-digest SHA256 client-signature RSA-PSS (4096 bits) client-digest SHA256) (Client CN "mxrelay.nyi.freebsd.org", Issuer "YR1" (not verified)) by mx1.freebsd.org (Postfix) with ESMTPS id 4j57bD2B14z3LQ9 for ; Thu, 15 Oct 2026 13:05:12 +0000 (UTC) (envelope-from git@FreeBSD.org) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1792069512; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=PKWzuJPR2Kzg+x4uivspLCGs5QSqUGRw74kcfTiLO2w=; b=WX0GbxIxsW79qi6om//o4KBGR8cfSs9mREGkhLYhEYuYENVpudjvUQrfpVnUdGF9PFxRPm 6q4N5dB1gv18dKjPrSxzFcHJfzcJAZQ10Y1nUlbmqz4xVC1B0K161gGk+OLAC8lgRhDAm6 0V4kipK9legabXlB1dB9wfBGOYFNx7v6w8NeuZQGXM90uj89En8jgaHlXSgkzqOyeKL2Gq GrFgIRWgQsLeMLASD+HmvAnVYBxs+F7Kxdub2VpktbYf2iYwcuOsPIoBK6a2XuVHSdeEIh zdCBfEwjK5awAqFM26JdIemtiS1QKT4Whr2U/w3AjSMpY/iJVplSo4A4OXcKjg== ARC-Seal: i=1; a=rsa-sha256; d=freebsd.org; s=dkim; cv=none; t=1792069512; b=uXE8bSKCIBlr419NMmiHf5fRBxVeXcespF1URbqNM24oEcdzPQYimUeR3Svs0MSUqGUEI6 G/7fURpP+2aQUfRgMjoM2rsgZgjeIpmG1UKakUhSyUdFg0YClz7803DLy5zpPt0vhqVFil reiR8gGXlO+uEDQCU+XobPdmGfLXnx2SKXNof6O23RrS0Buv9jefIoMCvZVoMTMoxwYCyS MTFLj297vYQsUL51nih/oMfFiA21N2AMB8iMKippkZOWojY3hl8cUMxOtG6Jrwiz0mut35 /rcruNNURPSuFcsXwmnFtXyA+k269GbgfJU1ejUsOZzEqzhjFbESq2fN4RQ9OQ== ARC-Message-Signature: i=1; a=rsa-sha256; c=relaxed/relaxed; d=freebsd.org; s=dkim; t=1792069512; h=from:from:reply-to:subject:subject:date:date:message-id:message-id: to:to:cc:mime-version:mime-version:content-type:content-type: content-transfer-encoding:content-transfer-encoding; bh=PKWzuJPR2Kzg+x4uivspLCGs5QSqUGRw74kcfTiLO2w=; b=ROdWdOFYpJpFkm39ruA+OhV+MGTloU7ugekULagJbAqK7PnrFN0b5DzSXqSY6cR8MCSaMk bq3jl0Jwadsj9MlQ/7KWsDfL9YC2+3mlU+1McfxkV8yNEQCiI2TyKio8vPehJ+bIu4KRuT teSAQBmHTYpSnjhFc/Cc3QBGEf7VPdXXAjyBxazFjbcBo95xPeRGDg2nfEodcoZPAXYkDu SYbdsI+5x9GUXZOCXBk8cpcsqb+bxmpxRpBx4cAUnYHqrDktPgY38uNhSjM48lN3WNzcIG rAGY6KT+ci5vgdth68kdr3XGTYSsO6jgGZE2LlHRQ01QMHTa2bdcre2JNU+A2g== ARC-Authentication-Results: i=1; mx1.freebsd.org; none Received: from gitrepo.freebsd.org (gitrepo.freebsd.org [IPv6:2610:1c1:1:6068::e6a:5]) by mxrelay.nyi.freebsd.org (Postfix) with ESMTP id 4j57bD0n1PzZLF for ; Thu, 15 Oct 2026 13:05:12 +0000 (UTC) (envelope-from git@FreeBSD.org) Received: from git (uid 1279) (envelope-from git@FreeBSD.org) id 20cf6 by gitrepo.freebsd.org (DragonFly Mail Agent v0.13+ on gitrepo.freebsd.org); Thu, 15 Oct 2026 13:05:12 +0000 To: src-committers@FreeBSD.org, dev-commits-src-all@FreeBSD.org, dev-commits-src-main@FreeBSD.org From: Abdelkader Boudih Subject: git: d7d237ce731f - main - acpi_apple: Apple platform ACPI driver, List-Archive: https://lists.freebsd.org/archives/dev-commits-src-main X-BeenThere: dev-commits-src-main@freebsd.org Sender: owner-dev-commits-src-main@FreeBSD.org List-Id: Commit messages for the main branch of the src repository List-Post: List-Help: List-Subscribe: List-Unsubscribe: List-Owner: Precedence: list MIME-Version: 1.0 Content-Type: text/plain; charset=utf-8 Content-Transfer-Encoding: 8bit X-Git-Committer: seuros X-Git-Repository: src X-Git-Refname: refs/heads/main X-Git-Reftype: branch X-Git-Commit: d7d237ce731f0897107ca0f9301b4cef1846a182 Auto-Submitted: auto-generated Date: Thu, 15 Oct 2026 13:05:12 +0000 Message-Id: <6ad0cf88.20cf6.7bf2f87@gitrepo.freebsd.org> The branch main has been updated by seuros: URL: https://cgit.FreeBSD.org/src/commit/?id=d7d237ce731f0897107ca0f9301b4cef1846a182 commit d7d237ce731f0897107ca0f9301b4cef1846a182 Author: Abdelkader Boudih AuthorDate: 2026-10-15 13:03:22 +0000 Commit: Abdelkader Boudih 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 +.\" +.\" 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 + * + * 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 +#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 + * + * SPDX-License-Identifier: BSD-2-Clause + */ + +#ifndef _APPLE_ACPI_H_ +#define _APPLE_ACPI_H_ + +#include +#include +#include +#include +#include +#include +#include +#include + +#include + +#include +#include + +#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 + * + * 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 +#include "apple_acpi.h" + +#include +#include + +#include +#include +#include +#include +#include +#include + +#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 ***