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
This commit is contained in:
committed by
Enji Cooper
parent
e949ce9dc0
commit
a48b900300
@@ -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
|
||||
|
||||
+146
-24
@@ -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));
|
||||
|
||||
@@ -0,0 +1,402 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
|
||||
*
|
||||
* 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);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Copyright (c) 2026 Abdelkader Boudih <freebsd@seuros.com>
|
||||
*
|
||||
* 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_ */
|
||||
@@ -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;
|
||||
|
||||
@@ -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>
|
||||
|
||||
Reference in New Issue
Block a user