Index | Thread | Search

From:
Marcus Glocker <marcus@nazgul.ch>
Subject:
sambat(4) - Samsung laptop battery monitor
To:
tech@openbsd.org
Date:
Sun, 17 May 2026 20:31:43 +0200

Download raw body.

Thread
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>;
 +	};
 +};
 +