/
framebuffer_linux.lua
291 lines (253 loc) · 9.92 KB
/
framebuffer_linux.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
local ffi = require("ffi")
local bit = require("bit")
local BB = require("ffi/blitbuffer")
local dummy = require("ffi/linux_fb_h")
local dummy = require("ffi/posix_h")
local framebuffer = {}
local framebuffer_mt = {__index={}}
-- Init our marker to 0, which happens to be an invalid value, so we can detect our first update
local update_marker = ffi.new("uint32_t[1]", 0)
local function einkfb_update(fb, refreshtype, waveform_mode, x, y, w, h)
local refarea = ffi.new("struct update_area_t[1]")
refarea[0].x1 = x or 0
refarea[0].y1 = y or 0
refarea[0].x2 = x + (w or (fb.vinfo.xres-x))
refarea[0].y2 = y + (h or (fb.vinfo.yres-y))
refarea[0].buffer = nil
if refreshtype == 0 then
refarea[0].which_fx = ffi.C.fx_update_partial
else
refarea[0].which_fx = ffi.C.fx_update_full
end
return ffi.C.ioctl(fb.fd, ffi.C.FBIO_EINK_UPDATE_DISPLAY_AREA, refarea);
end
local function mxc_new_update_marker()
-- Simply increment our current marker
local new_update_marker = ffi.new("uint32_t[1]", update_marker[0] + 1)
-- 1 to 16, strictly clamped.
if new_update_marker[0] > 16 or new_update_marker[0] < 1 then
new_update_marker[0] = 1
end
-- Keep track of it, and return it
update_marker[0] = new_update_marker[0]
return new_update_marker[0]
end
-- Kindle's MXCFB_WAIT_FOR_UPDATE_COMPLETE_PEARL == 0x4004462f
local function kindle_pearl_mxc_wait_for_update_complete(fb)
-- Wait for the previous update to be completed
return ffi.C.ioctl(fb.fd, ffi.C.MXCFB_WAIT_FOR_UPDATE_COMPLETE_PEARL, update_marker)
end
-- Kindle's MXCFB_WAIT_FOR_UPDATE_COMPLETE == 0xc008462f
local function kindle_carta_mxc_wait_for_update_complete(fb)
-- Wait for the previous update to be completed
local carta_update_marker = ffi.new("struct mxcfb_update_marker_data[1]")
carta_update_marker[0].update_marker = update_marker[0];
-- We're not using EPDC_FLAG_TEST_COLLISION, assume 0 is okay.
carta_update_marker[0].collision_test = 0;
return ffi.C.ioctl(fb.fd, ffi.C.MXCFB_WAIT_FOR_UPDATE_COMPLETE, carta_update_marker)
end
-- Kobo's MXCFB_WAIT_FOR_UPDATE_COMPLETE == 0x4004462f
-- Kindle's MXCFB_SEND_UPDATE == 0x4048462e | Kobo's MXCFB_SEND_UPDATE == 0x4044462e
local function mxc_update(fb, refarea, refreshtype, waveform_mode, x, y, w, h)
refarea[0].update_mode = refreshtype or 0
refarea[0].waveform_mode = waveform_mode or 2
refarea[0].update_region.left = x or 0
refarea[0].update_region.top = y or 0
refarea[0].update_region.width = w or fb.vinfo.xres
refarea[0].update_region.height = h or fb.vinfo.yres
-- Get a new update marker
refarea[0].update_marker = mxc_new_update_marker()
-- TODO: make the flag configurable from UI,
-- e.g., the EPDC_FLAG_ENABLE_INVERSION flag inverts all the pixels on display 09.01 2013 (houqp)
refarea[0].flags = 0
-- 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
return ffi.C.ioctl(fb.fd, ffi.C.MXCFB_SEND_UPDATE, refarea)
end
-- Kindle's MXCFB_WAIT_FOR_UPDATE_SUBMISSION == 0x40044637
local function kindle_mxc_wait_for_update_submission(fb)
-- Wait for the current (the one we just sent) update to be submitted
return ffi.C.ioctl(fb.fd, ffi.C.MXCFB_WAIT_FOR_UPDATE_SUBMISSION, update_marker)
end
local function k51_update(fb, refreshtype, waveform_mode, x, y, w, h)
local refarea = ffi.new("struct mxcfb_update_data[1]")
-- only for Amazon's driver (NOTE: related to debugPaint prefbw & prefgray?):
refarea[0].hist_bw_waveform_mode = 0
refarea[0].hist_gray_waveform_mode = 0
-- TEMP_USE_PAPYRUS on Touch/PW1, TEMP_USE_AUTO on PW2
refarea[0].temp = 0x1001
return mxc_update(fb, refarea, refreshtype, waveform_mode, x, y, w, h)
end
local function kobo_update(fb, refreshtype, waveform_mode, x, y, w, h)
local refarea = ffi.new("struct mxcfb_update_data[1]")
-- only for Kobo's driver:
refarea[0].alt_buffer_data.virt_addr = nil
-- TEMP_USE_AMBIENT
refarea[0].temp = 0x1000
return mxc_update(fb, refarea, refreshtype, waveform_mode, x, y, w, h)
end
function framebuffer.open(device)
local fb = {
fd = -1,
finfo = ffi.new("struct fb_fix_screeninfo"),
vinfo = ffi.new("struct fb_var_screeninfo"),
fb_size = -1,
einkWaitForCompleteFunc = nil,
einkUpdateFunc = nil,
einkWaitForSubmissionFunc = nil,
wait_for_full_updates = false,
wait_for_every_updates = false,
bb = nil,
data = nil
}
setmetatable(fb, framebuffer_mt)
fb.fd = ffi.C.open(device, ffi.C.O_RDWR)
assert(fb.fd ~= -1, "cannot open framebuffer")
-- Get fixed screen information
assert(ffi.C.ioctl(fb.fd, ffi.C.FBIOGET_FSCREENINFO, fb.finfo) == 0,
"cannot get screen info")
assert(ffi.C.ioctl(fb.fd, ffi.C.FBIOGET_VSCREENINFO, fb.vinfo) == 0,
"cannot get variable screen info")
assert(fb.finfo.type == ffi.C.FB_TYPE_PACKED_PIXELS,
"video type not supported")
--Kindle Paperwhite doesn't set this properly?
--assert(fb.vinfo.grayscale == 0, "only grayscale is supported but framebuffer says it isn't")
assert(fb.vinfo.xres_virtual > 0 and fb.vinfo.yres_virtual > 0, "invalid framebuffer resolution")
-- it seems that fb.finfo.smem_len is unreliable on kobo
-- Figure out the size of the screen in bytes
fb.fb_size = fb.vinfo.xres_virtual * fb.vinfo.yres_virtual * fb.vinfo.bits_per_pixel / 8
fb.data = ffi.C.mmap(nil, fb.fb_size, bit.bor(ffi.C.PROT_READ, ffi.C.PROT_WRITE), ffi.C.MAP_SHARED, fb.fd, 0)
assert(fb.data ~= ffi.C.MAP_FAILED, "can not mmap() framebuffer")
if ffi.string(fb.finfo.id, 11) == "mxc_epdc_fb" then
-- TODO: implement a better check for Kobo
if fb.vinfo.bits_per_pixel == 16 then
-- this ought to be a Kobo
local dummy = require("ffi/mxcfb_kobo_h")
fb.einkUpdateFunc = kobo_update
fb.bb = BB.new(fb.vinfo.xres, fb.vinfo.yres, BB.TYPE_BBRGB16, fb.data, fb.finfo.line_length)
fb.bb:invert()
if fb.vinfo.xres > fb.vinfo.yres then
-- Kobo framebuffers need to be rotated counter-clockwise (they start in landscape mode)
fb.bb:rotate(-90)
end
elseif fb.vinfo.bits_per_pixel == 8 then
-- Kindle PaperWhite and KT with 5.1 or later firmware
local dummy = require("ffi/mxcfb_kindle_h")
-- NOTE: We need to differentiate the PW2 from the Touch/PW1... I hope this check is solid enough... (cf #550).
if fb.finfo.smem_len == 3145728 then
-- We're a PW2! Use the correct function, and ask to wait for every update.
fb.wait_for_every_updates = true
fb.einkWaitForCompleteFunc = kindle_carta_mxc_wait_for_update_complete
elseif fb.finfo.smem_len == 2179072 or fb.finfo.smem_len == 4718592 then
-- We're a Touch/PW1
fb.wait_for_full_updates = true
fb.einkWaitForCompleteFunc = kindle_pearl_mxc_wait_for_update_complete
else
error("unknown smem_len value for the Kindle mxc eink driver")
end
fb.einkUpdateFunc = k51_update
fb.einkWaitForSubmissionFunc = kindle_mxc_wait_for_update_submission
fb.bb = BB.new(fb.vinfo.xres, fb.vinfo.yres, BB.TYPE_BB8, fb.data, fb.finfo.line_length)
fb.bb:invert()
else
error("unknown bpp value for the mxc eink driver")
end
elseif ffi.string(fb.finfo.id, 7) == "eink_fb" then
local dummy = require("ffi/einkfb_h")
fb.einkUpdateFunc = einkfb_update
if fb.vinfo.bits_per_pixel == 8 then
fb.bb = BB.new(fb.vinfo.xres, fb.vinfo.yres, BB.TYPE_BB8, fb.data, fb.finfo.line_length)
elseif fb.vinfo.bits_per_pixel == 4 then
fb.bb = BB.new(fb.vinfo.xres, fb.vinfo.yres, BB.TYPE_BB4, fb.data, fb.finfo.line_length)
else
error("unknown bpp value for the classic eink driver")
end
else
error("eink model not supported");
end
return fb
end
function framebuffer_mt:getOrientation()
local mode = ffi.new("int[1]")
ffi.C.ioctl(self.fd, ffi.C.FBIO_EINK_GET_DISPLAY_ORIENTATION, mode)
-- adjust ioctl's rotate mode definition to KPV's
-- refer to screen.lua
if mode == 2 then
return 1
elseif mode == 1 then
return 2
end
return mode
end
function framebuffer_mt:setOrientation(mode)
mode = ffi.cast("int", mode or 0)
if mode < 0 or mode > 3 then
error("Wrong rotation mode given!")
end
--[[
ioctl has a different definition for rotation mode.
1
+--------------+
| +----------+ |
| | | |
| | Freedom! | |
| | | |
| | | |
3 | | | | 2
| | | |
| | | |
| +----------+ |
| |
| |
+--------------+
0
--]]
if mode == 1 then
mode = 2
elseif mode == 2 then
mode = 1
end
ffi.C.ioctl(self.fd, ffi.C.FBIO_EINK_SET_DISPLAY_ORIENTATION, mode)
end
function framebuffer_mt.__index:refresh(refreshtype, waveform_mode, x, y, w, h)
-- The Touch/PW1 only do this for full updates
if refreshtype == 1 and self.wait_for_full_updates or self.wait_for_every_updates then
-- Start by checking that our previous update has completed
if self.einkWaitForCompleteFunc then
-- We have nothing to check on our first refresh() call!
if update_marker[0] ~= 0 then
self:einkWaitForCompleteFunc()
end
end
end
w, x = BB.checkBounds(w or self.bb:getWidth(), x or 0, 0, self.bb:getWidth(), 0xFFFF)
h, y = BB.checkBounds(h or self.bb:getHeight(), y or 0, 0, self.bb:getHeight(), 0xFFFF)
x, y, w, h = self.bb:getPhysicalRect(x, y, w, h)
self:einkUpdateFunc(refreshtype, waveform_mode, x, y, w, h)
-- Finish by waiting for our curren tupdate to be submitted
if refreshtype == 1 and self.wait_for_full_updates or self.wait_for_every_updates then
if self.einkWaitForSubmissionFunc then
self:einkWaitForSubmissionFunc()
end
end
end
function framebuffer_mt.__index:getSize()
return self.bb:getWidth(), self.bb:getHeight()
end
function framebuffer_mt.__index:getPitch()
return self.bb.pitch
end
function framebuffer_mt.__index:close()
ffi.C.munmap(self.data, self.fb_size)
ffi.C.close(self.fd)
self.fd = -1
self.data = nil
self.bb = nil
end
return framebuffer