-
Notifications
You must be signed in to change notification settings - Fork 108
/
Copy pathframebuffer_mxcfb.lua
1207 lines (1056 loc) · 59.5 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
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
local bit = require("bit")
local ffi = require("ffi")
local lfs = require("libs/libkoreader-lfs")
local ffiUtil = require("ffi/util")
local C = ffi.C
require("ffi/posix_h")
local band = bit.band
local bor = bit.bor
local bxor = bit.bxor
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,
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_color = nil,
waveform_color_reagl = 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,
-- Used to enforce an alignment constraint on devices with quirky drivers
alignment_constraint = nil,
dither_alignment_constraint = 8,
-- We recycle ffi cdata
marker_data = nil,
update_data = nil,
submission_data = nil,
-- CFA post-processing flag
CFA_PROCESSING_FLAG = 0,
}
--[[ refresh list management: --]]
-- Returns an incrementing marker value, w/ a sane wraparound for an uint32_t.
function framebuffer:_get_next_marker()
local marker = self.marker + 1
if marker > 0xFFFFFFFF 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()
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
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
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 partial waveform mode for the current device
function framebuffer:_isPartialWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_partial
end
-- Returns true if waveform_mode arg matches the full waveform mode for the current device
function framebuffer:_isFullWaveFormMode(waveform_mode)
return waveform_mode == self.waveform_full
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 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_nxp_wakeup_epdc()
-- First integer is the toggle, second is the poweroff delay in ms (only relevant when toggling power *off*, we just need it to make sscanf happy).
ffiUtil.writeToSysfs("1,0", "/sys/class/graphics/fb0/power_state")
end
local function kobo_mtk_wakeup_epdc()
ffiUtil.writeToSysfs("fiti_power 1", "/proc/hwtcon/cmd")
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
fb.marker_data[0] = marker
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE_PEARL, fb.marker_data)
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
fb.marker_data[0] = marker
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE_V1, fb.marker_data)
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
fb.marker_data.update_marker = marker
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE_V3, fb.marker_data)
end
-- Specialized variant for funky devices with an unreliable ioctl, where we're hoping doing this in pairs like Nickel *might* help...
-- NOTE: This is yet another attempt at dealing with those spurious timeouts...
-- FWIW, I can still randomly encounter those, even when doing this.
-- Which makes sense, as I have actually reproduced these in Nickel myself...
local function kobo_mk7_unreliable_mxc_wait_for_update_complete(fb, marker)
-- If we can, wait for the *previous* marker first...
if marker > 1 then
-- Marker sanity check (the driver handles that, too, but it'll throw an EINVAL)
fb.debug("refresh: wait for completion of buddy marker", marker - 1)
fb.marker_data.update_marker = marker - 1
if C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE_V3, fb.marker_data) == -1 then
local err = ffi.errno()
fb.debug("MXCFB_WAIT_FOR_UPDATE_COMPLETE ioctl failed:", ffi.string(C.strerror(err)))
end
end
-- Wait for a specific update to be completed
fb.marker_data.update_marker = marker
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE_V3, fb.marker_data)
end
-- Kobo's HWTCON_WAIT_FOR_UPDATE_COMPLETE
local function kobo_mtk_wait_for_update_complete(fb, marker)
-- Wait for a specific update to be completed
fb.marker_data[0] = marker
return C.ioctl(fb.fd, C.HWTCON_WAIT_FOR_UPDATE_COMPLETE, fb.marker_data)
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
fb.marker_data.update_marker = marker
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE_PB, fb.marker_data)
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
fb.marker_data.update_marker = marker
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE, fb.marker_data)
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
fb.marker_data[0] = marker
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE, fb.marker_data)
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
fb.marker_data[0] = marker
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE, fb.marker_data)
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
fb.marker_data.update_marker = marker
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_COMPLETE, fb.marker_data)
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
fb.submission_data[0] = marker
return C.ioctl(fb.fd, C.MXCFB_WAIT_FOR_UPDATE_SUBMISSION, fb.submission_data)
end
-- Kobo's HWTCON_WAIT_FOR_UPDATE_SUBMISSION
local function kobo_mtk_wait_for_update_submission(fb, marker)
-- Wait for a specific update to be submitted
fb.submission_data[0] = marker
return C.ioctl(fb.fd, C.HWTCON_WAIT_FOR_UPDATE_SUBMISSION, fb.submission_data)
end
-- Stub version that simply sleeps for 2.5ms
-- This is roughly ten 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(fb, marker, us)
return C.usleep(us or 2500)
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, ioc_cmd, ioc_data, is_flashing, waveform_mode, x, y, w, h, dither)
local bb = fb.full_bb or fb.bb
-- 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...).
-- 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 fb.dither_alignment_constraint 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)
-- 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
-- Wake the EPDC up manually (don't ask me why this would make any sort of sense, it's simply what Nickel does on devices where we *can* do it...)
-- NOTE: Technically, it makes a little more sense when you put it in context of *how* Nickel does that: it's not actually tied to the refresh or the wait there,
-- but to *touch* input: on any registered *touch* input (physical buttons are exempt, for some reason), Nickel will forcibly awaken the EPDC,
-- unless that was already done less than roughly 1.5s ago.
-- Think of something like some properly implemented & tuned kernels with the interactive cpufreq governor that will boost on touch input, for instance.
-- In practice, that still trips *very* close to the ioctl (but may not always come *before* a [wait -> refresh -> wait] sandwich;
-- e.g., I mostly see it before the refresh on NXP & sunxi, but I mostly see it in front of everything on MTK).
-- TL;DR: Because, on devices flagged !hasReliableMxcWaitFor, we were seeing deadlock issues with refreshes that aren't necessarily tied to input,
-- (and for simplicity's sake), we simply unconditionally do this here as early possible.
-- NOTE: This might be a gigantic red herring, and simply a case of the very few extra cpu cycles involved throwing off the race...
if fb.mech_poweron then
fb:mech_poweron()
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.
-- 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.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)
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
-- 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 (so far, the constant value for that one is universal),
-- * Full-screen, flashing UI 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 (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("MXCFB_WAIT_FOR_UPDATE_COMPLETE ioctl failed:", ffi.string(C.strerror(err)))
end
end
ioc_data.update_mode = is_flashing and C.UPDATE_MODE_FULL or C.UPDATE_MODE_PARTIAL
ioc_data.waveform_mode = waveform_mode or C.WAVEFORM_MODE_GC16
ioc_data.update_region.left = x
ioc_data.update_region.top = y
ioc_data.update_region.width = w
ioc_data.update_region.height = h
marker = fb:_get_next_marker()
ioc_data.update_marker = marker
-- Handle promotion to Kaleido waveform modes.
-- We assume the dither flag is only set on image content, so we rely on that as our main trigger.
-- REAGL (via partial) => GLRC16
-- GC16 (via full) => GCC16
-- NOTE: That leaves ui & flashui alone, which suits us just fine in order not to affect and slow down the FM,
-- while still making manual flashing refreshes via diagonal swipes automatically switch to CFA modes when relevant.
-- For instance, anything based on a mosaic view (say, History), will refresh with ui/flashui on its own (so no CFA),
-- but since it is a view flagged as `dithered`, a manual refresh *will* use GCC16 ;).
if dither and fb.device:hasKaleidoWfm() and fb:isColorEnabled() then
if fb:_isREAGLWaveFormMode(waveform_mode) then
-- NOTE: If we wanted to be really fancy, we could check if fb is actually grayscale or not
-- (e.g., lerp/nearest bb to 1x1 and see if r==g==b?).
-- It's probably much much simpler to just set the dither flag when ReaderHighlight draws something, though ;).
waveform_mode = fb.waveform_color_reagl
ioc_data.waveform_mode = waveform_mode
elseif fb:_isFullWaveFormMode(waveform_mode) then
waveform_mode = fb.waveform_color
ioc_data.waveform_mode = waveform_mode
end
-- Boost saturation for CFA modes
if fb:_isKaleidoWaveFormMode(waveform_mode) then
ioc_data.flags = bor(ioc_data.flags, fb.CFA_PROCESSING_FLAG)
end
end
-- Handle night mode shenanigans
if fb.night_mode then
-- We're in nightmode!
-- If the device can do HW inversion safely, and doesn't already handle setting the flag automatically, do that!
if fb.device:canHWInvert() and not fb:getHWNightmode() then
ioc_data.flags = bor(ioc_data.flags, C.EPDC_FLAG_ENABLE_INVERSION)
end
-- Leave Kaleido waveform modes alone
if not fb:_isKaleidoWaveFormMode(waveform_mode) then
-- 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.waveform_night
ioc_data.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
ioc_data.update_mode = C.UPDATE_MODE_FULL
end
elseif waveform_mode == C.WAVEFORM_MODE_GC16 or is_flashing then
waveform_mode = fb.waveform_flashnight
ioc_data.waveform_mode = waveform_mode
end
end
end
-- 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) 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
-- 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, ioc_data.waveform_mode, ioc_data.update_mode))
if C.ioctl(fb.fd, ioc_cmd, ioc_data) == -1 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 ioc_data.update_mode == C.UPDATE_MODE_FULL
and fb.mech_wait_update_complete then
fb.debug("refresh: wait for completion of marker", marker)
if fb:mech_wait_update_complete(marker) == -1 then
local err = ffi.errno()
fb.debug("MXCFB_WAIT_FOR_UPDATE_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
-- 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 -_-".
-- NOTE: This doesn't happen when CFA processing is disabled
-- (at which point, it starts to look like lab126 MTK, where highlights are often optimized out...),
-- so I guess this all comes from the CFA processing :/.
end
local function refresh_k51(fb, is_flashing, waveform_mode, x, y, w, h)
-- 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!
fb.update_data.hist_bw_waveform_mode = waveform_mode
fb.update_data.hist_gray_waveform_mode = waveform_mode
else
fb.update_data.hist_bw_waveform_mode = C.WAVEFORM_MODE_DU
fb.update_data.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
fb.update_data.hist_gray_waveform_mode = waveform_mode
end
-- NOTE: Enable the appropriate flag when requesting an any->2bit update.
-- Since we mainly use DU for highlights, the color decimation quantization will effectively crush antialiasing on text,
-- avoiding making the text look fuzzy during the refresh (it'll instead look blockier, because of the lack of AA).
-- The higher density the screen is, the better this approach will look vs. fuzzy refresh artifacts ;).
-- In the very few cases we use A2 (i.e., the keyboard), using FORCE_MONOCHROME would be actively harmful:
-- we only highlight a border around the key, so we don't actually modify the glyph;
-- since non-GC updates leave untouched pixels alone, we leave the glyph (and its AA!) alone.
-- If we used FORCE_MONOCHROME, it would affect the whole rectangle, crushing the AA,
-- and the A2 update would have to pick up the modified pixels, leading to worse results.
if waveform_mode == C.WAVEFORM_MODE_DU then
fb.update_data.flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
fb.update_data.flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE, fb.update_data, is_flashing, waveform_mode, x, y, w, h)
end
local function refresh_zelda(fb, is_flashing, waveform_mode, x, y, w, h, dither)
-- 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!
fb.update_data.hist_bw_waveform_mode = waveform_mode
fb.update_data.hist_gray_waveform_mode = waveform_mode
else
fb.update_data.hist_bw_waveform_mode = C.WAVEFORM_MODE_DU
fb.update_data.hist_gray_waveform_mode = C.WAVEFORM_MODE_GC16 -- NOTE: GC16_FAST points to GC16
end
-- Did we request HW dithering on a device where it works?
if dither and fb.device:canHWDither() then
fb.update_data.dither_mode = C.EPDC_FLAG_USE_DITHERING_ORDERED
if waveform_mode == C.WAVEFORM_MODE_ZELDA_A2 or waveform_mode == C.WAVEFORM_MODE_DU then
fb.update_data.quant_bit = 1
else
fb.update_data.quant_bit = 7
end
else
fb.update_data.dither_mode = C.EPDC_FLAG_USE_DITHERING_PASSTHROUGH
fb.update_data.quant_bit = 0
end
-- Enable the REAGLD algo when requested
if waveform_mode == C.WAVEFORM_MODE_ZELDA_GLD16 then
fb.update_data.flags = C.EPDC_FLAG_USE_ZELDA_REGAL
-- Enable the appropriate flag when requesting an any->2bit update, provided we're not dithering.
elseif waveform_mode == C.WAVEFORM_MODE_DU and not dither then
fb.update_data.flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
fb.update_data.flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE_ZELDA, fb.update_data, is_flashing, waveform_mode, x, y, w, h, dither)
end
local function refresh_rex(fb, is_flashing, waveform_mode, x, y, w, h, dither)
-- 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!
fb.update_data.hist_bw_waveform_mode = waveform_mode
fb.update_data.hist_gray_waveform_mode = waveform_mode
else
fb.update_data.hist_bw_waveform_mode = C.WAVEFORM_MODE_DU
fb.update_data.hist_gray_waveform_mode = C.WAVEFORM_MODE_GC16 -- NOTE: GC16_FAST points to GC16
end
-- Did we request HW dithering on a device where it works?
if dither and fb.device:canHWDither() then
fb.update_data.dither_mode = C.EPDC_FLAG_USE_DITHERING_ORDERED
if waveform_mode == C.WAVEFORM_MODE_ZELDA_A2 or waveform_mode == C.WAVEFORM_MODE_DU then
fb.update_data.quant_bit = 1
else
fb.update_data.quant_bit = 7
end
else
fb.update_data.dither_mode = C.EPDC_FLAG_USE_DITHERING_PASSTHROUGH
fb.update_data.quant_bit = 0
end
-- Enable the REAGLD algo when requested
if waveform_mode == C.WAVEFORM_MODE_ZELDA_GLD16 then
fb.update_data.flags = C.EPDC_FLAG_USE_ZELDA_REGAL
-- Enable the appropriate flag when requesting an any->2bit update, provided we're not dithering.
elseif waveform_mode == C.WAVEFORM_MODE_DU and not dither then
fb.update_data.flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
fb.update_data.flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE_REX, fb.update_data, is_flashing, waveform_mode, x, y, w, h, dither)
end
local function refresh_mtk(fb, is_flashing, waveform_mode, x, y, w, h, dither)
-- Actually unused by the driver...
if waveform_mode == C.MTK_WAVEFORM_MODE_GLR16 or waveform_mode == C.MTK_WAVEFORM_MODE_GLD16 then
-- If we're requesting MTK_WAVEFORM_MODE_GLR16, it's REAGL all around!
fb.update_data.hist_bw_waveform_mode = waveform_mode
fb.update_data.hist_gray_waveform_mode = waveform_mode
else
fb.update_data.hist_bw_waveform_mode = C.MTK_WAVEFORM_MODE_DU
fb.update_data.hist_gray_waveform_mode = C.MTK_WAVEFORM_MODE_GC16 -- NOTE: GC16_FAST points to GC16
end
-- 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.MTK_WAVEFORM_MODE_DU and not dither then
fb.update_data.flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
fb.update_data.flags = 0
end
-- Did we request HW dithering?
if dither and fb.device:canHWDither() then
fb.update_data.flags = bor(fb.update_data.flags, C.MTK_EPDC_FLAG_USE_DITHERING_Y4)
end
if fb.swipe_animations then
-- The MTK Driver will crash if given a area smaller than the number of steps.
-- If direction is L/R and w is smaller or if it is U/D and h is smaller
-- Being as one genneraly will only want animations on a larger area, and I am too
-- lazy to test for direction, disable animations when w or h is less than steps.
if w >= fb.update_data.swipe_data.steps and h >= fb.update_data.swipe_data.steps then
fb.update_data.flags = bor(fb.update_data.flags, C.MTK_EPDC_FLAG_ENABLE_SWIPE)
end
fb.swipe_animations = false
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE_MTK, fb.update_data, is_flashing, waveform_mode, x, y, w, h, dither)
end
-- Enable swipe animations. They will be reset at the next refresh.
function framebuffer:_MTK_SetSwipeAnimations(enabled)
self.swipe_animations = enabled
end
-- Set the swipe animation direction. This is sticky
function framebuffer:_MTK_SetSwipeDirection(left)
local swipe_direction = left and C.MTK_SWIPE_LEFT or C.MTK_SWIPE_RIGHT
-- Account for KOReader rotation not matching the FB rotation with some bit trickery.
-- Alternatively here is a LUT:
-- local lut = { -- L R D U
-- { 2,3,0,1},
-- { 0,1,3,2},
-- { 3,2,1,0},
-- { 1,0,2,3}
-- }
-- swipe_direction = lut[self.cur_rotation_mode][swipe_direction]
local rota = self.cur_rotation_mode
if rota ~= self.DEVICE_ROTATED_UPRIGHT then
if rota == self.DEVICE_ROTATED_UPSIDE_DOWN then
swipe_direction = bxor(swipe_direction, 1)
elseif bor(swipe_direction, 1) == bor(rota, 1) then
swipe_direction = bxor(swipe_direction, 3)
else
swipe_direction = bxor(swipe_direction, 2)
end
end
--self.debug("MTK Rota:", rota, "Swipe:", swipe_direction, "Comp:", swipe_direction)
self.update_data.swipe_data.direction = swipe_direction
end
-- Don't let the driver silently upgrade to REAGL
function framebuffer:_MTK_ToggleFastMode(toggle)
local flags = ffi.new("uint32_t[1]", bor(C.UPDATE_FLAGS_FAST_MODE, toggle and C.UPDATE_FLAGS_MODE_FAST_FLAG or 0))
if C.ioctl(self.fd, C.MXCFB_SET_UPDATE_FLAGS_MTK, flags) == -1 then
local err = ffi.errno()
self.debug("MXCFB_SET_UPDATE_FLAGS_MTK ioctl failed:", ffi.string(C.strerror(err)))
end
end
local function refresh_kobo(fb, is_flashing, waveform_mode, x, y, w, h)
-- Enable the appropriate flag when requesting a REAGLD waveform (WAVEFORM_MODE_REAGLD on the Aura)
if waveform_mode == C.WAVEFORM_MODE_REAGLD then
fb.update_data.flags = C.EPDC_FLAG_USE_AAD
elseif waveform_mode == C.WAVEFORM_MODE_DU then
-- As well as when requesting an any->2bit waveform
fb.update_data.flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
fb.update_data.flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE_V1_NTX, fb.update_data, is_flashing, waveform_mode, x, y, w, h)
end
local function refresh_kobo_mk7(fb, is_flashing, waveform_mode, x, y, w, h, dither)
-- Did we request HW dithering?
if dither then
fb.update_data.dither_mode = C.EPDC_FLAG_USE_DITHERING_ORDERED
if waveform_mode == C.WAVEFORM_MODE_A2 or waveform_mode == C.WAVEFORM_MODE_DU then
fb.update_data.quant_bit = 1
else
fb.update_data.quant_bit = 7
end
else
fb.update_data.dither_mode = C.EPDC_FLAG_USE_DITHERING_PASSTHROUGH
fb.update_data.quant_bit = 0
end
-- Enable the appropriate flag when requesting an any->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 barely ever 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).
if waveform_mode == C.WAVEFORM_MODE_DU and not dither then
fb.update_data.flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
fb.update_data.flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE_V2, fb.update_data, is_flashing, waveform_mode, x, y, w, h, dither)
end
local function refresh_kobo_mtk(fb, is_flashing, waveform_mode, x, y, w, h, dither)
-- 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)
-- 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
--[[
-- Disable CFA processing on A2/DU
-- NOTE: Well, that leads to... interesting... results when used @ 32bpp...
-- The driver seems to have trouble choosing the right working buffer,
-- so you get to see a lot of weird crap ;).
if waveform_mode == C.HWTCON_WAVEFORM_MODE_A2 or waveform_mode == C.HWTCON_WAVEFORM_MODE_DU then
fb.update_data.flags = bor(fb.update_data.flags, C.HWTCON_FLAG_CFA_SKIP)
end
--]]
return mxc_update(fb, C.HWTCON_SEND_UPDATE, fb.update_data, is_flashing, waveform_mode, x, y, w, h, dither)
end
local function refresh_pocketbook(fb, is_flashing, waveform_mode, x, y, w, h)
-- TEMP_USE_AMBIENT, not that there was ever any other choice...
fb.update_data.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
fb.update_data.flags = C.EPDC_FLAG_USE_AAD
elseif waveform_mode == C.WAVEFORM_MODE_DU then
-- As well as when requesting an any->2bit waveform
--- @note: Much like on rM, it appears faking 24°C instead of relying on ambient temp leads to lower latency
fb.update_data.temp = 24
fb.update_data.flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
fb.update_data.flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE, fb.update_data, is_flashing, waveform_mode, x, y, w, h)
end
local function refresh_remarkable(fb, is_flashing, waveform_mode, x, y, w, h)
if waveform_mode == C.WAVEFORM_MODE_DU then
fb.update_data.temp = C.TEMP_USE_REMARKABLE
else
fb.update_data.temp = C.TEMP_USE_AMBIENT
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE, fb.update_data, is_flashing, waveform_mode, x, y, w, h)
end
local function refresh_sony_prstux(fb, is_flashing, waveform_mode, x, y, w, h)
return mxc_update(fb, C.MXCFB_SEND_UPDATE, fb.update_data, is_flashing, waveform_mode, x, y, w, h)
end
local function refresh_cervantes(fb, is_flashing, waveform_mode, x, y, w, h)
if waveform_mode == C.WAVEFORM_MODE_DU then
fb.update_data.flags = C.EPDC_FLAG_FORCE_MONOCHROME
else
fb.update_data.flags = 0
end
return mxc_update(fb, C.MXCFB_SEND_UPDATE, fb.update_data, is_flashing, 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)
self:mech_refresh(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, 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, 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, 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, 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, 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, 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 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)
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
-- 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
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
if self.device: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 self.device:isZelda() or self.device:isRex() then
if self.device:isZelda() then
self.mech_refresh = refresh_zelda
else
self.mech_refresh = refresh_rex
end
self.waveform_a2 = C.WAVEFORM_MODE_ZELDA_A2
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 self.device: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
-- NOTE: Despite the vastly different SoC, lab126 did the sane thing, and designed the new driver after the mxcfb API ;).
if self.device:isMTK() then
self.mech_refresh = refresh_mtk
self.waveform_a2 = C.MTK_WAVEFORM_MODE_A2
self.waveform_fast = C.MTK_WAVEFORM_MODE_DU
self.waveform_ui = C.WAVEFORM_MODE_AUTO
self.waveform_flashui = self.waveform_ui
self.waveform_reagl = C.MTK_WAVEFORM_MODE_GLR16
self.waveform_partial = self.waveform_reagl
-- NOTE: We switch to nightmode globally, via the grayscale vinfo fb flag,
-- as the driver uses that to make a few nightmode-specific decisions:
-- * Proper pattern color for the halftone grid
-- * Automatic GCK16/GLKW16/DUNM waveform mode selection
-- * Disable the silent REAGL updates in nightmode
-- * Use the proper waveform mode during animated swipes
-- * Make better decisions for AUTO in nightmode
self.waveform_night = C.MTK_WAVEFORM_MODE_GLKW16
self.night_is_reagl = true
self.waveform_flashnight = C.MTK_WAVEFORM_MODE_GCK16
self.setSwipeAnimations = self._MTK_SetSwipeAnimations
self.setSwipeDirection = self._MTK_SetSwipeDirection
end
-- Keep our data structures around, and setup constants
if self.mech_refresh == refresh_k51 then
self.update_data = ffi.new("struct mxcfb_update_data")
-- TEMP_USE_PAPYRUS on Touch/PW1, TEMP_USE_AUTO on PW2 (same value in both cases, 0x1001)
self.update_data.temp = C.TEMP_USE_AUTO
elseif self.mech_refresh == refresh_zelda then
self.update_data = ffi.new("struct mxcfb_update_data_zelda")
self.update_data.temp = C.TEMP_USE_AMBIENT
elseif self.mech_refresh == refresh_rex then
self.update_data = ffi.new("struct mxcfb_update_data_rex")
self.update_data.temp = C.TEMP_USE_AMBIENT
elseif self.mech_refresh == refresh_mtk then
self.update_data = ffi.new("struct mxcfb_update_data_mtk")
self.update_data.temp = C.TEMP_USE_AMBIENT
-- The stock reader uses 12. valid values are 1 - 60
self.update_data.swipe_data.steps = 12
end
if self.mech_wait_update_complete == kindle_pearl_mxc_wait_for_update_complete then
self.marker_data = ffi.new("uint32_t[1]")
elseif self.mech_wait_update_complete == kindle_carta_mxc_wait_for_update_complete then
self.marker_data = ffi.new("struct mxcfb_update_marker_data")
-- 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.
self.marker_data.collision_test = 0
end
if self.mech_wait_update_submission == kindle_mxc_wait_for_update_submission then
self.submission_data = ffi.new("uint32_t[1]")
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_a2 = C.WAVEFORM_MODE_A2
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
if self.device:isREAGL() then
self.waveform_reagl = C.WAVEFORM_MODE_REAGLD
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 self.device:isMk7() then
self.mech_refresh = refresh_kobo_mk7
if self.device:hasReliableMxcWaitFor() then
self.mech_wait_update_complete = kobo_mk7_mxc_wait_for_update_complete
else
-- Funky variant that will do this in pairs, like Nickel itself...
-- Spoiler alert: doesn't actually prevent timeouts.
-- Which means this branch and imp is basically for show,
-- as it'll be replaced by stub_mxc_wait_for_update_complete below...
self.mech_wait_update_complete = kobo_mk7_unreliable_mxc_wait_for_update_complete
end
self.waveform_partial = C.WAVEFORM_MODE_REAGL
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...
end
if self.device:hasEclipseWfm() then
self.waveform_night = C.WAVEFORM_MODE_GLKW16
self.waveform_flashnight = C.WAVEFORM_MODE_GCK16
end
-- If the (NXP) device has a sysfs knob to control the EPDC power, use it
if lfs.attributes("/sys/class/graphics/fb0/power_state", "mode") ~= nil then