mirror of
https://github.com/ALLFATHER-BV/wadamesh.git
synced 2026-11-07 20:48:34 +00:00
newMsgImpl's chime/vibration decision moves into src/ui-touch/NotifyAlert.h (same rules, a static_assert ties its slots to the prefs' TOUCH_SND_* and TOUCH_CHMUTE_*); the P4's vibrate task takes its beat count from it. test/test_notify_alert.cpp: Do Not Disturb silences both for every event, the master Sound switch only the chime, Vibrate off only the buzz; the per-event switches and channel mutes gate the buzz as they gate the chime; a DM with its switch off does not fall back to the message slot; a muted mention is a plain message; 1, 2 and 3 beats for message, DM and mention. test/test_p4_haptic.cpp runs the production P4Haptic.cpp against a register model of the AW86224 on a fake Wire1 (test/p4_haptic_fakes), each case in its own process: first use loads the 711-byte waveform at 0x0800 and verifies it, the FIFO thresholds match the vendor formula, a retained SRAM is not rewritten and one differing byte reloads it; a buzz plays waveform 1 looped with auto brake at the given gain for exactly its duration and is stopped; 0 or negative durations touch nothing; an absent chip stays off the bus after the first probe; a busy bus skips the buzz; a chip that never idles does not hang; every transaction happens with the I2C_2 bus held. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
160 lines
6.0 KiB
C++
160 lines
6.0 KiB
C++
// SPDX-License-Identifier: GPL-3.0-or-later
|
|
//
|
|
// Host test for the T-Display P4 vibration motor driver (#730,
|
|
// variants/tdisplay_p4/P4Haptic.cpp): the production file against a register-level model
|
|
// of the AW86224 on a fake Wire1 (test/p4_haptic_fakes/Wire.h), with virtual time. Each
|
|
// scenario runs in a child process, as the driver's state is file-static and lazy.
|
|
//
|
|
// c++ -std=c++17 -Wall -Wextra -I test/p4_haptic_fakes test/test_p4_haptic.cpp \
|
|
// -o /tmp/test_p4_haptic && /tmp/test_p4_haptic
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
#include <sys/wait.h>
|
|
#include <unistd.h>
|
|
|
|
#include "Arduino.h"
|
|
#include "Wire.h"
|
|
|
|
uint32_t g_ms = 1000;
|
|
bool g_bus_held = false;
|
|
bool g_bus_busy = false; // another user holds I2C_2 (the codec) past the wait
|
|
int g_acquires = 0;
|
|
Aw86224 g_aw;
|
|
TwoWire Wire1;
|
|
|
|
#define HAS_TDISPLAY_P4 1
|
|
#include "../variants/tdisplay_p4/P4I2c1.h"
|
|
TwoWire* p4I2c1Acquire(P4I2c1Route route, uint32_t) {
|
|
assert(route == P4_I2C1_AUDIO); // the motor shares the codec's pins
|
|
assert(!g_bus_held);
|
|
++g_acquires;
|
|
if (g_bus_busy) return nullptr;
|
|
g_bus_held = true;
|
|
return &Wire1;
|
|
}
|
|
void p4I2c1Release() { assert(g_bus_held); g_bus_held = false; }
|
|
|
|
#include "../variants/tdisplay_p4/P4Haptic.cpp"
|
|
|
|
static const uint16_t BASE = 0x0800;
|
|
|
|
static bool sramHoldsTable() {
|
|
return memcmp(g_aw.sram + BASE, kP4HapticWaveform, sizeof kP4HapticWaveform) == 0;
|
|
}
|
|
static int lastWrite(uint8_t reg) {
|
|
for (size_t i = g_aw.log.size(); i-- > 0;) if (g_aw.log[i].first == reg) return g_aw.log[i].second;
|
|
return -1;
|
|
}
|
|
|
|
static void scenarioFreshChip() {
|
|
// First use on a chip with an empty SRAM: probe, configure, load the waveform, verify.
|
|
assert(p4HapticReady());
|
|
assert(sramHoldsTable());
|
|
assert(g_aw.sram_writes == (int)sizeof kP4HapticWaveform);
|
|
assert((g_aw.reg[0x44] & 0x03) == 0x02); // 12 kHz
|
|
assert((g_aw.reg[0x2D] & 0x0F) == 0x08 && g_aw.reg[0x2E] == 0x00); // RAM base 0x0800
|
|
const uint16_t ae = BASE >> 1, af = BASE - (BASE >> 2);
|
|
assert(g_aw.reg[0x2F] == (uint8_t)(((ae >> 8) << 4) | ((af >> 8) & 0x0F)));
|
|
assert(g_aw.reg[0x30] == (uint8_t)(ae & 0xFF) && g_aw.reg[0x31] == (uint8_t)(af & 0xFF));
|
|
assert((g_aw.reg[0x43] & 0x08) == 0); // RAM init (SRAM access) left off
|
|
assert(!g_bus_held);
|
|
// Idempotent: a second call touches nothing.
|
|
const int t = g_aw.transactions;
|
|
assert(p4HapticReady() && g_aw.transactions == t);
|
|
|
|
// One buzz: 120 ms at the default gain, slot 1 = waveform 1 looped, auto brake, then stopped.
|
|
g_aw.log.clear();
|
|
const uint32_t t0 = g_ms;
|
|
p4HapticBuzz(120);
|
|
assert(g_aw.played_ms.size() == 1 && g_aw.played_ms[0] == 120);
|
|
assert(g_aw.played_gain[0] == P4_HAPTIC_GAIN_DEFAULT);
|
|
assert(lastWrite(0x0A) == 1 && lastWrite(0x0B) == 0); // WAVCFG1 = wave 1, WAVCFG2 = end
|
|
assert((lastWrite(0x12) & 0xF0) == 0xF0); // loop forever
|
|
assert((lastWrite(0x08) & 0x07) == 0x04); // RAM mode + auto brake
|
|
assert((lastWrite(0x49) & 0x40) == 0x40);
|
|
assert(!g_aw.playing && (g_aw.reg[0x44] & 0x40) == 0); // stopped, standby force released
|
|
assert(g_ms - t0 >= 120 && g_ms - t0 < 120 + 20);
|
|
assert(!g_bus_held);
|
|
|
|
// Gain passes through; a second buzz is a second play.
|
|
p4HapticBuzz(60, 0x50);
|
|
assert(g_aw.played_ms.size() == 2 && g_aw.played_ms[1] == 60 && g_aw.played_gain[1] == 0x50);
|
|
// Nothing to do for 0 or negative durations: no bus traffic at all.
|
|
const int tr = g_aw.transactions;
|
|
p4HapticBuzz(0); p4HapticBuzz(-5);
|
|
assert(g_aw.transactions == tr && g_aw.played_ms.size() == 2);
|
|
}
|
|
|
|
static void scenarioRetainedSram() {
|
|
// The chip kept its SRAM across a P4 reset: verified, not rewritten.
|
|
memcpy(g_aw.sram + BASE, kP4HapticWaveform, sizeof kP4HapticWaveform);
|
|
assert(p4HapticReady());
|
|
assert(g_aw.sram_writes == 0);
|
|
}
|
|
|
|
static void scenarioOneByteDiffers() {
|
|
memcpy(g_aw.sram + BASE, kP4HapticWaveform, sizeof kP4HapticWaveform);
|
|
g_aw.sram[BASE + 300] ^= 0xFF;
|
|
assert(p4HapticReady());
|
|
assert(sramHoldsTable() && g_aw.sram_writes == (int)sizeof kP4HapticWaveform);
|
|
}
|
|
|
|
static void scenarioAbsent() {
|
|
// No chip at 0x58: not ready, and after the first try the driver stays off the bus.
|
|
g_aw.present = false;
|
|
assert(!p4HapticReady());
|
|
const int a = g_acquires;
|
|
p4HapticBuzz(120);
|
|
assert(!p4HapticReady());
|
|
assert(g_acquires == a && g_aw.played_ms.empty());
|
|
}
|
|
|
|
static void scenarioBusBusy() {
|
|
// The codec holds the bus past the 200 ms wait: no start, so nothing to stop.
|
|
assert(p4HapticReady());
|
|
g_bus_busy = true;
|
|
const uint32_t t0 = g_ms;
|
|
p4HapticBuzz(120);
|
|
assert(g_aw.played_ms.empty() && !g_aw.playing);
|
|
assert(g_ms == t0); // it did not sit out the duration either
|
|
}
|
|
|
|
static void scenarioStopNeverIdles() {
|
|
// A chip that never reports standby: three stop attempts, the bus released, no hang.
|
|
assert(p4HapticReady());
|
|
g_aw.stop_ignored = true;
|
|
p4HapticBuzz(50);
|
|
assert(!g_bus_held);
|
|
}
|
|
|
|
static int run(const char* name, void (*fn)()) {
|
|
fflush(stdout);
|
|
const pid_t pid = fork();
|
|
if (pid == 0) { fn(); _exit(0); }
|
|
int st = 0;
|
|
waitpid(pid, &st, 0);
|
|
const bool ok = WIFEXITED(st) && WEXITSTATUS(st) == 0;
|
|
printf(" %-24s %s\n", name, ok ? "ok" : "FAILED");
|
|
return ok ? 0 : 1;
|
|
}
|
|
|
|
int main() {
|
|
// The waveform table's own header: version, then 4 {start, end} pairs from 0x0811,
|
|
// ending at the table's last byte.
|
|
assert(kP4HapticWaveform[0] == 0x55);
|
|
assert(((kP4HapticWaveform[1] << 8) | kP4HapticWaveform[2]) == BASE + 1 + 4 * 4);
|
|
assert(((kP4HapticWaveform[15] << 8) | kP4HapticWaveform[16]) == BASE + sizeof kP4HapticWaveform - 1);
|
|
|
|
int fails = 0;
|
|
fails += run("fresh chip + buzz", scenarioFreshChip);
|
|
fails += run("retained SRAM", scenarioRetainedSram);
|
|
fails += run("one SRAM byte differs", scenarioOneByteDiffers);
|
|
fails += run("chip absent", scenarioAbsent);
|
|
fails += run("bus busy", scenarioBusBusy);
|
|
fails += run("stop never idles", scenarioStopNeverIdles);
|
|
if (fails) { printf("test_p4_haptic: %d FAILED\n", fails); return 1; }
|
|
printf("test_p4_haptic: ok\n");
|
|
return 0;
|
|
}
|