/* * This file is subject to the terms of the GFX License. If a copy of * the license was not distributed with this file, you can obtain one at: * * http://ugfx.io/license.html */ #include "gfx.h" #if GFX_USE_GDISP #define GDISP_DRIVER_VMT GDISPVMT_STM32LTDC #include "gdisp_lld_config.h" #include "../../../src/gdisp/gdisp_driver.h" #include "stm32_ltdc.h" #if STM32LTDC_USE_DMA2D #include "stm32_dma2d.h" #endif #if defined(GDISP_SCREEN_HEIGHT) || defined(GDISP_SCREEN_HEIGHT) #if GFX_COMPILER_WARNING_TYPE == GFX_COMPILER_WARNING_DIRECT #warning "GDISP: This low level driver does not support setting a screen size. It is being ignored." #elif GFX_COMPILER_WARNING_TYPE == GFX_COMPILER_WARNING_MACRO COMPILER_WARNING("GDISP: This low level driver does not support setting a screen size. It is being ignored.") #endif #undef GDISP_SCREEN_WIDTH #undef GDISP_SCREEN_HEIGHT #endif #ifndef STM32LTDC_DMA_CACHE_FLUSH #define STM32LTDC_DMA_CACHE_FLUSH GFXOFF #endif #ifndef STM32LTDC_USE_DMA2D #define STM32LTDC_USE_DMA2D GFXOFF #endif #ifndef STM32LTDC_USE_LAYER2 #define STM32LTDC_USE_LAYER2 GFXOFF #endif #ifndef STM32LTDC_USE_RGB565 #define STM32LTDC_USE_RGB565 GFXOFF #endif // Prevent usage of 2nd layer and double buffering at the same time. // See readme.md for more inforamtion. #if STM32LTDC_USE_LAYER2 && STM32LTDC_USE_DOUBLEBUFFERING #error "GDISP - STM32LTDC: Cannot use 2nd LTDC layer and double buffering at the same time. See the driver's readme.md for more information." #endif // Double buffering requires GDISP_NEED_CONTROL for the buffer swap command #if STM32LTDC_USE_DOUBLEBUFFERING && !GDISP_NEED_CONTROL #error "GDISP - STM32LTDC: Double buffering requires GDISP_NEED_CONTROL." #endif // Force DMA cache flushing on certain platforms/systems. #if STM32LTDC_USE_DMA2D #if defined(STM32F7) || defined(STM32H7) || defined(STM32F746xx) #undef STM32LTDC_DMA_CACHE_FLUSH #define STM32LTDC_DMA_CACHE_FLUSH GFXON #endif #endif typedef struct ltdcLayerConfig { // Frame LLDCOLOR_TYPE* frame; // Frame buffer address gCoord width, height; // Frame size in pixels gCoord pitch; // Line pitch, in bytes gU16 fmt; // Pixel format in LTDC format // Window gCoord x, y; // Start pixel position of the virtual layer gCoord cx, cy; // Size of the virtual layer gU32 defcolor; // Default color, ARGB8888 gU32 keycolor; // Color key, RGB888 gU32 blending; // Blending factors const gU32* palette; // The palette, RGB888 (can be NULL) gU16 palettelen; // Palette length gU8 alpha; // Constant alpha factor gU8 layerflags; // Layer configuration } ltdcLayerConfig; typedef struct ltdcConfig { gCoord width, height; // Screen size gCoord hsync, vsync; // Horizontal and Vertical sync pixels gCoord hbackporch, vbackporch; // Horizontal and Vertical back porch pixels gCoord hfrontporch, vfrontporch; // Horizontal and Vertical front porch pixels gU32 syncflags; // Sync flags gU32 bgcolor; // Clear screen color RGB888 ltdcLayerConfig bglayer; // Background layer config ltdcLayerConfig fglayer; // Foreground layer config } ltdcConfig; #define LTDC_UNUSED_LAYER_CONFIG { 0, 1, 1, 1, LTDC_FMT_L8, 0, 0, 1, 1, 0x000000, 0x000000, LTDC_BLEND_FIX1_FIX2, 0, 0, 0, 0 } #if GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_RGB565 #define LTDC_PIXELFORMAT LTDC_FMT_RGB565 #define LTDC_PIXELBYTES 2 #define LTDC_PIXELBITS 16 #elif GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_RGB888 #define LTDC_PIXELFORMAT LTDC_FMT_ARGB8888 #define LTDC_PIXELBYTES 4 #define LTDC_PIXELBITS 32 #else #error "GDISP: STM32LTDC - unsupported pixel format" #endif #include "board_STM32LTDC.h" /*===========================================================================*/ /* Driver local definitions. */ /*===========================================================================*/ #ifndef GDISP_INITIAL_CONTRAST #define GDISP_INITIAL_CONTRAST 50 #endif #ifndef GDISP_INITIAL_BACKLIGHT #define GDISP_INITIAL_BACKLIGHT 100 #endif /*===========================================================================*/ /* Driver local routines. */ /*===========================================================================*/ #define PIXEL_POS(g, x, y) ((y) * ((ltdcLayerConfig *)g->priv)->pitch + (x) * LTDC_PIXELBYTES) #define PIXEL_ADDR(g, pos) ((LLDCOLOR_TYPE *)((gU8 *)((ltdcLayerConfig *)g->priv)->frame+pos)) /*===========================================================================*/ /* Driver exported functions. */ /*===========================================================================*/ static const ltdcLayerConfig layerOff = LTDC_UNUSED_LAYER_CONFIG; static void _ltdc_reload(void) { LTDC->SRCR |= LTDC_SRCR_IMR; while (LTDC->SRCR & (LTDC_SRCR_IMR | LTDC_SRCR_VBR)) gfxYield(); } static void _ltdc_layer_init(LTDC_Layer_TypeDef* pLayReg, const ltdcLayerConfig* pCfg) { static const gU8 fmt2Bpp[] = { 4, /* LTDC_FMT_ARGB8888 */ 3, /* LTDC_FMT_RGB888 */ 2, /* LTDC_FMT_RGB565 */ 2, /* LTDC_FMT_ARGB1555 */ 2, /* LTDC_FMT_ARGB4444 */ 1, /* LTDC_FMT_L8 */ 1, /* LTDC_FMT_AL44 */ 2 /* LTDC_FMT_AL88 */ }; gU32 start, stop; // Set the framebuffer dimensions and format pLayReg->PFCR = (pLayReg->PFCR & ~LTDC_LxPFCR_PF) | ((gU32)pCfg->fmt & LTDC_LxPFCR_PF); pLayReg->CFBAR = (gU32)pCfg->frame & LTDC_LxCFBAR_CFBADD; pLayReg->CFBLR = ((((gU32)pCfg->pitch << 16) & LTDC_LxCFBLR_CFBP) | (((gU32)fmt2Bpp[pCfg->fmt] * pCfg->width + 3) & LTDC_LxCFBLR_CFBLL)); pLayReg->CFBLNR = (gU32)pCfg->height & LTDC_LxCFBLNR_CFBLNBR; // Set the display window boundaries start = (gU32)pCfg->x + driverCfg.hsync + driverCfg.hbackporch; stop = start + pCfg->cx - 1; pLayReg->WHPCR = ((start << 0) & LTDC_LxWHPCR_WHSTPOS) | ((stop << 16) & LTDC_LxWHPCR_WHSPPOS); start = (gU32)pCfg->y + driverCfg.vsync + driverCfg.vbackporch; stop = start + pCfg->cy - 1; pLayReg->WVPCR = ((start << 0) & LTDC_LxWVPCR_WVSTPOS) | ((stop << 16) & LTDC_LxWVPCR_WVSPPOS); // Set colors pLayReg->DCCR = pCfg->defcolor; pLayReg->CKCR = (pLayReg->CKCR & ~0x00FFFFFF) | (pCfg->keycolor & 0x00FFFFFF); pLayReg->CACR = (pLayReg->CACR & ~LTDC_LxCACR_CONSTA) | ((gU32)pCfg->alpha & LTDC_LxCACR_CONSTA); pLayReg->BFCR = (pLayReg->BFCR & ~(LTDC_LxBFCR_BF1 | LTDC_LxBFCR_BF2)) | ((gU32)pCfg->blending & (LTDC_LxBFCR_BF1 | LTDC_LxBFCR_BF2)); for (start = 0; start < pCfg->palettelen; start++) pLayReg->CLUTWR = ((gU32)start << 24) | (pCfg->palette[start] & 0x00FFFFFF); // Final flags pLayReg->CR = (pLayReg->CR & ~LTDC_LEF_MASK) | ((gU32)pCfg->layerflags & LTDC_LEF_MASK); } static void _ltdc_init(void) { // Set up the display scanning gU32 hacc, vacc; // Let the board handle LTDC clock setups init_ltdc_clock(); // Turn off the controller and its interrupts LTDC->GCR = 0; LTDC->IER = 0; _ltdc_reload(); // Set synchronization params hacc = driverCfg.hsync - 1; vacc = driverCfg.vsync - 1; LTDC->SSCR = ((hacc << 16) & LTDC_SSCR_HSW) | ((vacc << 0) & LTDC_SSCR_VSH); // Set accumulated back porch params hacc += driverCfg.hbackporch; vacc += driverCfg.vbackporch; LTDC->BPCR = ((hacc << 16) & LTDC_BPCR_AHBP) | ((vacc << 0) & LTDC_BPCR_AVBP); // Set accumulated active params hacc += driverCfg.width; vacc += driverCfg.height; LTDC->AWCR = ((hacc << 16) & LTDC_AWCR_AAW) | ((vacc << 0) & LTDC_AWCR_AAH); // Set accumulated total params hacc += driverCfg.hfrontporch; vacc += driverCfg.vfrontporch; LTDC->TWCR = ((hacc << 16) & LTDC_TWCR_TOTALW) | ((vacc << 0) & LTDC_TWCR_TOTALH); // Set signal polarities and other flags LTDC->GCR = driverCfg.syncflags & (LTDC_EF_MASK & ~LTDC_EF_ENABLE); // Set background color LTDC->BCCR = (LTDC->BCCR & ~0x00FFFFFF) | (driverCfg.bgcolor & 0x00FFFFFF); // Load the background layer _ltdc_layer_init(LTDC_Layer1, &driverCfg.bglayer); // Load the foreground layer _ltdc_layer_init(LTDC_Layer2, &layerOff); // Interrupt handling // Possible flags - LTDC_IER_RRIE, LTDC_IER_LIE, LTDC_IER_FUIE, LTDC_IER_TERRIE etc LTDC->IER = 0; // Set everything going _ltdc_reload(); LTDC->GCR |= LTDC_GCR_LTDCEN; _ltdc_reload(); } LLDSPEC gBool gdisp_lld_init(GDisplay* g) { // Initialize the private structure g->priv = 0; g->board = 0; switch(g->controllerdisplay) { // Display 0 is the background layer case 0: // Init the board init_board(g); // Initialise the LTDC controller _ltdc_init(); // Initialise DMA2D #if STM32LTDC_USE_DMA2D dma2d_init(); #endif if (!(driverCfg.bglayer.layerflags & LTDC_LEF_ENABLE)) return gFalse; g->priv = (void *)&driverCfg.bglayer; // Finish Init the board post_init_board(g); // Turn on the back-light set_backlight(g, GDISP_INITIAL_BACKLIGHT); break; // Display 1 is the foreground layer or the 2nd buffer for double buffering case 1: g->priv = (void *)&driverCfg.fglayer; #if STM32LTDC_USE_LAYER2 if (!(driverCfg.fglayer.layerflags & LTDC_LEF_ENABLE)) return gFalse; // Load the foreground layer _ltdc_layer_init(LTDC_Layer2, &driverCfg.fglayer); _ltdc_reload(); // Finish Init the board post_init_board(g); #endif break; // There is only 1 LTDC in the CPU and only the 2 layers in the LTDC. default: return gFalse; } // Initialise the GDISP structure g->g.Width = ((ltdcLayerConfig *)g->priv)->width; g->g.Height = ((ltdcLayerConfig *)g->priv)->height; g->g.Orientation = gOrientation0; g->g.Powermode = gPowerOn; g->g.Backlight = GDISP_INITIAL_BACKLIGHT; g->g.Contrast = GDISP_INITIAL_CONTRAST; return gTrue; } LLDSPEC void gdisp_lld_draw_pixel(GDisplay* g) { unsigned pos; #if GDISP_NEED_CONTROL switch(g->g.Orientation) { case gOrientationPortrait: case gOrientationLandscape: case gOrientation0: default: pos = PIXEL_POS(g, g->p.x, g->p.y); break; case gOrientation90: pos = PIXEL_POS(g, g->p.y, g->g.Width-g->p.x-1); break; case gOrientation180: pos = PIXEL_POS(g, g->g.Width-g->p.x-1, g->g.Height-g->p.y-1); break; case gOrientation270: pos = PIXEL_POS(g, g->g.Height-g->p.y-1, g->p.x); break; } #else pos = PIXEL_POS(g, g->p.x, g->p.y); #endif #if STM32LTDC_USE_DMA2D while(DMA2D->CR & DMA2D_CR_START); #endif #if GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_RGB888 // As we don't support ARGB pixel types in uGFX yet we will // use RGB with an inverted alpha value for compatibility // ie. 0x00FFFFFF is fully opaque white, 0xFFFFFFFF is fully transparent white PIXEL_ADDR(g, pos)[0] = gdispColor2Native(g->p.color) ^ 0xFF000000; #else PIXEL_ADDR(g, pos)[0] = gdispColor2Native(g->p.color); #endif } LLDSPEC gColor gdisp_lld_get_pixel_color(GDisplay* g) { unsigned pos; LLDCOLOR_TYPE color; #if GDISP_NEED_CONTROL switch(g->g.Orientation) { case gOrientationPortrait: case gOrientationLandscape: case gOrientation0: default: pos = PIXEL_POS(g, g->p.x, g->p.y); break; case gOrientation90: pos = PIXEL_POS(g, g->p.y, g->g.Width-g->p.x-1); break; case gOrientation180: pos = PIXEL_POS(g, g->g.Width-g->p.x-1, g->g.Height-g->p.y-1); break; case gOrientation270: pos = PIXEL_POS(g, g->g.Height-g->p.y-1, g->p.x); break; } #else pos = PIXEL_POS(g, g->p.x, g->p.y); #endif #if STM32LTDC_USE_DMA2D while(DMA2D->CR & DMA2D_CR_START); #endif #if GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_RGB888 // As we don't support ARGB pixel types in uGFX yet we will // use RGB with an inverted alpha value for compatibility // ie. 0x00FFFFFF is fully opaque white, 0xFFFFFFFF is fully transparent white color = PIXEL_ADDR(g, pos)[0] ^ 0xFF000000; #else color = PIXEL_ADDR(g, pos)[0]; #endif return gdispNative2Color(color); } #if GDISP_NEED_CONTROL LLDSPEC void gdisp_lld_control(GDisplay* g) { switch(g->p.x) { case GDISP_CONTROL_ORIENTATION: if (g->g.Orientation == (gOrientation)g->p.ptr) return; switch((gOrientation)g->p.ptr) { case gOrientation0: case gOrientation180: if (g->g.Orientation == gOrientation90 || g->g.Orientation == gOrientation270) { gCoord tmp; tmp = g->g.Width; g->g.Width = g->g.Height; g->g.Height = tmp; } break; case gOrientation90: case gOrientation270: if (g->g.Orientation == gOrientation0 || g->g.Orientation == gOrientation180) { gCoord tmp; tmp = g->g.Width; g->g.Width = g->g.Height; g->g.Height = tmp; } break; case gOrientationPortrait: case gOrientationLandscape: default: return; } g->g.Orientation = (gOrientation)g->p.ptr; return; case GDISP_CONTROL_BACKLIGHT: if ((unsigned)g->p.ptr > 100) g->p.ptr = (void *)100; set_backlight(g, (unsigned)g->p.ptr); g->g.Backlight = (unsigned)g->p.ptr; return; #if STM32LTDC_USE_DOUBLEBUFFERING case STM32LTDC_CONTROL_SHOW_BUFFER: { // Wait for end-of-line interrupt // We use simple polling here as end-of-line interrupts are very frequent and usually happen in sub-millisecond intervals. while (LTDC->ISR & LTDC_ISR_LIF); // Update framebuffer address in LTDC register // As we currently only support one layer when doublebuffering is enabled, this change happens only to layer 1. LTDC_Layer1->CFBAR = (gU32)(((ltdcLayerConfig*)g->priv)->frame) & LTDC_LxCFBAR_CFBADD; // Reload after LTDC config register modifications _ltdc_reload(); return; } #endif default: return; } } #endif #if STM32LTDC_USE_DMA2D #if STM32LTDC_DMA_CACHE_FLUSH #if defined(__CC_ARM) #define __ugfxDSB() __dsb(0xF) #else // GCC like #define __ugfxDSB() __ASM volatile ("dsb 0xF":::"memory") #endif #endif static void dma2d_init(void) { // Let the board handle the clock setup init_dma2d_clock(); // Output color format #if GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_RGB565 DMA2D->OPFCCR = OPFCCR_RGB565; #elif GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_RGB888 DMA2D->OPFCCR = OPFCCR_ARGB8888; #endif // Foreground color format #if GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_RGB565 DMA2D->FGPFCCR = FGPFCCR_CM_RGB565; #elif GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_RGB888 DMA2D->FGPFCCR = FGPFCCR_CM_ARGB8888; #endif } // Uses p.x,p.y p.cx,p.cy p.color LLDSPEC void gdisp_lld_fill_area(GDisplay* g) { gU32 pos; gU32 lineadd; gU32 shape; #if GDISP_NEED_CONTROL switch(g->g.Orientation) { case gOrientationPortrait: case gOrientationLandscape: case gOrientation0: default: pos = PIXEL_POS(g, g->p.x, g->p.y); lineadd = g->g.Width - g->p.cx; shape = (g->p.cx << 16) | (g->p.cy); break; case gOrientation90: pos = PIXEL_POS(g, g->p.y, g->g.Width-g->p.x-g->p.cx); lineadd = g->g.Height - g->p.cy; shape = (g->p.cy << 16) | (g->p.cx); break; case gOrientation180: pos = PIXEL_POS(g, g->g.Width-g->p.x-g->p.cx, g->g.Height-g->p.y-g->p.cy); lineadd = g->g.Width - g->p.cx; shape = (g->p.cx << 16) | (g->p.cy); break; case gOrientation270: pos = PIXEL_POS(g, g->g.Height-g->p.y-g->p.cy, g->p.x); lineadd = g->g.Height - g->p.cy; shape = (g->p.cy << 16) | (g->p.cx); break; } #else pos = PIXEL_POS(g, g->p.x, g->p.y); lineadd = g->g.Width - g->p.cx; shape = (g->p.cx << 16) | (g->p.cy); #endif #if STM32LTDC_DMA_CACHE_FLUSH { // This is slightly less than optimal as we flush the whole line in the source and destination image // instead of just the cx portion but this saves us having to iterate over each line. gU32 f, e; // Data memory barrier __ugfxDSB(); // Flush then invalidate the destination area e = pos + (g->p.cy > 1 ? ((gU32)((ltdcLayerConfig *)g->priv)->pitch*(shape & 0xFFFF)) : ((shape>>16)*LTDC_PIXELBYTES)); for(f=(pos & ~31); f < e; f += 32) { SCB->DCCIMVAC = f; SCB->DCIMVAC = f; } // Data memory barrier __ugfxDSB(); } #endif // Wait until DMA2D is ready while(DMA2D->CR & DMA2D_CR_START); // Start the DMA2D DMA2D->OMAR = (gU32)PIXEL_ADDR(g, pos); DMA2D->OOR = lineadd; DMA2D->NLR = shape; #if GDISP_LLD_PIXELFORMAT == GDISP_PIXELFORMAT_RGB888 // As we don't support ARGB pixel types in uGFX yet we will // use RGB with an inverted alpha value for compatibility // ie. 0x00FFFFFF is fully opaque white, 0xFFFFFFFF is fully transparent white DMA2D->OCOLR = (gU32)(gdispColor2Native(g->p.color)) ^ 0xFF000000; #else DMA2D->OCOLR = (gU32)(gdispColor2Native(g->p.color)); #endif ; DMA2D->CR = DMA2D_CR_MODE_R2M | DMA2D_CR_START; } /* Oops - the DMA2D only supports gOrientation0. * * Where the width is 1 we can trick it for other orientations. * That is worthwhile as a width of 1 is common. For other * situations we need to fall back to pixel pushing. * * Additionally, although DMA2D can translate color formats * it can only do it for a small range of formats. For any * other formats we also need to fall back to pixel pushing. * * As the code to actually do all that for other than the * simplest case (orientation == gOrientation0 and * GDISP_PIXELFORMAT == GDISP_LLD_PIXELFORMAT) is very complex * we will always pixel push for now. In practice that is OK as * access to the framebuffer is fast - probably faster than DMA2D. * It just uses more CPU. */ #if GDISP_HARDWARE_BITFILLS // Uses p.x,p.y p.cx,p.cy p.x1,p.y1 (=srcx,srcy) p.x2 (=srccx), p.ptr (=buffer) LLDSPEC void gdisp_lld_blit_area(GDisplay* g) { gU32 srcstart, dststart; srcstart = LTDC_PIXELBYTES * ((gU32)g->p.x2 * g->p.y1 + g->p.x1) + (gU32)g->p.ptr; dststart = (gU32)PIXEL_ADDR(g, PIXEL_POS(g, g->p.x, g->p.y)); #if STM32LTDC_DMA_CACHE_FLUSH { // This is slightly less than optimal as we flush the whole line in the source and destination image // instead of just the cx portion but this saves us having to iterate over each line. gU32 f, e; // Data memory barrier __ugfxDSB(); // Flush the source area e = srcstart + (g->p.cy > 1 ? ((gU32)g->p.x2*g->p.cy) : (gU32)g->p.cx)*LTDC_PIXELBYTES; for(f=(srcstart & ~31); f < e; f += 32) SCB->DCCIMVAC = f; // Flush then invalidate the destination area e = dststart + (g->p.cy > 1 ? ((gU32)((ltdcLayerConfig *)g->priv)->pitch*g->p.cy) : ((gU32)g->p.cx*LTDC_PIXELBYTES)); for(f=(dststart & ~31); f < e; f += 32) { SCB->DCCIMVAC = f; SCB->DCIMVAC = f; } // Data memory barrier __ugfxDSB(); } #endif // Wait until DMA2D is ready while(DMA2D->CR & DMA2D_CR_START); // Source setup DMA2D->FGMAR = srcstart; DMA2D->FGOR = g->p.x2 - g->p.cx; // Output setup DMA2D->OMAR = dststart; DMA2D->OOR = g->g.Width - g->p.cx; DMA2D->NLR = (g->p.cx << 16) | (g->p.cy); // Set MODE to M2M and Start the process DMA2D->CR = DMA2D_CR_MODE_M2M | DMA2D_CR_START; } #endif #endif /* STM32LTDC_USE_DMA2D */ #endif /* GFX_USE_GDISP */