Skip to content

← Firmware Downloads

Post-install setup · Reborn (Y2Linux) · Y2

Internal speaker and haptic feedback — research and plan

Debian-based Linux with a fast clickwheel interface for the Innioasis Y2.

Instructions published by the project, shown here word for word.

Internal speaker and haptic feedback — research and plan

Status: implemented and published in Beta 8.41 (Baseline 159): haptics as stock's key press (60 ms per button, no wheel ticks, owner's choice), speaker steps S1–S3 as planned (jack by polling, no IRQ). See the Beta 8.41 report. The sections below are the research as written.

Research 2026-10-27 for the next update. Sources: the stock FM kernel through REA (out/rea/fm-kernel.elf, sha256 329466b3…, Ghidra 12.1.2 + llvm-objdump), the stock system.img (MyLauncher.apk strings only), the donor tree (donorSource/, Chris Hendrickson, GPL-2.0) and our current kernel/Reborn. Nothing was built, flashed or run on a player. Issue: #28 (haptics); no issue for the speaker yet.

This adds two hardware subsystems, so per AGENTS.md the roadmap/gap audit must be repeated before implementation starts. This document is research, not authorization.

Summary

HapticsSpeaker
HardwareERM motor on the MT6323 VIBR LDOCS43131 HP outputs → AW87559 (or OCA72559) class-D amp → speaker
Stock driverMTK timed_output "vibrator": switch VIBR on, hrtimer offvendor CS43131 driver + aw87559_on/off, jack detect on EINT16
Upstream/donor reuseupstream regulator-haptic + mt6323-regulator (same registers as stock)donor aw87559.c (tables verified identical to stock) + donor speaker DAPM widget
Effortsmall: DT + config + one PMIC bit + Rebornmedium: amp driver, machine-driver route, jack detection, Reborn output + downmix
Main risknone known; voltage above stock untestedjack detect needs the codec powered (we power it off when idle)

Recommendation: ship haptics first (one small, self-contained kernel step plus a Reborn setting). Do the speaker as a second step, starting with jack detection, because automatic switching depends on it.

1. Haptics (vibration motor)

What stock does (REA, observed)

What we have

Plan

  1. DT: ldo_vibr regulator (regulator-name = "vibr", 1.8–2.8 V) and

    `haptics { compatible = "regulator-haptic"; haptic-supply = <&vibr>; min-microvolt = <1800000>; max-microvolt = <2800000>; }`. Cap at the stock 2.8 V; a "strong" setting = stock strength.

  2. Config: CONFIG_INPUT_MISC=y, CONFIG_INPUT_REGULATOR_HAPTIC=y (selects

    INPUT_FF_MEMLESS).

  3. PMIC: set 0x542 bit 2 (thermal shutdown) once at probe, as stock does. This

    is the existing golden-table row, so the change is stock parity.

  4. Reborn: open the input device named regulator-haptic, upload one

    FF_RUMBLE effect, and play it on a wheel detent (reborn-platform/src/input.rs WheelCadence::detent) and on button presses. Settings > Customize (or Sound): Haptics Off / Light / Strong (magnitude → voltage between 1.8 and 2.8 V). Rate-limit to about one tick per 25–30 ms so a fast spin doesn't hum continuously. No haptics while the screen is dark.

  5. Tick length: start at 20 ms and 50 ms (stock floor) and choose on the player.

    An ERM motor needs time to spin up, so very short pulses may not be felt.

Device checks owed

2. Internal speaker

Hardware path (stock + donor, observed)

CS43131 HPOUTA/HPOUTB → AW87559 input (I2C1 0x58, enable = GPIO8) → speaker. Stock bound 1-0058 aw87559_pa. Some units have the pin-compatible OCA72559 (chip ID 0x09); the donor unit reported ID 0x09. Stock and donor handle both.

What stock does (REA, observed)

Amplifier — Sound_Speaker_Turnon (0xc04a2900) → aw87559_on (0xc054acc4): GPIO8 mode 0, output, low → 10 ms → high → 100 ms, read ID (0x5A AW87559 / 0x09 OCA72559, up to 5 retries with the same pulse), write the register table, then wait 80 ms. aw87559_off: SYSCTRL 0x01 = 0x30, GPIO8 low. If neither ID answers, stock falls back to pulsing GPIO8 three times (a one-wire mode for a different amp); not needed for I2C parts. **The donor sound/soc/codecs/aw87559.c tables are identical to stock, write for write** (both AW87559 and OCA72559), and so are its timings.

DAC route — cs43131_apply_route (0xc054baa8):

HeadphonesSpeaker
PCM_PATH_CTL_2 (0x90004)0x00 (L-R)0x05 (L-L + bit 2, i.e. L and −L, differential)
ASP_CH_1_SZ_EN (0x5000A)0x060x06
ASP_CH_2_SZ_EN (0x5000B)0x0E0x06
HP_OUT_CTL_1 OUT_FS (0x80000 bits 5:4)cs43131_hp_gain_fs[level] (0x10 default)0x30 (1.73 Vrms)
PCM volume (0x90001/2)cs43131_vol_table[i](15 − i) × 4 + 6 half-dB steps: −3 dB at top, 0xFF mute at 0

The speaker gets only the left channel: channel B carries inverted left. Our side must downmix (L+R)/2 before the DAC, otherwise right-only content is lost. That is a conclusion from the register values; the exact meanings of PCM_PATH_CTL_2 bit 2 and ASP_CH_2 bit 3 are not in the upstream header.

Switching — cs43131_hp_switch_to_hp / …_to_speaker: mute (PCM volume 0xFF), amp off or on, apply_route, 10 ms, restore the route volume. Order: premute → amp → route → unmute.

Jack detection — EINT16 (GPIO16, input, no pull, level-low, debounce 0x20) → cs43131_hp_work_callback: sleep 200 ms, read HP_STATUS (0xD0001) bit 6. Plugged → speaker off, headphone route. Unplugged → mute, route state = speaker, report the h2w switch as 0. The Android app pauses on that event ("becoming noisy"). The amp only comes on when playback is routed to the speaker (AudDrv_ioctl → Sound_Speaker_Turnon). Re-plugging within 350 ms is debounced by cs43131_hp_deferred_check_callback.

Codec power — cs43131_off only sets PDN bits and keeps power on. Stock removes codec power (cs43131_power_down_hw: mask EINT16, GPIO 15/18/20 low, VGP2 off) only at cs43131_early_suspend (screen off, not playing) and restores it at late_resume. **So stock jack detection works only while the codec is powered: screen on, or playing.**

App — MyLauncher has "Speaker On/Off" (getSpeakerOn/Off, icon_setting_speaker_on/off) and listens for HEADSET_PLUG.

What we have

Plan

Step S1 — jack detection (useful on its own: auto pause on unplug, #12/PHY-011)

Step S2 — amplifier + route

Step S3 — Reborn

Device checks owed

Open questions

  1. Stock app tick duration/pattern: needs the APK decompiled (jadx was blocked

    by the permission classifier this session). It only tunes the default.

  2. Motor voltage rating: stay at stock's 2.8 V max until known.
  3. PCM_PATH_CTL_2 bit 2 / ASP_CH_2_SZ_EN bit 3 exact semantics: the CS43131

    datasheet would confirm the "inverted left" reading; the on-device listen test settles it either way.

  4. Speaker power budget/thermal: stock has no speaker protection; mirror stock

    limits only.

Evidence index (stock FM kernel addresses)

FunctionAddress
vib_mod_init / vibr_power_set0xc0492784 / 0xc056316c
vibrator_enable / update_vibrator0xc0492a0c / 0xc0492bf8
dct_pmic_VIBR_enable / dct_pmic_VIBR_sel0xc04b7024 / 0xc04b7818
cust vibrator struct0xc0a01500 = {50, 9, 5}
Sound_Speaker_Turnon / Turnoff0xc04a2900 / 0xc04a29f0
aw87559_on / aw87559_off0xc054acc4 / 0xc054b080
cs43131_apply_route / apply_out_fs0xc054baa8 / 0xc054b78c
cs43131_refresh_volume_for_route0xc054b304
cs43131_hp_switch_to_hp / to_speaker0xc054bdc0 / 0xc06edc90
cs43131_hp_work_callback / deferred_check0xc054bf30 / 0xc054c060
cs43131_on / off / early_suspend / power_down_hw0xc054c648 / 0xc054c7a0 / 0xc054bed0 / 0xc054b620
cs43131_vol_table / hp_gain_fs0xc0760918 (ff 6e 68 62 5c 56 50 4a 44 3b 32 29 20 17 0e 06) / 0xc0760928 (00 10 20 30)

Published by the schulzcode/y2linux project [1] [3]