Skip to content

fix(uc8279-x4): HALF was a plain DU partial, not a clean - #56

Closed
alexanderbsd wants to merge 1 commit into
Free-Ink:mainfrom
alexanderbsd:fix/uc8279-half-refresh-is-not-a-clean
Closed

fix(uc8279-x4): HALF was a plain DU partial, not a clean#56
alexanderbsd wants to merge 1 commit into
Free-Ink:mainfrom
alexanderbsd:fix/uc8279-half-refresh-is-not-a-clean

Conversation

@alexanderbsd

Copy link
Copy Markdown

The bug

Uc8279X4Driver::displayStart() classified the refresh with:

const bool fast = (mode != RefreshMode::Full) && !_needFullClear && _oldPlaneValid;

RefreshMode::Half satisfies mode != Full, so it fell into the partial path and became indistinguishable from Fast — same DU CDI (0xD7), same forced temperature (0x5A), same PFS/gate-scan re-assert, same PTIN/PTL window. There was no code path on this driver where HALF did anything a FAST didn't.

That matters because HALF is what consumers use everywhere they believe they're cleaning the panel. In CrossPoint Reader that's the reader's periodic refresh cycle (ReaderUtils::displayWithRefreshCycle, default every 15 pages), every sleep-screen paint (all eight SleepActivity variants), and the manual power-button refresh. None of them cleaned anything here. A true FULL is only reached at boot/wake (requestResync) or from the control-centre refresh tile, so nothing scrubbed the panel during a session.

The failure mode is the one the UC8179 sibling already documents:

a white target paired with a white OLD plane selects WW and can look clean while leaving old text charge parked underneath; gray exposes that latent charge later

Latent charge is invisible at first and surfaces over the following seconds, then compounds because nothing ever clears it.

The fix

Give HALF the sibling's charge scrub (Uc8179Driver::displayStart): the GC-class setup with the OLD plane written as the target's complement, so every pixel is forced into a real transition cell and driven to its endpoint.

const bool absoluteClear = (mode == RefreshMode::Full) || _needFullClear || !_oldPlaneValid;
const bool scrub = !absoluteClear && (mode == RefreshMode::Half);
const bool fast = !absoluteClear && !scrub;

The complement-write already existed in this file for dark-background content; this just triggers it for HALF too.

One deliberate difference from the UC8179 sibling: _needFullClear and !_oldPlaneValid still route to the white-seeded absolute GC rather than the scrub, so the first paint after boot or wake keeps clearing whatever is physically on the panel. The sibling gives scrub priority there.

Testing

Confirmed on an Xteink X4 Pro, probe-promoted to UC8279 800x480, LUT_VER=0x02:

[XTDET] promoted SSD1677 -> UC8279 800x480 (LUT_VER=02)

Symptom before: a screen painted clean and dark, then residue of the previous content bloomed back in over a few seconds, worsening across a session — reader pages, menus, cover art, and most visibly the sleep screen. After: gone.

Builds clean in the consumer (crosspoint-reader) across x4pro, default (C3, X4+X3) and sticky.

Tradeoff worth flagging

HALF now costs a real waveform, so the periodic refresh cycle and sleep entry flash where they previously did not. That is the behaviour those callers already assumed they were getting, and it makes the consumer's "refresh frequency" setting meaningful on this panel — it previously had no effect at all.

If you'd prefer something gentler, the alternative is to keep HALF as a DU partial but write the OLD plane inverted (re-driving every pixel without the GC flash). I have no evidence that scrubs hard enough, and the sibling driver chose the strong version for this exact role, so I went with parity. Happy to switch if you'd rather.

I only have UC8279 LUT_VER=0x02 hardware — a second pair of eyes on 0x68/0x69 units would be welcome.

🤖 Generated with Claude Code

https://claude.ai/code/session_01VNSi6FM8Yebueuv31hFkqz

displayStart() classified the refresh with

    const bool fast = (mode != RefreshMode::Full) && !_needFullClear && _oldPlaneValid;

so RefreshMode::Half fell into the partial path and became
indistinguishable from Fast: same DU CDI (0xD7), same forced temperature
(0x5A), same PFS/gate-scan re-assert, same PTIN/PTL window.

Every caller that asks for HALF is asking for a clean. In CrossPoint that
is the reader's periodic refresh cycle, every sleep-screen paint, and the
manual power-button refresh. On this driver none of them cleaned
anything, and a true FULL is only reached at boot/wake or from the
control-centre refresh tile, so nothing scrubbed the panel during a
session.

The failure mode is the one the UC8179 sibling documents: an unchanged
pixel pairs (white, white), selects WW, and idles with whatever charge an
earlier page parked under it. That charge is invisible at first and
surfaces over the following seconds, then compounds because nothing ever
clears it.

Give HALF the sibling's charge scrub (Uc8179Driver::displayStart): the
GC-class setup with the OLD plane written as the target's COMPLEMENT, so
every pixel is forced into a real transition cell and driven to its
endpoint. _needFullClear and !_oldPlaneValid still route to the
white-seeded absolute GC, so the first paint after boot or wake keeps
clearing whatever is physically on the panel.

Confirmed on an Xteink X4 Pro (UC8279 800x480, LUT_VER=0x02): the
ghosting that built up across a reading session and bloomed into the
sleep screen is gone. HALF now costs a real waveform, so the periodic
refresh cycle and sleep entry flash where they previously did not — that
is the behaviour those callers already assumed they were getting.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VNSi6FM8Yebueuv31hFkqz
@itsthisjustin

Copy link
Copy Markdown
Contributor

Thanks for your work on this @alexanderbsd. It encouraged me to take another look at the driver and I worked with a user on reddit and implemented this fix plus a bunch of others in the process. Going to close as already solved but this pushed me to take another look at the driver so I appreciate it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants