The branch main has been updated by ngie:

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

commit a48b900300ebdbd5c47e664b4cc06e705da91bd8
Author:     Abdelkader Boudih <[email protected]>
AuthorDate: 2026-06-03 05:57:49 +0000
Commit:     Enji Cooper <[email protected]>
CommitDate: 2026-06-03 05:59:55 +0000

    asmc: add MMIO backend for T2 Macs
    
    T2 Macs (2018+) expose the SMC via memory-mapped registers instead of
    I/O ports. Add asmcmmio.c/asmcmmio.h implementing the MMIO transport:
    key read/write, getinfo, getbyindex, and a poll-based wait with
    exponential backoff.
    
    The driver probes for MMIO at attach time by checking the LDKN firmware
    version key; if MMIO is available it is used, otherwise the standard
    I/O port backend is used.
    
    T2 fan speeds use IEEE 754 floats instead of fpe2 fixed-point.
    Per-fan manual mode uses F%dMd keys instead of the FS! bitmask.
    Battery charge limit is exposed via dev.asmc.N.battery_charge_limit.
    
    Tested on:
      MacBookPro16,2 (A2251, iBridge2,10)
      MacBookPro15,4 (A2159, iBridge2,8)
      MacBookAir8,2  (A1932, iBridge2,5)
      Mac mini 8,1   (A1993, iBridge2,7)
      iMac20,2       (A2115, iBridge2,16)
      iMacPro1,1     (A1862, iBridge1,1)
    
    MFC after:      2 weeks
    Reviewed by:    ngie, adrian
    Differential Revision:  https://reviews.freebsd.org/D57086
---
 sys/conf/files.amd64      |   1 +
 sys/dev/asmc/asmc.c       | 170 +++++++++++++++++---
 sys/dev/asmc/asmcmmio.c   | 402 ++++++++++++++++++++++++++++++++++++++++++++++
 sys/dev/asmc/asmcmmio.h   |  56 +++++++
 sys/dev/asmc/asmcvar.h    |   5 +
 sys/modules/asmc/Makefile |   2 +-
 6 files changed, 611 insertions(+), 25 deletions(-)

diff --git a/sys/conf/files.amd64 b/sys/conf/files.amd64
index 88f9b1d5f10f..718fc4097002 100644
--- a/sys/conf/files.amd64
+++ b/sys/conf/files.amd64
@@ -113,6 +113,7 @@ crypto/openssl/amd64/ossl_aes_gcm_avx512.c  optional ossl
 crypto/openssl/ossl_aes_gcm.c  optional ossl
 dev/amdgpio/amdgpio.c          optional        amdgpio
 dev/asmc/asmc.c                        optional        asmc isa
+dev/asmc/asmcmmio.c            optional        asmc isa
 dev/axgbe/if_axgbe_pci.c       optional        axp
 dev/axgbe/xgbe-desc.c          optional        axp
 dev/axgbe/xgbe-dev.c           optional        axp
diff --git a/sys/dev/asmc/asmc.c b/sys/dev/asmc/asmc.c
index 8cd7842d03fd..9b7adb61e6de 100644
--- a/sys/dev/asmc/asmc.c
+++ b/sys/dev/asmc/asmc.c
@@ -57,6 +57,7 @@
 
 #include <dev/acpica/acpivar.h>
 #include <dev/asmc/asmcvar.h>
+#include <dev/asmc/asmcmmio.h>
 
 #include <dev/backlight/backlight.h>
 #include "backlight_if.h"
@@ -426,17 +427,40 @@ asmc_attach(device_t dev)
        struct sysctl_ctx_list *sysctlctx;
        struct sysctl_oid *sysctlnode;
 
-       sc->sc_ioport = bus_alloc_resource_any(dev, SYS_RES_IOPORT,
-           &sc->sc_rid_port, RF_ACTIVE);
-       if (sc->sc_ioport == NULL) {
-               device_printf(dev, "unable to allocate IO port\n");
-               return (ENOMEM);
+       /*
+        * Try MMIO first (T2 Macs expose SMC via memory-mapped I/O).
+        * Fall back to standard I/O port if MMIO is not available.
+        */
+       sc->sc_rid_mem = 0;
+       sc->sc_iomem = bus_alloc_resource_any(dev, SYS_RES_MEMORY,
+           &sc->sc_rid_mem, RF_ACTIVE);
+       if (sc->sc_iomem != NULL) {
+               if (asmc_mmio_probe(dev) == 0) {
+                       sc->sc_is_mmio = 1;
+                       device_printf(dev, "using MMIO backend (T2)\n");
+               } else {
+                       bus_release_resource(dev, SYS_RES_MEMORY,
+                           sc->sc_rid_mem, sc->sc_iomem);
+                       sc->sc_iomem = NULL;
+               }
+       }
+
+       if (!sc->sc_is_mmio) {
+               sc->sc_ioport = bus_alloc_resource_any(dev, SYS_RES_IOPORT,
+                   &sc->sc_rid_port, RF_ACTIVE);
+               if (sc->sc_ioport == NULL) {
+                       device_printf(dev, "unable to allocate IO port\n");
+                       ret = ENOMEM;
+                       goto err;
+               }
        }
 
        sysctlctx = device_get_sysctl_ctx(dev);
        sysctlnode = device_get_sysctl_tree(dev);
 
-       mtx_init(&sc->sc_mtx, "asmc", NULL, MTX_SPIN);
+       /* Mutex may already be initialized by asmc_mmio_probe() */
+       if (!mtx_initialized(&sc->sc_mtx))
+               mtx_init(&sc->sc_mtx, "asmc", NULL, MTX_SPIN);
 
        /* Read SMC revision, key count, fan count */
        ret = asmc_init(dev);
@@ -615,6 +639,18 @@ asmc_attach(device_t dev)
            "SMC key type (4 chars)");
 #endif
 
+       /*
+        * Battery charge limit (T2 Macs).
+        */
+       if (sc->sc_is_t2 &&
+           asmc_key_getinfo(dev, ASMC_KEY_BCLM, NULL, NULL) == 0) {
+               SYSCTL_ADD_PROC(sysctlctx,
+                   SYSCTL_CHILDREN(sysctlnode), OID_AUTO, 
"battery_charge_limit",
+                   CTLTYPE_INT | CTLFLAG_RW | CTLFLAG_MPSAFE,
+                   dev, 0, asmc_bclm_sysctl, "I",
+                   "Battery charge limit (0-100)");
+       }
+
        if (!sc->sc_has_sms)
                goto nosms;
 
@@ -736,6 +772,7 @@ asmc_detach(device_t dev)
                    sc->sc_ioport);
                sc->sc_ioport = NULL;
        }
+       asmc_mmio_detach(dev, sc);
        if (mtx_initialized(&sc->sc_mtx)) {
                mtx_destroy(&sc->sc_mtx);
        }
@@ -788,10 +825,25 @@ asmc_init(device_t dev)
        sysctlctx = device_get_sysctl_ctx(dev);
 
        error = asmc_key_read(dev, ASMC_KEY_REV, buf, 6);
-       if (error != 0)
-               goto out;
-       device_printf(dev, "SMC revision: %x.%x%x%x\n", buf[0], buf[1], buf[2],
-           ntohs(*(uint16_t *)buf + 4));
+       if (error != 0) {
+               /*
+                * Could not read REV key; T2 Macs may not have it.
+                * Use #KEY as a liveness check instead.
+                */
+               if (sc->sc_is_t2) {
+                       error = asmc_key_read(dev, ASMC_NKEYS, buf, 4);
+                       if (error != 0)
+                               goto out;
+                       device_printf(dev, "T2 SMC: %d keys\n",
+                           be32dec(buf));
+               } else {
+                       goto out;
+               }
+       } else {
+               device_printf(dev, "SMC revision: %x.%x%x%x\n",
+                   buf[0], buf[1], buf[2],
+                   ntohs(*(uint16_t *)buf + 4));
+       }
 
        /* Auto power-on after AC power loss (AUPO). */
        if (asmc_key_read(dev, ASMC_KEY_AUPO, buf, 1) == 0) {
@@ -1041,8 +1093,11 @@ asmc_command(device_t dev, uint8_t command)
 static int
 asmc_key_read(device_t dev, const char *key, uint8_t *buf, uint8_t len)
 {
-       int i, error = 1, try = 0;
        struct asmc_softc *sc = device_get_softc(dev);
+       int i, error = 1, try = 0;
+
+       if (sc->sc_is_mmio)
+               return (asmc_mmio_key_read(dev, key, buf, len));
 
        mtx_lock_spin(&sc->sc_mtx);
 
@@ -1180,6 +1235,9 @@ asmc_key_getinfo(device_t dev, const char *key, uint8_t 
*len, char *type)
        uint8_t info[ASMC_KEYINFO_RESPLEN];
        int i, error = -1, try = 0;
 
+       if (sc->sc_is_mmio)
+               return (asmc_mmio_key_getinfo(dev, key, len, type));
+
        mtx_lock_spin(&sc->sc_mtx);
 
 begin:
@@ -1715,6 +1773,14 @@ asmc_key_dump_by_index(device_t dev, int index, char 
*key_out,
        int error = ENXIO, try = 0;
        int i;
 
+       if (sc->sc_is_mmio) {
+               error = asmc_mmio_key_getbyindex(dev, index, key_out);
+               if (error != 0)
+                       return (error);
+               return (asmc_mmio_key_getinfo(dev, key_out, len_out,
+                   type_out));
+       }
+
        mtx_lock_spin(&sc->sc_mtx);
 
        index_buf[0] = (index >> 24) & 0xff;
@@ -1808,8 +1874,11 @@ asmc_key_search(device_t dev, const char *prefix, 
unsigned int *idx)
 static int
 asmc_key_write(device_t dev, const char *key, uint8_t *buf, uint8_t len)
 {
-       int i, error = -1, try = 0;
        struct asmc_softc *sc = device_get_softc(dev);
+       int i, error = -1, try = 0;
+
+       if (sc->sc_is_mmio)
+               return (asmc_mmio_key_write(dev, key, buf, len));
 
        mtx_lock_spin(&sc->sc_mtx);
 
@@ -1865,14 +1934,30 @@ asmc_fan_count(device_t dev)
 static int
 asmc_fan_getvalue(device_t dev, const char *key, int fan)
 {
+       struct asmc_softc *sc = device_get_softc(dev);
        int speed;
-       uint8_t buf[2];
+       uint8_t buf[4];
        char fankey[5];
+       char type[ASMC_TYPELEN + 1];
 
        snprintf(fankey, sizeof(fankey), key, fan);
-       if (asmc_key_read(dev, fankey, buf, sizeof(buf)) != 0)
-               return (-1);
-       speed = (buf[0] << 6) | (buf[1] >> 2);
+
+       /*
+        * T2 Macs use IEEE 754 float ("flt ") for fan speeds,
+        * stored little-endian in the MMIO data register.
+        * Standard Macs use s14.2 fixed-point ("fpe2", 2 bytes).
+        */
+       if (sc->sc_is_t2 &&
+           asmc_key_getinfo(dev, fankey, NULL, type) == 0 &&
+           strncmp(type, "flt ", 4) == 0) {
+               if (asmc_key_read(dev, fankey, buf, 4) != 0)
+                       return (-1);
+               speed = (int)asmc_float_to_u32(le32dec(buf));
+       } else {
+               if (asmc_key_read(dev, fankey, buf, 2) != 0)
+                       return (-1);
+               speed = (buf[0] << 6) | (buf[1] >> 2);
+       }
 
        return (speed);
 }
@@ -1895,17 +1980,30 @@ asmc_fan_getstring(device_t dev, const char *key, int 
fan, uint8_t *buf,
 static int
 asmc_fan_setvalue(device_t dev, const char *key, int fan, int speed)
 {
-       uint8_t buf[2];
+       struct asmc_softc *sc = device_get_softc(dev);
+       uint8_t buf[4];
        char fankey[5];
-
-       speed *= 4;
-
-       buf[0] = speed >> 8;
-       buf[1] = speed;
+       char type[ASMC_TYPELEN + 1];
 
        snprintf(fankey, sizeof(fankey), key, fan);
-       if (asmc_key_write(dev, fankey, buf, sizeof(buf)) < 0)
-               return (-1);
+
+       if (sc->sc_is_t2 &&
+           asmc_key_getinfo(dev, fankey, NULL, type) == 0 &&
+           strncmp(type, "flt ", 4) == 0) {
+               uint32_t fval;
+               speed = MAX(speed, 0);
+               speed = MIN(speed, 65535);
+               fval = asmc_u32_to_float((uint32_t)speed);
+               le32enc(buf, fval);
+               if (asmc_key_write(dev, fankey, buf, 4) != 0)
+                       return (-1);
+       } else {
+               speed *= 4;
+               buf[0] = speed >> 8;
+               buf[1] = speed;
+               if (asmc_key_write(dev, fankey, buf, 2) != 0)
+                       return (-1);
+       }
 
        return (0);
 }
@@ -2016,11 +2114,35 @@ static int
 asmc_mb_sysctl_fanmanual(SYSCTL_HANDLER_ARGS)
 {
        device_t dev = (device_t)arg1;
+       struct asmc_softc *sc = device_get_softc(dev);
        int fan = arg2;
        int error;
        int32_t v;
        uint8_t buf[2];
        uint16_t val;
+       char fmkey[5];
+
+       /*
+        * T2 Macs use per-fan F%dMd keys (1 byte each).
+        * Standard Macs use FS! bitmask (2 bytes).
+        */
+       snprintf(fmkey, sizeof(fmkey), ASMC_KEY_FANMANUAL_T2, fan);
+       if (sc->sc_is_t2 &&
+           asmc_key_getinfo(dev, fmkey, NULL, NULL) == 0) {
+               error = asmc_key_read(dev, fmkey, buf, 1);
+               if (error != 0)
+                       return (error);
+               v = buf[0] ? 1 : 0;
+
+               error = sysctl_handle_int(oidp, &v, 0, req);
+               if (error == 0 && req->newptr != NULL) {
+                       if (v != 0 && v != 1)
+                               return (EINVAL);
+                       buf[0] = (uint8_t)v;
+                       error = asmc_key_write(dev, fmkey, buf, 1);
+               }
+               return (error);
+       }
 
        /* Read current FS! bitmask (asmc_key_read locks internally) */
        error = asmc_key_read(dev, ASMC_KEY_FANMANUAL, buf, sizeof(buf));
diff --git a/sys/dev/asmc/asmcmmio.c b/sys/dev/asmc/asmcmmio.c
new file mode 100644
index 000000000000..016c50f6170f
--- /dev/null
+++ b/sys/dev/asmc/asmcmmio.c
@@ -0,0 +1,402 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <[email protected]>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+/*
+ * MMIO backend for Apple SMC (T2 and later Macs).
+ *
+ * T2 Macs expose the SMC via memory-mapped registers instead of I/O ports.
+ * Protocol: clear status, write key/cmd, poll for ready, read result.
+ */
+
+#include <sys/param.h>
+#include <sys/bus.h>
+#include <sys/endian.h>
+#include <sys/kernel.h>
+#include <sys/lock.h>
+#include <sys/mutex.h>
+#include <sys/module.h>
+#include <sys/rman.h>
+#include <sys/sysctl.h>
+#include <sys/systm.h>
+#include <sys/taskqueue.h>
+
+#include <machine/bus.h>
+
+#include <dev/asmc/asmcvar.h>
+#include <dev/asmc/asmcmmio.h>
+
+/*
+ * Wait for MMIO status register bit 5 (ready) with exponential backoff.
+ * Caller must hold sc_mtx.
+ */
+static int
+asmc_mmio_wait(device_t dev)
+{
+       struct asmc_softc *sc = device_get_softc(dev);
+       int i;
+       uint8_t status;
+       int delay_us = 10;
+
+       for (i = 0; i < ASMC_MMIO_MAX_WAIT; i++) {
+               status = bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS);
+               if (status & ASMC_MMIO_STATUS_READY)
+                       return (0);
+               DELAY(delay_us);
+               if (delay_us < 3200)
+                       delay_us *= 2;
+       }
+
+       return (ETIMEDOUT);
+}
+
+int
+asmc_mmio_key_read(device_t dev, const char *key, uint8_t *buf, uint8_t len)
+{
+       struct asmc_softc *sc = device_get_softc(dev);
+       uint32_t key_int;
+       int error, i;
+       uint8_t cmd_result, rlen;
+
+       if (len > ASMC_MAXVAL)
+               return (EINVAL);
+
+       mtx_lock_spin(&sc->sc_mtx);
+
+       /* Clear status if non-zero */
+       if (bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS))
+               bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0);
+
+       /* Write key name as raw 4 bytes */
+       memcpy(&key_int, key, 4);
+       bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, key_int);
+
+       /* Write SMC ID (always 0) and command */
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0);
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDREAD);
+
+       /* Wait for ready */
+       error = asmc_mmio_wait(dev);
+       if (error != 0) {
+               uint8_t st = bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS);
+               uint8_t cm = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD);
+               mtx_unlock_spin(&sc->sc_mtx);
+               device_printf(dev,
+                   "%s: timeout key %.4s status=0x%02x cmd=0x%02x\n",
+                   __func__, key, st, cm);
+               return (error);
+       }
+
+       /* Check command result (0 = success, 0x84 = key not found) */
+       cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD);
+       if (cmd_result != 0) {
+               mtx_unlock_spin(&sc->sc_mtx);
+               device_printf(dev,
+                   "%s: key %.4s cmd error 0x%02x\n",
+                   __func__, key, cmd_result);
+               return (EIO);
+       }
+
+       /* Read data length and data bytes; zero-fill remainder */
+       rlen = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA_LEN);
+       rlen = MIN(rlen, len);
+       for (i = rlen; i < len; i++)
+               buf[i] = 0;
+       for (i = 0; i < rlen; i++)
+               buf[i] = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA + i);
+
+       mtx_unlock_spin(&sc->sc_mtx);
+       return (0);
+}
+
+int
+asmc_mmio_key_write(device_t dev, const char *key, uint8_t *buf, uint8_t len)
+{
+       struct asmc_softc *sc = device_get_softc(dev);
+       uint32_t key_int;
+       int error, i;
+       uint8_t cmd_result;
+
+       if (len > ASMC_MAXVAL)
+               return (EINVAL);
+
+       mtx_lock_spin(&sc->sc_mtx);
+
+       /* Clear status */
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0);
+
+       /* Write data bytes first */
+       for (i = 0; i < len; i++)
+               bus_write_1(sc->sc_iomem, ASMC_MMIO_DATA + i, buf[i]);
+
+       /* Write key name as raw 4 bytes */
+       memcpy(&key_int, key, 4);
+       bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, key_int);
+
+       /* Write length, SMC ID, command */
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_DATA_LEN, len);
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0);
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDWRITE);
+
+       /* Wait for ready */
+       error = asmc_mmio_wait(dev);
+       if (error != 0) {
+               mtx_unlock_spin(&sc->sc_mtx);
+               device_printf(dev, "%s: timeout writing key %.4s\n",
+                   __func__, key);
+               return (error);
+       }
+
+       cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD);
+       mtx_unlock_spin(&sc->sc_mtx);
+
+       return (cmd_result == 0 ? 0 : EIO);
+}
+
+int
+asmc_mmio_key_getinfo(device_t dev, const char *key, uint8_t *len, char *type)
+{
+       struct asmc_softc *sc = device_get_softc(dev);
+       uint32_t key_int;
+       int error, i;
+       uint8_t cmd_result;
+
+       mtx_lock_spin(&sc->sc_mtx);
+
+       /* Clear status */
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0);
+
+       /* Write key name as raw 4 bytes */
+       memcpy(&key_int, key, 4);
+       bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, key_int);
+
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0);
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDGETINFO);
+
+       error = asmc_mmio_wait(dev);
+       if (error != 0) {
+               mtx_unlock_spin(&sc->sc_mtx);
+               return (error);
+       }
+
+       cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD);
+       if (cmd_result != 0) {
+               mtx_unlock_spin(&sc->sc_mtx);
+               return (EIO);
+       }
+
+       /*
+        * GETINFO response layout (MMIO):
+        *   data[0..3] = type code (4 chars)
+        *   data[4]    = reserved
+        *   data[5]    = data length
+        *   data[6]    = flags/attributes
+        */
+       if (type != NULL) {
+               for (i = 0; i < ASMC_TYPELEN; i++)
+                       type[i] = bus_read_1(sc->sc_iomem,
+                           ASMC_MMIO_DATA + i);
+               type[ASMC_TYPELEN] = '\0';
+       }
+       if (len != NULL)
+               *len = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA + 5);
+
+       mtx_unlock_spin(&sc->sc_mtx);
+       return (0);
+}
+
+int
+asmc_mmio_key_getbyindex(device_t dev, int index, char *key)
+{
+       struct asmc_softc *sc = device_get_softc(dev);
+       uint32_t idx_val;
+       int error, i;
+       uint8_t cmd_result;
+
+       mtx_lock_spin(&sc->sc_mtx);
+
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_STATUS, 0);
+
+       /* Write index as big-endian 4 bytes to key name register */
+       idx_val = htobe32(index);
+       bus_write_4(sc->sc_iomem, ASMC_MMIO_KEY_NAME, idx_val);
+
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_SMC_ID, 0);
+       bus_write_1(sc->sc_iomem, ASMC_MMIO_CMD, ASMC_CMDGETBYINDEX);
+
+       error = asmc_mmio_wait(dev);
+       if (error != 0) {
+               mtx_unlock_spin(&sc->sc_mtx);
+               return (error);
+       }
+
+       cmd_result = bus_read_1(sc->sc_iomem, ASMC_MMIO_CMD);
+       if (cmd_result != 0) {
+               mtx_unlock_spin(&sc->sc_mtx);
+               return (EIO);
+       }
+
+       /* Result: 4-byte key name in DATA */
+       for (i = 0; i < ASMC_KEYLEN; i++)
+               key[i] = bus_read_1(sc->sc_iomem, ASMC_MMIO_DATA + i);
+       key[ASMC_KEYLEN] = '\0';
+
+       mtx_unlock_spin(&sc->sc_mtx);
+       return (0);
+}
+
+/*
+ * Validate MMIO and detect T2.
+ * Check that status register is accessible and LDKN firmware version >= 2.
+ */
+int
+asmc_mmio_probe(device_t dev)
+{
+       struct asmc_softc *sc = device_get_softc(dev);
+       rman_res_t size;
+       uint8_t status, ldkn;
+       int error;
+
+       size = rman_get_size(sc->sc_iomem);
+       if (size < ASMC_MMIO_MIN_SIZE) {
+               device_printf(dev, "MMIO region too small (%jd < %d)\n",
+                   (intmax_t)size, ASMC_MMIO_MIN_SIZE);
+               return (ENXIO);
+       }
+
+       /* Check status register isn't stuck at 0xFF */
+       status = bus_read_1(sc->sc_iomem, ASMC_MMIO_STATUS);
+       if (status == 0xFF) {
+               device_printf(dev, "MMIO status register reads 0xFF\n");
+               return (ENXIO);
+       }
+
+       /*
+        * We need the mutex initialized before calling mmio_key_read,
+        * but attach hasn't done it yet. Initialize early.
+        */
+       if (!mtx_initialized(&sc->sc_mtx))
+               mtx_init(&sc->sc_mtx, "asmc", NULL, MTX_SPIN);
+
+       /* Read LDKN (firmware version) -- must be >= 2 for MMIO */
+       error = asmc_mmio_key_read(dev, ASMC_KEY_LDKN, &ldkn, 1);
+       if (error != 0) {
+               device_printf(dev, "MMIO: failed to read LDKN key\n");
+               return (ENXIO);
+       }
+
+       if (ldkn < 2) {
+               device_printf(dev, "MMIO: LDKN=%d (need >= 2)\n", ldkn);
+               return (ENXIO);
+       }
+
+       device_printf(dev, "MMIO: LDKN=%d, T2 SMC detected\n", ldkn);
+       sc->sc_is_t2 = 1;
+
+       return (0);
+}
+
+void
+asmc_mmio_detach(device_t dev, struct asmc_softc *sc)
+{
+
+       if (sc->sc_iomem != NULL) {
+               bus_release_resource(dev, SYS_RES_MEMORY, sc->sc_rid_mem,
+                   sc->sc_iomem);
+               sc->sc_iomem = NULL;
+       }
+       sc->sc_is_mmio = 0;
+       sc->sc_is_t2 = 0;
+}
+
+/*
+ * Convert IEEE 754 float (as u32) to unsigned integer.
+ * Kernel soft-float: extract integer part only.
+ * Used for T2 fan RPM values (always positive, reasonable range).
+ */
+uint32_t
+asmc_float_to_u32(uint32_t d)
+{
+       int32_t exp;
+       uint32_t fr;
+
+       /* Negative or zero */
+       if (d == 0 || (d >> 31) != 0)
+               return (0);
+
+       exp = (int32_t)((d >> 23) & 0xff) - 0x7f;
+       fr = d & 0x7fffff;      /* 23-bit mantissa */
+
+       if (exp < 0)
+               return (0);
+       if (exp > 23) {
+               if (exp > 30)
+                       return (0xffffffffu);
+               return ((1u << exp) | (fr << (exp - 23)));
+       }
+       /* Normal case: 0 <= exp <= 23 */
+       return ((1u << exp) + (fr >> (23 - exp)));
+}
+
+/*
+ * Convert unsigned integer to IEEE 754 float (as u32).
+ * Only handles values in fan RPM range (0-65535).
+ */
+uint32_t
+asmc_u32_to_float(uint32_t d)
+{
+       uint32_t dc, bc, exp;
+
+       if (d == 0)
+               return (0);
+
+       /* Find highest set bit position */
+       dc = d;
+       bc = 0;
+       while (dc >>= 1)
+               ++bc;
+
+       bc = MIN(bc, 30);
+
+       exp = 0x7f + bc;
+
+       /*
+        * Mantissa: strip the implicit leading 1-bit and place
+        * remaining bits into the 23-bit mantissa field.
+        */
+       if (bc >= 23)
+               return ((exp << 23) | ((d >> (bc - 23)) & 0x7fffff));
+       else
+               return ((exp << 23) | ((d << (23 - bc)) & 0x7fffff));
+}
+
+/*
+ * Battery charge limit sysctl (T2 Macs).
+ * BCLM key: 1 byte, 0-100 (percentage).
+ */
+int
+asmc_bclm_sysctl(SYSCTL_HANDLER_ARGS)
+{
+       device_t dev = (device_t)arg1;
+       uint8_t bclm;
+       int val, error;
+
+       error = asmc_mmio_key_read(dev, ASMC_KEY_BCLM, &bclm, 1);
+       if (error != 0)
+               return (EIO);
+
+       val = (int)bclm;
+       error = sysctl_handle_int(oidp, &val, 0, req);
+       if (error != 0 || req->newptr == NULL)
+               return (error);
+
+       if (val < 0 || val > 100)
+               return (EINVAL);
+
+       bclm = (uint8_t)val;
+       error = asmc_mmio_key_write(dev, ASMC_KEY_BCLM, &bclm, 1);
+
+       return (error != 0 ? EIO : 0);
+}
diff --git a/sys/dev/asmc/asmcmmio.h b/sys/dev/asmc/asmcmmio.h
new file mode 100644
index 000000000000..51e81707ece1
--- /dev/null
+++ b/sys/dev/asmc/asmcmmio.h
@@ -0,0 +1,56 @@
+/*
+ * Copyright (c) 2026 Abdelkader Boudih <[email protected]>
+ *
+ * SPDX-License-Identifier: BSD-2-Clause
+ */
+
+#ifndef _DEV_ASMC_ASMCMMIO_H_
+#define _DEV_ASMC_ASMCMMIO_H_
+
+struct asmc_softc;
+
+/*
+ * MMIO register offsets.
+ */
+#define ASMC_MMIO_DATA         0x0000
+#define ASMC_MMIO_KEY_NAME     0x0078
+#define ASMC_MMIO_DATA_LEN     0x007D
+#define ASMC_MMIO_SMC_ID       0x007E
+#define ASMC_MMIO_CMD          0x007F
+#define ASMC_MMIO_STATUS       0x4005
+#define ASMC_MMIO_MIN_SIZE     0x4006
+#define ASMC_MMIO_STATUS_READY 0x20    /* Bit 5 */
+#define ASMC_MMIO_MAX_WAIT     24
+
+/*
+ * T2-specific keys.
+ */
+#define ASMC_KEY_LDKN          "LDKN"  /* RO; 1 byte, firmware version */
+#define ASMC_KEY_BCLM          "BCLM"  /* RW; 1 byte, battery charge limit 
0-100 */
+#define ASMC_KEY_FANMANUAL_T2  "F%dMd" /* RW; 1 byte per fan (T2) */
+
+/*
+ * MMIO backend functions.
+ */
+int    asmc_mmio_probe(device_t dev);
+void   asmc_mmio_detach(device_t dev, struct asmc_softc *sc);
+int    asmc_mmio_key_read(device_t dev, const char *key,
+           uint8_t *buf, uint8_t len);
+int    asmc_mmio_key_write(device_t dev, const char *key,
+           uint8_t *buf, uint8_t len);
+int    asmc_mmio_key_getinfo(device_t dev, const char *key,
+           uint8_t *len, char *type);
+int    asmc_mmio_key_getbyindex(device_t dev, int index, char *key);
+
+/*
+ * IEEE 754 float <-> uint32 conversion for T2 fan RPM values.
+ */
+uint32_t       asmc_float_to_u32(uint32_t d);
+uint32_t       asmc_u32_to_float(uint32_t d);
+
+/*
+ * T2-specific sysctls.
+ */
+int    asmc_bclm_sysctl(SYSCTL_HANDLER_ARGS);
+
+#endif /* _DEV_ASMC_ASMCMMIO_H_ */
diff --git a/sys/dev/asmc/asmcvar.h b/sys/dev/asmc/asmcvar.h
index 6388fc78fb69..bc0c624eb7a2 100644
--- a/sys/dev/asmc/asmcvar.h
+++ b/sys/dev/asmc/asmcvar.h
@@ -53,6 +53,11 @@ struct asmc_softc {
        struct resource         *sc_ioport;
        struct resource         *sc_irq;
        void                    *sc_cookie;
+       /* MMIO backend (T2 Macs) */
+       int                     sc_rid_mem;
+       struct resource         *sc_iomem;
+       int                     sc_is_mmio;
+       int                     sc_is_t2;       /* T2 fan float + per-fan 
manual */
        int                     sc_sms_intrtype;
        struct taskqueue        *sc_sms_tq;
        struct task             sc_sms_task;
diff --git a/sys/modules/asmc/Makefile b/sys/modules/asmc/Makefile
index 17f6c7eec731..2947b073d4fa 100644
--- a/sys/modules/asmc/Makefile
+++ b/sys/modules/asmc/Makefile
@@ -1,7 +1,7 @@
 .PATH: ${SRCTOP}/sys/dev/asmc
 
 KMOD=  asmc
-SRCS=  asmc.c opt_acpi.h opt_asmc.h
+SRCS=  asmc.c asmcmmio.c opt_acpi.h opt_asmc.h
 SRCS+= acpi_if.h backlight_if.h bus_if.h device_if.h
 
 .include <bsd.kmod.mk>

Reply via email to