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>
