1 /**
2 * @file lv_nuttx_fbdev.c
3 *
4 */
5
6 /*********************
7 * INCLUDES
8 *********************/
9 #include "lv_nuttx_fbdev.h"
10 #if LV_USE_NUTTX
11
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <stddef.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17 #include <errno.h>
18 #include <poll.h>
19 #include <sys/mman.h>
20 #include <sys/ioctl.h>
21 #include <nuttx/video/fb.h>
22
23 #include "../../../lvgl.h"
24 #include "../../lvgl_private.h"
25
26 /*********************
27 * DEFINES
28 *********************/
29
30 /**********************
31 * TYPEDEFS
32 **********************/
33
34 typedef struct {
35 /* fd should be defined at the beginning */
36 int fd;
37 struct fb_videoinfo_s vinfo;
38 struct fb_planeinfo_s pinfo;
39
40 void * mem;
41 void * mem2;
42 void * mem_off_screen;
43 uint32_t mem2_yoffset;
44
45 lv_draw_buf_t buf1;
46 lv_draw_buf_t buf2;
47 } lv_nuttx_fb_t;
48
49 /**********************
50 * STATIC PROTOTYPES
51 **********************/
52
53 static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p);
54 static lv_color_format_t fb_fmt_to_color_format(int fmt);
55 static int fbdev_get_pinfo(int fd, struct fb_planeinfo_s * pinfo);
56 static int fbdev_init_mem2(lv_nuttx_fb_t * dsc);
57 static void display_refr_timer_cb(lv_timer_t * tmr);
58 static void display_release_cb(lv_event_t * e);
59 #if defined(CONFIG_FB_UPDATE)
60 static void fbdev_join_inv_areas(lv_display_t * disp, lv_area_t * final_inv_area);
61 #endif
62
63 /**********************
64 * STATIC VARIABLES
65 **********************/
66
67 /**********************
68 * MACROS
69 **********************/
70
71 /**********************
72 * GLOBAL FUNCTIONS
73 **********************/
74
lv_nuttx_fbdev_create(void)75 lv_display_t * lv_nuttx_fbdev_create(void)
76 {
77 lv_nuttx_fb_t * dsc = lv_malloc_zeroed(sizeof(lv_nuttx_fb_t));
78 LV_ASSERT_MALLOC(dsc);
79 if(dsc == NULL) return NULL;
80
81 lv_display_t * disp = lv_display_create(800, 480);
82 if(disp == NULL) {
83 lv_free(dsc);
84 return NULL;
85 }
86 dsc->fd = -1;
87 lv_display_set_driver_data(disp, dsc);
88 lv_display_add_event_cb(disp, display_release_cb, LV_EVENT_DELETE, disp);
89 lv_display_set_flush_cb(disp, flush_cb);
90 return disp;
91 }
92
lv_nuttx_fbdev_set_file(lv_display_t * disp,const char * file)93 int lv_nuttx_fbdev_set_file(lv_display_t * disp, const char * file)
94 {
95 int ret;
96 LV_ASSERT(disp && file);
97 lv_nuttx_fb_t * dsc = lv_display_get_driver_data(disp);
98
99 if(dsc->fd >= 0) close(dsc->fd);
100
101 /* Open the file for reading and writing*/
102
103 dsc->fd = open(file, O_RDWR);
104 if(dsc->fd < 0) {
105 LV_LOG_ERROR("Error: cannot open framebuffer device");
106 return -errno;
107 }
108 LV_LOG_USER("The framebuffer device was opened successfully");
109
110 if(ioctl(dsc->fd, FBIOGET_VIDEOINFO, (unsigned long)((uintptr_t)&dsc->vinfo)) < 0) {
111 LV_LOG_ERROR("ioctl(FBIOGET_VIDEOINFO) failed: %d", errno);
112 ret = -errno;
113 goto errout;
114 }
115
116 LV_LOG_USER("VideoInfo:");
117 LV_LOG_USER(" fmt: %u", dsc->vinfo.fmt);
118 LV_LOG_USER(" xres: %u", dsc->vinfo.xres);
119 LV_LOG_USER(" yres: %u", dsc->vinfo.yres);
120 LV_LOG_USER(" nplanes: %u", dsc->vinfo.nplanes);
121
122 if((ret = fbdev_get_pinfo(dsc->fd, &dsc->pinfo)) < 0) {
123 goto errout;
124 }
125
126 lv_color_format_t color_format = fb_fmt_to_color_format(dsc->vinfo.fmt);
127 if(color_format == LV_COLOR_FORMAT_UNKNOWN) {
128 goto errout;
129 }
130
131 dsc->mem = mmap(NULL, dsc->pinfo.fblen, PROT_READ | PROT_WRITE,
132 MAP_SHARED | MAP_FILE, dsc->fd, 0);
133 if(dsc->mem == MAP_FAILED) {
134 LV_LOG_ERROR("ioctl(FBIOGET_PLANEINFO) failed: %d", errno);
135 ret = -errno;
136 goto errout;
137 }
138
139 uint32_t w = dsc->vinfo.xres;
140 uint32_t h = dsc->vinfo.yres;
141 uint32_t stride = dsc->pinfo.stride;
142 uint32_t data_size = h * stride;
143 lv_draw_buf_init(&dsc->buf1, w, h, color_format, stride, dsc->mem, data_size);
144
145 /* Check buffer mode */
146 bool double_buffer = dsc->pinfo.yres_virtual == (dsc->vinfo.yres * 2);
147 if(double_buffer) {
148 if((ret = fbdev_init_mem2(dsc)) < 0) {
149 goto errout;
150 }
151
152 lv_draw_buf_init(&dsc->buf2, w, h, color_format, stride, dsc->mem2, data_size);
153 lv_display_set_draw_buffers(disp, &dsc->buf1, &dsc->buf2);
154 }
155 else {
156 dsc->mem_off_screen = malloc(data_size);
157 LV_ASSERT_MALLOC(dsc->mem_off_screen);
158 if(!dsc->mem_off_screen) {
159 ret = -ENOMEM;
160 LV_LOG_ERROR("Failed to allocate memory for off-screen buffer");
161 goto errout;
162 }
163
164 LV_LOG_USER("Use off-screen mode, memory: %p, size: %" LV_PRIu32, dsc->mem_off_screen, data_size);
165 lv_draw_buf_init(&dsc->buf2, w, h, color_format, stride, dsc->mem_off_screen, data_size);
166 lv_display_set_draw_buffers(disp, &dsc->buf2, NULL);
167 }
168
169 lv_display_set_color_format(disp, color_format);
170 lv_display_set_render_mode(disp, LV_DISPLAY_RENDER_MODE_DIRECT);
171 lv_display_set_resolution(disp, dsc->vinfo.xres, dsc->vinfo.yres);
172 lv_timer_set_cb(disp->refr_timer, display_refr_timer_cb);
173
174 LV_LOG_USER("Resolution is set to %dx%d at %" LV_PRId32 "dpi",
175 dsc->vinfo.xres, dsc->vinfo.yres, lv_display_get_dpi(disp));
176 return 0;
177
178 errout:
179 close(dsc->fd);
180 dsc->fd = -1;
181 return ret;
182 }
183
184 /**********************
185 * STATIC FUNCTIONS
186 **********************/
187
188 #if defined(CONFIG_FB_UPDATE)
fbdev_join_inv_areas(lv_display_t * disp,lv_area_t * final_inv_area)189 static void fbdev_join_inv_areas(lv_display_t * disp, lv_area_t * final_inv_area)
190 {
191 uint16_t inv_index;
192
193 bool area_joined = false;
194
195 for(inv_index = 0; inv_index < disp->inv_p; inv_index++) {
196 if(disp->inv_area_joined[inv_index] == 0) {
197 const lv_area_t * area_p = &disp->inv_areas[inv_index];
198
199 /* Join to final_area */
200
201 if(!area_joined) {
202 /* copy first area */
203 lv_area_copy(final_inv_area, area_p);
204 area_joined = true;
205 }
206 else {
207 lv_area_join(final_inv_area,
208 final_inv_area,
209 area_p);
210 }
211 }
212 }
213 }
214 #endif
215
display_refr_timer_cb(lv_timer_t * tmr)216 static void display_refr_timer_cb(lv_timer_t * tmr)
217 {
218 lv_display_t * disp = lv_timer_get_user_data(tmr);
219 lv_nuttx_fb_t * dsc = lv_display_get_driver_data(disp);
220 struct pollfd pfds[1];
221
222 lv_memzero(pfds, sizeof(pfds));
223 pfds[0].fd = dsc->fd;
224 pfds[0].events = POLLOUT;
225
226 /* Query free fb to draw */
227
228 if(poll(pfds, 1, 0) < 0) {
229 return;
230 }
231
232 if(pfds[0].revents & POLLOUT) {
233 lv_display_refr_timer(tmr);
234 }
235 }
236
flush_cb(lv_display_t * disp,const lv_area_t * area,uint8_t * color_p)237 static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p)
238 {
239 LV_UNUSED(color_p);
240 lv_nuttx_fb_t * dsc = lv_display_get_driver_data(disp);
241
242 if(dsc->mem_off_screen) {
243 /* When rendering in off-screen mode, copy the drawing buffer to fb */
244 /* buf2(off-screen buffer) -> buf1(fbmem)*/
245 lv_draw_buf_copy(&dsc->buf1, area, &dsc->buf2, area);
246 }
247
248 /* Skip the non-last flush */
249
250 if(!lv_display_flush_is_last(disp)) {
251 lv_display_flush_ready(disp);
252 return;
253 }
254
255 #if defined(CONFIG_FB_UPDATE)
256 /*May be some direct update command is required*/
257 int yoffset = disp->buf_act == disp->buf_1 ?
258 0 : dsc->mem2_yoffset;
259
260 /* Join the areas to update */
261 lv_area_t final_inv_area;
262 lv_memzero(&final_inv_area, sizeof(final_inv_area));
263 fbdev_join_inv_areas(disp, &final_inv_area);
264
265 struct fb_area_s fb_area;
266 fb_area.x = final_inv_area.x1;
267 fb_area.y = final_inv_area.y1 + yoffset;
268 fb_area.w = lv_area_get_width(&final_inv_area);
269 fb_area.h = lv_area_get_height(&final_inv_area);
270 if(ioctl(dsc->fd, FBIO_UPDATE, (unsigned long)((uintptr_t)&fb_area)) < 0) {
271 LV_LOG_ERROR("ioctl(FBIO_UPDATE) failed: %d", errno);
272 }
273 #endif
274
275 /* double framebuffer */
276
277 if(dsc->mem2 != NULL) {
278 if(disp->buf_act == disp->buf_1) {
279 dsc->pinfo.yoffset = 0;
280 }
281 else {
282 dsc->pinfo.yoffset = dsc->mem2_yoffset;
283 }
284
285 if(ioctl(dsc->fd, FBIOPAN_DISPLAY, (unsigned long)((uintptr_t) & (dsc->pinfo))) < 0) {
286 LV_LOG_ERROR("ioctl(FBIOPAN_DISPLAY) failed: %d", errno);
287 }
288 }
289 lv_display_flush_ready(disp);
290 }
291
fb_fmt_to_color_format(int fmt)292 static lv_color_format_t fb_fmt_to_color_format(int fmt)
293 {
294 switch(fmt) {
295 case FB_FMT_RGB16_565:
296 return LV_COLOR_FORMAT_RGB565;
297 case FB_FMT_RGB24:
298 return LV_COLOR_FORMAT_RGB888;
299 case FB_FMT_RGB32:
300 return LV_COLOR_FORMAT_XRGB8888;
301 case FB_FMT_RGBA32:
302 return LV_COLOR_FORMAT_ARGB8888;
303 default:
304 break;
305 }
306
307 LV_LOG_ERROR("Unsupported color format: %d", fmt);
308
309 return LV_COLOR_FORMAT_UNKNOWN;
310 }
311
fbdev_get_pinfo(int fd,FAR struct fb_planeinfo_s * pinfo)312 static int fbdev_get_pinfo(int fd, FAR struct fb_planeinfo_s * pinfo)
313 {
314 if(ioctl(fd, FBIOGET_PLANEINFO, (unsigned long)((uintptr_t)pinfo)) < 0) {
315 LV_LOG_ERROR("ERROR: ioctl(FBIOGET_PLANEINFO) failed: %d", errno);
316 return -errno;
317 }
318
319 LV_LOG_USER("PlaneInfo (plane %d):", pinfo->display);
320 LV_LOG_USER(" mem: %p", pinfo->fbmem);
321 LV_LOG_USER(" fblen: %zu", pinfo->fblen);
322 LV_LOG_USER(" stride: %u", pinfo->stride);
323 LV_LOG_USER(" display: %u", pinfo->display);
324 LV_LOG_USER(" bpp: %u", pinfo->bpp);
325
326 return 0;
327 }
328
fbdev_init_mem2(lv_nuttx_fb_t * dsc)329 static int fbdev_init_mem2(lv_nuttx_fb_t * dsc)
330 {
331 uintptr_t buf_offset;
332 struct fb_planeinfo_s pinfo;
333 int ret;
334
335 lv_memzero(&pinfo, sizeof(pinfo));
336
337 /* Get display[1] planeinfo */
338
339 pinfo.display = dsc->pinfo.display + 1;
340
341 if((ret = fbdev_get_pinfo(dsc->fd, &pinfo)) < 0) {
342 return ret;
343 }
344
345 /* Check bpp */
346
347 if(pinfo.bpp != dsc->pinfo.bpp) {
348 LV_LOG_WARN("mem2 is incorrect");
349 return -EINVAL;
350 }
351
352 /* Check the buffer address offset,
353 * It needs to be divisible by pinfo.stride
354 */
355
356 buf_offset = pinfo.fbmem - dsc->mem;
357
358 if((buf_offset % dsc->pinfo.stride) != 0) {
359 LV_LOG_WARN("It is detected that buf_offset(%" PRIuPTR ") "
360 "and stride(%d) are not divisible, please ensure "
361 "that the driver handles the address offset by itself.",
362 buf_offset, dsc->pinfo.stride);
363 }
364
365 /* Calculate the address and yoffset of mem2 */
366
367 if(buf_offset == 0) {
368 dsc->mem2_yoffset = dsc->vinfo.yres;
369 dsc->mem2 = pinfo.fbmem + dsc->mem2_yoffset * pinfo.stride;
370 LV_LOG_USER("Use consecutive mem2 = %p, yoffset = %" LV_PRIu32,
371 dsc->mem2, dsc->mem2_yoffset);
372 }
373 else {
374 dsc->mem2_yoffset = buf_offset / dsc->pinfo.stride;
375 dsc->mem2 = pinfo.fbmem;
376 LV_LOG_USER("Use non-consecutive mem2 = %p, yoffset = %" LV_PRIu32,
377 dsc->mem2, dsc->mem2_yoffset);
378 }
379
380 return 0;
381 }
382
display_release_cb(lv_event_t * e)383 static void display_release_cb(lv_event_t * e)
384 {
385 lv_display_t * disp = (lv_display_t *) lv_event_get_user_data(e);
386 lv_nuttx_fb_t * dsc = lv_display_get_driver_data(disp);
387 if(dsc) {
388 lv_display_set_driver_data(disp, NULL);
389 lv_display_set_flush_cb(disp, NULL);
390
391 if(dsc->fd >= 0) {
392 close(dsc->fd);
393 dsc->fd = -1;
394 }
395
396 if(dsc->mem_off_screen) {
397 /* Free the off-screen buffer */
398 free(dsc->mem_off_screen);
399 dsc->mem_off_screen = NULL;
400 }
401
402 lv_free(dsc);
403 }
404 LV_LOG_USER("Done");
405 }
406
407 #endif /*LV_USE_NUTTX*/
408