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