1 /**
2  * @file lv_linux_fbdev.c
3  *
4  */
5 
6 /*********************
7  *      INCLUDES
8  *********************/
9 #include "lv_linux_fbdev.h"
10 #if LV_USE_LINUX_FBDEV
11 
12 #include <stdlib.h>
13 #include <unistd.h>
14 #include <stddef.h>
15 #include <stdio.h>
16 #include <fcntl.h>
17 #include <sys/mman.h>
18 #include <sys/ioctl.h>
19 #include <time.h>
20 
21 #if LV_LINUX_FBDEV_BSD
22     #include <sys/fcntl.h>
23     #include <sys/consio.h>
24     #include <sys/fbio.h>
25 #else
26     #include <linux/fb.h>
27 #endif /* LV_LINUX_FBDEV_BSD */
28 
29 #include "../../../display/lv_display_private.h"
30 #include "../../../draw/sw/lv_draw_sw.h"
31 
32 /*********************
33  *      DEFINES
34  *********************/
35 
36 /**********************
37  *      TYPEDEFS
38  **********************/
39 struct bsd_fb_var_info {
40     uint32_t xoffset;
41     uint32_t yoffset;
42     uint32_t xres;
43     uint32_t yres;
44     int bits_per_pixel;
45 };
46 
47 struct bsd_fb_fix_info {
48     long int line_length;
49     long int smem_len;
50 };
51 
52 typedef struct {
53     const char * devname;
54     lv_color_format_t color_format;
55 #if LV_LINUX_FBDEV_BSD
56     struct bsd_fb_var_info vinfo;
57     struct bsd_fb_fix_info finfo;
58 #else
59     struct fb_var_screeninfo vinfo;
60     struct fb_fix_screeninfo finfo;
61 #endif /* LV_LINUX_FBDEV_BSD */
62     char * fbp;
63     uint8_t * rotated_buf;
64     size_t rotated_buf_size;
65     long int screensize;
66     int fbfd;
67     bool force_refresh;
68 } lv_linux_fb_t;
69 
70 /**********************
71  *  STATIC PROTOTYPES
72  **********************/
73 
74 static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p);
75 static uint32_t tick_get_cb(void);
76 
77 /**********************
78  *  STATIC VARIABLES
79  **********************/
80 
81 /**********************
82  *      MACROS
83  **********************/
84 
85 #if LV_LINUX_FBDEV_BSD
86     #define FBIOBLANK FBIO_BLANK
87 #endif /* LV_LINUX_FBDEV_BSD */
88 
89 #ifndef DIV_ROUND_UP
90     #define DIV_ROUND_UP(n, d) (((n) + (d) - 1) / (d))
91 #endif
92 
93 /**********************
94  *   GLOBAL FUNCTIONS
95  **********************/
96 
lv_linux_fbdev_create(void)97 lv_display_t * lv_linux_fbdev_create(void)
98 {
99     lv_tick_set_cb(tick_get_cb);
100 
101     lv_linux_fb_t * dsc = lv_malloc_zeroed(sizeof(lv_linux_fb_t));
102     LV_ASSERT_MALLOC(dsc);
103     if(dsc == NULL) return NULL;
104 
105     lv_display_t * disp = lv_display_create(800, 480);
106     if(disp == NULL) {
107         lv_free(dsc);
108         return NULL;
109     }
110     dsc->fbfd = -1;
111     lv_display_set_driver_data(disp, dsc);
112     lv_display_set_flush_cb(disp, flush_cb);
113 
114     return disp;
115 }
116 
lv_linux_fbdev_set_file(lv_display_t * disp,const char * file)117 void lv_linux_fbdev_set_file(lv_display_t * disp, const char * file)
118 {
119     char * devname = lv_malloc(lv_strlen(file) + 1);
120     LV_ASSERT_MALLOC(devname);
121     if(devname == NULL) return;
122     lv_strcpy(devname, file);
123 
124     lv_linux_fb_t * dsc = lv_display_get_driver_data(disp);
125     dsc->devname = devname;
126 
127     if(dsc->fbfd > 0) close(dsc->fbfd);
128 
129     /* Open the file for reading and writing*/
130     dsc->fbfd = open(dsc->devname, O_RDWR);
131     if(dsc->fbfd == -1) {
132         perror("Error: cannot open framebuffer device");
133         return;
134     }
135     LV_LOG_INFO("The framebuffer device was opened successfully");
136 
137     /* Make sure that the display is on.*/
138     if(ioctl(dsc->fbfd, FBIOBLANK, FB_BLANK_UNBLANK) != 0) {
139         perror("ioctl(FBIOBLANK)");
140         /* Don't return. Some framebuffer drivers like efifb or simplefb don't implement FBIOBLANK.*/
141     }
142 
143 #if LV_LINUX_FBDEV_BSD
144     struct fbtype fb;
145     unsigned line_length;
146 
147     /*Get fb type*/
148     if(ioctl(dsc->fbfd, FBIOGTYPE, &fb) != 0) {
149         perror("ioctl(FBIOGTYPE)");
150         return;
151     }
152 
153     /*Get screen width*/
154     if(ioctl(dsc->fbfd, FBIO_GETLINEWIDTH, &line_length) != 0) {
155         perror("ioctl(FBIO_GETLINEWIDTH)");
156         return;
157     }
158 
159     dsc->vinfo.xres = (unsigned) fb.fb_width;
160     dsc->vinfo.yres = (unsigned) fb.fb_height;
161     dsc->vinfo.bits_per_pixel = fb.fb_depth;
162     dsc->vinfo.xoffset = 0;
163     dsc->vinfo.yoffset = 0;
164     dsc->finfo.line_length = line_length;
165     dsc->finfo.smem_len = dsc->finfo.line_length * dsc->vinfo.yres;
166 #else /* LV_LINUX_FBDEV_BSD */
167 
168     /* Get fixed screen information*/
169     if(ioctl(dsc->fbfd, FBIOGET_FSCREENINFO, &dsc->finfo) == -1) {
170         perror("Error reading fixed information");
171         return;
172     }
173 
174     /* Get variable screen information*/
175     if(ioctl(dsc->fbfd, FBIOGET_VSCREENINFO, &dsc->vinfo) == -1) {
176         perror("Error reading variable information");
177         return;
178     }
179 #endif /* LV_LINUX_FBDEV_BSD */
180 
181     LV_LOG_INFO("%dx%d, %dbpp", dsc->vinfo.xres, dsc->vinfo.yres, dsc->vinfo.bits_per_pixel);
182 
183     /* Figure out the size of the screen in bytes*/
184     dsc->screensize =  dsc->finfo.smem_len;/*finfo.line_length * vinfo.yres;*/
185 
186     /* Map the device to memory*/
187     dsc->fbp = (char *)mmap(0, dsc->screensize, PROT_READ | PROT_WRITE, MAP_SHARED, dsc->fbfd, 0);
188     if((intptr_t)dsc->fbp == -1) {
189         perror("Error: failed to map framebuffer device to memory");
190         return;
191     }
192 
193     /* Don't initialise the memory to retain what's currently displayed / avoid clearing the screen.
194      * This is important for applications that only draw to a subsection of the full framebuffer.*/
195 
196     LV_LOG_INFO("The framebuffer device was mapped to memory successfully");
197 
198     switch(dsc->vinfo.bits_per_pixel) {
199         case 16:
200             lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB565);
201             break;
202         case 24:
203             lv_display_set_color_format(disp, LV_COLOR_FORMAT_RGB888);
204             break;
205         case 32:
206             lv_display_set_color_format(disp, LV_COLOR_FORMAT_XRGB8888);
207             break;
208         default:
209             LV_LOG_WARN("Not supported color format (%d bits)", dsc->vinfo.bits_per_pixel);
210             return;
211     }
212 
213     int32_t hor_res = dsc->vinfo.xres;
214     int32_t ver_res = dsc->vinfo.yres;
215     int32_t width = dsc->vinfo.width;
216     uint32_t draw_buf_size = hor_res * (dsc->vinfo.bits_per_pixel >> 3);
217     if(LV_LINUX_FBDEV_RENDER_MODE == LV_DISPLAY_RENDER_MODE_PARTIAL) {
218         draw_buf_size *= LV_LINUX_FBDEV_BUFFER_SIZE;
219     }
220     else {
221         draw_buf_size *= ver_res;
222     }
223 
224     uint8_t * draw_buf = NULL;
225     uint8_t * draw_buf_2 = NULL;
226     draw_buf = malloc(draw_buf_size);
227 
228     if(LV_LINUX_FBDEV_BUFFER_COUNT == 2) {
229         draw_buf_2 = malloc(draw_buf_size);
230     }
231 
232     lv_display_set_resolution(disp, hor_res, ver_res);
233     lv_display_set_buffers(disp, draw_buf, draw_buf_2, draw_buf_size, LV_LINUX_FBDEV_RENDER_MODE);
234 
235     if(width > 0) {
236         lv_display_set_dpi(disp, DIV_ROUND_UP(hor_res * 254, width * 10));
237     }
238 
239     LV_LOG_INFO("Resolution is set to %" LV_PRId32 "x%" LV_PRId32 " at %" LV_PRId32 "dpi",
240                 hor_res, ver_res, lv_display_get_dpi(disp));
241 }
242 
lv_linux_fbdev_set_force_refresh(lv_display_t * disp,bool enabled)243 void lv_linux_fbdev_set_force_refresh(lv_display_t * disp, bool enabled)
244 {
245     lv_linux_fb_t * dsc = lv_display_get_driver_data(disp);
246     dsc->force_refresh = enabled;
247 }
248 
249 /**********************
250  *   STATIC FUNCTIONS
251  **********************/
252 
flush_cb(lv_display_t * disp,const lv_area_t * area,uint8_t * color_p)253 static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p)
254 {
255     lv_linux_fb_t * dsc = lv_display_get_driver_data(disp);
256 
257     if(dsc->fbp == NULL) {
258         lv_display_flush_ready(disp);
259         return;
260     }
261 
262     int32_t w = lv_area_get_width(area);
263     int32_t h = lv_area_get_height(area);
264     lv_color_format_t cf = lv_display_get_color_format(disp);
265     uint32_t px_size = lv_color_format_get_size(cf);
266 
267     lv_area_t rotated_area;
268     lv_display_rotation_t rotation = lv_display_get_rotation(disp);
269 
270     /* Not all framebuffer kernel drivers support hardware rotation, so we need to handle it in software here */
271     if(rotation != LV_DISPLAY_ROTATION_0 && LV_LINUX_FBDEV_RENDER_MODE == LV_DISPLAY_RENDER_MODE_PARTIAL) {
272         /* (Re)allocate temporary buffer if needed */
273         size_t buf_size = w * h * px_size;
274         if(!dsc->rotated_buf || dsc->rotated_buf_size != buf_size) {
275             dsc->rotated_buf = realloc(dsc->rotated_buf, buf_size);
276             dsc->rotated_buf_size = buf_size;
277         }
278 
279         /* Rotate the pixel buffer */
280         uint32_t w_stride = lv_draw_buf_width_to_stride(w, cf);
281         uint32_t h_stride = lv_draw_buf_width_to_stride(h, cf);
282 
283         switch(rotation) {
284             case LV_DISPLAY_ROTATION_0:
285                 break;
286             case LV_DISPLAY_ROTATION_90:
287                 lv_draw_sw_rotate(color_p, dsc->rotated_buf, w, h, w_stride, h_stride, rotation, cf);
288                 break;
289             case LV_DISPLAY_ROTATION_180:
290                 lv_draw_sw_rotate(color_p, dsc->rotated_buf, w, h, w_stride, w_stride, rotation, cf);
291                 break;
292             case LV_DISPLAY_ROTATION_270:
293                 lv_draw_sw_rotate(color_p, dsc->rotated_buf, w, h, w_stride, h_stride, rotation, cf);
294                 break;
295         }
296         color_p = dsc->rotated_buf;
297 
298         /* Rotate the area */
299         rotated_area = *area;
300         lv_display_rotate_area(disp, &rotated_area);
301         area = &rotated_area;
302 
303         if(rotation != LV_DISPLAY_ROTATION_180) {
304             w = lv_area_get_width(area);
305             h = lv_area_get_height(area);
306         }
307     }
308 
309     /* Ensure that we're within the framebuffer's bounds */
310     if(area->x2 < 0 || area->y2 < 0 || area->x1 > (int32_t)dsc->vinfo.xres - 1 || area->y1 > (int32_t)dsc->vinfo.yres - 1) {
311         lv_display_flush_ready(disp);
312         return;
313     }
314 
315     uint32_t fb_pos =
316         (area->x1 + dsc->vinfo.xoffset) * px_size +
317         (area->y1 + dsc->vinfo.yoffset) * dsc->finfo.line_length;
318 
319     uint8_t * fbp = (uint8_t *)dsc->fbp;
320     int32_t y;
321     if(LV_LINUX_FBDEV_RENDER_MODE == LV_DISPLAY_RENDER_MODE_DIRECT) {
322         uint32_t color_pos =
323             area->x1 * px_size +
324             area->y1 * disp->hor_res * px_size;
325 
326         for(y = area->y1; y <= area->y2; y++) {
327             lv_memcpy(&fbp[fb_pos], &color_p[color_pos], w * px_size);
328             fb_pos += dsc->finfo.line_length;
329             color_pos += disp->hor_res * px_size;
330         }
331     }
332     else {
333         w = lv_area_get_width(area);
334         for(y = area->y1; y <= area->y2; y++) {
335             lv_memcpy(&fbp[fb_pos], color_p, w * px_size);
336             fb_pos += dsc->finfo.line_length;
337             color_p += w * px_size;
338         }
339     }
340 
341     if(dsc->force_refresh) {
342         dsc->vinfo.activate |= FB_ACTIVATE_NOW | FB_ACTIVATE_FORCE;
343         if(ioctl(dsc->fbfd, FBIOPUT_VSCREENINFO, &(dsc->vinfo)) == -1) {
344             perror("Error setting var screen info");
345         }
346     }
347 
348     lv_display_flush_ready(disp);
349 }
350 
tick_get_cb(void)351 static uint32_t tick_get_cb(void)
352 {
353     struct timespec t;
354     clock_gettime(CLOCK_MONOTONIC, &t);
355     uint64_t time_ms = t.tv_sec * 1000 + (t.tv_nsec / 1000000);
356     return time_ms;
357 }
358 
359 #endif /*LV_USE_LINUX_FBDEV*/
360