/** * @file lv_st_ltdc.c * */ /********************* * INCLUDES *********************/ #include "../../../lv_conf_internal.h" #if LV_USE_ST_LTDC #include "lv_st_ltdc.h" #include "../../../display/lv_display_private.h" #include "../../../draw/sw/lv_draw_sw.h" #include "ltdc.h" #if LV_ST_LTDC_USE_DMA2D_FLUSH #if LV_USE_DRAW_DMA2D #error cannot use LV_ST_LTDC_USE_DMA2D_FLUSH with LV_USE_DRAW_DMA2D #endif /*LV_USE_DRAW_DMA2D*/ #include "dma2d.h" #endif /*LV_ST_LTDC_USE_DMA2D_FLUSH*/ /********************* * DEFINES *********************/ /********************** * TYPEDEFS **********************/ #if LV_USE_OS != LV_OS_NONE typedef lv_thread_sync_t sync_t; #else typedef volatile bool sync_t; #endif /********************** * STATIC PROTOTYPES **********************/ static lv_display_t * create(void * buf1, void * buf2, uint32_t buf_size, uint32_t layer_idx, lv_display_render_mode_t mode); static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map); static void flush_wait_cb(lv_display_t * disp); static lv_color_format_t get_lv_cf_from_layer_cf(uint32_t cf); static void reload_event_callback(LTDC_HandleTypeDef * hltdc); #if LV_ST_LTDC_USE_DMA2D_FLUSH static void transfer_complete_callback(DMA2D_HandleTypeDef * hdma2d); static uint32_t get_dma2d_output_cf_from_layer_cf(uint32_t cf); static uint32_t get_dma2d_input_cf_from_lv_cf(uint32_t cf); #endif /********************** * STATIC VARIABLES **********************/ static struct { bool disp_flushed_in_flush_cb[MAX_LAYER]; sync_t sync[MAX_LAYER]; volatile bool layer_interrupt_is_owned[MAX_LAYER]; #if LV_ST_LTDC_USE_DMA2D_FLUSH volatile uint32_t dma2d_interrupt_owner; /*layer_idx + 1, or 0 for none*/ #endif } g_data; /********************** * MACROS **********************/ #if LV_USE_OS != LV_OS_NONE #define SYNC_INIT(layer_idx) lv_thread_sync_init(&g_data.sync[layer_idx]) #define SYNC_WAIT(layer_idx) lv_thread_sync_wait(&g_data.sync[layer_idx]) #define SYNC_SIGNAL_ISR(layer_idx) lv_thread_sync_signal_isr(&g_data.sync[layer_idx]) #else #define SYNC_INIT(layer_idx) do { g_data.sync[layer_idx] = false; } while(0) #define SYNC_WAIT(layer_idx) do { while(!g_data.sync[layer_idx]); g_data.sync[layer_idx] = false; } while(0) #define SYNC_SIGNAL_ISR(layer_idx) do { g_data.sync[layer_idx] = true; } while(0) #endif /********************** * GLOBAL FUNCTIONS **********************/ lv_display_t * lv_st_ltdc_create_direct(void * fb_adr_1, void * fb_adr_2, uint32_t layer_idx) { return create(fb_adr_1, fb_adr_2, 0, layer_idx, LV_DISPLAY_RENDER_MODE_DIRECT); } lv_display_t * lv_st_ltdc_create_partial(void * render_buf_1, void * render_buf_2, uint32_t buf_size, uint32_t layer_idx) { return create(render_buf_1, render_buf_2, buf_size, layer_idx, LV_DISPLAY_RENDER_MODE_PARTIAL); } /********************** * STATIC FUNCTIONS **********************/ static lv_display_t * create(void * buf1, void * buf2, uint32_t buf_size, uint32_t layer_idx, lv_display_render_mode_t mode) { LTDC_LayerCfgTypeDef * layer_cfg = &hltdc.LayerCfg[layer_idx]; uint32_t layer_width = layer_cfg->ImageWidth; uint32_t layer_height = layer_cfg->ImageHeight; uint32_t layer_cf = layer_cfg->PixelFormat; lv_color_format_t cf = get_lv_cf_from_layer_cf(layer_cf); lv_display_t * disp = lv_display_create(layer_width, layer_height); lv_display_set_color_format(disp, cf); lv_display_set_flush_cb(disp, flush_cb); lv_display_set_flush_wait_cb(disp, flush_wait_cb); lv_display_set_driver_data(disp, (void *)(uintptr_t)layer_idx); if(mode == LV_DISPLAY_RENDER_MODE_DIRECT) { uint32_t cf_size = lv_color_format_get_size(cf); lv_display_set_buffers(disp, buf1, buf2, layer_width * layer_height * cf_size, LV_DISPLAY_RENDER_MODE_DIRECT); if(buf1 != NULL && buf2 != NULL) { HAL_LTDC_RegisterCallback(&hltdc, HAL_LTDC_RELOAD_EVENT_CB_ID, reload_event_callback); SYNC_INIT(layer_idx); } } else { lv_display_set_buffers(disp, buf1, buf2, buf_size, LV_DISPLAY_RENDER_MODE_PARTIAL); #if LV_ST_LTDC_USE_DMA2D_FLUSH hdma2d.XferCpltCallback = transfer_complete_callback; SYNC_INIT(layer_idx); #endif } return disp; } static void flush_cb(lv_display_t * disp, const lv_area_t * area, uint8_t * px_map) { uint32_t layer_idx = (uint32_t)(uintptr_t)lv_display_get_driver_data(disp); g_data.disp_flushed_in_flush_cb[layer_idx] = false; if(disp->render_mode == LV_DISPLAY_RENDER_MODE_DIRECT) { if(lv_display_is_double_buffered(disp) && lv_display_flush_is_last(disp)) { HAL_LTDC_SetAddress_NoReload(&hltdc, (uint32_t)px_map, layer_idx); g_data.layer_interrupt_is_owned[layer_idx] = true; HAL_LTDC_Reload(&hltdc, LTDC_RELOAD_VERTICAL_BLANKING); } else { g_data.disp_flushed_in_flush_cb[layer_idx] = true; } } else { LTDC_LayerCfgTypeDef * layer_cfg = &hltdc.LayerCfg[layer_idx]; lv_color_format_t cf = lv_display_get_color_format(disp); int32_t disp_width = disp->hor_res; uint8_t * fb = (uint8_t *) layer_cfg->FBStartAdress; uint32_t px_size = lv_color_format_get_size(cf); uint32_t fb_stride = px_size * disp_width; lv_area_t rotated_area = *area; lv_display_rotate_area(disp, &rotated_area); uint8_t * first_pixel = fb + fb_stride * rotated_area.y1 + px_size * rotated_area.x1; int32_t area_width = lv_area_get_width(area); int32_t area_height = lv_area_get_height(area); lv_display_rotation_t rotation = lv_display_get_rotation(disp); if(rotation == LV_DISPLAY_ROTATION_0) { #if LV_ST_LTDC_USE_DMA2D_FLUSH uint32_t dma2d_input_cf = get_dma2d_input_cf_from_lv_cf(cf); uint32_t dma2d_output_cf = get_dma2d_output_cf_from_layer_cf(layer_cfg->PixelFormat); while(DMA2D->CR & DMA2D_CR_START); DMA2D->FGPFCCR = dma2d_input_cf; DMA2D->FGMAR = (uint32_t)px_map; DMA2D->FGOR = 0; DMA2D->OPFCCR = dma2d_output_cf; DMA2D->OMAR = (uint32_t)first_pixel; DMA2D->OOR = disp_width - area_width; DMA2D->NLR = (area_width << DMA2D_NLR_PL_Pos) | (area_height << DMA2D_NLR_NL_Pos); g_data.dma2d_interrupt_owner = layer_idx + 1; DMA2D->CR = DMA2D_CR_START | DMA2D_CR_TCIE | (0x1U << DMA2D_CR_MODE_Pos); /* memory-to-memory with PFC */ #else uint32_t area_stride = px_size * area_width; uint8_t * fb_p = first_pixel; uint8_t * px_map_p = px_map; for(int i = 0; i < area_height; i++) { lv_memcpy(fb_p, px_map_p, area_stride); fb_p += fb_stride; px_map_p += area_stride; } g_data.disp_flushed_in_flush_cb[layer_idx] = true; #endif } else { uint32_t area_stride = px_size * area_width; lv_draw_sw_rotate(px_map, first_pixel, area_width, area_height, area_stride, fb_stride, rotation, cf); g_data.disp_flushed_in_flush_cb[layer_idx] = true; } } } static void flush_wait_cb(lv_display_t * disp) { uint32_t layer_idx = (uint32_t)(uintptr_t)lv_display_get_driver_data(disp); if(!g_data.disp_flushed_in_flush_cb[layer_idx]) { SYNC_WAIT(layer_idx); } } static lv_color_format_t get_lv_cf_from_layer_cf(uint32_t cf) { switch(cf) { case LTDC_PIXEL_FORMAT_ARGB8888: return LV_COLOR_FORMAT_ARGB8888; case LTDC_PIXEL_FORMAT_RGB888: return LV_COLOR_FORMAT_RGB888; case LTDC_PIXEL_FORMAT_RGB565: return LV_COLOR_FORMAT_RGB565; case LTDC_PIXEL_FORMAT_L8: return LV_COLOR_FORMAT_L8; case LTDC_PIXEL_FORMAT_AL88: return LV_COLOR_FORMAT_AL88; default: LV_ASSERT_MSG(0, "the LTDC color format is not supported"); } } static void reload_event_callback(LTDC_HandleTypeDef * hltdc) { uint32_t i; for(i = 0; i < MAX_LAYER; i++) { if(g_data.layer_interrupt_is_owned[i]) { g_data.layer_interrupt_is_owned[i] = false; SYNC_SIGNAL_ISR(i); } } } #if LV_ST_LTDC_USE_DMA2D_FLUSH static void transfer_complete_callback(DMA2D_HandleTypeDef * hdma2d) { DMA2D->IFCR = 0x3FU; uint32_t owner = g_data.dma2d_interrupt_owner; if(owner) { g_data.dma2d_interrupt_owner = 0; owner -= 1; SYNC_SIGNAL_ISR(owner); } } static uint32_t get_dma2d_output_cf_from_layer_cf(uint32_t cf) { switch(cf) { case LTDC_PIXEL_FORMAT_ARGB8888: return DMA2D_OUTPUT_ARGB8888; case LTDC_PIXEL_FORMAT_RGB888: return DMA2D_OUTPUT_RGB888; case LTDC_PIXEL_FORMAT_RGB565: return DMA2D_OUTPUT_RGB565; default: LV_ASSERT_MSG(0, "DMA2D cannot output to the LTDC color format"); } } static uint32_t get_dma2d_input_cf_from_lv_cf(uint32_t cf) { switch(cf) { case LV_COLOR_FORMAT_ARGB8888: return DMA2D_INPUT_ARGB8888; case LV_COLOR_FORMAT_RGB888: return DMA2D_INPUT_RGB888; case LV_COLOR_FORMAT_RGB565: return DMA2D_INPUT_RGB565; case LV_COLOR_FORMAT_L8: return DMA2D_INPUT_L8; case LV_COLOR_FORMAT_AL88: return DMA2D_INPUT_AL88; case LV_COLOR_FORMAT_A8: return DMA2D_INPUT_A8; default: LV_ASSERT_MSG(0, "the LVGL color format is not a DMA2D input color format"); } } #endif /*LV_ST_LTDC_USE_DMA2D_FLUSH*/ #endif /*LV_USE_ST_LTDC*/