Skip to content

Commit

Permalink
Initial support for NTX + MTK + Kaleido devices (#1779)
Browse files Browse the repository at this point in the history
* Bump LuaJIT to 20240422 (there's a potentially interesting 20-year old memory leak fixed)
* Update FBInk & KoboUSBMS to handle the new devices properly
* Update MTK headers for whatever we could glean of the new stuff
* Add dedicated refresh modes for Kaleido-specific waveform modes (basically, once for images, and one for text/highlights).
* Implemented a halfway sensible wait_for logic for those new devices
* Special-cased the Elipsa 2E because it behaves differently.
* "Fix" nightmode on the Elipsa 2E (thanks to #1768).
* And, as usual, workaround a few driver bugs in the process (as far as possible) ;p
  • Loading branch information
NiLuJe committed May 9, 2024
1 parent 59ff187 commit 9795451
Show file tree
Hide file tree
Showing 8 changed files with 144 additions and 35 deletions.
6 changes: 6 additions & 0 deletions ffi-cdecl/include/mtk-kobo.h
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,9 @@

// NOTE: That confusing `enable_night_mode_by_wfm` mapping is never actually used, unless you enable it via the debug procfs knob.
// FWIW, lab126 does GC16 => GCK16 & GLR16 => GLKW16...
// NOTE: Like it's always been the case on lab126 devices, on newer devices (i.e., not the Elipsa 2E),
// anything involving REAGL (i.e., GLR16, GLKW16 & GLRC16) is *always* paired with FULL.
// Likewise, stuff tweaked for Kaleido (GCC16 & GLRC16) is also *always* paired with FULL.
enum HWTCON_WAVEFORM_MODE_ENUM
{
// Matches MXCFB
Expand All @@ -70,6 +73,9 @@ enum HWTCON_WAVEFORM_MODE_ENUM
HWTCON_WAVEFORM_MODE_A2 = 6,
HWTCON_WAVEFORM_MODE_GCK16 = 8,
HWTCON_WAVEFORM_MODE_GLKW16 = 9, // AKA. GCKW16; REAGL DARK
// Introduced with Kaleido devices
HWTCON_WAVEFORM_MODE_GCC16 = 10, // Used for images on color panels.
HWTCON_WAVEFORM_MODE_GLRC16 = 11, // Used for color highlights on text. (GLKW16 in dark mode).
// Matches MXCFB
HWTCON_WAVEFORM_MODE_AUTO = 257,
};
Expand Down
14 changes: 14 additions & 0 deletions ffi/framebuffer.lua
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,12 @@ end
function fb:refreshA2Imp(x, y, w, h, d)
return self:refreshFastImp(x, y, w, h, d)
end
function fb:refreshColorImp(x, y, w, h, d)
return self:refreshPartialImp(x, y, w, h, d)
end
function fb:refreshColorTextImp(x, y, w, h, d)
return self:refreshPartialImp(x, y, w, h, d)
end
function fb:refreshWaitForLastImp()
-- default is NOP
end
Expand Down Expand Up @@ -238,6 +244,14 @@ function fb:refreshA2(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshA2Imp(x, y, w, h, d)
end
function fb:refreshColor(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshColorImp(x, y, w, h, d)
end
function fb:refreshColorText(x, y, w, h, d)
x, y, w, h = self:calculateRealCoordinates(x, y, w, h)
return self:refreshColorTextImp(x, y, w, h, d)
end
function fb:refreshWaitForLast()
return self:refreshWaitForLastImp()
end
Expand Down
146 changes: 116 additions & 30 deletions ffi/framebuffer_mxcfb.lua
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,16 @@ local framebuffer = {
mech_poweron = nil,
mech_wait_update_complete = nil,
mech_wait_update_submission = nil,
waveform_partial = nil,
wait_for_submission_before = false,
wait_for_submission_after = false,
waveform_a2 = nil,
waveform_fast = nil,
waveform_ui = nil,
waveform_partial = nil,
waveform_flashui = nil,
waveform_full = nil,
waveform_fast = nil,
waveform_color = nil,
waveform_color_reagl = nil,
waveform_reagl = nil,
waveform_night = nil,
waveform_flashnight = nil,
Expand All @@ -33,6 +38,8 @@ local framebuffer = {
dont_wait_for_marker = nil,
-- Set by frontend to 3 on Pocketbook Color Lux that refreshes based on bytes (not based on pixel)
refresh_pixel_size = 1,
-- Used to enforce an alignment constraint on devices with quirky drivers
alignment_constraint = nil,

-- We recycle ffi cdata
marker_data = nil,
Expand All @@ -55,21 +62,16 @@ end

-- Returns true if waveform_mode arg matches the UI waveform mode for the current device
-- NOTE: This is to avoid explicit comparison against device-specific waveform constants in mxc_update()
-- Here, it's because of the Kindle-specific WAVEFORM_MODE_GC16_FAST
function framebuffer:_isUIWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_ui
end

-- Returns true if waveform_mode arg matches the FlashUI waveform mode for the current device
-- NOTE: This is to avoid explicit comparison against device-specific waveform constants in mxc_update()
-- Here, it's because of the Kindle-specific WAVEFORM_MODE_GC16_FAST
function framebuffer:_isFlashUIWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_flashui
end

-- Returns true if waveform_mode arg matches the REAGL waveform mode for the current device
-- NOTE: This is to avoid explicit comparison against device-specific waveform constants in mxc_update()
-- Here, it's Kindle's various WAVEFORM_MODE_REAGL vs. Kobo's WAVEFORM_MODE_REAGLD
function framebuffer:_isREAGLWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_reagl
end
Expand All @@ -80,12 +82,30 @@ function framebuffer:_isNightREAGL()
end

-- Returns true if waveform_mode arg matches the partial waveform mode for the current device
-- NOTE: This is to avoid explicit comparison against device-specific waveform constants in mxc_update()
-- Here, because of REAGL or device-specific quirks.
function framebuffer:_isPartialWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_partial
end

-- Returns true if waveform_mode arg matches the fast waveform mode for the current device
function framebuffer:_isFastWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_fast
end

-- Returns true if waveform_mode arg does *NOT* match the A2 or fast waveform mode for the current device
function framebuffer:_isNotFastWaveFormMode(waveform_mode)
return waveform_mode ~= self.waveform_a2 and waveform_mode ~= self.waveform_fast
end

-- Returns true if waveform_mode arg does *NOT* match the A2 waveform mode for the current device
function framebuffer:_isNotA2WaveFormMode(waveform_mode)
return waveform_mode ~= self.waveform_a2
end

-- Returns true if waveform_mode arg matches a Kaleido-specific waveform mode for the current device
function framebuffer:_isKaleidoWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_color or waveform_mode == self.waveform_color_reagl
end

-- Returns the device-specific nightmode waveform mode
function framebuffer:_getNightWaveFormMode()
return self.waveform_night
Expand Down Expand Up @@ -261,7 +281,7 @@ local function mxc_update(fb, ioc_cmd, ioc_data, is_flashing, waveform_mode, x,
-- (Sidebar: this is probably a kernel issue, the EPDC driver is responsible for the alignment fixup,
-- c.f., epdc_process_update @ drivers/video/fbdev/mxc/mxc_epdc_v2_fb.c on a Kobo Mk. 7 kernel...).
-- And regardless of alignment constraints, make sure the rectangle is strictly bounded inside the screen.
x, y, w, h = bb:getBoundedRect(x, y, w, h, dither and 8)
x, y, w, h = bb:getBoundedRect(x, y, w, h, dither and 8 or fb.alignment_constraint)
-- The ioctl operates in the native rotation, so, make sure we rotate the rectangle as needed
x, y, w, h = bb:getPhysicalRect(x, y, w, h)

Expand Down Expand Up @@ -294,13 +314,14 @@ local function mxc_update(fb, ioc_cmd, ioc_data, is_flashing, waveform_mode, x,
-- * true FULL update,
-- * GC16_FAST update (i.e., popping-up a menu),
-- then wait for submission of previous marker first.
-- NOTE: This is mainly used on Kindles
local marker = fb.marker
-- NOTE: Technically, we might not always want to wait for *exactly* the previous marker
-- (we might actually want the one before that), but in the vast majority of cases, that's good enough,
-- and saves us a lot of annoying and hard-to-get-right heuristics anyway ;).
-- Make sure it's a valid marker, to avoid doing something stupid on our first update.
-- Also make sure we haven't already waited on this marker ;).
if fb.mech_wait_update_submission
if fb.wait_for_submission_before
and (is_flashing or fb:_isUIWaveFormMode(waveform_mode))
and (marker ~= 0 and marker ~= fb.dont_wait_for_marker) then
fb.debug("refresh: wait for submission of (previous) marker", marker)
Expand Down Expand Up @@ -363,10 +384,10 @@ local function mxc_update(fb, ioc_cmd, ioc_data, is_flashing, waveform_mode, x,
end
end

-- Handle REAGL promotion...
-- Handle promotion to FULL for the specific waveform modes that require it...
-- NOTE: We need to do this here, because we rely on the pre-promotion actual is_flashing in previous heuristics.
if fb:_isREAGLWaveFormMode(waveform_mode) then
-- NOTE: REAGL updates always need to be full.
if fb:_isREAGLWaveFormMode(waveform_mode) or fb:_isKaleidoWaveFormMode(waveform_mode) then
-- NOTE: REAGL & Kaleido updates (almost) always need to be full.
ioc_data.update_mode = C.UPDATE_MODE_FULL
end

Expand Down Expand Up @@ -397,6 +418,33 @@ local function mxc_update(fb, ioc_cmd, ioc_data, is_flashing, waveform_mode, x,
-- And make sure we won't wait for it again, in case the next refresh trips one of our wait_for_* heuristics ;).
fb.dont_wait_for_marker = marker
end

-- NOTE: For PARTIAL, as long as they're not DU/A2, we'll instead just wait for the update's submission, if that's available.
-- NOTE: This is mainly used on (MTK) Kobos.
if fb.wait_for_submission_after
and ioc_data.update_mode == C.UPDATE_MODE_PARTIAL
and fb:_isNotFastWaveFormMode(waveform_mode)
and (marker ~= 0 and marker ~= fb.dont_wait_for_marker) then
fb.debug("refresh: wait for submission of marker", marker)
if fb:mech_wait_update_submission(marker) == -1 then
local err = ffi.errno()
fb.debug("MXCFB_WAIT_FOR_UPDATE_SUBMISSION ioctl failed:", ffi.string(C.strerror(err)))
end
end

-- NOTE: Jotting down some notes about the flickering of disappearing highlights on Kobo MTK:
-- * Waiting for complete *before* Fast helps when we do HL -> UnHL -> UI
-- * Waiting for complete *before* UI helps when we do HL -> UI (i.e., when we elide the UnHL)
-- Both are... pretty bad for latency, though.
-- * Waiting for submission *after* Fast doesn't help.
-- * Waiting for complete *after* Fast helps everywhere, but obviously murders latency, too.
-- FWIW, Nickel does (using AUTO everywhere): Complete -> HL -> Submission -> UI -> Complete -> UnHL -> Submission
-- * Doing something similar with our usual DU + AUTO combos doesn't help.
-- NOTE: Much like on lab126 MTK, bumping UIManager:yieldToEPDC to something along the lines of 175ms helps,
-- but is also obviously not desirable latency-wise.
-- Interestingly enough, lab126 devices are affected the *other* way around:
-- they tend to optimize *out* the highlight, instead of having trouble dealing with the unhighlight...
-- In a fun twist, the flickers are gone in night mode -_-".
end

local function refresh_k51(fb, is_flashing, waveform_mode, x, y, w, h)
Expand Down Expand Up @@ -626,27 +674,19 @@ local function refresh_kobo_mk7(fb, is_flashing, waveform_mode, x, y, w, h, dith
end

local function refresh_kobo_mtk(fb, is_flashing, waveform_mode, x, y, w, h, dither)
-- FIXME: Enable this, assuming it actually fits our purpose.
-- Enable the appropriate flag when requesting an any->2bit update, provided we're not dithering.
-- NOTE: See FBInk note about DITHER + MONOCHROME
--[[
if waveform_mode == C.HWTCON_WAVEFORM_MODE_DU and not dither then
fb.update_data.flags = C.HWTCON_FLAG_FORCE_A2_OUTPUT
else
fb.update_data.flags = 0
end
--]]
fb.update_data.flags = 0

-- Did we request HW dithering?
if dither and fb.device:canHWDither() then
fb.update_data.flags = bor(fb.update_data.flags, C.HWTCON_FLAG_USE_DITHERING)

if waveform_mode == C.HWTCON_WAVEFORM_MODE_DU or waveform_mode == C.HWTCON_WAVEFORM_MODE_A2 then
-- NOTE: We only use A2 for the virtual keyboard, and Nickel forgoes dithering in that context.
if waveform_mode == C.HWTCON_WAVEFORM_MODE_DU then
fb.update_data.dither_mode = C.HWTCON_FLAG_USE_DITHERING_Y8_Y1_S
else
fb.update_data.dither_mode = C.HWTCON_FLAG_USE_DITHERING_Y8_Y4_S
end
else
fb.update_data.flags = 0
fb.update_data.dither_mode = 0
end

return mxc_update(fb, C.HWTCON_SEND_UPDATE, fb.update_data, is_flashing, waveform_mode, x, y, w, h, dither)
Expand Down Expand Up @@ -730,6 +770,16 @@ function framebuffer:refreshA2Imp(x, y, w, h, dither)
self:mech_refresh(false, self.waveform_a2, x, y, w, h, dither)
end

function framebuffer:refreshColorImp(x, y, w, h, dither)
self.debug("refresh: color", x, y, w, h, dither)
self:mech_refresh(false, self.waveform_color, x, y, w, h, dither)
end

function framebuffer:refreshColorTextImp(x, y, w, h, dither)
self.debug("refresh: color text", x, y, w, h, dither)
self:mech_refresh(false, self.waveform_color_reagl, x, y, w, h, dither)
end

function framebuffer:refreshWaitForLastImp()
if self.mech_wait_update_complete and self.dont_wait_for_marker ~= self.marker then
self.debug("refresh: waiting for previous update", self.marker)
Expand All @@ -754,6 +804,8 @@ function framebuffer:init()
self.mech_refresh = refresh_k51
self.mech_wait_update_complete = kindle_pearl_mxc_wait_for_update_complete
self.mech_wait_update_submission = kindle_mxc_wait_for_update_submission
-- Kindles wait for submission of the *previous* marker
self.wait_for_submission_before = true

self.waveform_a2 = C.WAVEFORM_MODE_A2
self.waveform_fast = C.WAVEFORM_MODE_DU
Expand Down Expand Up @@ -924,21 +976,55 @@ function framebuffer:init()
if self.device:isMTK() then
self.mech_refresh = refresh_kobo_mtk
self.mech_wait_update_complete = kobo_mtk_wait_for_update_complete
-- FIXME: Enable this, assuming it behaves...
--self.mech_wait_update_submission = kobo_mtk_wait_for_update_submission
self.mech_wait_update_submission = kobo_mtk_wait_for_update_submission
-- Kobos wait for submission of the *just sent* marker
self.wait_for_submission_after = true

-- Regardless of dithering, there appears to be an off-by-one issue somewhere in the driver,
-- so partial refreshes at exact coordinates will sometimes be cut-off one pixel short...
-- That can obviously lead to leftover stale content visible on screen,
-- so, just enforce larger refresh regions on our side...
self.alignment_constraint = 8

self.waveform_a2 = C.HWTCON_WAVEFORM_MODE_A2
self.waveform_fast = C.HWTCON_WAVEFORM_MODE_DU
self.waveform_ui = C.HWTCON_WAVEFORM_MODE_AUTO
self.waveform_flashui = self.waveform_ui
self.waveform_full = C.HWTCON_WAVEFORM_MODE_GC16
-- REAGL is *always* available
self.waveform_partial = C.HWTCON_WAVEFORM_MODE_GLR16
self.waveform_reagl = C.HWTCON_WAVEFORM_MODE_GLR16
self.waveform_partial = self.waveform_reagl
-- Eclipse waveform modes are *always* available
self.waveform_night = C.HWTCON_WAVEFORM_MODE_GLKW16
self.waveform_flashnight = C.HWTCON_WAVEFORM_MODE_GCK16
self.night_is_reagl = true
-- Kaleido waveform modes are only ever used *conditionally*
self.waveform_color = C.HWTCON_WAVEFORM_MODE_GCC16
self.waveform_color_reagl = C.HWTCON_WAVEFORM_MODE_GLRC16

self.mech_poweron = kobo_mtk_wakeup_epdc

-- The Elipsa 2E was the first MTK device, and it does... a few things differently :/.
if self.device.model == "Kobo_condor" then
-- It doesn't use WAIT_FOR_UPDATE_SUBMISSION
self.wait_for_submission_after = false
-- It does *NOT* enforce FULL on REAGL updates
self.waveform_reagl = nil
self.night_is_reagl = false
-- For some mysterious reason, Eclipse waveform modes are completely broken outside of 32bpp.
-- (This is no longer a concern on later devices, as they no longer allow switching to 8bpp at all).
if self.fb_bb ~= 32 then
-- Trust @katadelos and let AUTO figure it out (https://github.com/koreader/koreader-base/pull/1768)
self.waveform_night = C.HWTCON_WAVEFORM_MODE_AUTO
self.waveform_flashnight = C.HWTCON_WAVEFORM_MODE_AUTO
end
end

-- Disable Kaleido refresh imps when it's unsupported
if not self.device:hasColorScreen() then
self.refreshColorImp = self.refreshPartialImp
self.refreshColorTextImp = self.refreshPartialImp
end
end

local bypass_wait_for = self:getMxcWaitForBypass()
Expand Down
5 changes: 3 additions & 2 deletions ffi/framebuffer_sunxi.lua
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,12 @@ local framebuffer = {
mech_poweron = nil,
mech_wait_update_complete = nil,
mech_wait_update_submission = nil,
waveform_partial = nil,
waveform_a2 = nil,
waveform_fast = nil,
waveform_ui = nil,
waveform_partial = nil,
waveform_flashui = nil,
waveform_full = nil,
waveform_fast = nil,
waveform_reagl = nil,
waveform_night = nil,
waveform_flashnight = nil,
Expand Down
2 changes: 2 additions & 0 deletions ffi/mxcfb_kobo_h.lua
Original file line number Diff line number Diff line change
Expand Up @@ -109,6 +109,8 @@ enum HWTCON_WAVEFORM_MODE_ENUM {
HWTCON_WAVEFORM_MODE_A2 = 6,
HWTCON_WAVEFORM_MODE_GCK16 = 8,
HWTCON_WAVEFORM_MODE_GLKW16 = 9,
HWTCON_WAVEFORM_MODE_GCC16 = 10,
HWTCON_WAVEFORM_MODE_GLRC16 = 11,
HWTCON_WAVEFORM_MODE_AUTO = 257,
};
enum hwtcon_dithering_mode {
Expand Down
2 changes: 1 addition & 1 deletion thirdparty/fbink/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ endif()
ko_write_gitclone_script(
GIT_CLONE_SCRIPT_FILENAME
https://github.com/NiLuJe/FBInk.git
1a989b30a195ca240a3cf37f9de61b4b3c7e891c
a60c6c814b910d24be5b9be43d1b4514a8ef6aef
${SOURCE_DIR}
)

Expand Down
2 changes: 1 addition & 1 deletion thirdparty/kobo-usbms/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ endif()
ko_write_gitclone_script(
GIT_CLONE_SCRIPT_FILENAME
https://github.com/koreader/KoboUSBMS.git
v1.3.9
ef5b3a4f13565b0b282ce377517b6da252884597
${SOURCE_DIR}
)

Expand Down
2 changes: 1 addition & 1 deletion thirdparty/luajit/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,7 @@ list(APPEND BUILD_CMD COMMAND ${KO_MAKE_RECURSIVE} ${BUILD_ARGS})
ko_write_gitclone_script(
GIT_CLONE_SCRIPT_FILENAME
https://github.com/LuaJIT/LuaJIT
d06beb0480c5d1eb53b3343e78063950275aa281
5790d253972c9d78a0c2aece527eda5b134bbbf7
${SOURCE_DIR}
)

Expand Down

0 comments on commit 9795451

Please sign in to comment.