Download raw body.
sambat(4) - Samsung laptop battery monitor
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)
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)
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 <mglocker@openbsd.org>
+ *
+ * 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 <sys/param.h>
+#include <sys/systm.h>
+#include <sys/device.h>
+#include <sys/sensors.h>
+
+#include <machine/apmvar.h>
+
+#include <dev/i2c/i2cvar.h>
+
+#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 */
+ 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 <mglocker@openbsd.org>
+.\"
+.\" 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>;
+ };
+};
+
sambat(4) - Samsung laptop battery monitor