10.6. Wacom EMR pen digitizer driver

Register/protocol-level reverse engineering of the active-pen kernel driver — split out from the general input-peripherals sweep because the pen is tightly coupled to the display stack (it shares the panel surface and the same “recovered by static disassembly” method as the e-ink driver). Built into artifacts/kernel and bound by the DTB overlay (DTBO overlays). The e-ink display path itself is in E-ink driver internals (register-level, from the kernel); the front-light (lm3630a) and its class devices are in The e-ink software stack (Onyx EPDC). Capacitive touch (the separate finger-layer digitizer) is in Capacitive touch (Parade pt_core).

Method: the driver compatible/GPIO bindings are read from artifacts/dtbo_a/overlay_0.dts; the protocol details are from static disassembly of the kernel (function starts located by __func__ string cross-reference, then read with capstone — the same toolkit as E-ink driver internals (register-level, from the kernel), scripts in artifacts/re_static/). No code was executed.

10.6.1. DT binding

Device

compatible

Bus

GPIOs / notes

Wacom pen (wacom@09)

onyx,wacom_i2c

I²C 0x09

irq gpio52, rst gpio54, vdd-ctrl gpio10, vcc-ctrl gpio49; onyx,report-rate-high

The active pen is an electromagnetic-resonance (EMR) digitizer built on a Wacom W9021 MPU, reached over I²C at address 0x09. The driver names the chip family explicitly (wacom_flash_w9021 / wacom_write_block_w9021 / wacom_set_query_w9021) and prints the detected part as "Wacom-0x%X".

10.6.2. Capability query

wacom_query_device (@0x9ea628) → wacom_get_hid_desc reads the device’s HID/query descriptor and logs the pen’s capabilities from fixed offsets in the descriptor struct:

  • x_max / y_max — active-area resolution (log "x_max:%d, y_max:%d, pressure:%d, fw:%d");

  • pressure — pressure range;

  • height — hover height (log "height:%d, tilt_x:%d, tilt_y:%d");

  • tilt_x / tilt_y — tilt range (bytes); wacom_tilt_flags gates whether tilt is reported;

  • fw — firmware version byte; the MPU type byte becomes the input-device name Wacom-0x%X.

10.6.3. Report (coordinate) packet

wacom_i2c_data_read (@0x9eb1d8) performs an I²C read of the input report (length = descriptor input report length) into the client buffer and decodes, from fixed offsets:

Field

Encoding

Meaning

flags

byte

tool/tip/side-button state

X

u16 LE

raw X coordinate

Y

u16 LE

raw Y coordinate

pressure

u16 LE

pen pressure, clamped to 12-bit (0xfff)

The raw X/Y are then transformed by (a) an orientation word — swap X↔Y and/or invert against x_max/y_max per rotation bits — and (b) a 2×3 calibration matrix ((m00·x + m01·y + off)/denom) applied before the values are reported to the input subsystem. The read has a 10-attempt retry loop with a short delay between tries; after 11 consecutive failures it triggers a controller reset (@0x9eaee8).

10.6.4. Firmware update and features

The driver can reflash the pen MPU: wacom_get_bootloader_version / wacom_get_mpu_type / wacom_flash_cmd / wacom_flash_w9021 / wacom_write_block_w9021 / wacom_write_data_w9021 / wacom_flash_end_w9021 implement the W9021 flashing protocol (wacom_fw_update / wacom-gpio_update drive it). Runtime features: wacom_set_report_rate (with the DT onyx,report-rate-high knob), wacom_i2c_set_feature / …_get_feature (HID feature reports), and USI active-pen support (the recovery menu exposes /sys/onyx_misc/onyx_active_pen/usi_enableRecovery ramdisk (recovery environment)). Power management: wacom_suspend / wacom_resume with an IRQ wake-lock (wacom_irq_wakelock) and a wacom_pm sysfs store.

10.6.5. Flashing protocol opcodes (recovered from the userspace flash tool)

The kernel driver above only names the flashing functions; the actual command opcodes are not present there. They are recovered here from a standalone userspace tool, artifacts/vendor_a/bin/wacom_flash (stripped ARM64 ELF, entry 0x2000; its own debug strings call the flashable part “WEZ01”, the internal MCU behind the W9021 EMR front-end, and its usage string names a sample image WEZ01_056.hex). All protocol logic is local to the binary (only libc is imported), so it was disassembled directly — no shortcut via imported symbols was available.

Two local helpers do all the I/O, identified by their own embedded debug strings ("wacom_i2c_set_feature" / "wacom_i2c_get_feature", loaded right next to each call):

  • ``wacom_i2c_set_feature`` (file/vaddr ``0x27b4``) builds an (len+8)-byte buffer — [reg 0x0004 u16 LE][opcode|0x30][0x03] [reg 0x0005 u16 LE][len+2 as u16 LE][payload…] — and sends it with a plain write(2) (the fortified __write_chk) directly on the /dev/i2c-1 fd. Registers 0x0004/0x0005 are distinct from the runtime touch-report register documented above.

  • ``wacom_i2c_get_feature`` (file/vaddr ``0x28f4``) builds a near-identical header but issues it via ioctl(fd, 0x0707 /* I2C_RDWR */, &i2c_rdwr_ioctl_data) — a standard two-i2c_msg write-then-read I²C transaction (confirmed by resolving the call’s PLT target through .rela.plt).

Each flashing sub-command is a distinct function, its name confirmed by its own adjacent debug-string load (not inferred):

Function (offset)

Sub-op

SET payload (hex)

Notes

flash_query_wez01 (0x2a7c)

0x07

07 07 07

handshake/identity; GET (opcode 0x08, len 6) must return resp[1]=7, resp[2]=7, resp[3]=6

flash_blver_wez01 (0x2bdc)

0x04

07 04 07

get bootloader version; resp[1]=4, resp[2]=7 gate the check, resp[3] is the version byte ("BL version: %x")

flash_mputype_wez01 (0x2d38)

0x05

07 05 07

get MPU/chip type; resp[1]=5, resp[2]=7 gate, resp[3] is the MPU-type byte ("MPU type: %x")

flash_erase_all (0x2e94)

0x10

07 10 02 03 d8

erase; resp[1]=0x10, resp[2]=2 gate, then resp[3] (an MPU-type byte in 0x810x87) is bit-tested against mask 0x43 — only MPU types 0x81/0x82/0x87 are allowed to proceed

flash_write_block_wez01 (0x3060)

0x07

[addr:2][≤264-byte data][1-byte checksum] (len 0x10a)

address bytes come from the Intel-HEX record’s own address field; checksum is an 8-bit two’s-complement (negated running sum), computed in a loop immediately before the call

GetAlignmentInfo” / flash_write_wez01 (0x35e4)

0x07

large (~0x138-byte) payload

write+verify step; GET opcode 0x0e, len 269; its own, different resp[1]=0xf, resp[2]=1 gate — numerically distinct from flash_erase_all’s resp[1]=0x10, resp[2]=2 — but the same downstream MPU-type/mask-0x43 check

flash_end_wez01 (0x3b20)

— (top-level opcode 0x02, not 0x07)

02 02

closes boot mode

Every opcode above is read directly off wacom_flash’s own machine code and cited by file/vaddr offset in artifacts/re_static/item_mxo_wacom_recheck.txt (§3), including the PLT resolution that confirms ioctl/__write_chk and the I2C_RDWR request code. libonyx_pen_touch_reader.so was also checked for flashing detail: its only flash/erase-adjacent symbols are EraseReader / SideEraseReader (the stylus eraser-tip / side-button input handlers, unrelated to MCU flashing) — no flashing logic exists in that library.

Note

Honest limit. No W9021/WEZ01 firmware image (.hex or otherwise) exists anywhere in this dump (checked broadly across _READONLY and artifacts) — only the flashing tool. So while the wire protocol (registers, opcodes, response gating, checksum) has been recovered, what a flashed image’s contents mean — the WEZ01 MCU’s own instruction set / memory map — is not, for the same reason the MXO NVCM register semantics aren’t (E-ink driver internals (register-level, from the kernel)’s MXO section): no datasheet or firmware sample is present in this dump. This is a hard “sample not present” limit: recovering it would require a firmware image that does not exist anywhere in this dump.

A shared Onyx “TP info” debug function (0x9e94a8) covers both this chip and the capacitive-touch controller through one common struct — the “compile every candidate vendor driver” house convention this confirms is detailed in Capacitive touch (Parade pt_core).

10.6.6. Provenance

Source:

artifacts/dtbo_a/overlay_0.dts (the compatible binding, I²C address, GPIO assignments); artifacts/kernel (Wacom driver functions and the query/report decode — disassembled via the __func__-xref + capstone toolkit, script artifacts/re_static/wacom.py); artifacts/vendor_a/bin/wacom_flash (standalone flashing tool, disassembled directly with capstone/elfdis.py plus manual .rela.plt resolution — the flashing opcode table above; full evidence in artifacts/re_static/item_mxo_wacom_recheck.txt).

Method:

device-tree reading + targeted static disassembly (no ELF symbols / no kallsyms for the kernel; wacom_flash is a stripped ELF resolved via its own .dynsym/.rela.plt and embedded debug strings). No code executed; nothing run on a device.

Cross-refs:

E-ink driver internals (register-level, from the kernel), The e-ink software stack (Onyx EPDC), Capacitive touch (Parade pt_core), Misc sensors (fingerprint, hall, keyboard cover), Recovery ramdisk (recovery environment), DTBO overlays.