/
framebuffer_mxcfb.lua
907 lines (800 loc) · 42.8 KB
/
framebuffer_mxcfb.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
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
local bit = require("bit")
local ffi = require("ffi")
local ffiUtil = require("ffi/util")
local BB = require("ffi/blitbuffer")
local C = ffi.C
local dummy = require("ffi/posix_h")
local band = bit.band
local bor = bit.bor
local bnot = bit.bnot
local function yes() return true end
-- A couple helper functions to compute aligned values...
-- c.f., <linux/kernel.h> & ffi/framebuffer_linux.lua
local function ALIGN_DOWN(x, a)
-- x & ~(a-1)
local mask = a - 1
return band(x, bnot(mask))
end
local function ALIGN_UP(x, a)
-- (x + (a-1)) & ~(a-1)
local mask = a - 1
return band(x + mask, bnot(mask))
end
local framebuffer = {
-- pass device object here for proper model detection:
device = 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,
night_is_reagl = nil,
mech_refresh = nil,
-- start with an invalid marker value to avoid doing something stupid on our first update
marker = 0,
-- used to avoid waiting twice on the same marker
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,
}
--[[ refresh list management: --]]
-- Returns an incrementing marker value, w/ a sane wraparound.
function framebuffer:_get_next_marker()
local marker = self.marker + 1
if marker > 128 then
marker = 1
end
self.marker = marker
return marker
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 NTX_WFM_MODE_GLD16
function framebuffer:_isREAGLWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_reagl
end
-- Returns true if the night waveform mode for the current device requires a REAGL promotion to FULL
function framebuffer:_isNightREAGL()
return self.night_is_reagl
end
-- Returns true if waveform_mode arg matches the fast 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 some devices use A2, while other prefer DU
function framebuffer:_isFastWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_fast
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 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
-- Clamp w & h to the screen's physical dimensions
function framebuffer:_clampToPhysicalDim(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...
local max_w, max_h
if band(self:getRotationMode(), 1) == 1 then
max_w = self:getHeight()
max_h = self:getWidth()
else
max_w = self:getWidth()
max_h = self:getHeight()
end
if w > max_w then
w = max_w
end
if h > max_h then
h = max_h
end
return w, h
end
--[[ handlers for the wait API of the eink driver --]]
-- Kindle's MXCFB_WAIT_FOR_UPDATE_COMPLETE_PEARL == 0x4004462f
local function kindle_pearl_mxc_wait_for_update_complete(fb, marker)
-- Wait for a specific update to be completed
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE_PEARL, ffi.new("uint32_t[1]", marker))
end
-- Kobo's MXCFB_WAIT_FOR_UPDATE_COMPLETE_V1 == 0x4004462f
local function kobo_mxc_wait_for_update_complete(fb, marker)
-- Wait for a specific update to be completed
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE_V1, ffi.new("uint32_t[1]", marker))
end
-- Kobo's Mk7 MXCFB_WAIT_FOR_UPDATE_COMPLETE_V3
local function kobo_mk7_mxc_wait_for_update_complete(fb, marker)
-- Wait for a specific update to be completed
local mk7_update_marker = ffi.new("struct mxcfb_update_marker_data[1]")
mk7_update_marker[0].update_marker = marker
-- NOTE: 0 seems to be a fairly safe assumption for "we don't care about collisions".
-- On a slightly related note, the EPDC_FLAG_TEST_COLLISION flag is for dry-run collision tests, never set it.
mk7_update_marker[0].collision_test = 0
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE_V3, mk7_update_marker)
end
-- Pocketbook's MXCFB_WAIT_FOR_UPDATE_COMPLETE_PB... with a twist.
local function pocketbook_mxc_wait_for_update_complete(fb, marker)
-- Wait for a specific update to be completed
-- NOTE: While the ioctl *should* only expect to read an uint32_t, some kernels still write back as if it were a struct,
-- like on newer MXCFB_WAIT_FOR_UPDATE_COMPLETE ioctls...
-- So, account for that by always passing an address to a mxcfb_update_marker_data struct to make the write safe.
-- Given the layout of said struct (marker first), this thankfully works out just fine...
-- c.f., https://github.com/koreader/koreader/issues/6000 & https://github.com/koreader/koreader/pull/6669
local update_marker = ffi.new("struct mxcfb_update_marker_data[1]")
update_marker[0].update_marker = marker
-- NOTE: 0 seems to be a fairly safe assumption for "we don't care about collisions".
-- On a slightly related note, the EPDC_FLAG_TEST_COLLISION flag is for dry-run collision tests, never set it.
update_marker[0].collision_test = 0
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE_PB, update_marker)
end
-- Remarkable MXCFB_WAIT_FOR_UPDATE_COMPLETE
local function remarkable_mxc_wait_for_update_complete(fb, marker)
-- Wait for a specific update to be completed
local update_marker = ffi.new("struct mxcfb_update_marker_data[1]")
update_marker[0].update_marker = marker
-- NOTE: 0 seems to be a fairly safe assumption for "we don't care about collisions".
-- On a slightly related note, the EPDC_FLAG_TEST_COLLISION flag is for dry-run collision tests, never set it.
update_marker[0].collision_test = 0
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE, update_marker)
end
-- Sony PRS MXCFB_WAIT_FOR_UPDATE_COMPLETE
local function sony_prstux_mxc_wait_for_update_complete(fb, marker)
-- Wait for a specific update to be completed
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE, ffi.new("uint32_t[1]", marker))
end
-- BQ Cervantes MXCFB_WAIT_FOR_UPDATE_COMPLETE == 0x4004462f
local function cervantes_mxc_wait_for_update_complete(fb, marker)
-- Wait for a specific update to be completed
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE, ffi.new("uint32_t[1]", marker))
end
-- Kindle's MXCFB_WAIT_FOR_UPDATE_COMPLETE == 0xc008462f
local function kindle_carta_mxc_wait_for_update_complete(fb, marker)
-- Wait for a specific update to be completed
local carta_update_marker = ffi.new("struct mxcfb_update_marker_data[1]")
carta_update_marker[0].update_marker = marker
-- NOTE: 0 seems to be a fairly safe assumption for "we don't care about collisions".
-- On a slightly related note, the EPDC_FLAG_TEST_COLLISION flag is for dry-run collision tests, never set it.
carta_update_marker[0].collision_test = 0
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE, carta_update_marker)
end
-- Kindle's MXCFB_WAIT_FOR_UPDATE_SUBMISSION == 0x40044637
local function kindle_mxc_wait_for_update_submission(fb, marker)
-- Wait for a specific update to be submitted
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_SUBMISSION, ffi.new("uint32_t[1]", marker))
end
-- Stub version that simply sleeps for 1ms
-- This is roughly five times the amount of time a real *NOP* WAIT_FOR_UPDATE_COMPLETE would take.
-- An effective one could block for ~150ms to north of 500ms, depending on the waveform mode of the waited on marker.
local function stub_mxc_wait_for_update_complete()
return ffiUtil.usleep(1000)
end
--[[ refresh functions ]]--
-- Kindle's MXCFB_SEND_UPDATE == 0x4048462e
-- Kobo's MXCFB_SEND_UPDATE == 0x4044462e
-- Pocketbook's MXCFB_SEND_UPDATE == 0x4040462e
-- Cervantes MXCFB_SEND_UPDATE == 0x4044462e
local function mxc_update(fb, update_ioctl, refarea, refresh_type, waveform_mode, x, y, w, h, dither)
local bb = fb.full_bb or fb.bb
w, x = BB.checkBounds(w or bb:getWidth(), x or 0, 0, bb:getWidth(), 0xFFFF)
h, y = BB.checkBounds(h or bb:getHeight(), y or 0, 0, bb:getHeight(), 0xFFFF)
x, y, w, h = bb:getPhysicalRect(x, y, w, h)
-- NOTE: Discard empty or bogus regions, as they might murder some kernels with extreme prejudice...
-- (c.f., https://github.com/NiLuJe/FBInk/blob/5449a03d3be28823991b425cd20aa048d2d71845/fbink.c#L1755).
-- We have practical experience of that with 1x1 pixel blocks on Kindle PW2 and KV,
-- c.f., koreader/koreader#1299 and koreader/koreader#1486
if w <= 1 or h <= 1 then
fb.debug("discarding bogus refresh region, w:", w, "h:", h)
return
end
-- NOTE: If we're requesting hardware dithering on a partial update, make sure the rectangle is using
-- coordinates aligned to the previous multiple of 8, and dimensions aligned to the next multiple of 8.
-- Otherwise, some unlucky coordinates will play badly with the PxP's own alignment constraints,
-- leading to a refresh where content appears to have moved a few pixels to the side...
-- (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...).
if dither then
local x_orig = x
x = ALIGN_DOWN(x_orig, 8)
local x_fixup = x_orig - x
w = ALIGN_UP(w + x_fixup, 8)
local y_orig = y
y = ALIGN_DOWN(y_orig, 8)
local y_fixup = y_orig - y
h = ALIGN_UP(h + y_fixup, 8)
-- NOTE: The Forma is the rare beast with screen dimensions that are themselves a multiple of 8.
-- Unfortunately, this may not be true for every device, so,
-- make sure we clamp w & h to the physical screen's size, otherwise the ioctl will fail ;).
w, h = fb:_clampToPhysicalDim(w, h)
end
w = w * fb.refresh_pixel_size
-- NOTE: If we're trying to send a:
-- * true FULL update,
-- * GC16_FAST update (i.e., popping-up a menu),
-- then wait for submission of previous marker first.
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
and (refresh_type == C.UPDATE_MODE_FULL
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)
fb.mech_wait_update_submission(fb, marker)
-- NOTE: We don't set dont_wait_for_marker here,
-- as we *do* want to chain wait_for_submission & wait_for_complete in some rare instances...
end
-- NOTE: If we're trying to send a:
-- * REAGL update,
-- * GC16 update,
-- * Full-screen, flashing GC16_FAST update,
-- then wait for completion of previous marker first.
-- Again, make sure the marker is valid, too.
if (fb:_isREAGLWaveFormMode(waveform_mode)
or waveform_mode == C.WAVEFORM_MODE_GC16
or (refresh_type == C.UPDATE_MODE_FULL 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)
fb.mech_wait_update_complete(fb, marker)
end
refarea[0].update_mode = refresh_type or C.UPDATE_MODE_PARTIAL
refarea[0].waveform_mode = waveform_mode or C.WAVEFORM_MODE_GC16
refarea[0].update_region.left = x
refarea[0].update_region.top = y
refarea[0].update_region.width = w
refarea[0].update_region.height = h
marker = fb:_get_next_marker()
refarea[0].update_marker = marker
-- NOTE: We're not using EPDC_FLAG_USE_ALT_BUFFER
refarea[0].alt_buffer_data.phys_addr = 0
refarea[0].alt_buffer_data.width = 0
refarea[0].alt_buffer_data.height = 0
refarea[0].alt_buffer_data.alt_update_region.top = 0
refarea[0].alt_buffer_data.alt_update_region.left = 0
refarea[0].alt_buffer_data.alt_update_region.width = 0
refarea[0].alt_buffer_data.alt_update_region.height = 0
-- Handle night mode shenanigans
if fb.night_mode then
-- We're in nightmode! If the device can do HW inversion safely, do that!
if fb.device:canHWInvert() then
refarea[0].flags = bor(refarea[0].flags, C.EPDC_FLAG_ENABLE_INVERSION)
end
-- Enforce a nightmode-specific mode (usually, GC16), to limit ghosting, where appropriate (i.e., partial & flashes).
-- There's nothing much we can do about crappy flashing behavior on some devices, though (c.f., base/#884),
-- that's in the hands of the EPDC. Kindle PW2+ behave sanely, for instance, even when flashing on AUTO or GC16 ;).
if fb:_isPartialWaveFormMode(waveform_mode) then
waveform_mode = fb:_getNightWaveFormMode()
refarea[0].waveform_mode = waveform_mode
-- And handle devices like the KOA2/PW4, where night is a REAGL waveform that needs to be FULL...
if fb:_isNightREAGL() then
refarea[0].update_mode = C.UPDATE_MODE_FULL
end
elseif waveform_mode == C.WAVEFORM_MODE_GC16 or refresh_type == C.UPDATE_MODE_FULL then
waveform_mode = fb:_getFlashNightWaveFormMode()
refarea[0].waveform_mode = waveform_mode
end
end
-- Handle REAGL promotion...
-- NOTE: We need to do this here, because we rely on the pre-promotion actual refresh_type in previous heuristics.
if fb:_isREAGLWaveFormMode(waveform_mode) then
-- NOTE: REAGL updates always need to be full.
refarea[0].update_mode = C.UPDATE_MODE_FULL
end
-- Recap the actual details of the ioctl, vs. what UIManager asked for...
fb.debug(string.format("mxc_update: %ux%u region @ (%u, %u) with marker %u (WFM: %u & UPD: %u)", w, h, x, y, marker, refarea[0].waveform_mode, refarea[0].update_mode))
local rv = C.ioctl(fb.fd, update_ioctl, refarea)
if rv < 0 then
local err = ffi.errno()
fb.debug("MXCFB_SEND_UPDATE ioctl failed:", ffi.string(C.strerror(err)))
end
-- NOTE: We want to fence off FULL updates.
-- Mainly to mimic stock readers, but also because there's a good reason to do it:
-- forgoing that can yield slightly "jittery" looking screens when multiple flashes are shown on screen and not in sync.
-- To achieve that, we could simply store this marker, and wait for it on the *next* refresh,
-- ensuring the wait would potentially be shorter, or even null.
-- In practice, we won't actually be busy for a bit after most (if not all) flashing refresh calls,
-- so we can instead afford to wait for it right now, which *will* block for a while,
-- but will save us an ioctl before the next refresh, something which, even if it didn't block at all,
-- would possibly end up being more detrimental to latency/reactivity.
if refarea[0].update_mode == C.UPDATE_MODE_FULL
and fb.mech_wait_update_complete then
fb.debug("refresh: wait for completion of marker", marker)
fb.mech_wait_update_complete(fb, marker)
-- 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_k51(fb, refreshtype, waveform_mode, x, y, w, h)
local refarea = ffi.new("struct mxcfb_update_data[1]")
-- only for Amazon's driver, try to mostly follow what the stock reader does...
if waveform_mode == C.WAVEFORM_MODE_REAGL then
-- If we're requesting WAVEFORM_MODE_REAGL, it's REAGL all around!
refarea[0].hist_bw_waveform_mode = waveform_mode
refarea[0].hist_gray_waveform_mode = waveform_mode
else
refarea[0].hist_bw_waveform_mode = C.WAVEFORM_MODE_DU
refarea[0].hist_gray_waveform_mode = C.WAVEFORM_MODE_GC16_FAST
end
-- And we're only left with true full updates to special-case.
if waveform_mode == C.WAVEFORM_MODE_GC16 then
refarea[0].hist_gray_waveform_mode = waveform_mode
end
-- TEMP_USE_PAPYRUS on Touch/PW1, TEMP_USE_AUTO on PW2 (same value in both cases, 0x1001)
refarea[0].temp = C.TEMP_USE_AUTO
-- Enable the appropriate flag when requesting a 2bit update
if waveform_mode == C.WAVEFORM_MODE_A2 or waveform_mode == C.WAVEFORM_MODE_DU then
refarea[0].flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
refarea[0].flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE, refarea, refreshtype, waveform_mode, x, y, w, h)
end
local function refresh_zelda(fb, refreshtype, waveform_mode, x, y, w, h, dither)
local refarea = ffi.new("struct mxcfb_update_data_zelda[1]")
-- only for Amazon's driver, try to mostly follow what the stock reader does...
if waveform_mode == C.WAVEFORM_MODE_ZELDA_GLR16 or waveform_mode == C.WAVEFORM_MODE_ZELDA_GLD16 then
-- If we're requesting WAVEFORM_MODE_ZELDA_GLR16, it's REAGL all around!
refarea[0].hist_bw_waveform_mode = waveform_mode
refarea[0].hist_gray_waveform_mode = waveform_mode
else
refarea[0].hist_bw_waveform_mode = C.WAVEFORM_MODE_DU
refarea[0].hist_gray_waveform_mode = C.WAVEFORM_MODE_GC16 -- NOTE: GC16_FAST points to GC16
end
-- NOTE: Since there's no longer a distinction between GC16_FAST & GC16, we're done!
refarea[0].temp = C.TEMP_USE_AMBIENT
-- Did we request HW dithering on a device where it works?
if dither and fb.device:canHWDither() then
refarea[0].dither_mode = C.EPDC_FLAG_USE_DITHERING_ORDERED
if waveform_mode == C.WAVEFORM_MODE_ZELDA_A2 or waveform_mode == C.WAVEFORM_MODE_DU then
refarea[0].quant_bit = 1;
else
refarea[0].quant_bit = 7;
end
else
refarea[0].dither_mode = C.EPDC_FLAG_USE_DITHERING_PASSTHROUGH
refarea[0].quant_bit = 0;
end
-- Enable the REAGLD algo when requested
if waveform_mode == C.WAVEFORM_MODE_ZELDA_GLD16 then
refarea[0].flags = C.EPDC_FLAG_USE_ZELDA_REGAL
-- Enable the appropriate flag when requesting a 2bit update, provided we're not dithering.
elseif waveform_mode == C.WAVEFORM_MODE_ZELDA_A2 and not dither then
refarea[0].flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
refarea[0].flags = 0
end
-- TODO: There's also the HW-backed NightMode which should be somewhat accessible...
return mxc_update(fb, C.MXCFB_SEND_UPDATE_ZELDA, refarea, refreshtype, waveform_mode, x, y, w, h, dither)
end
local function refresh_rex(fb, refreshtype, waveform_mode, x, y, w, h, dither)
local refarea = ffi.new("struct mxcfb_update_data_rex[1]")
-- only for Amazon's driver, try to mostly follow what the stock reader does...
if waveform_mode == C.WAVEFORM_MODE_ZELDA_GLR16 or waveform_mode == C.WAVEFORM_MODE_ZELDA_GLD16 then
-- If we're requesting WAVEFORM_MODE_ZELDA_GLR16, it's REAGL all around!
refarea[0].hist_bw_waveform_mode = waveform_mode
refarea[0].hist_gray_waveform_mode = waveform_mode
else
refarea[0].hist_bw_waveform_mode = C.WAVEFORM_MODE_DU
refarea[0].hist_gray_waveform_mode = C.WAVEFORM_MODE_GC16 -- NOTE: GC16_FAST points to GC16
end
-- NOTE: Since there's no longer a distinction between GC16_FAST & GC16, we're done!
refarea[0].temp = C.TEMP_USE_AMBIENT
-- Did we request HW dithering on a device where it works?
if dither and fb.device:canHWDither() then
refarea[0].dither_mode = C.EPDC_FLAG_USE_DITHERING_ORDERED
if waveform_mode == C.WAVEFORM_MODE_ZELDA_A2 or waveform_mode == C.WAVEFORM_MODE_DU then
refarea[0].quant_bit = 1;
else
refarea[0].quant_bit = 7;
end
else
refarea[0].dither_mode = C.EPDC_FLAG_USE_DITHERING_PASSTHROUGH
refarea[0].quant_bit = 0;
end
-- Enable the REAGLD algo when requested
if waveform_mode == C.WAVEFORM_MODE_ZELDA_GLD16 then
refarea[0].flags = C.EPDC_FLAG_USE_ZELDA_REGAL
-- Enable the appropriate flag when requesting a 2bit update, provided we're not dithering.
elseif waveform_mode == C.WAVEFORM_MODE_ZELDA_A2 and not dither then
refarea[0].flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
refarea[0].flags = 0
end
-- TODO: There's also the HW-backed NightMode which should be somewhat accessible...
return mxc_update(fb, C.MXCFB_SEND_UPDATE_REX, refarea, refreshtype, waveform_mode, x, y, w, h, dither)
end
local function refresh_kobo(fb, refreshtype, waveform_mode, x, y, w, h)
local refarea = ffi.new("struct mxcfb_update_data_v1_ntx[1]")
-- only for Kobo's driver:
refarea[0].alt_buffer_data.virt_addr = nil
-- TEMP_USE_AMBIENT, not that there was ever any other choice on Kobo...
refarea[0].temp = C.TEMP_USE_AMBIENT
-- Enable the appropriate flag when requesting a REAGLD waveform (NTX_WFM_MODE_GLD16 on the Aura)
if waveform_mode == C.NTX_WFM_MODE_GLD16 then
refarea[0].flags = C.EPDC_FLAG_USE_AAD
elseif waveform_mode == C.WAVEFORM_MODE_A2 or waveform_mode == C.WAVEFORM_MODE_DU then
-- As well as when requesting a 2bit waveform
refarea[0].flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
refarea[0].flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE_V1_NTX, refarea, refreshtype, waveform_mode, x, y, w, h)
end
local function refresh_kobo_mk7(fb, refreshtype, waveform_mode, x, y, w, h, dither)
local refarea = ffi.new("struct mxcfb_update_data_v2[1]")
-- TEMP_USE_AMBIENT, not that there was ever any other choice on Kobo...
refarea[0].temp = C.TEMP_USE_AMBIENT
-- Did we request HW dithering?
if dither then
refarea[0].dither_mode = C.EPDC_FLAG_USE_DITHERING_ORDERED
if waveform_mode == C.WAVEFORM_MODE_A2 or waveform_mode == C.WAVEFORM_MODE_DU then
refarea[0].quant_bit = 1;
else
refarea[0].quant_bit = 7;
end
else
refarea[0].dither_mode = C.EPDC_FLAG_USE_DITHERING_PASSTHROUGH
refarea[0].quant_bit = 0;
end
-- Enable the appropriate flag when requesting a 2bit update, provided we're not dithering.
-- NOTE: As of right now (FW 4.9.x), WAVEFORM_MODE_GLD16 appears not to be used by Nickel,
-- so we don't have to care about EPDC_FLAG_USE_REGAL
-- NOTE: We never actually request A2 updates anymore (on any platform, actually), but,
-- on Mk. 7 specifically, we want to avoid stacking EPDC_FLAGs,
-- because the kernel is buggy (c.f., https://github.com/NiLuJe/FBInk/blob/96a2cd6a93f5184c595c0e53a844fd883adfd75b/fbink.c#L2422-L2440).
-- For our use-cases, FORCE_MONOCHROME is mostly unnecessary anyway (the effect being fuzzier text instead of blockier text, i.e., choose your poison ;p).
if waveform_mode == C.WAVEFORM_MODE_A2 and not dither then
refarea[0].flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
refarea[0].flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE_V2, refarea, refreshtype, waveform_mode, x, y, w, h, dither)
end
local function refresh_pocketbook(fb, refreshtype, waveform_mode, x, y, w, h)
local refarea = ffi.new("struct mxcfb_update_data[1]")
-- TEMP_USE_AMBIENT, not that there was ever any other choice...
refarea[0].temp = C.TEMP_USE_AMBIENT
-- Enable the appropriate flag when requesting a REAGLD waveform (EPDC_WFTYPE_AAD on PB631)
if waveform_mode == C.EPDC_WFTYPE_AAD then
refarea[0].flags = C.EPDC_FLAG_USE_AAD
elseif waveform_mode == C.WAVEFORM_MODE_A2 or waveform_mode == C.WAVEFORM_MODE_DU then
-- As well as when requesting a 2bit waveform
--- @note: Much like on rM, it appears faking 24°C instead of relying on ambient temp leads to lower latency
refarea[0].temp = 24
refarea[0].flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
refarea[0].flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE, refarea, refreshtype, waveform_mode, x, y, w, h)
end
local function refresh_remarkable(fb, refreshtype, waveform_mode, x, y, w, h)
local refarea = ffi.new("struct mxcfb_update_data[1]")
if waveform_mode == C.WAVEFORM_MODE_DU then
refarea[0].temp = C.TEMP_USE_REMARKABLE
else
refarea[0].temp = C.TEMP_USE_AMBIENT
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE, refarea, refreshtype, waveform_mode, x, y, w, h)
end
local function refresh_sony_prstux(fb, refreshtype, waveform_mode, x, y, w, h)
local refarea = ffi.new("struct mxcfb_update_data[1]")
refarea[0].temp = C.TEMP_USE_AMBIENT
return mxc_update(fb, C.MXCFB_SEND_UPDATE, refarea, refreshtype, waveform_mode, x, y, w, h)
end
local function refresh_cervantes(fb, refreshtype, waveform_mode, x, y, w, h)
local refarea = ffi.new("struct mxcfb_update_data[1]")
refarea[0].temp = C.TEMP_USE_AMBIENT
if waveform_mode == C.WAVEFORM_MODE_A2 or waveform_mode == C.WAVEFORM_MODE_DU then
refarea[0].flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
refarea[0].flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE, refarea, refreshtype, waveform_mode, x, y, w, h)
end
--[[ framebuffer API ]]--
function framebuffer:refreshPartialImp(x, y, w, h, dither)
self.debug("refresh: partial", x, y, w, h, dither and "w/ HW dithering")
self:mech_refresh(C.UPDATE_MODE_PARTIAL, self.waveform_partial, x, y, w, h, dither)
end
-- NOTE: UPDATE_MODE_FULL doesn't mean full screen or no region, it means ask for a black flash!
-- The only exception to that rule is with REAGL waveform modes, where it will *NOT* flash.
-- That's regardless of whether the REAGL waveform mode is of the "always enforce FULL" variety or not ;).
function framebuffer:refreshFlashPartialImp(x, y, w, h, dither)
self.debug("refresh: partial w/ flash", x, y, w, h, dither and "w/ HW dithering")
self:mech_refresh(C.UPDATE_MODE_FULL, 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 and "w/ HW dithering")
self:mech_refresh(C.UPDATE_MODE_PARTIAL, 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 and "w/ HW dithering")
self:mech_refresh(C.UPDATE_MODE_FULL, 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 and "w/ HW dithering")
self:mech_refresh(C.UPDATE_MODE_FULL, 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 and "w/ HW dithering")
self:mech_refresh(C.UPDATE_MODE_PARTIAL, self.waveform_fast, 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)
self:mech_wait_update_complete(self.marker)
self.dont_wait_for_marker = self.marker
end
end
-- Detect Allwinner boards. Those emulate mxcfb API in a custom driver (poorly).
function framebuffer:isB288(fb)
require("ffi/mxcfb_pocketbook_h")
-- On a real MXC driver, it returns -EINVAL
return C.ioctl(self.fd, C.EPDC_GET_UPDATE_STATE, ffi.new("uint32_t[1]")) == 0
end
function framebuffer:init()
framebuffer.parent.init(self)
self.refresh_list = {}
if self.device:isKindle() then
require("ffi/mxcfb_kindle_h")
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
self.waveform_fast = C.WAVEFORM_MODE_A2 -- NOTE: Mostly here for archeological purposes, we switch to DU on every device right below this ;).
self.waveform_ui = C.WAVEFORM_MODE_GC16_FAST
self.waveform_flashui = self.waveform_ui
self.waveform_full = C.WAVEFORM_MODE_GC16
self.waveform_night = C.WAVEFORM_MODE_GC16
self.waveform_flashnight = self.waveform_night
self.night_is_reagl = false
-- New devices are REAGL-aware, default to REAGL
local isREAGL = true
-- Zelda uses a new eink driver, one that massively breaks backward compatibility.
local isZelda = false
-- And because that worked well enough the first time, lab126 did the same with Rex!
local isRex = false
-- But of course, some devices don't actually support all the features the kernel exposes...
local isNightModeChallenged = false
if self.device.model == "Kindle2" then
isREAGL = false
elseif self.device.model == "KindleDXG" then
isREAGL = false
elseif self.device.model == "Kindle3" then
isREAGL = false
elseif self.device.model == "Kindle4" then
isREAGL = false
elseif self.device.model == "KindleTouch" then
isREAGL = false
elseif self.device.model == "KindlePaperWhite" then
isREAGL = false
end
if self.device.model == "KindleOasis2" then
isZelda = true
end
if self.device.model == "KindlePaperWhite4" then
isRex = true
elseif self.device.model == "KindleBasic3" then
isRex = true
-- NOTE: Apparently, the KT4 doesn't actually support the fancy nightmode waveforms, c.f., ko/#5076
-- It also doesn't handle HW dithering, c.f., base/#1039
isNightModeChallenged = true
end
if isREAGL then
self.mech_wait_update_complete = kindle_carta_mxc_wait_for_update_complete
self.waveform_fast = C.WAVEFORM_MODE_DU -- NOTE: DU, because A2 looks terrible on REAGL devices. Older devices/FW may be using AUTO in this instance.
self.waveform_reagl = C.WAVEFORM_MODE_REAGL
self.waveform_partial = self.waveform_reagl
-- NOTE: GL16_INV is available since FW >= 5.6.x only, but it'll safely fall-back to AUTO on older FWs.
-- Most people with those devices should be running at least FW 5.9.7 by now, though ;).
self.waveform_night = C.WAVEFORM_MODE_GL16_INV
self.waveform_flashnight = C.WAVEFORM_MODE_GC16
else
self.waveform_fast = C.WAVEFORM_MODE_DU -- NOTE: DU, because A2 looks terrible on the Touch, and ghosts horribly. Framework is actually using AUTO for UI feedback inverts.
self.waveform_partial = C.WAVEFORM_MODE_GL16_FAST -- NOTE: Depending on FW, might instead be AUTO w/ hist_gray_waveform_mode set to GL16_FAST
end
-- NOTE: Devices on the Rex platform essentially use the same driver as the Zelda platform, they're just passing a slightly smaller mxcfb_update_data struct
if isZelda or isRex then
-- NOTE: Turns out, nope, it really doesn't work on *any* of 'em :/ (c.f., ko#5884).
--[[
self.device.canHWDither = yes
--]]
if isZelda then
self.mech_refresh = refresh_zelda
else
self.mech_refresh = refresh_rex
end
self.waveform_fast = C.WAVEFORM_MODE_DU
self.waveform_ui = C.WAVEFORM_MODE_AUTO
-- NOTE: Possibly to bypass the possibility that AUTO, even when FULL, might not flash (something which holds true for a number of devices, especially on small regions),
-- Zelda explicitly requests GC16 when flashing an UI element that doesn't cover the full screen...
-- And it resorts to AUTO when PARTIAL, because GC16_FAST is no more (it points to GC16).
self.waveform_flashui = C.WAVEFORM_MODE_GC16
self.waveform_reagl = C.WAVEFORM_MODE_ZELDA_GLR16
self.waveform_partial = self.waveform_reagl
-- NOTE: Because we can't have nice things, we have to account for devices that do not actuallly support the fancy inverted waveforms...
if isNightModeChallenged then
self.waveform_night = C.WAVEFORM_MODE_ZELDA_GL16_INV -- NOTE: Currently points to the bog-standard GL16, but one can hope...
self.waveform_flashnight = C.WAVEFORM_MODE_GC16
else
self.waveform_night = C.WAVEFORM_MODE_ZELDA_GLKW16
self.night_is_reagl = true
self.waveform_flashnight = C.WAVEFORM_MODE_ZELDA_GCK16
end
end
elseif self.device:isKobo() then
require("ffi/mxcfb_kobo_h")
self.mech_refresh = refresh_kobo
self.mech_wait_update_complete = kobo_mxc_wait_for_update_complete
self.waveform_fast = C.WAVEFORM_MODE_DU
self.waveform_ui = C.WAVEFORM_MODE_AUTO
self.waveform_flashui = self.waveform_ui
self.waveform_full = C.NTX_WFM_MODE_GC16
self.waveform_partial = C.WAVEFORM_MODE_AUTO
self.waveform_night = C.NTX_WFM_MODE_GC16
self.waveform_flashnight = self.waveform_night
self.night_is_reagl = false
-- New devices *may* be REAGL-aware, but generally don't expect explicit REAGL requests, default to not.
local isREAGL = false
-- Mark 7 devices sport an updated driver.
-- For now, it appears backward compatibility has been somewhat preserved,
-- but let's use the shiny new stuff!
local isMk7 = false
-- NOTE: AFAICT, the Aura was the only one explicitly requiring REAGL requests...
if self.device.model == "Kobo_phoenix" then
isREAGL = true
end
if self.device.model == "Kobo_star_r2" then
isMk7 = true
elseif self.device.model == "Kobo_snow_r2" then
isMk7 = true
elseif self.device.model == "Kobo_nova" then
isMk7 = true
elseif self.device.model == "Kobo_frost" then
isMk7 = true
elseif self.device.model == "Kobo_storm" then
isMk7 = true
elseif self.device.model == "Kobo_luna" then
isMk7 = true
end
if isREAGL then
self.waveform_reagl = C.NTX_WFM_MODE_GLD16
self.waveform_partial = self.waveform_reagl
self.waveform_fast = C.WAVEFORM_MODE_DU -- Mainly menu HLs, compare to Kindle's use of AUTO or DU also in these instances ;).
end
-- NOTE: There's a fun twist to Mark 7 devices:
-- they do use GLR16 update modes (i.e., REAGL), but they do NOT need/do the PARTIAL -> FULL trick...
-- We handle that by NOT setting waveform_reagl (so _isREAGLWaveFormMode never matches), and just customizing waveform_partial.
-- Nickel doesn't wait for completion of previous markers on those PARTIAL GLR16, so that's enough to keep our heuristics intact,
-- while still doing the right thing everywhere ;).
-- Turns out there's a good reason for that: the EPDC will fence REAGL updates internally (possibly via the PxP).
-- This makes interaction between partial and other modes slightly finicky in practice in some corner-cases,
-- (c.f., the SkimTo/Button widgets workaround where we batch a button's 'fast' highlight with the reader's 'partial',
-- and then fence *that batch* manually to avoid the (REAGL) 'partial' being delayed by the button's 'fast' highlight).
if isMk7 then
self.device.canHWDither = yes
self.mech_refresh = refresh_kobo_mk7
self.mech_wait_update_complete = kobo_mk7_mxc_wait_for_update_complete
self.waveform_partial = C.WAVEFORM_MODE_GLR16
self.waveform_fast = C.WAVEFORM_MODE_DU -- A2 is much more prone to artifacts on Mk. 7 than before, because everything's faster.
-- Nickel sometimes uses DU, but never w/ the MONOCHROME flag, so, do the same.
-- Plus, DU + MONOCHROME + INVERT is much more prone to the Mk. 7 EPDC bug where some/all
-- EPDC flags just randomly go bye-bye...
-- NOTE: The Libra apparently suffers from a mysterious issue where completely innocuous WAIT_FOR_UPDATE_COMPLETE ioctls
-- will mysteriously fail with a timeout (5s)...
-- This obviously leads to *terrible* user experience, so, until more is understood avout the issue,
-- just fake this ioctl by sleeping for a tiny amount of time instead... :/.
-- c.f., https://github.com/koreader/koreader/issues/7340
if self.device.model == "Kobo_storm" then
self.mech_wait_update_complete = stub_mxc_wait_for_update_complete
end
end
elseif self.device:isPocketBook() then
require("ffi/mxcfb_pocketbook_h")
self.mech_refresh = refresh_pocketbook
self.mech_wait_update_complete = pocketbook_mxc_wait_for_update_complete
self.wf_level_max = 3
local level = self:getWaveformLevel()
-- Level 0 is most conservative.
-- This is what inkview does on all platforms.
-- Slow (>150ms on B288 Carta).
if level == 0 then
self.waveform_fast = C.WAVEFORM_MODE_GC16
self.waveform_partial = C.WAVEFORM_MODE_GC16
elseif level == 1 then
self.waveform_fast = C.WAVEFORM_MODE_DU
self.waveform_partial = C.WAVEFORM_MODE_GC16
elseif level == 2 then
self.waveform_fast = C.WAVEFORM_MODE_DU
self.waveform_partial = self:isB288() and C.WAVEFORM_MODE_GS16 or C.WAVEFORM_MODE_GC16
-- Level 3 is most aggressive.
-- Fast (>80ms on B288 Carta), but flickers and may be buggy.
elseif level == 3 then
self.waveform_fast = C.WAVEFORM_MODE_DU
self.waveform_partial = C.WAVEFORM_MODE_GL16
end
self.waveform_ui = self.waveform_partial
self.waveform_flashui = C.WAVEFORM_MODE_GC16
self.waveform_full = C.WAVEFORM_MODE_GC16
self.waveform_night = C.WAVEFORM_MODE_GC16
self.waveform_flashnight = self.waveform_night
self.night_is_reagl = false
elseif self.device:isRemarkable() then
require("ffi/mxcfb_remarkable_h")
self.mech_refresh = refresh_remarkable
self.mech_wait_update_complete = remarkable_mxc_wait_for_update_complete
self.waveform_fast = C.WAVEFORM_MODE_DU
self.waveform_ui = C.WAVEFORM_MODE_GL16
self.waveform_flashui = C.WAVEFORM_MODE_GC16
self.waveform_full = C.WAVEFORM_MODE_GC16
self.waveform_partial = C.WAVEFORM_MODE_GL16
self.waveform_night = C.WAVEFORM_MODE_GC16
self.waveform_flashnight = self.waveform_night
self.night_is_reagl = false
elseif self.device:isSonyPRSTUX() then
require("ffi/mxcfb_sony_h")
self.mech_refresh = refresh_sony_prstux
self.mech_wait_update_complete = sony_prstux_mxc_wait_for_update_complete
self.waveform_fast = C.WAVEFORM_MODE_DU
self.waveform_ui = C.WAVEFORM_MODE_AUTO
self.waveform_flashui = self.waveform_ui
self.waveform_full = C.WAVEFORM_MODE_GC16
self.waveform_partial = C.WAVEFORM_MODE_AUTO
self.waveform_night = C.WAVEFORM_MODE_GC16
self.waveform_flashnight = self.waveform_night
self.night_is_reagl = false
elseif self.device:isCervantes() then
require("ffi/mxcfb_cervantes_h")
self.mech_refresh = refresh_cervantes
self.mech_wait_update_complete = cervantes_mxc_wait_for_update_complete
self.waveform_fast = C.WAVEFORM_MODE_DU
self.waveform_ui = C.WAVEFORM_MODE_AUTO
self.waveform_flashui = self.waveform_ui
self.waveform_full = C.WAVEFORM_MODE_GC16
self.waveform_partial = C.WAVEFORM_MODE_AUTO
self.waveform_night = C.WAVEFORM_MODE_GC16
self.waveform_flashnight = self.waveform_night
self.night_is_reagl = false
else
error("unknown device type")
end
end
return require("ffi/framebuffer_linux"):extend(framebuffer)