From: Mark Kettenis Subject: Re: sambat(4) - Samsung laptop battery monitor To: Marcus Glocker Cc: tech@openbsd.org Date: Sun, 17 May 2026 22:33:50 +0200 > Date: Sun, 17 May 2026 20:31:43 +0200 > From: Marcus Glocker > > On the Samsung Galaxy Book4 Edge qcpas(4) never was returning the > battery status. Now I found out that on this laptop the ADSP doesn't > host an charger_pd (protection domain), instead we need to read the > battery status values directly from the SAM060B EC over I2C. > > To attach the SAM060B EC we obviously require a new node entry for our > DTB, so this diff includes the arm64-qcom-dtb-firmware port update as > well. > > This gives me working apm finally: > > x1e$ dmesg | grep sambat > sambat0 at iic1 addr 0x64: EC flags 0x05 > > x1e$ sysctl hw.sensors.sambat0 > hw.sensors.sambat0.volt0=17.57 VDC (battery voltage) > hw.sensors.sambat0.volt1=46.24 VDC (battery design voltage) This is an odd value. I'd expect the battery design voltage to be similar as the actual battery voltage. > hw.sensors.sambat0.current0=0.42 A (battery current) > hw.sensors.sambat0.amphour0=3.40 Ah (battery remaining) > hw.sensors.sambat0.amphour1=3.53 Ah (battery full charge) > hw.sensors.sambat0.amphour2=45.75 Ah (battery design capacity) The design capacity is odd as well. The combination of design voltage and design capacity suggests the capacity of the battery is 2kWh. Please be careful... I don't see any obvious bugs in the driver code though, at least when using the ACPI code as a reference. Looking at the ACPI it should be possible to read the discharge cycle count by reading the value of "register" 0xd0. > hw.sensors.sambat0.raw0=0 (battery state) > hw.sensors.sambat0.percent0=96.29% (battery charge) > > x1e$ apm > Battery state: high, 96% remaining, unknown life estimate > AC adapter state: connected > Performance adjustment mode: manual (4012 MHz) > > Ok? > > > Index: sys/arch/arm64/conf/GENERIC > =================================================================== > RCS file: /cvs/src/sys/arch/arm64/conf/GENERIC,v > diff -u -p -u -p -r1.315 GENERIC > --- sys/arch/arm64/conf/GENERIC 14 May 2026 16:20:27 -0000 1.315 > +++ sys/arch/arm64/conf/GENERIC 17 May 2026 18:15:09 -0000 > @@ -627,6 +627,7 @@ tascodec* at iic? # TAS2770 audio code > tcpci* at iic? # USB Type-C controller > tipd* at iic? # TPS6598x Type-C controller > pijuice* at iic? # PiJuice HAT > +sambat* at iic? # Samsung SAM060B battery monitor > > # GPIO "pin bus" drivers > gpioiic* at gpio? # I2C bus bit-banging > Index: sys/dev/i2c/files.i2c > =================================================================== > RCS file: /cvs/src/sys/dev/i2c/files.i2c,v > diff -u -p -u -p -r1.74 files.i2c > --- sys/dev/i2c/files.i2c 23 Nov 2025 21:28:15 -0000 1.74 > +++ sys/dev/i2c/files.i2c 17 May 2026 18:15:10 -0000 > @@ -274,6 +274,11 @@ device pijuice > attach pijuice at i2c > file dev/i2c/pijuice.c pijuice > > +# Samsung SAM060B battery monitor > +device sambat > +attach sambat at i2c > +file dev/i2c/sambat.c sambat > + > # Consumer Control Keyboards > device icc: hid, hidcc, wskbddev > attach icc at ihidbus > Index: sys/dev/i2c/sambat.c > =================================================================== > RCS file: sys/dev/i2c/sambat.c > diff -N sys/dev/i2c/sambat.c > --- /dev/null 1 Jan 1970 00:00:00 -0000 > +++ sys/dev/i2c/sambat.c 17 May 2026 18:15:10 -0000 > @@ -0,0 +1,459 @@ > +/* $OpenBSD$ */ > + > +/* > + * Copyright (c) 2026 Marcus Glocker > + * > + * Permission to use, copy, modify, and distribute this software for any > + * purpose with or without fee is hereby granted, provided that the above > + * copyright notice and this permission notice appear in all copies. > + * > + * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > + * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > + * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > + * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > + * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > + * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > + * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > + */ > + > +/* > + * Battery monitor for Samsung laptops with the "SAM060B" embedded > + * controller (ENE KB9058 silicon with Samsung firmware). First > + * confirmed on the Galaxy Book4 Edge; the same EC personality is > + * likely shared across the Galaxy Book line. > + * > + * The host talks to the EC via a vendor "Mbox" command protocol on > + * I2C, reverse-engineered for Linux by the Saddytech project; this > + * driver is an independent reimplementation against that reference: > + * https://github.com/Saddytech/Galaxy-Book4-Edge-linux > + * > + * EC register layout (from the DSDT _SB.ECTC region the Windows > + * driver writes to): > + * 0x80 bit0 = battery present, bit2 = AC online > + * 0x84 bit0 = discharging, bit1 = charging, bit3 = full > + * 0xA0 upper big-endian word = remaining capacity (mAh) > + * 0xA4 upper BE = voltage (mV), lower BE = signed current (mA) > + * 0xB0 upper BE = design capacity (mAh), lower BE = full-charge (mAh) > + * 0xB4 lower BE = design voltage (mV) > + * > + * Each EC register read requires three Mbox transactions: > + * write target address (cmd 0xF480 = reg) > + * write execute (cmd 0xFF10 = 0x88 "read") > + * read result (cmd 0xF480) > + */ > + > +#include > +#include > +#include > +#include > + > +#include > + > +#include > + > +#include "apm.h" > + > +/* Mbox framing. */ > +#define SAM_MBOX_WRITE_PREFIX 0x40 > +#define SAM_MBOX_READ_PREFIX 0x30 > +#define SAM_MBOX_READ_SUCCESS 0x50 > + > +/* Mbox command address (the "hi"/"lo" pair). */ > +#define SAM_CMD_TARGET_HI 0xf4 > +#define SAM_CMD_TARGET_LO 0x80 > +#define SAM_CMD_EXEC_HI 0xff > +#define SAM_CMD_EXEC_LO 0x10 > +#define SAM_EXEC_READ 0x88 > + > +/* EC register offsets. */ > +#define SAM_REG_FLAGS 0x80 > +#define SAM_REG_B1ST 0x84 > +#define SAM_REG_B1RR 0xa0 > +#define SAM_REG_B1PV 0xa4 > +#define SAM_REG_B1AF 0xb0 > +#define SAM_REG_B1VL 0xb4 > + > +#define SAM_FLAG_B1EX (1 << 0) > +#define SAM_FLAG_ACEX (1 << 2) > + > +#define SAM_B1ST_DISCHARGE (1 << 0) > +#define SAM_B1ST_CHARGE (1 << 1) > +#define SAM_B1ST_FULL (1 << 3) > + > +#define SAM_REFRESH_INTERVAL 30 /* seconds */ > + > +/* Sensor indices. */ > +enum sambat_sensors { > + SAMBAT_SENSOR_CHARGE, /* percent */ > + SAMBAT_SENSOR_VOLT_NOW, /* current voltage */ > + SAMBAT_SENSOR_VOLT_DESIGN, /* design voltage */ > + SAMBAT_SENSOR_CURRENT, /* signed current */ > + SAMBAT_SENSOR_CHARGE_NOW, /* remaining capacity (Ah) */ > + SAMBAT_SENSOR_CHARGE_FULL, /* full-charge capacity */ > + SAMBAT_SENSOR_CHARGE_DESIGN, /* design capacity */ > + SAMBAT_SENSOR_STATE, /* drive state */ Maybe you meant battery state here? > + SAMBAT_NSENSORS > +}; > + > +struct sambat_softc { > + struct device sc_dev; > + i2c_tag_t sc_tag; > + int sc_addr; > + > + struct ksensor sc_sensor[SAMBAT_NSENSORS]; > + struct ksensordev sc_sensordev; > + > + /* Latest decoded readings. */ > + int sc_have_data; > + int sc_present; > + int sc_ac_online; > + uint8_t sc_b1st; > + uint16_t sc_remaining_mah; > + uint16_t sc_voltage_mv; > + int16_t sc_current_ma; > + uint16_t sc_design_mah; > + uint16_t sc_fullchg_mah; > + uint16_t sc_design_mv; > +}; > + > +struct sambat_softc *sambat_sc; > + > +int sambat_match(struct device *, void *, void *); > +void sambat_attach(struct device *, struct device *, void *); > + > +int sambat_mbox_write(struct sambat_softc *, uint8_t, uint8_t, uint8_t); > +int sambat_mbox_read(struct sambat_softc *, uint8_t, uint8_t, uint8_t *); > +int sambat_ec_read_byte(struct sambat_softc *, uint8_t, uint8_t *); > +int sambat_ec_read_block(struct sambat_softc *, uint8_t, uint8_t *, int); > + > +void sambat_refresh(void *); > +int sambat_apminfo(struct apm_power_info *); > + > +const struct cfattach sambat_ca = { > + sizeof(struct sambat_softc), sambat_match, sambat_attach > +}; > + > +struct cfdriver sambat_cd = { > + NULL, "sambat", DV_DULL > +}; > + > +int > +sambat_match(struct device *parent, void *match, void *aux) > +{ > + struct i2c_attach_args *ia = aux; > + > + if (strcmp(ia->ia_name, "samsung,galaxybook-battery") == 0) > + return 1; > + > + return 0; > +} > + > +void > +sambat_attach(struct device *parent, struct device *self, void *aux) > +{ > + struct sambat_softc *sc = (struct sambat_softc *)self; > + struct i2c_attach_args *ia = aux; > + uint8_t probe; > + int i; > + > + sambat_sc = sc; > + sc->sc_tag = ia->ia_tag; > + sc->sc_addr = ia->ia_addr; > + > + /* > + * Probe the EC with a single byte read to confirm we're > + * actually talking to it before exposing sensors. > + */ > + if (sambat_ec_read_byte(sc, SAM_REG_FLAGS, &probe) != 0) { > + printf(": EC probe failed\n"); > + return; > + } > + > + /* Sensor framework setup. */ > + strlcpy(sc->sc_sensor[SAMBAT_SENSOR_CHARGE].desc, > + "battery charge", sizeof(sc->sc_sensor[0].desc)); > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE].type = SENSOR_PERCENT; > + > + strlcpy(sc->sc_sensor[SAMBAT_SENSOR_VOLT_NOW].desc, > + "battery voltage", sizeof(sc->sc_sensor[0].desc)); > + sc->sc_sensor[SAMBAT_SENSOR_VOLT_NOW].type = SENSOR_VOLTS_DC; > + > + strlcpy(sc->sc_sensor[SAMBAT_SENSOR_VOLT_DESIGN].desc, > + "battery design voltage", sizeof(sc->sc_sensor[0].desc)); > + sc->sc_sensor[SAMBAT_SENSOR_VOLT_DESIGN].type = SENSOR_VOLTS_DC; > + > + strlcpy(sc->sc_sensor[SAMBAT_SENSOR_CURRENT].desc, > + "battery current", sizeof(sc->sc_sensor[0].desc)); > + sc->sc_sensor[SAMBAT_SENSOR_CURRENT].type = SENSOR_AMPS; > + > + strlcpy(sc->sc_sensor[SAMBAT_SENSOR_CHARGE_NOW].desc, > + "battery remaining", sizeof(sc->sc_sensor[0].desc)); > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE_NOW].type = SENSOR_AMPHOUR; > + > + strlcpy(sc->sc_sensor[SAMBAT_SENSOR_CHARGE_FULL].desc, > + "battery full charge", sizeof(sc->sc_sensor[0].desc)); > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE_FULL].type = SENSOR_AMPHOUR; > + > + strlcpy(sc->sc_sensor[SAMBAT_SENSOR_CHARGE_DESIGN].desc, > + "battery design capacity", sizeof(sc->sc_sensor[0].desc)); > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE_DESIGN].type = SENSOR_AMPHOUR; > + > + strlcpy(sc->sc_sensor[SAMBAT_SENSOR_STATE].desc, > + "battery state", sizeof(sc->sc_sensor[0].desc)); > + sc->sc_sensor[SAMBAT_SENSOR_STATE].type = SENSOR_INTEGER; > + > + strlcpy(sc->sc_sensordev.xname, sc->sc_dev.dv_xname, > + sizeof(sc->sc_sensordev.xname)); > + for (i = 0; i < SAMBAT_NSENSORS; i++) { > + sc->sc_sensor[i].flags |= SENSOR_FINVALID; > + sensor_attach(&sc->sc_sensordev, &sc->sc_sensor[i]); > + } > + sensordev_install(&sc->sc_sensordev); > + > + if (sensor_task_register(sc, sambat_refresh, > + SAM_REFRESH_INTERVAL) == NULL) { > + printf(": can't register update task\n"); > + return; > + } > + > + printf(": EC flags 0x%02x\n", probe); > + > +#if NAPM > 0 > + apm_setinfohook(sambat_apminfo); > +#endif > +} > + > +/* > + * Mbox primitives. All Mbox traffic uses the same I2C slave address > + * (sc->sc_addr); the "hi"/"lo" pair selects the Mbox register inside > + * the EC. Write: 5-byte transfer. Read: 4-byte write followed by > + * 2-byte read (first byte must be SAM_MBOX_READ_SUCCESS = 0x50). > + */ > +int > +sambat_mbox_write(struct sambat_softc *sc, uint8_t hi, uint8_t lo, > + uint8_t data) > +{ > + uint8_t buf[5]; > + int error; > + > + buf[0] = SAM_MBOX_WRITE_PREFIX; > + buf[1] = 0x00; > + buf[2] = hi; > + buf[3] = lo; > + buf[4] = data; > + > + iic_acquire_bus(sc->sc_tag, I2C_F_POLL); > + error = iic_exec(sc->sc_tag, I2C_OP_WRITE_WITH_STOP, sc->sc_addr, > + NULL, 0, buf, sizeof(buf), I2C_F_POLL); > + iic_release_bus(sc->sc_tag, I2C_F_POLL); > + > + if (error) > + return error; > + > + /* > + * Short settle delay between Mbox commands. The Linux driver > + * uses 5ms; the EC tolerates much less in practice and we > + * share the I2C bus with the keyboard, so keep this small. > + */ > + delay(500); > + return 0; > +} > + > +int > +sambat_mbox_read(struct sambat_softc *sc, uint8_t hi, uint8_t lo, > + uint8_t *out) > +{ > + uint8_t cmd[4]; > + uint8_t rsp[2]; > + int error; > + > + cmd[0] = SAM_MBOX_READ_PREFIX; > + cmd[1] = 0x00; > + cmd[2] = hi; > + cmd[3] = lo; > + > + iic_acquire_bus(sc->sc_tag, I2C_F_POLL); > + error = iic_exec(sc->sc_tag, I2C_OP_READ_WITH_STOP, sc->sc_addr, > + cmd, sizeof(cmd), rsp, sizeof(rsp), I2C_F_POLL); > + iic_release_bus(sc->sc_tag, I2C_F_POLL); > + > + if (error) > + return error; > + if (rsp[0] != SAM_MBOX_READ_SUCCESS) > + return EIO; > + > + *out = rsp[1]; > + return 0; > +} > + > +int > +sambat_ec_read_byte(struct sambat_softc *sc, uint8_t reg, uint8_t *out) > +{ > + int error; > + > + error = sambat_mbox_write(sc, SAM_CMD_TARGET_HI, SAM_CMD_TARGET_LO, > + reg); > + if (error) > + return error; > + error = sambat_mbox_write(sc, SAM_CMD_EXEC_HI, SAM_CMD_EXEC_LO, > + SAM_EXEC_READ); > + if (error) > + return error; > + return sambat_mbox_read(sc, SAM_CMD_TARGET_HI, SAM_CMD_TARGET_LO, > + out); > +} > + > +int > +sambat_ec_read_block(struct sambat_softc *sc, uint8_t reg, uint8_t *buf, > + int n) > +{ > + int i, error; > + > + for (i = 0; i < n; i++) { > + error = sambat_ec_read_byte(sc, reg + i, &buf[i]); > + if (error) > + return error; > + } > + return 0; > +} > + > +void > +sambat_refresh(void *arg) > +{ > + struct sambat_softc *sc = arg; > + uint8_t flags, b1st; > + uint8_t rr[4], pv[4], af[4], vl[4]; > + uint16_t remaining, voltage, design_c, fullchg_c, design_v; > + int16_t cur_ma; > + int i; > + > + for (i = 0; i < SAMBAT_NSENSORS; i++) > + sc->sc_sensor[i].flags |= SENSOR_FINVALID; > + > + if (sambat_ec_read_byte(sc, SAM_REG_FLAGS, &flags) != 0 || > + sambat_ec_read_byte(sc, SAM_REG_B1ST, &b1st) != 0 || > + sambat_ec_read_block(sc, SAM_REG_B1RR, rr, sizeof(rr)) != 0 || > + sambat_ec_read_block(sc, SAM_REG_B1PV, pv, sizeof(pv)) != 0 || > + sambat_ec_read_block(sc, SAM_REG_B1AF, af, sizeof(af)) != 0 || > + sambat_ec_read_block(sc, SAM_REG_B1VL, vl, sizeof(vl)) != 0) > + return; > + > + /* > + * Each 4-byte EC field carries two big-endian 16-bit values. > + * The DSDT ByteSwap16(reg >> 16) and ByteSwap16(reg & 0xffff) > + * idioms extract the upper and lower BE words respectively. > + */ > + remaining = ((uint16_t)rr[2] << 8) | rr[3]; > + voltage = ((uint16_t)pv[2] << 8) | pv[3]; > + cur_ma = (int16_t)(((uint16_t)pv[0] << 8) | pv[1]); > + design_c = ((uint16_t)af[2] << 8) | af[3]; > + fullchg_c = ((uint16_t)af[0] << 8) | af[1]; > + design_v = ((uint16_t)vl[0] << 8) | vl[1]; > + > + if (remaining == 0xffff) > + remaining = 0; > + if (design_c == 0xffff) > + design_c = 0; > + if (fullchg_c == 0xffff) > + fullchg_c = 0; > + > + sc->sc_present = !!(flags & SAM_FLAG_B1EX); > + sc->sc_ac_online = !!(flags & SAM_FLAG_ACEX); > + sc->sc_b1st = b1st; > + sc->sc_remaining_mah = remaining; > + sc->sc_voltage_mv = voltage; > + sc->sc_current_ma = cur_ma; > + sc->sc_design_mah = design_c; > + sc->sc_fullchg_mah = fullchg_c ? fullchg_c : design_c; > + sc->sc_design_mv = design_v; > + sc->sc_have_data = 1; > + > + if (sc->sc_fullchg_mah > 0) { > + /* SENSOR_PERCENT: value = percent * 1000 (100% = 100000). */ > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE].value = > + (1000ULL * 100 * remaining) / sc->sc_fullchg_mah; > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE].flags &= ~SENSOR_FINVALID; > + } > + sc->sc_sensor[SAMBAT_SENSOR_VOLT_NOW].value = > + (uint64_t)voltage * 1000; /* mV -> uV */ > + sc->sc_sensor[SAMBAT_SENSOR_VOLT_NOW].flags &= ~SENSOR_FINVALID; > + > + sc->sc_sensor[SAMBAT_SENSOR_VOLT_DESIGN].value = > + (uint64_t)design_v * 1000; > + sc->sc_sensor[SAMBAT_SENSOR_VOLT_DESIGN].flags &= ~SENSOR_FINVALID; > + > + sc->sc_sensor[SAMBAT_SENSOR_CURRENT].value = > + (int64_t)cur_ma * 1000; /* mA -> uA */ > + sc->sc_sensor[SAMBAT_SENSOR_CURRENT].flags &= ~SENSOR_FINVALID; > + > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE_NOW].value = > + (uint64_t)remaining * 1000; /* mAh -> uAh */ > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE_NOW].flags &= ~SENSOR_FINVALID; > + > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE_FULL].value = > + (uint64_t)sc->sc_fullchg_mah * 1000; > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE_FULL].flags &= ~SENSOR_FINVALID; > + > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE_DESIGN].value = > + (uint64_t)design_c * 1000; > + sc->sc_sensor[SAMBAT_SENSOR_CHARGE_DESIGN].flags &= ~SENSOR_FINVALID; > + > + sc->sc_sensor[SAMBAT_SENSOR_STATE].value = b1st; > + sc->sc_sensor[SAMBAT_SENSOR_STATE].flags &= ~SENSOR_FINVALID; > +} > + > +#if NAPM > 0 > +int > +sambat_apminfo(struct apm_power_info *info) > +{ > + struct sambat_softc *sc = sambat_sc; > + > + info->battery_state = APM_BATT_UNKNOWN; > + info->ac_state = APM_AC_UNKNOWN; > + info->battery_life = 0; > + info->minutes_left = -1; > + > + if (!sc->sc_have_data) > + return 0; > + > + if (sc->sc_ac_online) > + info->ac_state = APM_AC_ON; > + else > + info->ac_state = APM_AC_OFF; > + > + if (!sc->sc_present) { > + info->battery_state = APM_BATTERY_ABSENT; > + return 0; > + } > + > + if (sc->sc_fullchg_mah > 0) > + info->battery_life = > + (100 * sc->sc_remaining_mah) / sc->sc_fullchg_mah; > + > + if (sc->sc_b1st & SAM_B1ST_FULL) { > + info->battery_state = APM_BATT_HIGH; > + } else if (sc->sc_b1st & SAM_B1ST_CHARGE) { > + info->battery_state = APM_BATT_CHARGING; > + } else { > + if (info->battery_life > 50) > + info->battery_state = APM_BATT_HIGH; > + else if (info->battery_life > 25) > + info->battery_state = APM_BATT_LOW; > + else > + info->battery_state = APM_BATT_CRITICAL; > + } > + > + /* > + * Estimate minutes-left from instantaneous current (mA) and > + * remaining capacity (mAh). Only meaningful while discharging. > + */ > + if ((sc->sc_b1st & SAM_B1ST_CHARGE) == 0 && sc->sc_current_ma < 0) { > + int draw = -sc->sc_current_ma; > + if (draw > 0) > + info->minutes_left = > + (60 * sc->sc_remaining_mah) / draw; > + } > + > + return 0; > +} > +#endif > Index: share/man/man4/sambat.4 > =================================================================== > RCS file: share/man/man4/sambat.4 > diff -N share/man/man4/sambat.4 > --- /dev/null 1 Jan 1970 00:00:00 -0000 > +++ share/man/man4/sambat.4 17 May 2026 16:59:54 -0000 > @@ -0,0 +1,59 @@ > +.\" $OpenBSD$ > +.\" > +.\" Copyright (c) 2026 Marcus Glocker > +.\" > +.\" Permission to use, copy, modify, and distribute this software for any > +.\" purpose with or without fee is hereby granted, provided that the above > +.\" copyright notice and this permission notice appear in all copies. > +.\" > +.\" THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES > +.\" WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF > +.\" MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR > +.\" ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES > +.\" WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN > +.\" ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF > +.\" OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. > +.\" > +.Dd $Mdocdate: May 17 2026 $ > +.Dt SAMBAT 4 > +.Os > +.Sh NAME > +.Nm sambat > +.Nd Samsung laptop battery monitor > +.Sh SYNOPSIS > +.Cd "sambat* at iic?" > +.Sh DESCRIPTION > +The > +.Nm > +driver provides support for the embedded controller found in Samsung > +laptops based on the Qualcomm Snapdragon X1E80100, such as the Galaxy > +Book4 Edge. > +The controller, identified as > +.Dq SAM060B > +in ACPI, is an ENE KB9058 micro controller running Samsung firmware, > +and provides battery and AC adapter status over the I2C bus using a > +vendor > +.Dq Mbox > +command protocol. > +.Pp > +Battery values are exposed through the > +.Xr sysctl 8 > +sensor framework and the > +.Xr apm 8 > +power management interface. > +.Sh SEE ALSO > +.Xr iic 4 , > +.Xr intro 4 , > +.Xr apm 8 , > +.Xr sysctl 8 > +.Sh HISTORY > +The > +.Nm > +driver first appeared in > +.Ox 8.0 . > +.Sh AUTHORS > +.An -nosplit > +The > +.Nm > +driver was written by > +.An Marcus Glocker Aq Mt mglocker@openbsd.org . > Index: sysutils/firmware/arm64-qcom-dtb/Makefile > =================================================================== > RCS file: /cvs/ports/sysutils/firmware/arm64-qcom-dtb/Makefile,v > diff -u -p -u -p -r1.28 Makefile > --- sysutils/firmware/arm64-qcom-dtb/Makefile 29 Mar 2026 20:53:10 -0000 1.28 > +++ sysutils/firmware/arm64-qcom-dtb/Makefile 17 May 2026 17:16:57 -0000 > @@ -1,6 +1,6 @@ > FW_DRIVER= arm64-qcom-dtb > FW_VER= 2.7 > -REVISION= 0 > +REVISION= 1 > > DISTNAME= devicetree-rebasing-6.17-dts > > Index: sysutils/firmware/arm64-qcom-dtb/patches/patch-src_arm64_qcom_x1e80100-samsung-galaxy-book4-edge_dts > =================================================================== > RCS file: /cvs/ports/sysutils/firmware/arm64-qcom-dtb/patches/patch-src_arm64_qcom_x1e80100-samsung-galaxy-book4-edge_dts,v > diff -u -p -u -p -r1.2 patch-src_arm64_qcom_x1e80100-samsung-galaxy-book4-edge_dts > --- sysutils/firmware/arm64-qcom-dtb/patches/patch-src_arm64_qcom_x1e80100-samsung-galaxy-book4-edge_dts 8 Sep 2024 18:53:25 -0000 1.2 > +++ sysutils/firmware/arm64-qcom-dtb/patches/patch-src_arm64_qcom_x1e80100-samsung-galaxy-book4-edge_dts 17 May 2026 17:16:57 -0000 > @@ -1,7 +1,7 @@ > Index: src/arm64/qcom/x1e80100-samsung-galaxy-book4-edge.dts > --- src/arm64/qcom/x1e80100-samsung-galaxy-book4-edge.dts.orig > +++ src/arm64/qcom/x1e80100-samsung-galaxy-book4-edge.dts > -@@ -0,0 +1,957 @@ > +@@ -0,0 +1,977 @@ > +// SPDX-License-Identifier: BSD-3-Clause > +/* > + * Copyright (c) 2023 Qualcomm Innovation Center, Inc. All rights reserved. > @@ -549,6 +549,26 @@ Index: src/arm64/qcom/x1e80100-samsung-g > + pinctrl-names = "default"; > + > + wakeup-source; > ++ }; > ++}; > ++ > ++/* > ++ * Samsung Galaxy Book4 Edge embedded controller (ENE KB9058, Samsung > ++ * SAM060B in ACPI). Provides battery and AC adapter status via a > ++ * vendor "Mbox" protocol on I2C. Reverse-engineered Linux driver: > ++ * https://github.com/Saddytech/Galaxy-Book4-Edge-linux > ++ * > ++ * The DSDT _SB.ECTC._CRS lists this device on two channels: > ++ * - ACPI I2C1 (MMIO 0xb80000, FDT &i2c0) at addr 0x64 -- Mbox > ++ * command set used by the battery driver. > ++ * - ACPI I2C6 (MMIO 0xb94000, FDT &i2c5) at addr 0x62 -- "SABI" > ++ * command set used by fan/keyboard backlight tools. > ++ * Only the Mbox channel (0x64 on &i2c0) is needed for battery status. > ++ */ > ++&i2c0 { > ++ ec: ec@64 { > ++ compatible = "samsung,galaxybook-battery"; > ++ reg = <0x64>; > + }; > +}; > + > >