/** * @file lv_nuttx_fbdev.c * */ /********************* * INCLUDES *********************/ #include "lv_nuttx_fbdev.h" #if LV_USE_NUTTX #include #include #include #include #include #include #include #include #include #include #include "../../../lvgl.h" #include "../../lvgl_private.h" /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ typedef struct { /* fd should be defined at the beginning */ int fd; struct fb_videoinfo_s vinfo; struct fb_planeinfo_s pinfo; void * mem; void * mem2; void * mem_off_screen; uint32_t mem2_yoffset; lv_draw_buf_t buf1; lv_draw_buf_t buf2; } lv_nuttx_fb_t; /********************** * STATIC PROTOTYPES **********************/ static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p); static lv_color_format_t fb_fmt_to_color_format(int fmt); static int fbdev_get_pinfo(int fd, struct fb_planeinfo_s * pinfo); static int fbdev_init_mem2(lv_nuttx_fb_t * dsc); static void display_refr_timer_cb(lv_timer_t * tmr); static void display_release_cb(lv_event_t * e); #if defined(CONFIG_FB_UPDATE) static void fbdev_join_inv_areas(lv_display_t * disp, lv_area_t * final_inv_area); #endif /********************** * STATIC VARIABLES **********************/ /********************** * MACROS **********************/ /********************** * GLOBAL FUNCTIONS **********************/ lv_display_t * lv_nuttx_fbdev_create(void) { lv_nuttx_fb_t * dsc = lv_malloc_zeroed(sizeof(lv_nuttx_fb_t)); LV_ASSERT_MALLOC(dsc); if(dsc == NULL) return NULL; lv_display_t * disp = lv_display_create(800, 480); if(disp == NULL) { lv_free(dsc); return NULL; } dsc->fd = -1; lv_display_set_driver_data(disp, dsc); lv_display_add_event_cb(disp, display_release_cb, LV_EVENT_DELETE, disp); lv_display_set_flush_cb(disp, flush_cb); return disp; } int lv_nuttx_fbdev_set_file(lv_display_t * disp, const char * file) { int ret; LV_ASSERT(disp && file); lv_nuttx_fb_t * dsc = lv_display_get_driver_data(disp); if(dsc->fd >= 0) close(dsc->fd); /* Open the file for reading and writing*/ dsc->fd = open(file, O_RDWR); if(dsc->fd < 0) { LV_LOG_ERROR("Error: cannot open framebuffer device"); return -errno; } LV_LOG_USER("The framebuffer device was opened successfully"); if(ioctl(dsc->fd, FBIOGET_VIDEOINFO, (unsigned long)((uintptr_t)&dsc->vinfo)) < 0) { LV_LOG_ERROR("ioctl(FBIOGET_VIDEOINFO) failed: %d", errno); ret = -errno; goto errout; } LV_LOG_USER("VideoInfo:"); LV_LOG_USER(" fmt: %u", dsc->vinfo.fmt); LV_LOG_USER(" xres: %u", dsc->vinfo.xres); LV_LOG_USER(" yres: %u", dsc->vinfo.yres); LV_LOG_USER(" nplanes: %u", dsc->vinfo.nplanes); if((ret = fbdev_get_pinfo(dsc->fd, &dsc->pinfo)) < 0) { goto errout; } lv_color_format_t color_format = fb_fmt_to_color_format(dsc->vinfo.fmt); if(color_format == LV_COLOR_FORMAT_UNKNOWN) { goto errout; } dsc->mem = mmap(NULL, dsc->pinfo.fblen, PROT_READ | PROT_WRITE, MAP_SHARED | MAP_FILE, dsc->fd, 0); if(dsc->mem == MAP_FAILED) { LV_LOG_ERROR("ioctl(FBIOGET_PLANEINFO) failed: %d", errno); ret = -errno; goto errout; } uint32_t w = dsc->vinfo.xres; uint32_t h = dsc->vinfo.yres; uint32_t stride = dsc->pinfo.stride; uint32_t data_size = h * stride; lv_draw_buf_init(&dsc->buf1, w, h, color_format, stride, dsc->mem, data_size); /* Check buffer mode */ bool double_buffer = dsc->pinfo.yres_virtual == (dsc->vinfo.yres * 2); if(double_buffer) { if((ret = fbdev_init_mem2(dsc)) < 0) { goto errout; } lv_draw_buf_init(&dsc->buf2, w, h, color_format, stride, dsc->mem2, data_size); lv_display_set_draw_buffers(disp, &dsc->buf1, &dsc->buf2); } else { dsc->mem_off_screen = malloc(data_size); LV_ASSERT_MALLOC(dsc->mem_off_screen); if(!dsc->mem_off_screen) { ret = -ENOMEM; LV_LOG_ERROR("Failed to allocate memory for off-screen buffer"); goto errout; } LV_LOG_USER("Use off-screen mode, memory: %p, size: %" LV_PRIu32, dsc->mem_off_screen, data_size); lv_draw_buf_init(&dsc->buf2, w, h, color_format, stride, dsc->mem_off_screen, data_size); lv_display_set_draw_buffers(disp, &dsc->buf2, NULL); } lv_display_set_color_format(disp, color_format); lv_display_set_render_mode(disp, LV_DISPLAY_RENDER_MODE_DIRECT); lv_display_set_resolution(disp, dsc->vinfo.xres, dsc->vinfo.yres); lv_timer_set_cb(disp->refr_timer, display_refr_timer_cb); LV_LOG_USER("Resolution is set to %dx%d at %" LV_PRId32 "dpi", dsc->vinfo.xres, dsc->vinfo.yres, lv_display_get_dpi(disp)); return 0; errout: close(dsc->fd); dsc->fd = -1; return ret; } /********************** * STATIC FUNCTIONS **********************/ #if defined(CONFIG_FB_UPDATE) static void fbdev_join_inv_areas(lv_display_t * disp, lv_area_t * final_inv_area) { uint16_t inv_index; bool area_joined = false; for(inv_index = 0; inv_index < disp->inv_p; inv_index++) { if(disp->inv_area_joined[inv_index] == 0) { const lv_area_t * area_p = &disp->inv_areas[inv_index]; /* Join to final_area */ if(!area_joined) { /* copy first area */ lv_area_copy(final_inv_area, area_p); area_joined = true; } else { lv_area_join(final_inv_area, final_inv_area, area_p); } } } } #endif static void display_refr_timer_cb(lv_timer_t * tmr) { lv_display_t * disp = lv_timer_get_user_data(tmr); lv_nuttx_fb_t * dsc = lv_display_get_driver_data(disp); struct pollfd pfds[1]; lv_memzero(pfds, sizeof(pfds)); pfds[0].fd = dsc->fd; pfds[0].events = POLLOUT; /* Query free fb to draw */ if(poll(pfds, 1, 0) < 0) { return; } if(pfds[0].revents & POLLOUT) { lv_display_refr_timer(tmr); } } static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * color_p) { LV_UNUSED(color_p); lv_nuttx_fb_t * dsc = lv_display_get_driver_data(disp); if(dsc->mem_off_screen) { /* When rendering in off-screen mode, copy the drawing buffer to fb */ /* buf2(off-screen buffer) -> buf1(fbmem)*/ lv_draw_buf_copy(&dsc->buf1, area, &dsc->buf2, area); } /* Skip the non-last flush */ if(!lv_display_flush_is_last(disp)) { lv_display_flush_ready(disp); return; } #if defined(CONFIG_FB_UPDATE) /*May be some direct update command is required*/ int yoffset = disp->buf_act == disp->buf_1 ? 0 : dsc->mem2_yoffset; /* Join the areas to update */ lv_area_t final_inv_area; lv_memzero(&final_inv_area, sizeof(final_inv_area)); fbdev_join_inv_areas(disp, &final_inv_area); struct fb_area_s fb_area; fb_area.x = final_inv_area.x1; fb_area.y = final_inv_area.y1 + yoffset; fb_area.w = lv_area_get_width(&final_inv_area); fb_area.h = lv_area_get_height(&final_inv_area); if(ioctl(dsc->fd, FBIO_UPDATE, (unsigned long)((uintptr_t)&fb_area)) < 0) { LV_LOG_ERROR("ioctl(FBIO_UPDATE) failed: %d", errno); } #endif /* double framebuffer */ if(dsc->mem2 != NULL) { if(disp->buf_act == disp->buf_1) { dsc->pinfo.yoffset = 0; } else { dsc->pinfo.yoffset = dsc->mem2_yoffset; } if(ioctl(dsc->fd, FBIOPAN_DISPLAY, (unsigned long)((uintptr_t) & (dsc->pinfo))) < 0) { LV_LOG_ERROR("ioctl(FBIOPAN_DISPLAY) failed: %d", errno); } } lv_display_flush_ready(disp); } static lv_color_format_t fb_fmt_to_color_format(int fmt) { switch(fmt) { case FB_FMT_RGB16_565: return LV_COLOR_FORMAT_RGB565; case FB_FMT_RGB24: return LV_COLOR_FORMAT_RGB888; case FB_FMT_RGB32: return LV_COLOR_FORMAT_XRGB8888; case FB_FMT_RGBA32: return LV_COLOR_FORMAT_ARGB8888; default: break; } LV_LOG_ERROR("Unsupported color format: %d", fmt); return LV_COLOR_FORMAT_UNKNOWN; } static int fbdev_get_pinfo(int fd, FAR struct fb_planeinfo_s * pinfo) { if(ioctl(fd, FBIOGET_PLANEINFO, (unsigned long)((uintptr_t)pinfo)) < 0) { LV_LOG_ERROR("ERROR: ioctl(FBIOGET_PLANEINFO) failed: %d", errno); return -errno; } LV_LOG_USER("PlaneInfo (plane %d):", pinfo->display); LV_LOG_USER(" mem: %p", pinfo->fbmem); LV_LOG_USER(" fblen: %zu", pinfo->fblen); LV_LOG_USER(" stride: %u", pinfo->stride); LV_LOG_USER(" display: %u", pinfo->display); LV_LOG_USER(" bpp: %u", pinfo->bpp); return 0; } static int fbdev_init_mem2(lv_nuttx_fb_t * dsc) { uintptr_t buf_offset; struct fb_planeinfo_s pinfo; int ret; lv_memzero(&pinfo, sizeof(pinfo)); /* Get display[1] planeinfo */ pinfo.display = dsc->pinfo.display + 1; if((ret = fbdev_get_pinfo(dsc->fd, &pinfo)) < 0) { return ret; } /* Check bpp */ if(pinfo.bpp != dsc->pinfo.bpp) { LV_LOG_WARN("mem2 is incorrect"); return -EINVAL; } /* Check the buffer address offset, * It needs to be divisible by pinfo.stride */ buf_offset = pinfo.fbmem - dsc->mem; if((buf_offset % dsc->pinfo.stride) != 0) { LV_LOG_WARN("It is detected that buf_offset(%" PRIuPTR ") " "and stride(%d) are not divisible, please ensure " "that the driver handles the address offset by itself.", buf_offset, dsc->pinfo.stride); } /* Calculate the address and yoffset of mem2 */ if(buf_offset == 0) { dsc->mem2_yoffset = dsc->vinfo.yres; dsc->mem2 = pinfo.fbmem + dsc->mem2_yoffset * pinfo.stride; LV_LOG_USER("Use consecutive mem2 = %p, yoffset = %" LV_PRIu32, dsc->mem2, dsc->mem2_yoffset); } else { dsc->mem2_yoffset = buf_offset / dsc->pinfo.stride; dsc->mem2 = pinfo.fbmem; LV_LOG_USER("Use non-consecutive mem2 = %p, yoffset = %" LV_PRIu32, dsc->mem2, dsc->mem2_yoffset); } return 0; } static void display_release_cb(lv_event_t * e) { lv_display_t * disp = (lv_display_t *) lv_event_get_user_data(e); lv_nuttx_fb_t * dsc = lv_display_get_driver_data(disp); if(dsc) { lv_display_set_driver_data(disp, NULL); lv_display_set_flush_cb(disp, NULL); if(dsc->fd >= 0) { close(dsc->fd); dsc->fd = -1; } if(dsc->mem_off_screen) { /* Free the off-screen buffer */ free(dsc->mem_off_screen); dsc->mem_off_screen = NULL; } lv_free(dsc); } LV_LOG_USER("Done"); } #endif /*LV_USE_NUTTX*/