forked from koreader/koreader-base
-
Notifications
You must be signed in to change notification settings - Fork 0
/
framebuffer_sunxi.lua
346 lines (286 loc) · 13.5 KB
/
framebuffer_sunxi.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
local bit = require("bit")
local ffi = require("ffi")
local ffiUtil = require("ffi/util")
local C = ffi.C
require("ffi/posix_h")
local band = bit.band
local bor = bit.bor
local bnot = bit.bnot
local framebuffer = {
-- pass device object here for proper model detection:
device = nil,
mech_poweron = nil,
mech_wait_update_complete = nil,
mech_wait_update_submission = nil,
waveform_partial = nil,
waveform_ui = nil,
waveform_flashui = nil,
waveform_full = nil,
waveform_fast = nil,
waveform_reagl = nil,
waveform_night = nil,
waveform_flashnight = nil,
mech_refresh = nil,
-- used to avoid waiting twice on the same marker
dont_wait_for_marker = nil,
-- We recycle ffi cdata
marker_data = nil,
g2d_rota = nil,
area = nil,
update = nil,
ioc_cmd = nil,
}
--[[ refresh list management: --]]
-- 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 disp_update()
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 disp_update()
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 disp_update()
function framebuffer:_isREAGLWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_reagl
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 disp_update()
function framebuffer:_isPartialWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_partial
end
-- Returns the device-specific nightmode waveform mode
function framebuffer:_getNightWaveFormMode()
return self.waveform_night
end
-- Returns the device-specific flashing nightmode waveform mode
function framebuffer:_getFlashNightWaveFormMode()
return self.waveform_flashnight
end
-- Returns true if w & h are equal or larger than our visible screen estate (i.e., we asked for a full-screen update)
function framebuffer:_isFullScreen(w, h)
-- NOTE: fb:getWidth() & fb:getHeight() return the viewport size, but obey rotation, which means we can't rely on them directly.
-- fb:getScreenWidth() & fb:getScreenHeight return the full screen size, without the viewport, and in the default rotation, which doesn't help either.
-- Settle for getWidth() & getHeight() w/ rotation handling, like what bb:getPhysicalRect() does...
if band(self:getRotationMode(), 1) == 1 then w, h = h, w end
if w >= self:getWidth() and h >= self:getHeight() then
return true
else
return false
end
end
--[[ handlers for the power management API of the eink driver --]]
local function kobo_sunxi_wakeup_epdc()
ffiUtil.writeToSysfs("lcd0", "/sys/kernel/debug/dispdbg/name")
ffiUtil.writeToSysfs("enable", "/sys/kernel/debug/dispdbg/command")
ffiUtil.writeToSysfs("1", "/sys/kernel/debug/dispdbg/start")
end
--[[ handlers for the wait API of the eink driver --]]
-- Kobo's Mk8 DISP_EINK_WAIT_FRAME_SYNC_COMPLETE
local function kobo_sunxi_wait_for_update_complete(fb, marker)
-- Wait for a specific update to be completed
fb.ioc_cmd.wait_for.frame_id = marker
return C.ioctl(fb.fd, C.DISP_EINK_WAIT_FRAME_SYNC_COMPLETE, fb.ioc_cmd)
end
-- Stub version that simply sleeps for 1ms
local function stub_wait_for_update_complete()
return C.usleep(1000)
end
--[[ refresh functions ]]--
-- NOTE: Heavily based on framebuffer_mxcfb's mxc_update ;)
local function disp_update(fb, ioc_cmd, ioc_data, no_merge, is_flashing, waveform_mode, waveform_info, x, y, w, h)
local bb = fb.full_bb or fb.bb
-- If we're fresh off a rotation, make it full-screen and no_merge to avoid layer blending glitches...
if fb._just_rotated then
x = 0
y = 0
w = bb:getWidth()
h = bb:getHeight()
fb._just_rotated = nil
no_merge = true
end
-- Sanitize refresh rect
x, y, w, h = bb:getBoundedRect(x, y, w, h)
x, y, w, h = bb:getPhysicalRect(x, y, w, h)
-- Discard empty or bogus regions
if w <= 1 or h <= 1 then
fb.debug("discarding bogus refresh region, w:", w, "h:", h)
return
end
-- Wake the EPDC up manually, in the vague hope it'll help with missed refreshes after a wakeup from standby...
if fb.mech_poweron then
fb:mech_poweron()
end
-- We've got the final region, update the area_info struct
ioc_data.area.x_top = x
ioc_data.area.y_top = y
ioc_data.area.x_bottom = x + w - 1
ioc_data.area.y_bottom = y + h - 1
-- NOTE: If we're trying to send a:
-- * REAGL update,
-- * GC16 update,
-- * Full-screen, flashing UI update,
-- then wait for completion of previous marker first.
local marker = fb.marker_data[0]
-- Make sure the marker is valid, too.
if (fb:_isREAGLWaveFormMode(waveform_mode)
or waveform_mode == C.EINK_GC16_MODE
or (is_flashing and fb:_isFlashUIWaveFormMode(waveform_mode) and fb:_isFullScreen(w, h)))
and fb.mech_wait_update_complete
and (marker ~= 0 and marker ~= fb.dont_wait_for_marker) then
fb.debug("refresh: wait for completion of (previous) marker", marker)
if fb:mech_wait_update_complete(marker) == -1 then
local err = ffi.errno()
fb.debug("DISP_EINK_WAIT_FRAME_SYNC_COMPLETE ioctl failed:", ffi.string(C.strerror(err)))
end
end
-- Handle night mode shenanigans
if fb.night_mode then
-- Enforce a nightmode-specific mode to limit ghosting, where appropriate (i.e., partial & flashes).
if fb:_isPartialWaveFormMode(waveform_mode) then
waveform_mode = fb:_getNightWaveFormMode()
waveform_info = band(waveform_info, bnot(C.EINK_REGAL_MODE))
elseif waveform_mode == C.EINK_GC16_MODE or is_flashing then
waveform_mode = fb:_getFlashNightWaveFormMode()
waveform_info = band(waveform_info, bnot(C.EINK_REGAL_MODE))
end
end
-- Handle the !flashing flag
if not is_flashing and waveform_mode ~= C.EINK_AUTO_MODE then
-- For some reason, AUTO shouldn't specify PARTIAL...
-- (it trips the unknown mode warning, which falls back to... plain AUTO ;)).
waveform_info = bor(waveform_info, C.EINK_PARTIAL_MODE)
end
-- Make sure we actually flash by bypassing the "working buffer was untouched" memcmp "optimization"...
-- NOTE: This appeared in the Sage kernel on FW 4.29.
-- NOTE: no_merge is always true if is_flashing is enabled, but we also have [ui] and [partial] modes
-- that will request NO_MERGE without flashing, to avoid some more kernel bugs around collision handling...
if no_merge then
waveform_info = bor(waveform_info, C.EINK_NO_MERGE)
end
-- And finally bake mode + info into the update_mode bitmask
ioc_data.update_mode = bor(waveform_mode, waveform_info)
-- Recap the actual details of the ioctl, vs. what UIManager asked for...
fb.debug(string.format("disp_update: %ux%u region @ (%u, %u) (WFM: %u [flash: %s])", w, h, x, y, waveform_mode, is_flashing))
if C.ioctl(fb.fd, ioc_cmd, ioc_data) == -1 then
local err = ffi.errno()
fb.debug("DISP_EINK_UPDATE2 ioctl failed:", ffi.string(C.strerror(err)))
end
-- NOTE: We want to fence off FULL updates.
-- c.f., framebuffer_mxcfb for more details.
-- On sunxi, the actual update calls will block for sensibly longer than on mxcfb,
-- so these are likely to always return immediately.
if is_flashing and fb.mech_wait_update_complete then
marker = fb.marker_data[0]
fb.debug("refresh: wait for completion of marker", marker)
if fb:mech_wait_update_complete(marker) == -1 then
local err = ffi.errno()
fb.debug("DISP_EINK_WAIT_FRAME_SYNC_COMPLETE ioctl failed:", ffi.string(C.strerror(err)))
end
-- 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
end
local function refresh_kobo_sunxi(fb, no_merge, is_flashing, waveform_mode, x, y, w, h)
-- Store the auxiliary update flags in a separate bitmask we'll bake in later,
-- as it makes identifying waveform modes easier in disp_update...
local update_info = 0
if waveform_mode == C.EINK_GLR16_MODE or waveform_mode == C.EINK_GLD16_MODE then
update_info = bor(update_info, C.EINK_REGAL_MODE)
end
--[[
-- NOTE: Unlike on mxcfb, this isn't HW assisted, this just uses the "simple" Y8->Y1 dither algorithm...
-- As such, given the use-case for A2 (or DU, for that matter), this is wholly counter-productive ;).
if waveform_mode == C.EINK_DU_MODE then
update_info = bor(update_info, C.EINK_MONOCHROME)
end
--]]
return disp_update(fb, C.DISP_EINK_UPDATE2, fb.update, no_merge, is_flashing, waveform_mode, update_info, x, y, w, h)
end
--[[ framebuffer API ]]--
function framebuffer:refreshPartialImp(x, y, w, h, dither)
self.debug("refresh: partial", x, y, w, h, dither)
self:mech_refresh(false, false, self.waveform_partial, x, y, w, h, dither)
end
function framebuffer:refreshNoMergePartialImp(x, y, w, h, dither)
self.debug("refresh: no-merge partial w/ flash", x, y, w, h, dither)
self:mech_refresh(true, false, self.waveform_partial, x, y, w, h, dither)
end
function framebuffer:refreshFlashPartialImp(x, y, w, h, dither)
self.debug("refresh: partial w/ flash", x, y, w, h, dither)
self:mech_refresh(true, true, self.waveform_partial, x, y, w, h, dither)
end
function framebuffer:refreshUIImp(x, y, w, h, dither)
self.debug("refresh: ui-mode", x, y, w, h, dither)
self:mech_refresh(false, false, self.waveform_ui, x, y, w, h, dither)
end
function framebuffer:refreshNoMergeUIImp(x, y, w, h, dither)
self.debug("refresh: no-merge ui-mode w/ flash", x, y, w, h, dither)
self:mech_refresh(true, false, self.waveform_ui, x, y, w, h, dither)
end
function framebuffer:refreshFlashUIImp(x, y, w, h, dither)
self.debug("refresh: ui-mode w/ flash", x, y, w, h, dither)
self:mech_refresh(true, true, self.waveform_flashui, x, y, w, h, dither)
end
function framebuffer:refreshFullImp(x, y, w, h, dither)
self.debug("refresh: full", x, y, w, h, dither)
self:mech_refresh(true, true, self.waveform_full, x, y, w, h, dither)
end
function framebuffer:refreshFastImp(x, y, w, h, dither)
self.debug("refresh: fast", x, y, w, h, dither)
self:mech_refresh(false, false, self.waveform_fast, x, y, w, h, dither)
end
function framebuffer:refreshA2Imp(x, y, w, h, dither)
self.debug("refresh: A2", x, y, w, h, dither)
self:mech_refresh(false, false, self.waveform_a2, x, y, w, h, dither)
end
function framebuffer:refreshWaitForLastImp()
if self.mech_wait_update_complete and self.dont_wait_for_marker ~= self.marker_data[0] then
self.debug("refresh: waiting for previous update", self.marker_data[0])
self:mech_wait_update_complete(self.marker_data[0])
self.dont_wait_for_marker = self.marker_data[0]
end
end
function framebuffer:init()
framebuffer.parent.init(self)
if self.device:isKobo() then
require("ffi/sunxi_kobo_h")
self.mech_refresh = refresh_kobo_sunxi
self.mech_wait_update_complete = kobo_sunxi_wait_for_update_complete
-- NOTE: Nickel uses a mix of A2 & DU for the keyboard (A2 on keys, DU on the input field).
-- We use A2 & GL16 to avoid losing AA on the input field.
self.waveform_a2 = C.EINK_A2_MODE
self.waveform_fast = C.EINK_DU_MODE
self.waveform_ui = C.EINK_GL16_MODE
-- You can't make GL16 flash :/
self.waveform_flashui = C.EINK_GC16_MODE
self.waveform_full = C.EINK_GC16_MODE
self.waveform_partial = C.EINK_GLR16_MODE
self.waveform_night = C.EINK_GLK16_MODE
self.waveform_flashnight = C.EINK_GCK16_MODE
self.mech_poweron = kobo_sunxi_wakeup_epdc
local bypass_wait_for = self:getMxcWaitForBypass()
-- If the user (or a device cap check) requested bypassing the WAIT_FOR ioctls, do so.
if bypass_wait_for then
-- The stub implementation just fakes this ioctl by sleeping for a tiny amount of time instead... :/.
self.mech_wait_update_complete = stub_wait_for_update_complete
end
-- Keep our data structures around
self.marker_data = ffi.new("uint32_t[1]")
self.area = ffi.new("struct area_info")
self.update = ffi.new("sunxi_disp_eink_update2")
self.ioc_cmd = ffi.new("sunxi_disp_eink_ioctl")
-- Start by setting up stuff that will never change
self.update.area = self.area
self.update.layer_num = 1
self.update.lyr_cfg2 = self.layer -- From framebuffer_ion
self.update.frame_id = self.marker_data
self.update.rotate = self.g2d_rota -- From framebuffer_ion
self.update.cfa_use = 0
else
error("unknown device type")
end
end
return require("ffi/framebuffer_ion"):extend(framebuffer)