Acer Chromebook Spin 713-3W coreboot EC firmware does not disable KB in tablet mode

The EC firmware correctly detects tablet mode, but does not disable the kb as the EC firmware packaged with the latest builds of chromeOS does. This appears to be handled correctly by the EC firmware bundled w/the chromeos builds (see for example board/voxel/sensors.c - chromiumos/platform/ec - Git at Google - my first clue was the last function “lid_angle_peripheral_enable(int enable)” at the end); Hopefully @MrChromebox or another person can confirm and investigate? I have the latest coreboot full UEFI firmware installed.
Also of interest is that using > sudo ectool motionsense list_activities returns an error (it should list the activities that montionsense performs… including enabling/disabling the kb)

Thanks!

what are the RO and RW EC firmware versions listed in cbmem?

RO: voxel_v2.0.7485-bfb93637f
RW: voxel_v2.0.10154-ea17325ac

thx, need to see what the latest Google RW is and maybe rebuild based on that hash instead of the volteer main branch

Awesome, thanks! I’m happy to help test as well. I have a SuzyQ so risk is low :).

I have the same device. I also got GNA and video encode/decode chips working, and stylus and touchscreen working independently with pressure sensitivity, stereo dmic and headphone jack/mic working and switching with a script. I am running Debian Trixie with custom kernel 7.2.8-rt5 … Here is my script for tablet mode and orientation switching: #!/usr/bin/env python3
“”"auto-rotate — tablet mode for the Chromebook Spin 713.

Folded into tablet mode (the EC’s “Tablet Mode Switch”), the screen follows the
lid accelerometer (iio label “accel-display”), the touchscreen and stylus are
remapped to match, and the keyboard and touchpad are disabled. Unfolded: back
to normal orientation with keyboard and touchpad on.

X11: xrandr + xinput (Coordinate Transformation Matrix on the ELAN touch/stylus,
including the proxied devices from elan_input_daemon.py). Wayland (mango):
wlr-randr output transform. Started from the openbox / mango autostart.

auto-rotate run
auto-rotate --watch only print tablet state + detected orientation
“”"
import glob
import os
import select
import signal
import subprocess
import sys
import fcntl

import evdev

OUTPUT = “eDP-1”
TOUCH = “ELAN90FC” # every touchscreen/stylus device, real + proxied
KEYBOARD, TOUCHPAD = “AT Translated Set 2 keyboard”, “Elan Touchpad”
THRESHOLD = 0.6 # of 1 g on the dominant axis before rotating

Lid accelerometer axis/sign → screen orientation, measured on this machine

(2026-09-27): open laptop +y; tablet hinge on the left -x, hinge on the right

+x, upside down -y. Hinge (screen bottom) on the left = panel turned clockwise,

which xrandr compensates with “left”.

AXIS_ORIENT = {(“y”, 1): “normal”, (“y”, -1): “inverted”, (“x”, -1): “left”, (“x”, 1): “right”}
MATRIX = {“normal”: “1 0 0 0 1 0 0 0 1”, “inverted”: “-1 0 1 0 -1 1 0 0 1”,
“left”: “0 -1 1 1 0 0 0 0 1”, “right”: “0 1 0 -1 0 1 0 0 1”}
WL_TRANSFORM = {“normal”: “normal”, “left”: “90”, “inverted”: “180”, “right”: “270”}
WAYLAND = bool(os.environ.get(“WAYLAND_DISPLAY”))

def find_accel():
for d in glob.glob(“/sys/bus/iio/devices/iio:device*”):
try:
if open(f"{d}/label").read().strip() == “accel-display”:
return d
except OSError:
pass
sys.exit(“auto-rotate: no accel-display iio device”)

def find_tablet_switch():
for path in evdev.list_devices():
d = evdev.InputDevice(path)
if d.name == “Tablet Mode Switch”:
return d
sys.exit(“auto-rotate: no Tablet Mode Switch input device”)

def tablet_mode(dev):
“”“Current SW_TABLET_MODE state via EVIOCGSW (the switch only sends changes).”“”
buf = bytearray(8)
fcntl.ioctl(dev.fd, 0x8008451B, buf) # EVIOCGSW(8)
return bool(buf[0] & (1 << evdev.ecodes.SW_TABLET_MODE))

def orientation(accel):
“”“Orientation the lid accelerometer shows, or None if too flat to tell.”“”
x, y = (int(open(f"{accel}/in_accel_{a}_raw").read()) for a in “xy”) # ~16384 = 1 g
axis, v = (“x”, x) if abs(x) > abs(y) else (“y”, y)
if abs(v) < THRESHOLD * 16384:
return None # lying flat: keep what we have
return AXIS_ORIENT[(axis, 1 if v > 0 else -1)]

def run(*cmd):
subprocess.run(cmd, stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL)

def x_devices():
out = subprocess.run([“xinput”, “list”, “–name-only”], capture_output=True, text=True).stdout
return out.splitlines()

def apply_orientation(orient):
if WAYLAND:
run(“wlr-randr”, “–output”, OUTPUT, “–transform”, WL_TRANSFORM[orient])
return
run(“xrandr”, “–output”, OUTPUT, “–rotate”, orient)
for name in x_devices():
if TOUCH in name:
run(“xinput”, “set-prop”, name, “Coordinate Transformation Matrix”, *MATRIX[orient].split())

def set_keyboard(enabled):
if WAYLAND:
return # mango: no generic per-device toggle
for name in (KEYBOARD, TOUCHPAD):
run(“xinput”, “enable” if enabled else “disable”, name)

def restore(*_):
“”“Never leave the screen rotated or the keyboard off when we stop.”“”
apply_orientation(“normal”)
set_keyboard(True)
sys.exit(0)

def main():
watch = “–watch” in sys.argv
if not watch:
for sig in (signal.SIGTERM, signal.SIGINT, signal.SIGHUP):
signal.signal(sig, restore)
accel, switch = find_accel(), find_tablet_switch()
tablet, current, candidate, seen = tablet_mode(switch), None, None, 0
if not watch:
set_keyboard(not tablet)
print(f"auto-rotate: {‘Wayland’ if WAYLAND else ‘X11’}, tablet={tablet}“, flush=True)
while True:
r, _, _ = select.select([switch.fd], , , 0.4)
if r:
for ev in switch.read():
if ev.type == evdev.ecodes.EV_SW and ev.code == evdev.ecodes.SW_TABLET_MODE:
tablet = bool(ev.value)
print(f"tablet mode {‘ON’ if tablet else ‘OFF’}”, flush=True)
if not watch:
set_keyboard(not tablet)
want = orientation(accel) if tablet else “normal”
if want is None or want == current:
candidate, seen = None, 0
continue
seen = seen + 1 if want == candidate else 1 # two readings in a row: no jitter at 45°
candidate = want
if seen >= 2:
current = want
print(f"orientation → {current}", flush=True)
if not watch:
apply_orientation(current)

if name == “main”:
main()

Debian Trixie 13 on Acer Spin 713-3W Chromebook

30 Sept 2026 · @stemsee

Overview

The Acer Chromebook Spin 713 (CB713-3W, board “Voxel”, Tiger Lake i5-1135G7) now boots Debian 13 “trixie” from its internal NVMe, on a custom PREEMPT_RT kernel, 7.2.8-cb-rt5. ChromeOS stays intact beside it. The GPU with hardware video encode, SOF audio, touchscreen, stylus, GNA, sensors and the webcam loopback devices all work.

Area

State on 7.2.8-cb-rt5

Boot

MrChromebox edk2 (UEFI) RW_LEGACY payload on stock coreboot (Ctrl+L), then GRUB on p12, then kernel and initrd from p14

Root filesystem

aufs union of trixie.sfs, a kernel-modules sfs and the save-trixie save directory

Kernel

7.2.8 with aufs 7.2 and RT patches, a trimmed config and a permanent signing key

GPU

i915 with GuC submission and HuC (enable_guc=3), PSR off, VAAPI H.264/HEVC encode and AV1 decode

Audio

SOF (dsp_driver=3), rt5682 headset codec, MAX98373 speaker amps, DMIC through PulseAudio

Input

ELAN90FC touch and stylus through a uinput proxy daemon with palm rejection

GNA

GNA 2.0 on renderD129, driven by libgna 3.0 and OpenVINO 2023.3

Tablet

auto-rotate from the lid accelerometer, 80% battery charge limit

The base came from Fatdog64/Bulldog: its initrd and aufs layering were kept, but systemd runs as PID 1 on a Debian trixie userland. This summary covers the platform only, not the apps built on it (CMC, Chameleon, TrXi-Ctrl).

Disk layout and boot chain

Linux has booted entirely from the internal NVMe since 2026-08-24, with no firmware reflash and no external drive. The stock firmware (Google_Voxel.13672.382.0) already carries MrChromebox’s edk2 UEFI payload in RW_LEGACY, reached with Ctrl+L at the developer-mode screen.

Partition

Size

Use

p1–p11

~44 GB

Original ChromeOS (kernels, rootfs A/B, OEM, stateful). Never written, mounted or repartitioned.

p12

64 MB vfat

EFI system partition. ChromeOS’s own GRUB (/efi/boot/bootx64.efi, grub.cfg), with the Trixie entries appended below the untouched stock ones

p13

16 GB

Swap

p14

178 GB ext4

Everything Linux: grub-boot-dir/ (kernels, initrds, sfs images), save-trixie/, and the /aufs/devbase project workspace

Boot sequence:

  1. Ctrl+L starts the MrChromebox edk2 payload, which reads NVMe through EFI Block I/O.

  2. bootx64.efi reads /efi/boot/grub.cfg on p12. The Trixie entry finds p14 by UUID (d48af2fd-…).

  3. GRUB loads grub-boot-dir/vmlinuz-7.2.8-cb-rt5 and grub-boot-dir/initrd-trix-fix.

  4. The initrd assembles the aufs root and switch_roots to systemd.

The current kernel command line:

i915.enable_guc=3 psi=1 ia32_emulation=1 threadirqs waitdev=5 pkeys=en
kernelmodules=direct:device:nvme0n1p14:grub-boot-dir/kernel-modules-7.2.8-cb-rt5.sfs
savefile=direct:device:nvme0n1p14:save-trixie
basesfs=direct:device:nvme0n1p14:grub-boot-dir/trixie.sfs
newinit=/sbin/init snd-intel-dspcfg.dsp_driver=3 i915.enable_psr=0

GRUB configs are edited by hand only, never by scripts.

Distro and aufs layering

The running / is an aufs union: read-only squashfs layers underneath, with the on-disk save-trixie directory as the writable top. Every change persists as soon as it’s written, so no save step is needed.

Branch (top to bottom)

Mount

Contents

pup_save (rw)

/aufs/pup_save

save-trixie/ on p14, a plain directory rather than a loop image. All writes land here.

pup_ro

/aufs/pup_ro

trixie.sfs, the lean Debian 13 base

kernel modules

/aufs/kernel-modules

kernel-modules-7.2.8-cb-rt5.sfs: modules, firmware, aufs-util

pup_init

/aufs/pup_init

The initrd’s own busybox rootfs, as the bottom fallback

The base images, built on 2026-08-15 from the live system:

  • trixie.sfs is the lean base. It leaves out /usr/lib/modules and /usr/lib/firmware, which ship in the kernel-modules sfs, so a kernel can change without a remaster.

  • trixie-devx.sfs, -extras, -docs, -locale hold what was stripped from the base (about 59,800 paths: compilers, audio plugins, docs, locales). Each can be loaded live with load_sfs.sh --load, which inserts it above the base and below the save layer.