/****************************************************************************
 * arch/arm/src/stm32/stm32_ltdc.c
 *
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.  The
 * ASF licenses this file to you under the Apache License, Version 2.0 (the
 * "License"); you may not use this file except in compliance with the
 * License.  You may obtain a copy of the License at
 *
 *   http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
 * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.  See the
 * License for the specific language governing permissions and limitations
 * under the License.
 *
 ****************************************************************************/

/* References:
 *   STM32F429 Technical Reference Manual and Data Sheet
 */

/****************************************************************************
 * Included Files
 ****************************************************************************/

#include <nuttx/config.h>

#include <inttypes.h>
#include <stdint.h>
#include <string.h>
#include <sys/param.h>
#include <assert.h>
#include <errno.h>
#include <debug.h>

#include <nuttx/irq.h>
#include <nuttx/kmalloc.h>
#include <nuttx/mutex.h>
#include <nuttx/video/fb.h>

#include <arch/board/board.h>

#include "arm_internal.h"
#include "stm32.h"
#include "hardware/stm32_ltdc.h"
#include "hardware/stm32_dma2d.h"
#include "stm32_ltdc.h"
#include "stm32_dma2d.h"

/****************************************************************************
 * Pre-processor Definitions
 ****************************************************************************/

/* Register definition ******************************************************/

#ifndef BOARD_LTDC_WIDTH
#  error BOARD_LTDC_WIDTH must be defined in the board.h header file
#endif

#ifndef BOARD_LTDC_HEIGHT
#  error BOARD_LTDC_HEIGHT must be defined in the board.h header file
#endif

#define STM32_LTDC_HEIGHT           BOARD_LTDC_HEIGHT
#define STM32_LTDC_WIDTH            BOARD_LTDC_WIDTH

/* Configure LTDC register */

/* LTDC_LxWHPCR register */

#define STM32_LTDC_LXWHPCR_WHSTPOS  (BOARD_LTDC_HSYNC + BOARD_LTDC_HBP - 1)
#define STM32_LTDC_LxWHPCR_WHSPPOS  (BOARD_LTDC_HSYNC + BOARD_LTDC_HBP + \
                                    STM32_LTDC_WIDTH - 1)

/* LTDC_LxWVPCR register */

#define STM32_LTDC_LXWVPCR_WVSTPOS  (BOARD_LTDC_VSYNC + BOARD_LTDC_VBP - 1)
#define STM32_LTDC_LxWVPCR_WVSPPOS  (BOARD_LTDC_VSYNC + BOARD_LTDC_VBP + \
                                    STM32_LTDC_HEIGHT - 1)

/* LTDC_SSCR register */

#define STM32_LTDC_SSCR_VSH         LTDC_SSCR_VSH(BOARD_LTDC_VSYNC - 1)
#define STM32_LTDC_SSCR_HSW         LTDC_SSCR_HSW(BOARD_LTDC_HSYNC - 1)

/* LTDC_BPCR register */

#define STM32_LTDC_BPCR_AVBP        LTDC_BPCR_AVBP(STM32_LTDC_LXWVPCR_WVSTPOS)
#define STM32_LTDC_BPCR_AHBP        LTDC_BPCR_AHBP(STM32_LTDC_LXWHPCR_WHSTPOS)

/* LTDC_AWCR register */

#define STM32_LTDC_AWCR_AAH         LTDC_AWCR_AAH(STM32_LTDC_LxWVPCR_WVSPPOS)
#define STM32_LTDC_AWCR_AAW         LTDC_AWCR_AAW(STM32_LTDC_LxWHPCR_WHSPPOS)

/* LTDC_TWCR register */

#define STM32_LTDC_TWCR_TOTALH      LTDC_TWCR_TOTALH(BOARD_LTDC_VSYNC + \
                                    BOARD_LTDC_VBP + \
                                    STM32_LTDC_HEIGHT + BOARD_LTDC_VFP - 1)
#define STM32_LTDC_TWCR_TOTALW      LTDC_TWCR_TOTALW(BOARD_LTDC_HSYNC + \
                                    BOARD_LTDC_HBP + \
                                    STM32_LTDC_WIDTH + BOARD_LTDC_HFP - 1)

/* Global GCR register */

/* Synchronisation and Polarity */

#define STM32_LTDC_GCR_PCPOL        BOARD_LTDC_GCR_PCPOL
#define STM32_LTDC_GCR_DEPOL        BOARD_LTDC_GCR_DEPOL
#define STM32_LTDC_GCR_VSPOL        BOARD_LTDC_GCR_VSPOL
#define STM32_LTDC_GCR_HSPOL        BOARD_LTDC_GCR_HSPOL

/* Dither */

#define STM32_LTDC_GCR_DEN          BOARD_LTDC_GCR_DEN
#define STM32_LTDC_GCR_DBW          LTDC_GCR_GBW(BOARD_LTDC_GCR_DBW)
#define STM32_LTDC_GCR_DGW          LTDC_GCR_DGW(BOARD_LTDC_GCR_DGW)
#define STN32_LTDC_GCR_DRW          LTDC_GCR_DBW(BOARD_LTDC_GCR_DRW)

/* LIPCR register */

#define STM32_LTDC_LIPCR_LIPOS      LTDC_LIPCR_LIPOS(STM32_LTDC_TWCR_TOTALW)

/* Configuration ************************************************************/

#ifndef CONFIG_STM32_LTDC_DEFBACKLIGHT
#  define CONFIG_STM32_LTDC_DEFBACKLIGHT 0xf0
#endif
#define STM32_LTDC_BACKLIGHT_OFF 0x00

/* Color/video formats */

/* Layer 1 format */

#if defined(CONFIG_STM32_LTDC_L1_L8)
#  define STM32_LTDC_L1_BPP         8
#  define STM32_LTDC_L1_COLOR_FMT   FB_FMT_RGB8
#  define STM32_LTDC_L1PFCR_PF      LTDC_LXPFCR_PF(LTDC_PF_L8)
#  define STM32_LTDC_L1_DMA2D_PF    DMA2D_PF_L8
#  define STM32_LTDC_L1CMAP
#elif defined(CONFIG_STM32_LTDC_L1_RGB565)
#  define STM32_LTDC_L1_BPP         16
#  define STM32_LTDC_L1_COLOR_FMT   FB_FMT_RGB16_565
#  define STM32_LTDC_L1PFCR_PF      LTDC_LXPFCR_PF(LTDC_PF_RGB565)
#  define STM32_LTDC_L1_DMA2D_PF    DMA2D_PF_RGB565
#elif defined(CONFIG_STM32_LTDC_L1_RGB888)
#  define STM32_LTDC_L1_BPP         24
#  define STM32_LTDC_L1_COLOR_FMT   FB_FMT_RGB24
#  define STM32_LTDC_L1PFCR_PF      LTDC_LXPFCR_PF(LTDC_PF_RGB888)
#  define STM32_LTDC_L1_DMA2D_PF    DMA2D_PF_RGB888
#elif defined(CONFIG_STM32_LTDC_L1_ARGB8888)
#  define STM32_LTDC_L1_BPP         32
#  define STM32_LTDC_L1_COLOR_FMT   FB_FMT_RGB32
#  define STM32_LTDC_L1PFCR_PF      LTDC_LXPFCR_PF(LTDC_PF_ARGB8888)
#  define STM32_LTDC_L1_DMA2D_PF    DMA2D_PF_ARGB8888
#else
#  error "LTDC pixel format not supported"
#endif

/* Layer 2 format */

#ifdef CONFIG_STM32_LTDC_L2
#  if defined(CONFIG_STM32_LTDC_L2_L8)
#   define STM32_LTDC_L2_BPP         8
#   define STM32_LTDC_L2_COLOR_FMT   FB_FMT_RGB8
#   define STM32_LTDC_L2PFCR_PF      LTDC_LXPFCR_PF(LTDC_PF_L8)
#   define STM32_LTDC_L2_DMA2D_PF    DMA2D_PF_L8
#   define STM32_LTDC_L2CMAP
#  elif defined(CONFIG_STM32_LTDC_L2_RGB565)
#   define STM32_LTDC_L2_BPP         16
#   define STM32_LTDC_L2_COLOR_FMT   FB_FMT_RGB16_565
#   define STM32_LTDC_L2PFCR_PF      LTDC_LXPFCR_PF(LTDC_PF_RGB565)
#   define STM32_LTDC_L2_DMA2D_PF    DMA2D_PF_RGB565
#  elif defined(CONFIG_STM32_LTDC_L2_RGB888)
#   define STM32_LTDC_L2_BPP         24
#   define STM32_LTDC_L2_COLOR_FMT   FB_FMT_RGB24
#   define STM32_LTDC_L2PFCR_PF      LTDC_LXPFCR_PF(LTDC_PF_RGB888)
#   define STM32_LTDC_L2_DMA2D_PF    DMA2D_PF_RGB888
#  elif defined(CONFIG_STM32_LTDC_L2_ARGB8888)
#   define STM32_LTDC_L2_BPP         32
#   define STM32_LTDC_L2_COLOR_FMT   FB_FMT_RGB32
#   define STM32_LTDC_L2PFCR_PF      LTDC_LXPFCR_PF(LTDC_PF_ARGB8888)
#   define STM32_LTDC_L2_DMA2D_PF    DMA2D_PF_ARGB8888
#  else
#   error "LTDC pixel format not supported"
#  endif
#endif /* CONFIG_STM32_LTDC_L2 */

/* Framebuffer sizes in bytes */

#if STM32_LTDC_L1_BPP == 8
#  define STM32_LTDC_L1_STRIDE      (STM32_LTDC_WIDTH)
#elif STM32_LTDC_L1_BPP == 16
#  define STM32_LTDC_L1_STRIDE      ((STM32_LTDC_WIDTH * 16 + 7) / 8)
#elif STM32_LTDC_L1_BPP == 24
#  define STM32_LTDC_L1_STRIDE      ((STM32_LTDC_WIDTH * 24 + 7) / 8)
#elif STM32_LTDC_L1_BPP == 32
#  define STM32_LTDC_L1_STRIDE      ((STM32_LTDC_WIDTH * 32 + 7) / 8)
#else
#  error Undefined or unrecognized base resolution
#endif

/* LTDC only supports 8 bit per pixel overal */

#define STM32_LTDC_LX_BYPP(n)       ((n) / 8)

#define STM32_LTDC_L1_FBSIZE        (STM32_LTDC_L1_STRIDE * STM32_LTDC_HEIGHT)

#ifdef CONFIG_STM32_LTDC_L2
#  ifndef CONFIG_STM32_LTDC_L2_WIDTH
#    define CONFIG_STM32_LTDC_L2_WIDTH STM32_LTDC_WIDTH
#  endif

#  if CONFIG_STM32_LTDC_L2_WIDTH > STM32_LTDC_WIDTH
#    error Width of Layer 2 exceeds the width of the display
#  endif

#  ifndef CONFIG_STM32_LTDC_L2_HEIGHT
#    define CONFIG_STM32_LTDC_L2_HEIGHT STM32_LTDC_HEIGHT
#  endif

#  if CONFIG_STM32_LTDC_L2_HEIGHT > STM32_LTDC_HEIGHT
#    error Height of Layer 2 exceeds the height of the display
#  endif

#  if STM32_LTDC_L2_BPP == 8
#    define STM32_LTDC_L2_STRIDE    (CONFIG_STM32_LTDC_L2_WIDTH)
#  elif STM32_LTDC_L2_BPP == 16
#    define STM32_LTDC_L2_STRIDE    ((CONFIG_STM32_LTDC_L2_WIDTH * 16 + 7) / 8)
#  elif STM32_LTDC_L2_BPP == 24
#    define STM32_LTDC_L2_STRIDE    ((CONFIG_STM32_LTDC_L2_WIDTH * 24 + 7) / 8)
#  elif STM32_LTDC_L2_BPP == 32
#    define STM32_LTDC_L2_STRIDE    ((CONFIG_STM32_LTDC_L2_WIDTH * 32 + 7) / 8)
#  else
#    error Undefined or unrecognized base resolution
#  endif

#  define STM32_LTDC_L2_FBSIZE      (STM32_LTDC_L2_STRIDE * \
                                     CONFIG_STM32_LTDC_L2_HEIGHT)

#else
#  define STM32_LTDC_L2_FBSIZE (0)
#endif

/* Total memory used for framebuffers */

#define STM32_LTDC_TOTAL_FBSIZE     (STM32_LTDC_L1_FBSIZE + \
                                     STM32_LTDC_L2_FBSIZE)

/* Debug option */

#ifdef CONFIG_STM32_LTDC_REGDEBUG
#  define regerr       lcderr
#  define reginfo      lcdinfo
#else
#  define regerr(x...)
#  define reginfo(x...)
#endif

/* Preallocated LTDC framebuffers */

/* Position the framebuffer memory in the center of the memory set aside.  We
 * will use any skirts before or after the framebuffer memory as a guard
 * against wild framebuffer writes.
 */

#define STM32_LTDC_BUFFER_SIZE      CONFIG_STM32_LTDC_FB_SIZE
#define STM32_LTDC_BUFFER_FREE      (STM32_LTDC_BUFFER_SIZE - \
                                    STM32_LTDC_TOTAL_FBSIZE)
#define STM32_LTDC_BUFFER_START     (CONFIG_STM32_LTDC_FB_BASE + \
                                    STM32_LTDC_BUFFER_FREE/2)

#if STM32_LTDC_BUFFER_FREE < 0
#  error "STM32_LTDC_BUFFER_SIZE not large enough for frame buffers"
#endif

/* Layer frame buffer */

#define STM32_LTDC_BUFFER_L1        STM32_LTDC_BUFFER_START
#define STM32_LTDC_ENDBUF_L1        (STM32_LTDC_BUFFER_L1 + \
                                     STM32_LTDC_L1_FBSIZE)

#ifdef CONFIG_STM32_LTDC_L2
#  define STM32_LTDC_BUFFER_L2      STM32_LTDC_ENDBUF_L1
#  define STM32_LTDC_ENDBUF_L2      (STM32_LTDC_BUFFER_L2 + \
                                     STM32_LTDC_L2_FBSIZE)
#else
#  define STM32_LTDC_ENDBUF_L2      STM32_LTDC_ENDBUF_L1
#endif

/* LTDC layer */

#ifdef CONFIG_STM32_LTDC_L2
#  define LTDC_NLAYERS 2
#else
#  define LTDC_NLAYERS 1
#endif

/* DMA2D layer */

#ifdef CONFIG_STM32_DMA2D
#  define DMA2D_NLAYERS             CONFIG_STM32_DMA2D_NLAYERS
#  if DMA2D_NLAYERS < 1
#    error "DMA2D must at least support 1 overlay"
#  endif

#define STM32_DMA2D_WIDTH           CONFIG_STM32_DMA2D_LAYER_PPLINE

#  if defined(CONFIG_STM32_DMA2D_L8)
#    define STM32_DMA2D_STRIDE      (STM32_DMA2D_WIDTH)
#    define STM32_DMA2D_BPP         8
#    define STM32_DMA2D_COLOR_FMT   DMA2D_PF_L8
#  elif defined(CONFIG_STM32_DMA2D_RGB565)
#    define STM32_DMA2D_STRIDE      ((STM32_DMA2D_WIDTH * 16 + 7) / 8)
#    define STM32_DMA2D_BPP         16
#    define STM32_DMA2D_COLOR_FMT   DMA2D_PF_RGB565
#  elif defined(CONFIG_STM32_DMA2D_RGB888)
#    define STM32_DMA2D_STRIDE      ((STM32_DMA2D_WIDTH * 24 + 7) / 8)
#    define STM32_DMA2D_BPP         24
#    define STM32_DMA2D_COLOR_FMT   DMA2D_PF_RGB888
#  elif defined(CONFIG_STM32_DMA2D_ARGB8888)
#    define STM32_DMA2D_STRIDE      ((STM32_DMA2D_WIDTH * 32 + 7) / 8)
#    define STM32_DMA2D_BPP         32
#    define STM32_DMA2D_COLOR_FMT   DMA2D_PF_ARGB8888
#  else
#    error "DMA2D pixel format not supported"
#  endif

#  ifdef CONFIG_STM32_DMA2D_LAYER_SHARED
#    define STM32_DMA2D_FBSIZE      CONFIG_STM32_DMA2D_FB_SIZE
#    define STM32_DMA2D_LAYER_SIZE  0
#  else
#    define STM32_DMA2D_FBSIZE      CONFIG_STM32_DMA2D_FB_SIZE / DMA2D_NLAYERS
#    define STM32_DMA2D_LAYER_SIZE  STM32_DMA2D_FBSIZE
#    if STM32_DMA2D_FBSIZE * DMA2D_NLAYERS > CONFIG_STM32_DMA2D_FB_SIZE
#      error "DMA2D framebuffer size to small for configured number of overlays"
#    endif
#  endif /* CONFIG_STM32_DMA2D_LAYER_SHARED */

#  define STM32_DMA2D_HEIGHT         STM32_DMA2D_FBSIZE / STM32_DMA2D_STRIDE

#  define STM32_DMA2D_BUFFER_START   CONFIG_STM32_DMA2D_FB_BASE
#else
#  define DMA2D_NLAYERS              0
#endif /* CONFIG_STM32_DMA2D */

#define LTDC_NOVERLAYS              LTDC_NLAYERS + DMA2D_NLAYERS

/* Dithering */

#ifndef CONFIG_STM32_LTDC_DITHER_RED
#  define STM32_LTDC_DITHER_RED     0
#else
#  define STM32_LTDC_DITHER_RED     CONFIG_STM32_LTDC_DITHER_RED
#endif
#ifndef CONFIG_STM32_LTDC_DITHER_GREEN
#  define STM32_LTDC_DITHER_GREEN   0
#else
#  define STM32_LTDC_DITHER_GREEN   CONFIG_STM32_LTDC_DITHER_GREEN
#endif
#ifndef CONFIG_STM32_LTDC_DITHER_BLUE
#  define STM32_LTDC_DITHER_BLUE    0
#else
#  define STM32_LTDC_DITHER_BLUE    CONFIG_STM32_LTDC_DITHER_BLUE
#endif

/* Background color */

#ifndef CONFIG_STM32_LTDC_BACKCOLOR
#  define STM32_LTDC_BACKCOLOR      0
#else
#  define STM32_LTDC_BACKCOLOR      CONFIG_STM32_LTDC_BACKCOLOR
#endif

/* Layer default color */

#ifdef CONFIG_STM32_LTDC_L1_COLOR
#  define STM32_LTDC_L1_COLOR        CONFIG_STM32_LTDC_L1_COLOR
#else
#  define STM32_LTDC_L1_COLOR        0x000000
#endif

#ifdef CONFIG_STM32_LTDC_L2
#  ifdef CONFIG_STM32_LTDC_L2_COLOR
#    define STM32_LTDC_L2_COLOR        CONFIG_STM32_LTDC_L2_COLOR
#  else
#    define STM32_LTDC_L2_COLOR        0x000000
#  endif
#endif

/* Internal operation flags */

#define LTDC_LAYER_SETAREA          (1 << 0) /* Change visible area */
#define LTDC_LAYER_SETALPHAVALUE    (1 << 1) /* Change constant alpha value */
#define LTDC_LAYER_SETBLENDMODE     (1 << 2) /* Change blendmode */
#define LTDC_LAYER_SETCOLORKEY      (1 << 3) /* Change color key */
#define LTDC_LAYER_ENABLECOLORKEY   (1 << 4) /* Enable colorkey */
#define LTDC_LAYER_SETCOLOR         (1 << 5) /* Change default color */
#define LTDC_LAYER_SETENABLE        (1 << 6) /* Change enabled state */
#define LTDC_LAYER_ENABLE           (1 << 7) /* Enable the layer */

/* Layer initializing state */

#define LTDC_LAYER_INIT             LTDC_LAYER_SETAREA | \
                                    LTDC_LAYER_SETALPHAVALUE | \
                                    LTDC_LAYER_SETBLENDMODE | \
                                    LTDC_LAYER_SETCOLORKEY | \
                                    LTDC_LAYER_SETCOLOR | \
                                    LTDC_LAYER_SETENABLE | \
                                    LTDC_LAYER_ENABLE

/* Blendfactor reset values for flip operation */

#define STM32_LTDC_BF1_RESET        6
#define STM32_LTDC_BF2_RESET        7

/* Check pixel format support by DMA2D driver */

#ifdef CONFIG_STM32_DMA2D
#  if defined(CONFIG_STM32_LTDC_L1_L8) || \
      defined(CONFIG_STM32_LTDC_L2_L8)
#    if !defined(CONFIG_STM32_DMA2D_L8)
#      error "DMA2D must support FB_FMT_RGB8 pixel format"
#    endif
#  endif
#  if defined(CONFIG_STM32_LTDC_L1_RGB565) || \
      defined(CONFIG_STM32_LTDC_L2_RGB565)
#    if !defined(CONFIG_STM32_DMA2D_RGB565)
#      error "DMA2D must support FB_FMT_RGB16_565 pixel format"
#    endif
#  endif
#  if defined(CONFIG_STM32_LTDC_L1_RGB888) || \
      defined(CONFIG_STM32_LTDC_L2_RGB888)
#    if !defined(CONFIG_STM32_DMA2D_RGB888)
#      error "DMA2D must support FB_FMT_RGB24 pixel format"
#    endif
#  endif
#  if defined(CONFIG_STM32_LTDC_L1_ARGB8888) || \
      defined(CONFIG_STM32_LTDC_L2_ARGB8888)
#    if !defined(CONFIG_STM32_DMA2D_ARGB8888)
#      error "DMA2D must support FB_FMT_RGB32 pixel format"
#    endif
#  endif
#endif

/* Calculate the size of the layers clut table */

#ifdef CONFIG_STM32_FB_CMAP
#  if defined(CONFIG_STM32_DMA2D) && !defined(CONFIG_STM32_DMA2D_L8)
#    error "DMA2D must also support L8 CLUT pixel format if supported by LTDC"
#  endif
#  ifdef STM32_LTDC_L1CMAP
#    ifdef CONFIG_STM32_FB_TRANSPARENCY
#      define STM32_LAYER_CLUT_SIZE STM32_LTDC_NCLUT * sizeof(uint32_t)
#    else
#      define STM32_LAYER_CLUT_SIZE STM32_LTDC_NCLUT * 3 * sizeof(uint8_t)
#    endif
#  endif
#  ifdef STM32_LTDC_L2CMAP
#    undef  STM32_LAYER_CLUT_SIZE
#    ifdef CONFIG_STM32_FB_TRANSPARENCY
#      define STM32_LAYER_CLUT_SIZE STM32_LTDC_NCLUT * sizeof(uint32_t) * 2
#    else
#      define STM32_LAYER_CLUT_SIZE STM32_LTDC_NCLUT * 3 * sizeof(uint8_t) * 2
#    endif
#  endif
#endif

#ifndef CONFIG_STM32_FB_CMAP
#  if defined(STM32_LTDC_L1CMAP) || defined(STM32_LTDC_L2CMAP)
#    undef STM32_LTDC_L1CMAP
#    undef STM32_LTDC_L2CMAP
#    error "Enable cmap to support the configured layer format!"
#  endif
#endif

/* Layer clut rgb value positioning */

#define LTDC_L1CLUT_REDOFFSET       0
#define LTDC_L1CLUT_GREENOFFSET     256
#define LTDC_L1CLUT_BLUEOFFSET      512
#define LTDC_L2CLUT_REDOFFSET       768
#define LTDC_L2CLUT_GREENOFFSET     1024
#define LTDC_L2CLUT_BLUEOFFSET      1280

/* Layer argb clut register position */

#define LTDC_CLUT_ADD(n)            ((uint32_t)(n) << 24)
#define LTDC_CLUT_ALPHA(n)          LTDC_CLUT_ADD(n)
#define LTDC_CLUT_RED(n)            ((uint32_t)(n) << 16)
#define LTDC_CLUT_GREEN(n)          ((uint32_t)(n) << 8)
#define LTDC_CLUT_BLUE(n)           ((uint32_t)(n) << 0)
#define LTDC_CLUT_RGB888_MASK       0xffffff

/* Layer argb cmap conversion */

#define LTDC_CMAP_ALPHA(n)          ((uint32_t)(n) >> 24)
#define LTDC_CMAP_RED(n)            ((uint32_t)(n) >> 16)
#define LTDC_CMAP_GREEN(n)          ((uint32_t)(n) >> 8)
#define LTDC_CMAP_BLUE(n)           ((uint32_t)(n) >> 0)

/* Hardware acceleration support */

/* Acceleration support for LTDC overlays */

#ifdef CONFIG_STM32_LTDC_L1_CHROMAKEYEN
#  define STM32_LTDC_L1_CHROMAEN    true
#  define STM32_LTDC_L1_CHROMAKEY   CONFIG_STM32_LTDC_L1_CHROMAKEY
#  define LTDC_LTDC_ACCL_L1         FB_ACCL_TRANSP | FB_ACCL_CHROMA
#else
#  define STM32_LTDC_L1_CHROMAEN    false
#  define STM32_LTDC_L1_CHROMAKEY   0
#  define LTDC_LTDC_ACCL_L1         FB_ACCL_TRANSP
#endif

#ifdef CONFIG_STM32_LTDC_L2_CHROMAKEYEN
#  define STM32_LTDC_L2_CHROMAEN    true
#  define STM32_LTDC_L2_CHROMAKEY   CONFIG_STM32_LTDC_L2_CHROMAKEY
#  define LTDC_LTDC_ACCL_L2         FB_ACCL_TRANSP | FB_ACCL_CHROMA
#else
#  define STM32_LTDC_L2_CHROMAEN    false
#  define STM32_LTDC_L2_CHROMAKEY   0
#  define LTDC_LTDC_ACCL_L2         FB_ACCL_TRANSP
#endif

#ifdef CONFIG_STM32_DMA2D
#  ifdef CONFIG_FB_OVERLAY_BLIT
#    ifdef CONFIG_STM32_FB_CMAP
#      define LTDC_BLIT_ACCL        FB_ACCL_BLIT
#    else
#      define LTDC_BLIT_ACCL        FB_ACCL_BLIT | FB_ACCL_BLEND
#    endif /* CONFIG_STM32_FB_CMAP */
#  else
#    define LTDC_BLIT_ACCL          0
#  endif /* CONFIG_FB_OVERLAY_BLIT */

#  ifdef CONFIG_STM32_FB_CMAP
#    define LTDC_DMA2D_ACCL         LTDC_BLIT_ACCL
#  else
#    define LTDC_DMA2D_ACCL         FB_ACCL_COLOR | LTDC_BLIT_ACCL
#  endif /* CONFIG_STM32_FB_CMAP */
#else
#  define LTDC_DMA2D_ACCL           0
#endif /* CONFIG_STM32_DMA2D */

#define LTDC_L1_ACCL                LTDC_LTDC_ACCL_L1 | LTDC_DMA2D_ACCL
#ifdef CONFIG_STM32_LTDC_L2
#  define LTDC_L2_ACCL              LTDC_LTDC_ACCL_L2 | LTDC_DMA2D_ACCL
#endif

/* Acceleration support for DMA2D overlays */

#ifdef CONFIG_STM32_FB_CMAP
#  ifdef CONFIG_FB_OVERLAY_BLIT
#    define DMA2D_ACCL              FB_ACCL_BLIT | FB_ACCL_AREA
#  else
#    define DMA2D_ACCL              FB_ACCL_AREA
#  endif
#else
#  ifdef CONFIG_FB_OVERLAY_BLIT
#    define DMA2D_ACCL              FB_ACCL_AREA  | \
                                    FB_ACCL_TRANSP | \
                                    FB_ACCL_COLOR | \
                                    FB_ACCL_BLIT | \
                                    FB_ACCL_BLEND
#  else
#    define DMA2D_ACCL              FB_ACCL_AREA  | \
                                    FB_ACCL_TRANSP | \
                                    FB_ACCL_COLOR
#  endif
#endif

/* Color normalization */

#if defined(CONFIG_STM32_LTDC_L1_RGB565)
#  define RGB888_R(x)               (((((x) >> 11) & 0x1f) * 527 + 23) >> 6)
#  define RGB888_G(x)               (((((x) >> 5) & 0x3f) * 259 + 33) >> 6)
#  define RGB888_B(x)               ((((x) & 0x1f) * 527 + 23) >> 6)
#  define ARGB8888(x)               ((RGB888_R(x) << 16) | \
                                     (RGB888_G(x) << 8)  | \
                                      RGB888_B(x))
#else
#  define ARGB8888(x)               (x)
#endif

/****************************************************************************
 * Private Types
 ****************************************************************************/

/* This enumeration names each layer supported by the hardware */

enum stm32_layer_e
{
  LTDC_LAYER_L1 = 0,       /* LCD Layer 1 */
  LTDC_LAYER_L2,           /* LCD Layer 2 */
};

/* LTDC General layer information */

struct stm32_ltdc_s
{
  int layerno;                                /* layer number */

#ifdef CONFIG_FB_OVERLAY
  struct   fb_overlayinfo_s oinfo;            /* Overlay info */
#endif

#ifdef CONFIG_STM32_DMA2D
  struct stm32_dma2d_overlay_s dma2dinfo;     /* Overlay info for DMA2D */
#endif

  mutex_t *lock;                              /* Layer exclusive access */
};

/* This structure provides the overall state of the LTDC layer */

struct stm32_ltdcdev_s
{
  /* Framebuffer interface */

  struct fb_vtable_s vtable;

  /* Framebuffer video information */

  struct fb_videoinfo_s vinfo;

  /* Framebuffer plane information */

  struct fb_planeinfo_s pinfo;

  /* Cmap information */

#ifdef CONFIG_STM32_FB_CMAP
  struct fb_cmap_s cmap;
#endif

  /* Layer information */

  struct stm32_ltdc_s layer[LTDC_NOVERLAYS];

#ifdef CONFIG_STM32_DMA2D
  /* Interface to the dma2d controller */

  struct dma2d_layer_s *dma2d;
#endif
};

/* Interrupt handling */

struct stm32_interrupt_s
{
  int   irq;        /* irq number */
  int error;        /* Interrupt error */
  sem_t *sem;       /* Semaphore for waiting for irq */
};

/****************************************************************************
 * Private Function Prototypes
 ****************************************************************************/

/* Overal LTDC helper */

static void stm32_ltdc_enable(bool enable);
static void stm32_ltdc_gpioconfig(void);
static void stm32_ltdc_periphconfig(void);
static void stm32_ltdc_bgcolor(uint32_t rgb);
static void stm32_ltdc_dither(bool enable, uint8_t red,
                              uint8_t green, uint8_t blue);
static int stm32_ltdcirq(int irq, void *context, void *arg);
static int stm32_ltdc_waitforirq(void);
static int stm32_ltdc_reload(uint8_t value, bool waitvblank);

/* Helper for layer register configuration */

static void stm32_ltdc_lpixelformat(struct stm32_ltdc_s *layer);
static void stm32_ltdc_lframebuffer(struct stm32_ltdc_s *layer);
static void stm32_ltdc_lenable(struct stm32_ltdc_s *layer, bool enable);
static void stm32_ltdc_ldefaultcolor(struct stm32_ltdc_s *layer,
                                     uint32_t rgb);
static void stm32_ltdc_ltransp(struct stm32_ltdc_s *layer,
                               uint8_t transp,
                               uint32_t mode);
static void stm32_ltdc_lchromakey(struct stm32_ltdc_s *layer,
                                  uint32_t chromakey);
static void stm32_ltdc_lchromakeyenable(struct stm32_ltdc_s *layer,
                                        bool enable);
static void stm32_ltdc_linit(uint8_t lid);

#ifdef CONFIG_STM32_DMA2D
static void stm32_ltdc_dma2dlinit(void);

#  ifdef CONFIG_FB_OVERLAY_BLIT
static bool stm32_ltdc_lvalidate(const struct stm32_ltdc_s *layer,
                                 const struct fb_area_s *area);
#  endif
#endif

#ifdef CONFIG_STM32_FB_CMAP
static void stm32_ltdc_lputclut(struct stm32_ltdc_s *layer,
                                const struct fb_cmap_s *cmap);
static void stm32_ltdc_lgetclut(struct stm32_ltdc_s *layer,
                                struct fb_cmap_s *cmap);
static void stm32_ltdc_lclutenable(struct stm32_ltdc_s *layer,
                                   bool enable);
#endif

static void stm32_ltdc_lclear(uint8_t overlayno);

/* Framebuffer interface */

static int stm32_getvideoinfo(struct fb_vtable_s *vtable,
                              struct fb_videoinfo_s *vinfo);
static int stm32_getplaneinfo(struct fb_vtable_s *vtable,
                              int planeno,
                              struct fb_planeinfo_s *pinfo);

/* The following is provided only if the video hardware supports RGB color
 * mapping
 */

#ifdef CONFIG_STM32_FB_CMAP
static int stm32_getcmap(struct fb_vtable_s *vtable,
                         struct fb_cmap_s *cmap);
static int stm32_putcmap(struct fb_vtable_s *vtable,
                         const struct fb_cmap_s *cmap);
#endif

/* The following is provided only if the video hardware signals vertical
 * synchronisation
 */

#ifdef CONFIG_FB_SYNC
static int stm32_waitforvsync(struct fb_vtable_s *vtable);
#endif

/* The following is provided only if the video hardware supports overlays */

#ifdef CONFIG_FB_OVERLAY
static int stm32_getoverlayinfo(struct fb_vtable_s *vtable,
                                int overlayno,
                                struct fb_overlayinfo_s *oinfo);
static int stm32_settransp(struct fb_vtable_s *vtable,
                           const struct fb_overlayinfo_s *oinfo);
static int stm32_setchromakey(struct fb_vtable_s *vtable,
                              const struct fb_overlayinfo_s *oinfo);
static int stm32_setcolor(struct fb_vtable_s *vtable,
                          const struct fb_overlayinfo_s *oinfo);
static int stm32_setblank(struct fb_vtable_s *vtable,
                          const struct fb_overlayinfo_s *oinfo);
static int stm32_setarea(struct fb_vtable_s *vtable,
                         const struct fb_overlayinfo_s *oinfo);

/* The following is provided only if the video hardware supports blit and
 * blend operation
 */

#  ifdef CONFIG_FB_OVERLAY_BLIT
static int stm32_blit(struct fb_vtable_s *vtable,
                      const struct fb_overlayblit_s *blit);
static int stm32_blend(struct fb_vtable_s *vtable,
                       const struct fb_overlayblend_s *blend);
#  endif /* CONFIG_FB_OVERLAY_BLIT */
#endif /* CONFIG_FB_OVERLAY */

/****************************************************************************
 * Private Data
 ****************************************************************************/

/* PIO pin configurations */

static const uint32_t g_ltdcpins[] =
{
  GPIO_LTDC_R4, GPIO_LTDC_R5, GPIO_LTDC_R6, GPIO_LTDC_R7,
  GPIO_LTDC_G4, GPIO_LTDC_G5, GPIO_LTDC_G6, GPIO_LTDC_G7,
  GPIO_LTDC_B4, GPIO_LTDC_B5, GPIO_LTDC_B6, GPIO_LTDC_B7,
#if BOARD_LTDC_OUTPUT_BPP > 12
  GPIO_LTDC_R3, GPIO_LTDC_G2, GPIO_LTDC_G3, GPIO_LTDC_B3,
#  if BOARD_LTDC_OUTPUT_BPP > 16
  GPIO_LTDC_R2, GPIO_LTDC_B2,
#    if BOARD_LTDC_OUTPUT_BPP > 18
  GPIO_LTDC_R0, GPIO_LTDC_R1, GPIO_LTDC_G0, GPIO_LTDC_G1,
  GPIO_LTDC_B0, GPIO_LTDC_B1,
#    endif
#  endif
#endif
  GPIO_LTDC_VSYNC, GPIO_LTDC_HSYNC, GPIO_LTDC_DE, GPIO_LTDC_CLK
};

#define STM32_LTDC_NPINCONFIGS (sizeof(g_ltdcpins) / sizeof(uint32_t))

#ifdef CONFIG_STM32_FB_CMAP
/* The layers clut table entries */

static uint8_t g_redclut[STM32_LTDC_NCLUT];
static uint8_t g_greenclut[STM32_LTDC_NCLUT];
static uint8_t g_blueclut[STM32_LTDC_NCLUT];
#  ifdef CONFIG_STM32_FB_TRANSPARENCY
static uint8_t g_transpclut[STM32_LTDC_NCLUT];
#  endif
#endif /* CONFIG_STM32_FB_CMAP */

/* The LTDC mutex that enforces mutually exclusive access */

static mutex_t g_lock = NXMUTEX_INITIALIZER;

/* The semaphore for interrupt handling */

static sem_t g_semirq = SEM_INITIALIZER(0);

/* This structure provides irq handling */

static struct stm32_interrupt_s g_interrupt =
{
  .irq     = STM32_IRQ_LTDCINT,
  .error   = OK,
  .sem     = &g_semirq
};

/* This structure provides the internal interface */

static struct stm32_ltdcdev_s g_vtable =
{
  .vtable =
    {
      .getvideoinfo    = stm32_getvideoinfo,
      .getplaneinfo    = stm32_getplaneinfo
#ifdef CONFIG_FB_SYNC
      ,
      .waitforvsync    = stm32_waitforvsync
#endif

#ifdef CONFIG_STM32_FB_CMAP
      ,
      .getcmap         = stm32_getcmap,
      .putcmap         = stm32_putcmap
#endif

#ifdef CONFIG_FB_OVERLAY
      ,
      .getoverlayinfo  = stm32_getoverlayinfo,
      .settransp       = stm32_settransp,
      .setchromakey    = stm32_setchromakey,
      .setcolor        = stm32_setcolor,
      .setblank        = stm32_setblank,
      .setarea         = stm32_setarea
#  ifdef CONFIG_FB_OVERLAY_BLIT
      ,
      .blit            = stm32_blit,
      .blend           = stm32_blend
#  endif
#endif /* CONFIG_FB_OVERLAY */
  },
#ifdef CONFIG_STM32_LTDC_L2
  .pinfo =
    {
      .fbmem           = (uint8_t *)STM32_LTDC_BUFFER_L2,
      .fblen           = STM32_LTDC_L2_FBSIZE,
      .stride          = STM32_LTDC_L2_STRIDE,
      .display         = 0,
      .bpp             = STM32_LTDC_L2_BPP
    },
  .vinfo =
    {
      .fmt             = STM32_LTDC_L2_COLOR_FMT,
      .xres            = STM32_LTDC_WIDTH,
      .yres            = STM32_LTDC_HEIGHT,
      .nplanes         = 1,
#  ifdef CONFIG_FB_OVERLAY
      .noverlays       = LTDC_NOVERLAYS
#  endif
    }
#else
  .pinfo =
    {
      .fbmem           = (uint8_t *)STM32_LTDC_BUFFER_L1,
      .fblen           = STM32_LTDC_L1_FBSIZE,
      .stride          = STM32_LTDC_L1_STRIDE,
      .display         = 0,
      .bpp             = STM32_LTDC_L1_BPP
    },
  .vinfo =
    {
      .fmt             = STM32_LTDC_L1_COLOR_FMT,
      .xres            = STM32_LTDC_WIDTH,
      .yres            = STM32_LTDC_HEIGHT,
      .nplanes         = 1,
#  ifdef CONFIG_FB_OVERLAY
      .noverlays       = LTDC_NOVERLAYS
#  endif
    }
#endif /* CONFIG_STM32_LTDC_L2 */
  ,
#ifdef CONFIG_STM32_FB_CMAP
  .cmap =
    {
      .first           = 0,
      .len             = STM32_LTDC_NCLUT,
      .red             = g_redclut,
      .green           = g_greenclut,
      .blue            = g_blueclut,
#  ifdef CONFIG_STM32_FB_TRANSPARENCY
      .transp          = g_transpclut
#  endif
    }
  ,
#endif
  .layer[LTDC_LAYER_L1] =
    {
      .layerno = LTDC_LAYER_L1,
#ifdef CONFIG_FB_OVERLAY
      .oinfo =
        {
          .fbmem            = (uint8_t *)STM32_LTDC_BUFFER_L1,
          .fblen            = STM32_LTDC_L1_FBSIZE,
          .stride           = STM32_LTDC_L1_STRIDE,
          .overlay          = LTDC_LAYER_L1,
          .bpp              = STM32_LTDC_L1_BPP,
          .blank            = 0,
          .chromakey        = 0,
          .color            = 0,
          .transp =
            {
              .transp       = 0xff,
              .transp_mode  = FB_CONST_ALPHA
            },
          .sarea =
            {
              .x            = 0,
              .y            = 0,
              .w            = STM32_LTDC_WIDTH,
              .h            = STM32_LTDC_HEIGHT
            },
          .accl             = LTDC_L1_ACCL
        },
#endif

#ifdef CONFIG_STM32_DMA2D
      .dma2dinfo =
        {
            .fmt            = STM32_LTDC_L1_DMA2D_PF,
            .transp_mode    = STM32_DMA2D_PFCCR_AM_NONE,
            .xres           = STM32_LTDC_WIDTH,
            .yres           = STM32_LTDC_HEIGHT,
            .oinfo          = &g_vtable.layer[LTDC_LAYER_L1].oinfo
        },
#endif
      .lock = &g_lock
    }
#ifdef CONFIG_STM32_LTDC_L2
  ,
  .layer[LTDC_LAYER_L2] =
    {
      .layerno = LTDC_LAYER_L2,
#ifdef CONFIG_FB_OVERLAY
      .oinfo =
        {
          .overlay          = LTDC_LAYER_L2,
          .fbmem            = (uint8_t *)STM32_LTDC_BUFFER_L2,
          .fblen            = STM32_LTDC_L2_FBSIZE,
          .stride           = STM32_LTDC_L2_STRIDE,
          .bpp              = STM32_LTDC_L2_BPP,
          .blank            = 0,
          .chromakey        = 0,
          .color            = 0,
          .transp =
            {
              .transp       = 0xff,
              .transp_mode  = FB_CONST_ALPHA
            },
          .sarea =
            {
              .x            = 0,
              .y            = 0,
              .w            = STM32_LTDC_WIDTH,
              .h            = STM32_LTDC_HEIGHT
            },
          .accl             = LTDC_L2_ACCL
        },
#endif

#ifdef CONFIG_STM32_DMA2D
      .dma2dinfo =
        {
            .fmt            = STM32_LTDC_L2_DMA2D_PF,
            .transp_mode    = STM32_DMA2D_PFCCR_AM_NONE,
            .xres           = STM32_LTDC_WIDTH,
            .yres           = STM32_LTDC_HEIGHT,
            .oinfo          = &g_vtable.layer[LTDC_LAYER_L2].oinfo
        },
#endif
      .lock = &g_lock
    }
#endif
};

/* Configuration lookup tables */

/* LTDC width */

static const uint32_t stm32_width_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_WIDTH
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_WIDTH
#endif
};

/* LTDC height */

static const uint32_t stm32_height_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_HEIGHT
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_HEIGHT
#endif
};

/* LTDC stride */

static const uint32_t stm32_stride_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1_STRIDE
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2_STRIDE
#endif
};

/* LTDC bpp */

static const uint32_t stm32_bpp_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1_BPP
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2_BPP
#endif
};

/* LTDC framebuffer len */

static const uint32_t stm32_fblen_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1_FBSIZE
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2_FBSIZE
#endif
};

/* LTDC framebuffer */

static const uint32_t stm32_fbmem_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_BUFFER_L1
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_BUFFER_L2
#endif
};

/* LTDC default color lookup table */

static const uint32_t stm32_defaultcolor_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1_COLOR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2_COLOR
#endif
};

/* LTDC default chromakey */

static const uint32_t stm32_chromakey_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1_CHROMAKEY
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2_CHROMAKEY
#endif
};

/* LTDC chromakey enabled state */

static const bool stm32_chromakeyen_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1_CHROMAEN
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2_CHROMAEN
#endif
};

/* LTDC pixel format lookup table */

static const uint32_t stm32_fmt_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1PFCR_PF
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2PFCR_PF
#endif
};

/* Register lookup tables */

/* LTDC_LxCR */

static const uintptr_t stm32_cr_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1CR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2CR
#endif
};

/* LTDC_LxWHPCR */

static const uintptr_t stm32_whpcr_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1WHPCR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2WHPCR
#endif
};

/* LTDC_LxWVPCR */

static const uintptr_t stm32_wvpcr_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1WVPCR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2WVPCR
#endif
};

/* LTDC_LxPFCR */

static const uintptr_t stm32_pfcr_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1PFCR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2PFCR
#endif
};

/* LTDC_LxDCCR */

static const uintptr_t stm32_dccr_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1DCCR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2DCCR
#endif
};

/* LTDC_LxCKCR */

static const uintptr_t stm32_ckcr_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1CKCR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2CKCR
#endif
};

/* LTDC_LxCACR */

static const uintptr_t stm32_cacr_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1CACR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2CACR
#endif
};

/* LTDC_LxBFCR */

static const uintptr_t stm32_bfcr_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1BFCR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2BFCR
#endif
};

/* LTDC_LxCFBAR */

static const uintptr_t stm32_cfbar_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1CFBAR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2CFBAR
#endif
};

/* LTDC_LxCFBLR */

static const uintptr_t stm32_cfblr_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1CFBLR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2CFBLR
#endif
};

/* LTDC_LxCFBLNR */

static const uintptr_t stm32_cfblnr_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1CFBLNR
#ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2CFBLNR
#endif
};

/* LTDC_LxCLUTWR */

#ifdef CONFIG_STM32_FB_CMAP
static const uintptr_t stm32_clutwr_layer_t[LTDC_NLAYERS] =
{
  STM32_LTDC_L1CLUTWR
#  ifdef CONFIG_STM32_LTDC_L2
  , STM32_LTDC_L2CLUTWR
#  endif
};
#endif /* CONFIG_STM32_FB_CMAP */

/* The initialized state of the driver */

static bool g_initialized;

/****************************************************************************
 * Private Functions
 ****************************************************************************/

/****************************************************************************
 * Name: stm32_ltdc_gpioconfig
 *
 * Description:
 *   Configure GPIO pins for use with the LTDC
 *
 ****************************************************************************/

static void stm32_ltdc_gpioconfig(void)
{
  int i;

  lcdinfo("Configuring pins\n");

  /* Configure each pin */

  for (i = 0; i < STM32_LTDC_NPINCONFIGS; i++)
    {
      reginfo("set gpio%d = %08x\n", i, g_ltdcpins[i]);
      stm32_configgpio(g_ltdcpins[i]);
    }
}

/****************************************************************************
 * Name: stm32_ltdc_periphconfig
 *
 * Description:
 *   Configures the synchronous timings
 *   Configures the synchronous signals and clock polarity
 *
 ****************************************************************************/

static void stm32_ltdc_periphconfig(void)
{
  uint32_t regval;

  /* Configure GPIO's */

  stm32_ltdc_gpioconfig();

  /* Configure APB2 LTDC clock external */

  reginfo("configured RCC_APB2ENR=%08x\n", getreg32(STM32_RCC_APB2ENR));

  /* Configure the SAI PLL external to provide the LCD_CLK */

  reginfo("configured RCC_PLLSAI=%08x\n", getreg32(STM32_RCC_PLLSAICFGR));

  /* Configure dedicated clock external */

  reginfo("configured RCC_DCKCFGR=%08x\n", getreg32(STM32_RCC_DCKCFGR));

  /* Configure LTDC_SSCR */

  regval = (STM32_LTDC_SSCR_VSH | STM32_LTDC_SSCR_HSW);
  reginfo("set LTDC_SSCR=%08x\n", regval);
  putreg32(regval, STM32_LTDC_SSCR);
  reginfo("configured LTDC_SSCR=%08x\n", getreg32(STM32_LTDC_SSCR));

  /* Configure LTDC_BPCR */

  regval = (STM32_LTDC_BPCR_AVBP | STM32_LTDC_BPCR_AHBP);
  reginfo("set LTDC_BPCR=%08x\n", regval);
  putreg32(regval, STM32_LTDC_BPCR);
  reginfo("configured LTDC_BPCR=%08x\n", getreg32(STM32_LTDC_BPCR));

  /* Configure LTDC_AWCR */

  regval = (STM32_LTDC_AWCR_AAH | STM32_LTDC_AWCR_AAW);
  reginfo("set LTDC_AWCR=%08x\n", regval);
  putreg32(regval, STM32_LTDC_AWCR);
  reginfo("configured LTDC_AWCR=%08x\n", getreg32(STM32_LTDC_AWCR));

  /* Configure LTDC_TWCR */

  regval = (STM32_LTDC_TWCR_TOTALH | STM32_LTDC_TWCR_TOTALW);
  reginfo("set LTDC_TWCR=%08x\n", regval);
  putreg32(regval, STM32_LTDC_TWCR);
  reginfo("configured LTDC_TWCR=%08x\n", getreg32(STM32_LTDC_TWCR));

  /* Configure LTDC_GCR */

  regval  = getreg32(STM32_LTDC_GCR);
  regval &= ~(LTDC_GCR_PCPOL | LTDC_GCR_DEPOL | LTDC_GCR_VSPOL |
              LTDC_GCR_HSPOL);
  regval |= (STM32_LTDC_GCR_PCPOL | STM32_LTDC_GCR_DEPOL |
             STM32_LTDC_GCR_VSPOL | STM32_LTDC_GCR_HSPOL);

  reginfo("set LTDC_GCR=%08x\n", regval);
  putreg32(regval, STM32_LTDC_GCR);
  reginfo("configured LTDC_GCR=%08x\n", getreg32(STM32_LTDC_GCR));
}

/****************************************************************************
 * Name: stm32_ltdc_ldefaultcolor
 *
 * Description:
 *   Configures layer default color.
 *
 * Input Parameters:
 *   layer - Reference to the layer control structure
 *   rgb - RGB888 background color
 *
 ****************************************************************************/

static void stm32_ltdc_ldefaultcolor(struct stm32_ltdc_s *layer,
                                     uint32_t rgb)
{
  DEBUGASSERT(layer->layerno < LTDC_NLAYERS);
  reginfo("set LTDC_L%dDCCR=%08x\n", layer->layerno + 1, rgb);

  putreg32(rgb, stm32_dccr_layer_t[layer->layerno]);

  /* Reload shadow register */

  stm32_ltdc_reload(LTDC_SRCR_IMR, false);

  reginfo("configured LTDC_L%dDCCR=%08x\n", layer->layerno + 1,
          getreg32(STM32_LTDC_BCCR));
}

/****************************************************************************
 * Name: stm32_ltdc_bgcolor
 *
 * Description:
 *   Configures background color of the LCD controller.
 *
 * Input Parameters:
 *   rgb - RGB888 background color
 *
 ****************************************************************************/

static void stm32_ltdc_bgcolor(uint32_t rgb)
{
  reginfo("set LTDC_BCCR=%08x\n", rgb);
  putreg32(rgb, STM32_LTDC_BCCR);
  reginfo("configured LTDC_BCCR=%08x\n", getreg32(STM32_LTDC_BCCR));
}

/****************************************************************************
 * Name: stm32_ltdc_dither
 *
 * Description:
 *   Configures dither settings of the LCD controller.
 *
 * Input Parameters:
 *   enable - Enable dithering
 *   red    - Red dither width
 *   green  - Green dither width
 *   blue   - Blue dither width
 *
 ****************************************************************************/

static void stm32_ltdc_dither(bool enable, uint8_t red,
                              uint8_t green, uint8_t blue)
{
  uint32_t regval;

  regval = getreg32(STM32_LTDC_GCR);

  if (enable == true)
    {
      regval |= LTDC_GCR_DEN;
    }
  else
    {
      regval &= ~LTDC_GCR_DEN;
    }

  regval &= ~(LTDC_GCR_DBW_MASK | LTDC_GCR_DGW_MASK | LTDC_GCR_DRW_MASK);
  regval |= (LTDC_GCR_DRW(red) | LTDC_GCR_DGW(green) | LTDC_GCR_DBW(blue));

  reginfo("set LTDC_GCR=%08x\n", regval);
  putreg32(regval, STM32_LTDC_GCR);
  reginfo("configured LTDC_GCR=%08x\n", getreg32(STM32_LTDC_GCR));
}

/****************************************************************************
 * Name: stm32_ltdc_linepos
 *
 * Description:
 *   Configures line position register
 *
 ****************************************************************************/

static void stm32_ltdc_linepos(void)
{
  /* Configure LTDC_LIPCR */

  reginfo("set LTDC_LIPCR=%08x\n", STM32_LTDC_LIPCR_LIPOS);
  putreg32(STM32_LTDC_LIPCR_LIPOS, STM32_LTDC_LIPCR);
  reginfo("configured LTDC_LIPCR=%08x\n", getreg32(STM32_LTDC_LIPCR));
}

/****************************************************************************
 * Name: stm32_ltdc_irqctrl
 *
 * Description:
 *   Control  interrupts generated by the ltdc controller
 *
 * Input Parameters:
 *   setirqs  - set interrupt mask
 *   clrirqs  - clear interrupt mask
 *
 ****************************************************************************/

static void stm32_ltdc_irqctrl(uint32_t setirqs, uint32_t clrirqs)
{
  uint32_t regval;

  regval = getreg32(STM32_LTDC_IER);
  regval &= ~clrirqs;
  regval |= setirqs;
  reginfo("set LTDC_IER=%08x\n", regval);
  putreg32(regval, STM32_LTDC_IER);
  reginfo("configured LTDC_IER=%08x\n", getreg32(STM32_LTDC_IER));
}

/****************************************************************************
 * Name: stm32_ltdcirq
 *
 * Description:
 *   LTDC interrupt handler
 *
 ****************************************************************************/

static int stm32_ltdcirq(int irq, void *context, void *arg)
{
  int ret;
  struct stm32_interrupt_s *priv = &g_interrupt;
  uint32_t regval = getreg32(STM32_LTDC_ISR);

  reginfo("irq = %d, regval = %08x\n", irq, regval);

  if (regval & LTDC_ISR_RRIF)
    {
      /* Register reload interrupt */

      /* Clear the interrupt status register */

      reginfo("Register reloaded\n");
      putreg32(LTDC_ICR_CRRIF, STM32_LTDC_ICR);
      priv->error = OK;
    }
  else if (regval & LTDC_IER_LIE)
    {
      /* Line interrupt */

      /* Clear the interrupt status register */

      reginfo("Line interrupt\n");
      putreg32(LTDC_ICR_CLIF, STM32_LTDC_ICR);
      priv->error = OK;
    }
  else if (regval & LTDC_IER_TERRIE)
    {
      /* Transfer error interrupt */

      /* Clear the interrupt status register */

      reginfo("Error transfer\n");
      putreg32(LTDC_ICR_CTERRIF, STM32_LTDC_ICR);
      priv->error = -ECANCELED;
    }
  else if (regval & LTDC_IER_FUIE)
    {
      /* Fifo underrun error interrupt */

      /* Clear the interrupt status register */

      reginfo("Error fifo underrun\n");
      putreg32(LTDC_ICR_CFUIF, STM32_LTDC_ICR);
      priv->error = -ECANCELED;
    }
  else
    {
      DEBUGASSERT("Unknown interrupt");
    }

  /* Unlock the semaphore if locked */

  ret = nxsem_post(priv->sem);

  if (ret < 0)
    {
      lcderr("ERROR: nxsem_post() failed\n");
    }

  return OK;
}

/****************************************************************************
 * Name: stm32_ltdc_waitforirq
 *
 * Description:
 *   Helper waits until the ltdc irq occurs. In the current design That means
 *   that a register reload was been completed.
 *   Note! The caller must use this function within a critical section.
 *
 * Returned Value:
 *   OK - On success otherwise ERROR
 *
 ****************************************************************************/

static int stm32_ltdc_waitforirq(void)
{
  int ret = OK;
  struct stm32_interrupt_s *priv = &g_interrupt;

  ret = nxsem_wait(priv->sem);

  if (ret < 0)
    {
      lcderr("ERROR: nxsem_wait() failed\n");
    }

  ret = priv->error;

  return ret;
}

/****************************************************************************
 * Name: stm32_ltdc_reload
 *
 * Description:
 *   Reload the layer shadow register and make layer changes visible.
 *   Note! The caller must ensure that a previous register reloading has been
 *   completed.
 *
 * Input Parameters:
 *   value      - Reload flag (e.g. upon vertical blank or immediately)
 *   waitvblank - Wait until register reload is finished
 *
 ****************************************************************************/

static int stm32_ltdc_reload(uint8_t value, bool waitvblank)
{
  int ret = OK;

  /* Reloads the shadow register.
   * Note! This will not trigger an register reload interrupt if
   * immediately reload is set.
   */

  reginfo("set LTDC_SRCR=%08x\n", value);
  putreg32(value, STM32_LTDC_SRCR);
  reginfo("configured LTDC_SRCR=%08x\n", getreg32(STM32_LTDC_SRCR));

  if (value == LTDC_SRCR_VBR && waitvblank)
    {
      /* Wait upon vertical blanking period */

      ret = stm32_ltdc_waitforirq();
    }
  else
    {
      /* Wait until register reload hase been done */

      while (getreg32(STM32_LTDC_SRCR) & value);
    }

  return ret;
}

/****************************************************************************
 * Name: stm32_ltdc_irqconfig
 *
 * Description:
 *   Configure interrupts
 *
 ****************************************************************************/

static void stm32_ltdc_irqconfig(void)
{
  /* Attach LTDC interrupt vector */

  irq_attach(g_interrupt.irq, stm32_ltdcirq, NULL);

  /* Enable the IRQ at the NVIC */

  up_enable_irq(g_interrupt.irq);

  /* Enable interrupts expect line interrupt */

  stm32_ltdc_irqctrl(LTDC_IER_RRIE |
                     LTDC_IER_TERRIE |
                     LTDC_IER_FUIE,
                     LTDC_IER_LIE);

  /* Configure line interrupt */

  stm32_ltdc_linepos();
}

/****************************************************************************
 * Name: stm32_ltdc_globalconfig
 *
 * Description:
 *   Configure background color
 *   Configure dithering
 *
 ****************************************************************************/

static void stm32_ltdc_globalconfig(void)
{
  /* Configure dither */

  stm32_ltdc_dither(
#ifdef CONFIG_STM32_LTDC_DITHER
                    true,
#else
                    false,
#endif
                    STM32_LTDC_DITHER_RED,
                    STM32_LTDC_DITHER_GREEN,
                    STM32_LTDC_DITHER_BLUE);

  /* Configure background color */

  stm32_ltdc_bgcolor(STM32_LTDC_BACKCOLOR);
}

/****************************************************************************
 * Name: stm32_ltdc_enable
 *
 * Description:
 *   Disable the LCD peripheral
 *
 * Input Parameters:
 *   enable - Enable or disable
 *
 ****************************************************************************/

static void stm32_ltdc_enable(bool enable)
{
  uint32_t    regval;

  regval = getreg32(STM32_LTDC_GCR);
  reginfo("get LTDC_GCR=%08x\n", regval);

  if (enable == true)
    {
      regval |= LTDC_GCR_LTDCEN;
    }
  else
    {
      regval &= ~LTDC_GCR_LTDCEN;
    }

  reginfo("set LTDC_GCR=%08x\n", regval);
  putreg32(regval, STM32_LTDC_GCR);
  reginfo("configured LTDC_GCR=%08x\n", getreg32(STM32_LTDC_GCR));
}

/****************************************************************************
 * Name: stm32_ltdc_lpixelformat
 *
 * Description:
 *   Set the layer pixel format.
 *   Note! This changes have no effect until the shadow register reload has
 *   been done.
 *
 * Input Parameters:
 *   Reference to the layer control structure
 *
 ****************************************************************************/

static void stm32_ltdc_lpixelformat(struct stm32_ltdc_s *layer)
{
  uint8_t overlay = layer->layerno;
  DEBUGASSERT(layer->layerno < LTDC_NLAYERS);

  /* Configure PFCR register */

  reginfo("set LTDC_L%dPFCR=%08x\n", overlay + 1,
          stm32_fmt_layer_t[overlay]);
  putreg32(stm32_fmt_layer_t[overlay], stm32_pfcr_layer_t[overlay]);

  /* Reload shadow register */

  stm32_ltdc_reload(LTDC_SRCR_IMR, false);
}

/****************************************************************************
 * Name: stm32_ltdc_lframebuffer
 *
 * Description:
 *   Configure layer framebuffer of the entire window.
 *   Note! This changes have no effect until the shadow register reload has
 *   been done.
 *
 * Input Parameters:
 *   Reference to the layer control structure
 *
 ****************************************************************************/

static void stm32_ltdc_lframebuffer(struct stm32_ltdc_s *layer)
{
  uint32_t cfblr;
  uint32_t rxpos;
  uint32_t rypos;
  uint32_t whpcr;
  uint32_t wvpcr;
  uint8_t  layerno = layer->layerno;

  DEBUGASSERT(layer->layerno < LTDC_NLAYERS);
  reginfo("xpos = %d, ypos = %d, xres = %d, yres = %d\n", 0, 0,
          stm32_width_layer_t[layerno], stm32_height_layer_t[layerno]);

  /* Calculate register position */

  rxpos = STM32_LTDC_LXWHPCR_WHSTPOS + 1;
  rypos = STM32_LTDC_LXWVPCR_WVSTPOS + 1;

  /* Accumulate horizontal position */

  whpcr =  LTDC_LXWHPCR_WHSTPOS(rxpos);
  whpcr |= LTDC_LXWHPCR_WHSPPOS(rxpos + stm32_width_layer_t[layerno] - 1);

  /* Accumulate vertical position */

  wvpcr =  LTDC_LXWVPCR_WVSTPOS(rypos);
  wvpcr |= LTDC_LXWVPCR_WVSPPOS(rypos + stm32_height_layer_t[layerno] - 1);

  /* Configure LxWHPCR / LxWVPCR register */

  reginfo("set LTDC_L%dWHPCR=%08x\n", layerno + 1, whpcr);
  putreg32(whpcr, stm32_whpcr_layer_t[layerno]);
  reginfo("set LTDC_L%dWVPCR=%08x\n", layerno + 1, wvpcr);
  putreg32(wvpcr, stm32_wvpcr_layer_t[layerno]);

  /* Configure LxCFBAR register */

  reginfo("set LTDC_L%dCFBAR=%08x\n", layerno + 1,
          stm32_fbmem_layer_t[layerno]);
  putreg32(stm32_fbmem_layer_t[layerno], stm32_cfbar_layer_t[layerno]);

  /* Configure LxCFBLR register */

  /* Calculate line length */

  cfblr = LTDC_LXCFBLR_CFBP(stm32_stride_layer_t[layerno]) |
          LTDC_LXCFBLR_CFBLL(stm32_width_layer_t[layerno] *
          STM32_LTDC_LX_BYPP(stm32_bpp_layer_t[layerno]) + 3);

  reginfo("set LTDC_L%dCFBLR=%08x\n", layerno + 1, cfblr);
  putreg32(cfblr, stm32_cfblr_layer_t[layerno]);

  /* Configure LxCFBLNR register */

  reginfo("set LTDC_L%dCFBLNR=%08x\n", layerno + 1,
          stm32_height_layer_t[layerno]);
  putreg32(stm32_height_layer_t[layerno], stm32_cfblnr_layer_t[layerno]);

  /* Reload shadow register */

  stm32_ltdc_reload(LTDC_SRCR_IMR, false);
}

/****************************************************************************
 * Name: stm32_ltdc_lenable
 *
 * Description:
 *   Enable or disable layer.
 *   Note! This changes have no effect until the shadow register reload has
 *   been done.
 *
 * Input Parameters:
 *   layer  - Reference to the layer control structure
 *   enable - Enable or disable layer
 *
 ****************************************************************************/

static void stm32_ltdc_lenable(struct stm32_ltdc_s *layer, bool enable)
{
  uint32_t   regval;
  DEBUGASSERT(layer->layerno < LTDC_NLAYERS);

  regval = getreg32(stm32_cr_layer_t[layer->layerno]);

  if (enable == true)
    {
      regval |= LTDC_LXCR_LEN;
    }
  else
    {
      regval &= ~LTDC_LXCR_LEN;
    }

  /* Enable/Disable layer */

  reginfo("set LTDC_L%dCR=%08x\n", layer->layerno + 1, regval);
  putreg32(regval, stm32_cr_layer_t[layer->layerno]);

  /* Reload shadow register */

  stm32_ltdc_reload(LTDC_SRCR_IMR, false);
}

/****************************************************************************
 * Name: stm32_ltdc_ltransp
 *
 * Description:
 *   Change layer transparency.
 *   Note! This changes have no effect until the shadow register reload has
 *   been done.
 *
 * Input Parameters:
 *   layer  - Reference to the layer control structure
 *   transp - Transparency
 *   mode   - Transparency mode
 *
 ****************************************************************************/

static void stm32_ltdc_ltransp(struct stm32_ltdc_s *layer,
                               uint8_t transp,
                               uint32_t mode)
{
  uint32_t bf1;
  uint32_t bf2;

  DEBUGASSERT(layer->layerno < LTDC_NLAYERS);

#ifdef CONFIG_FB_OVERLAY
  if (mode == FB_CONST_ALPHA)
    {
      bf1 = LTDC_BF1_CONST_ALPHA;
      bf2 = LTDC_BF2_CONST_ALPHA;
    }
  else
    {
      bf1 = LTDC_BF1_PIXEL_ALPHA;
      bf2 = LTDC_BF2_PIXEL_ALPHA;
    }
#else
  bf1 = LTDC_BF1_CONST_ALPHA;
  bf2 = LTDC_BF2_CONST_ALPHA;
#endif

  reginfo("set LTDC_L%dBFCR=%08x\n", layer->layerno + 1,
          (LTDC_LXBFCR_BF1(bf1) | LTDC_LXBFCR_BF2(bf2)));

  /* Set blendmode */

  putreg32((LTDC_LXBFCR_BF1(bf1) | LTDC_LXBFCR_BF2(bf2)),
            stm32_bfcr_layer_t[layer->layerno]);

  /* Set alpha */

  reginfo("set LTDC_L%dCACR=%02x\n", layer->layerno + 1, transp);
  putreg32(transp, stm32_cacr_layer_t[layer->layerno]);

  /* Reload shadow register */

  stm32_ltdc_reload(LTDC_SRCR_IMR, false);
}

/****************************************************************************
 * Name: stm32_ltdc_lchromakey
 *
 * Description:
 *   Change layer chromakey.
 *   Note! This changes have no effect until the shadow register reload has
 *   been done.
 *
 * Input Parameters:
 *   layer  - Reference to the layer control structure
 *   chroma - chromakey
 *
 ****************************************************************************/

static void stm32_ltdc_lchromakey(struct stm32_ltdc_s *layer,
                                  uint32_t chroma)
{
  uint32_t rgb;
  DEBUGASSERT(layer->layerno < LTDC_NLAYERS);

  reginfo("%08x\n", getreg32(stm32_cr_layer_t[layer->layerno]));

  /* Set chromakey */

#ifdef CONFIG_STM32_FB_CMAP
  uint8_t r = g_vtable.cmap.red[chroma];
  uint8_t g = g_vtable.cmap.green[chroma];
  uint8_t b = g_vtable.cmap.blue[chroma];
  rgb = ((r << 16) | (g << 8) | b);
#else
  rgb = ARGB8888(chroma);
#endif

  reginfo("set LTDC_L%dCKCR=%08x\n", layer->layerno + 1, rgb);
  putreg32(rgb, stm32_ckcr_layer_t[layer->layerno]);

  /* Reload shadow register */

  stm32_ltdc_reload(LTDC_SRCR_IMR, false);
}

/****************************************************************************
 * Name: stm32_ltdc_lchromakeyenable
 *
 * Description:
 *   Enable or disable layer chromakey support.
 *   Note! This changes have no effect until the shadow register reload has
 *   been done.
 *
 * Input Parameters:
 *   layer  - Reference to the layer control structure
 *   enable - Enable or disable chromakey
 *
 ****************************************************************************/

static void stm32_ltdc_lchromakeyenable(struct stm32_ltdc_s *layer,
                                        bool enable)
{
  uint32_t   regval;
  DEBUGASSERT(layer->layerno < LTDC_NLAYERS);

  regval = getreg32(stm32_cr_layer_t[layer->layerno]);

  /* Enable/Disable colorkey */

  if (enable == true)
    {
      regval |= LTDC_LXCR_COLKEN;
    }
  else
    {
      regval &= ~LTDC_LXCR_COLKEN;
    }

  reginfo("set LTDC_L%dCR=%08x\n", layer->layerno + 1, regval);
  putreg32(regval, stm32_cr_layer_t[layer->layerno]);

  /* Reload shadow register */

  stm32_ltdc_reload(LTDC_SRCR_IMR, false);
}

/****************************************************************************
 * Name: stm32_ltdc_lclutenable
 *
 * Description:
 *   Disable or enable the layer clut support
 *
 * Input Parameters:
 *   layer  - Reference to the layer control structure
 *   enable - Enable or disable
 *
 ****************************************************************************/

#ifdef CONFIG_STM32_FB_CMAP
static void stm32_ltdc_lclutenable(struct stm32_ltdc_s *layer,
                                   bool enable)
{
  uint32_t    regval;

  regval = getreg32(stm32_cr_layer_t[layer->oinfo.overlay]);
  reginfo("get LTDC_L%dCR=%08x\n", layer->oinfo.overlay + 1, regval);

  /* Disable the clut support during update the color table */

  if (enable == true)
    {
      regval |= LTDC_LXCR_CLUTEN;
    }
  else
    {
      regval &= ~LTDC_LXCR_CLUTEN;
    }

  reginfo("set LTDC_L%dCR=%08x\n", layer->oinfo.overlay, regval);
  putreg32(regval, stm32_cr_layer_t[layer->oinfo.overlay]);

  /* Reload shadow register */

  stm32_ltdc_reload(LTDC_SRCR_IMR, false);
}

/****************************************************************************
 * Name: stm32_ltdc_lputclut
 *
 * Description:
 *   Update the clut layer register during blank period.
 *   Note! The clut register is no shadow register.
 *
 * Input Parameters:
 *   layer  - Reference to the layer control structure
 *   cmap   - Color map
 *
 ****************************************************************************/

static void stm32_ltdc_lputclut(struct stm32_ltdc_s *layer,
                                const struct fb_cmap_s *cmap)
{
  int            n;
  irqstate_t flags;

  /* Disable clut during register update */

  stm32_ltdc_lclutenable(layer, false);

  /* Update the clut registers. Ensure operation is atomic or in interrupt
   * protected context.
   */

  flags = enter_critical_section();

  for (n = cmap->first; n < cmap->len && n < STM32_LTDC_NCLUT; n++)
    {
      uint32_t regval;

      regval = (uint32_t)LTDC_CLUT_ADD(n) |
               (uint32_t)LTDC_CLUT_RED(cmap->red[n]) |
               (uint32_t)LTDC_CLUT_GREEN(cmap->green[n]) |
               (uint32_t)LTDC_CLUT_BLUE(cmap->blue[n]);

      reginfo("set LTDC_L%dCLUTWR = %08x, first = %d, len = %d\n",
              layer->oinfo.overlay + 1, regval, cmap->first, cmap->len);
      putreg32(regval, stm32_clutwr_layer_t[layer->oinfo.overlay]);
    }

  leave_critical_section(flags);

  /* Enable clut after register update */

  stm32_ltdc_lclutenable(layer, true);

  /* Reload shadow control register */

  stm32_ltdc_reload(LTDC_SRCR_IMR, false);
}

/****************************************************************************
 * Name: stm32_ltdc_lgetclut
 *
 * Description:
 *   Copy the layers color lookup table.
 *
 * Input Parameters:
 *   layer  - Reference to the layer control structure
 *   cmap   - Color map
 *
 ****************************************************************************/

static void stm32_ltdc_lgetclut(struct stm32_ltdc_s *layer,
                                struct fb_cmap_s *cmap)
{
  int n;
  struct fb_cmap_s *priv_cmap = &g_vtable.cmap;

  /* Copy from internal cmap */

  for (n = cmap->first; n < cmap->len && n < STM32_LTDC_NCLUT; n++)
    {
#  ifdef CONFIG_STM32_FB_TRANSPARENCY
      cmap->transp[n] = priv_cmap->transp[n];
#  endif
      cmap->red[n]    = priv_cmap->red[n];
      cmap->green[n]  = priv_cmap->green[n];
      cmap->blue[n]   = priv_cmap->blue[n];

      reginfo("color = %d, transp=%02x, red=%02x, green=%02x, blue=%02x\n",
              n,
#  ifdef CONFIG_STM32_FB_TRANSPARENCY
              cmap->transp[n],
#  endif
              cmap->red[n],
              cmap->green[n],
              cmap->blue[n]);
    }
}
#endif /* CONFIG_STM32_FB_CMAP */

/****************************************************************************
 * Name: stm32_ltdc_lclear
 *
 * Description:
 *   Clear the whole layer
 *
 * Input Parameters:
 *   overlayno - Number overlay
 *
 ****************************************************************************/

static void stm32_ltdc_lclear(uint8_t overlayno)
{
  memset((uint8_t *)stm32_fbmem_layer_t[overlayno], 0,
        stm32_fblen_layer_t[overlayno]);
}

/****************************************************************************
 * Name: stm32_ltdc_lvalidate
 *
 * Description:
 *   Validates if the given area is within the overlay framebuffer memory
 *   region
 *
 * Input Parameters:
 *   layer  - Reference to the layer control structure
 *   area   - Reference to the overlay area
 *
 ****************************************************************************/

#if defined(CONFIG_STM32_DMA2D) && defined(CONFIG_FB_OVERLAY_BLIT)
static bool stm32_ltdc_lvalidate(const struct stm32_ltdc_s *layer,
                                 const struct fb_area_s *area)
{
  uint32_t offset;

  offset = (area->y + area->h - 1) * layer->oinfo.stride +
           (area->x + area->w) * layer->oinfo.bpp / 8;

  return (offset <= layer->oinfo.fblen && area->w > 0 && area->h > 0);
}
#endif /* defined(CONFIG_STM32_DMA2D) && defined(CONFIG_FB_OVERLAY_BLIT) */

/****************************************************************************
 * Name: stm32_ltdc_linit
 *
 * Description:
 *   Initialize layer to their default states.
 *
 *   Initialize:
 *   - layer framebuffer
 *   - layer pixelformat
 *   - layer defaultcolor
 *   - layer chromakey
 *   - layer transparency
 *   - layer clut
 *
 * Input Parameters:
 *   layer          - Reference to the layer control structure
 *
 ****************************************************************************/

static void stm32_ltdc_linit(uint8_t overlay)
{
  DEBUGASSERT(overlay < LTDC_NLAYERS);

  struct stm32_ltdcdev_s *dev = &g_vtable;
  struct stm32_ltdc_s *layer = &dev->layer[overlay];

  /* Disable layer */

  stm32_ltdc_lenable(layer, false);

  /* Clear the layer framebuffer */

  stm32_ltdc_lclear(overlay);

  /* Set layers framebuffer */

  stm32_ltdc_lframebuffer(layer);

  /* Set layers pixel input format */

  stm32_ltdc_lpixelformat(layer);

  /* Configure layer default color */

  stm32_ltdc_ldefaultcolor(layer, stm32_defaultcolor_layer_t[overlay]);

  /* Layers default transparency */

  stm32_ltdc_ltransp(layer, 0xff, 0);

  /* Layers chromakey */

  stm32_ltdc_lchromakey(layer, stm32_chromakey_layer_t[overlay]);

  /* Enable chromakey */

  stm32_ltdc_lchromakeyenable(layer, stm32_chromakeyen_layer_t[overlay]);

#ifdef CONFIG_STM32_FB_CMAP
  /* Disable clut by default */

  if (dev->vinfo.fmt == FB_FMT_RGB8)
    {
      /* Initialize LTDC clut register */

      stm32_ltdc_lputclut(layer, &g_vtable.cmap);

      /* Configure the clut register */

      stm32_ltdc_lclutenable(layer, true);
    }
#endif

  /* Finally enable the layer */

  stm32_ltdc_lenable(layer, true);
}

/****************************************************************************
 * Name: stm32_ltdc_dma2dlinit
 *
 * Description:
 *   Initialize dma2d layer to their default states.
 *
 *   Initialize:
 *   - layer framebuffer
 *   - layer pixelformat
 *   - layer size
 *   - layer color
 *   - layer chromakey
 *   - layer transparency
 *   - layer clut
 *
 * Input Parameters:
 *   layer - Reference to the layer control structure
 *
 ****************************************************************************/

#ifdef CONFIG_STM32_DMA2D
static void stm32_ltdc_dma2dlinit(void)
{
  int n;
  struct stm32_ltdcdev_s *dev = &g_vtable;

  for (n = 0; n < DMA2D_NLAYERS; n++)
    {
      uint32_t overlay = n + LTDC_NLAYERS;
      struct stm32_ltdc_s *layer = &dev->layer[overlay];
      uint8_t * fbmem = (uint8_t *)STM32_DMA2D_BUFFER_START;

      layer->layerno                  = overlay;
      layer->oinfo.fbmem              = fbmem + STM32_DMA2D_LAYER_SIZE * n;
      layer->oinfo.fblen              = STM32_DMA2D_FBSIZE;
      layer->oinfo.stride             = STM32_DMA2D_STRIDE;
      layer->oinfo.overlay            = overlay;
      layer->oinfo.bpp                = STM32_DMA2D_BPP;
      layer->oinfo.blank              = 0;
      layer->oinfo.chromakey          = 0;
      layer->oinfo.color              = 0;
      layer->oinfo.transp.transp      = 0xff;
      layer->oinfo.transp.transp_mode = 0;
      layer->oinfo.sarea.x            = 0;
      layer->oinfo.sarea.y            = 0;
      layer->oinfo.sarea.w            = STM32_DMA2D_WIDTH;
      layer->oinfo.sarea.h            = STM32_DMA2D_HEIGHT;
      layer->oinfo.accl               = DMA2D_ACCL;
      layer->lock                     = &g_lock;
      layer->dma2dinfo.fmt            = STM32_DMA2D_COLOR_FMT;
      layer->dma2dinfo.transp_mode    = STM32_DMA2D_PFCCR_AM_NONE;
      layer->dma2dinfo.xres           = layer->oinfo.sarea.w;
      layer->dma2dinfo.yres           = layer->oinfo.sarea.h;
      layer->dma2dinfo.oinfo          = &layer->oinfo;
    }
}
#endif /* CONFIG_STM32_DMA2D */

/****************************************************************************
 * Public Functions
 ****************************************************************************/

/****************************************************************************
 * Name: stm32_getvideoinfo
 *
 * Description:
 *   Entrypoint ioctl FBIOGET_VIDEOINFO
 *   Get the videoinfo for the framebuffer
 *
 * Input Parameters:
 *   vtable - The framebuffer driver object
 *   vinfo  - the videoinfo object
 *
 * Returned Value:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

static int stm32_getvideoinfo(struct fb_vtable_s *vtable,
                              struct fb_videoinfo_s *vinfo)
{
  struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)vtable;

  lcdinfo("vtable=%p vinfo=%p\n", vtable, vinfo);
  DEBUGASSERT(vtable != NULL && priv == &g_vtable && vinfo != NULL);

  memcpy(vinfo, &priv->vinfo, sizeof(struct fb_videoinfo_s));
  return OK;
}

/****************************************************************************
 * Name: stm32_getplaneinfo
 *
 * Description:
 *   Entrypoint ioctl FBIOGET_PLANEINFO
 *   Get the planeinfo for the framebuffer
 *
 * Input Parameters:
 *   vtable - The framebuffer driver object
 *   pinfo  - the planeinfo object
 *
 * Returned Value:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

static int stm32_getplaneinfo(struct fb_vtable_s *vtable, int planeno,
                              struct fb_planeinfo_s *pinfo)
{
  struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)vtable;

  DEBUGASSERT(vtable != NULL && priv == &g_vtable);
  lcdinfo("vtable=%p planeno=%d pinfo=%p\n", vtable, planeno, pinfo);

  if (planeno == 0)
    {
      memcpy(pinfo, &priv->pinfo, sizeof(struct fb_planeinfo_s));
      return OK;
    }

  lcderr("ERROR: Returning EINVAL\n");
  return -EINVAL;
}

/****************************************************************************
 * Name: stm32_getcmap
 *
 * Description:
 *   Entrypoint ioctl FBIOGET_CMAP
 *   Get a range of CLUT values for the LCD
 *
 * Input Parameters:
 *   vtable - The framebuffer driver object
 *   cmap   - the color table
 *
 * Returned Value:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

#ifdef CONFIG_STM32_FB_CMAP
static int stm32_getcmap(struct fb_vtable_s *vtable,
                         struct fb_cmap_s *cmap)
{
  int ret;
  struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)vtable;

  DEBUGASSERT(vtable != NULL && priv == &g_vtable && cmap != NULL);
  lcdinfo("vtable=%p cmap=%p\n", vtable, cmap);

  if (priv->vinfo.fmt != FB_FMT_RGB8)
    {
      lcderr("ERROR: CLUT is not supported for the pixel format: %d\n",
              priv->vinfo.fmt);
      ret = -EINVAL;
    }
  else if (cmap->first >= STM32_LTDC_NCLUT)
    {
      lcderr("ERROR: only %d color table entries supported\n",
              STM32_LTDC_NCLUT);
      ret = -EINVAL;
    }
  else
    {
      /* Currently, there is no api to set color map for each overlay
       * separately. LTDC layers can have different color maps. Get the cmap
       * from the main overlay.
       */

      struct stm32_ltdc_s *layer;
#  ifdef CONFIG_STM32_LTDC_L2
      layer = &priv->layer[LTDC_LAYER_L2];
#  else
      layer = &priv->layer[LTDC_LAYER_L1];
#  endif
      nxmutex_lock(layer->lock);
      stm32_ltdc_lgetclut(layer, cmap);
      nxmutex_unlock(layer->lock);

      ret = OK;
    }

  return ret;
}

/****************************************************************************
 * Name: stm32_putcmap
 *
 * Description:
 *   Entrypoint ioctl FBIOPUT_CMAP
 *   Set a range of the CLUT values for the LCD
 *
 * Input Parameters:
 *   vtable - The framebuffer driver object
 *   cmap   - the color table
 *
 * Returned Value:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

static int stm32_putcmap(struct fb_vtable_s *vtable,
                         const struct fb_cmap_s *cmap)
{
  int ret;
  struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)vtable;

  DEBUGASSERT(vtable != NULL && priv == &g_vtable && cmap != NULL);
  lcdinfo("vtable=%p cmap=%p\n", vtable, cmap);

  if (priv->vinfo.fmt != FB_FMT_RGB8)
    {
      lcderr("ERROR: CLUT is not supported for the pixel format: %d\n",
              priv->vinfo.fmt);
      ret = -EINVAL;
    }
  else if (cmap->first >= STM32_LTDC_NCLUT)
    {
      lcderr("ERROR: only %d color table entries supported\n",
              STM32_LTDC_NCLUT);
      ret = -EINVAL;
    }
  else
    {
      /* Currently, there is no api to set color map for each overlay
       * separately. LTDC layers can have different color maps, but is shared
       * for now.
       */

      int n;
      struct fb_cmap_s *priv_cmap = &g_vtable.cmap;

      /* First copy to internal cmap */

      for (n = cmap->first; n < cmap->len && n < STM32_LTDC_NCLUT; n++)
        {
          priv_cmap->red[n] = cmap->red[n];
          priv_cmap->green[n] = cmap->green[n];
          priv_cmap->blue[n] = cmap->blue[n];
#  ifdef CONFIG_STM32_FB_TRANSPARENCY
          /* Not supported by LTDC */

          priv_cmap->transp[n] = cmap->transp[n];
#  endif
        }

      priv_cmap->first = cmap->first;
      priv_cmap->len = cmap->len;

      /* Update the layer clut register */

      nxmutex_lock(&g_lock);

      for (n = 0; n < LTDC_NLAYERS; n++)
        {
          struct stm32_ltdc_s *layer = &priv->layer[n];
          stm32_ltdc_lputclut(layer, priv_cmap);
        }

#  ifdef CONFIG_STM32_DMA2D
      /* Update dma2d cmap */

      priv->dma2d->setclut(cmap);
#  endif
      nxmutex_unlock(&g_lock);

      ret = OK;
    }

  return ret;
}
#endif /* CONFIG_STM32_FB_CMAP */

/****************************************************************************
 * Name: stm32_ioctl_waitforvsync
 * Description:
 *   Entrypoint ioctl FBIO_WAITFORSYNC
 ****************************************************************************/

#ifdef CONFIG_FB_SYNC
static int stm32_waitforvsync(struct fb_vtable_s *vtable)
{
  int ret;

  DEBUGASSERT(vtable != NULL && vtable == &g_vtable.vtable);

  /* Wait upon vertical synchronization. */

  ret = stm32_ltdc_reload(LTDC_SRCR_VBR, true);

  return ret;
}
#endif /* CONFIG_FB_SYNC */

/****************************************************************************
 * Name: stm32_getoverlayinfo
 * Description:
 *   Entrypoint ioctl FBIOGET_OVERLAYINFO
 ****************************************************************************/

#ifdef CONFIG_FB_OVERLAY
static int stm32_getoverlayinfo(struct fb_vtable_s *vtable,
                                int overlayno,
                                struct fb_overlayinfo_s *oinfo)
{
  struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)vtable;

  lcdinfo("vtable=%p overlay=%d oinfo=%p\n", vtable, overlayno, oinfo);
  DEBUGASSERT(vtable != NULL && priv == &g_vtable);

  if (overlayno < LTDC_NOVERLAYS)
    {
      struct stm32_ltdc_s *layer = &priv->layer[overlayno];
      memcpy(oinfo, &layer->oinfo, sizeof(struct fb_overlayinfo_s));
      return OK;
    }

  lcderr("ERROR: Returning EINVAL\n");
  return -EINVAL;
}

/****************************************************************************
 * Name: stm32_settransp
 * Description:
 *   Entrypoint ioctl FBIOSET_TRANSP
 ****************************************************************************/

static int stm32_settransp(struct fb_vtable_s *vtable,
                           const struct fb_overlayinfo_s *oinfo)
{
  struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)vtable;

  DEBUGASSERT(vtable != NULL && priv == &g_vtable);
  lcdinfo("vtable=%p, overlay=%d, transp=%02x, transp_mode=%02x\n", vtable,
          oinfo->overlay, oinfo->transp.transp, oinfo->transp.transp_mode);

  if (oinfo->transp.transp_mode > 1)
    {
      lcderr("ERROR: Returning ENOSYS, transparency mode not supported\n");
      return -ENOSYS;
    }

  if (oinfo->overlay < LTDC_NOVERLAYS)
    {
      struct stm32_ltdc_s *layer = &priv->layer[oinfo->overlay];

      nxmutex_lock(layer->lock);
      layer->oinfo.transp.transp      = oinfo->transp.transp;
      layer->oinfo.transp.transp_mode = oinfo->transp.transp_mode;

#  ifdef CONFIG_STM32_DMA2D
      if (layer->oinfo.transp.transp_mode == 0)
        {
          layer->dma2dinfo.transp_mode = STM32_DMA2D_PFCCR_AM_CONST;
        }
      else if (layer->oinfo.transp.transp_mode == 1)
        {
          layer->dma2dinfo.transp_mode = STM32_DMA2D_PFCCR_AM_PIXEL;
        }

      if (oinfo->overlay < LTDC_NLAYERS)
#  endif
        {
          /* Set LTDC blendmode and alpha value */

          stm32_ltdc_ltransp(layer, layer->oinfo.transp.transp,
                             layer->oinfo.transp.transp_mode);
        }

      nxmutex_unlock(layer->lock);
      return OK;
    }

  lcderr("ERROR: Returning EINVAL\n");
  return -EINVAL;
}

/****************************************************************************
 * Name: stm32_setchromakey
 * Description:
 *   Entrypoint ioctl FBIOSET_CHROMAKEY
 ****************************************************************************/

static int stm32_setchromakey(struct fb_vtable_s *vtable,
                              const struct fb_overlayinfo_s *oinfo)
{
  struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)vtable;

  DEBUGASSERT(vtable != NULL && priv == &g_vtable && oinfo != NULL);
  lcdinfo("vtable=%p, overlay=%d, chromakey=%08" PRIx32 "\n", vtable,
          oinfo->overlay, oinfo->chromakey);

  if (oinfo->overlay < LTDC_NLAYERS)
    {
      int ret;
      struct stm32_ltdc_s *layer = &priv->layer[oinfo->overlay];

#  ifndef CONFIG_STM32_LTDC_L1_CHROMAKEY
      if (oinfo->overlay == LTDC_LAYER_L1)
        {
          return -ENOSYS;
        }
#  endif

#  ifndef CONFIG_STM32_LTDC_L2_CHROMAKEY
      if (oinfo->overlay == LTDC_LAYER_L2)
        {
          return -ENOSYS;
        }
#  endif

      nxmutex_lock(layer->lock);
#  ifdef CONFIG_STM32_FB_CMAP
      if (oinfo->chromakey >= g_vtable.cmap.len)
        {
          lcderr("ERROR: Clut index %" PRId32 " is out of range\n",
                 oinfo->chromakey);
          ret = -EINVAL;
        }
      else
#  endif
        {
          layer->oinfo.chromakey = oinfo->chromakey;

          /* Set chromakey */

          stm32_ltdc_lchromakey(layer, layer->oinfo.chromakey);
          ret = OK;
        }

      nxmutex_unlock(layer->lock);
      return ret;
    }
#  ifdef CONFIG_STM32_DMA2D
  else if (oinfo->overlay < LTDC_NOVERLAYS)
    {
      /* Chromakey not supported by DMA2D */

      return -ENOSYS;
    }
#  endif

  lcderr("ERROR: Returning EINVAL\n");
  return -EINVAL;
}

/****************************************************************************
 * Name: stm32_setcolor
 * Description:
 *   Entrypoint ioctl FBIOSET_COLOR
 ****************************************************************************/

static int stm32_setcolor(struct fb_vtable_s *vtable,
                          const struct fb_overlayinfo_s *oinfo)
{
  DEBUGASSERT(vtable != NULL && vtable == &g_vtable.vtable && oinfo != NULL);
  lcdinfo("vtable=%p, overlay=%d, color=%08" PRIx32 "\n",
          vtable, oinfo->overlay, oinfo->color);

  if (oinfo->overlay < LTDC_NOVERLAYS)
    {
#  ifdef CONFIG_STM32_DMA2D

      /* Set color within the active overlay is not supported by LTDC. So use
       * DMA2D controller instead when configured.
       */

      int ret;
      struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)
                                         vtable;
      struct stm32_ltdc_s *layer = &priv->layer[oinfo->overlay];
      struct fb_overlayinfo_s *poverlay = layer->dma2dinfo.oinfo;

      DEBUGASSERT(&layer->oinfo == poverlay);

      nxmutex_lock(layer->lock);
      poverlay->color = oinfo->color;
      ret = priv->dma2d->fillcolor(&layer->dma2dinfo, &poverlay->sarea,
                                   poverlay->color);
      nxmutex_unlock(layer->lock);

      return ret;
#  else
      /* Coloring not supported by LTDC */

      return -ENOSYS;
#  endif
    }

  lcderr("ERROR: Returning EINVAL\n");
  return -EINVAL;
}

/****************************************************************************
 * Name: stm32_setblank
 * Description:
 *   Entrypoint ioctl FBIOSET_BLANK
 ****************************************************************************/

static int stm32_setblank(struct fb_vtable_s *vtable,
                          const struct fb_overlayinfo_s *oinfo)
{
  struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)vtable;

  DEBUGASSERT(vtable != NULL && priv == &g_vtable && oinfo != NULL);
  lcdinfo("vtable=%p, overlay=%d, blank=%02x\n",
          vtable, oinfo->overlay, oinfo->blank);

  if (oinfo->overlay < LTDC_NLAYERS)
    {
      struct stm32_ltdc_s *layer = &priv->layer[oinfo->overlay];

      nxmutex_lock(layer->lock);
      layer->oinfo.blank = oinfo->blank;

      /* Enable or disable layer */

      stm32_ltdc_lenable(layer, (layer->oinfo.blank == 0));
      nxmutex_unlock(layer->lock);

      return OK;
    }
#  ifdef CONFIG_STM32_DMA2D
  else if (oinfo->overlay < LTDC_NOVERLAYS)
    {
      /* DMA2D overlays are non visible */

      return OK;
    }
#  endif

  lcderr("ERROR: Returning EINVAL\n");
  return -EINVAL;
}

/****************************************************************************
 * Name: stm32_setarea
 * Description:
 *   Entrypoint ioctl FBIOSET_AREA
 ****************************************************************************/

static int stm32_setarea(struct fb_vtable_s *vtable,
                         const struct fb_overlayinfo_s *oinfo)
{
  DEBUGASSERT(vtable != NULL && vtable == &g_vtable.vtable && oinfo != NULL);
  lcdinfo("vtable=%p, overlay=%d, x=%d, y=%d, w=%d, h=%d\n", vtable,
          oinfo->overlay, oinfo->sarea.x, oinfo->sarea.y, oinfo->sarea.w,
          oinfo->sarea.h);

  if (oinfo->overlay < LTDC_NLAYERS)
    {
      /* LTDC area is defined by the overlay size (display resolution) only */

      return -ENOSYS;
    }

#  ifdef CONFIG_STM32_DMA2D
  if (oinfo->overlay < LTDC_NOVERLAYS)
    {
      struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)
                                         vtable;
      struct stm32_ltdc_s *layer = &priv->layer[oinfo->overlay];

      nxmutex_lock(layer->lock);
      memcpy(&layer->oinfo.sarea, &oinfo->sarea, sizeof(struct fb_area_s));
      nxmutex_unlock(layer->lock);

      return OK;
    }
#  endif

  lcderr("ERROR: Returning EINVAL\n");
  return -EINVAL;
}

/****************************************************************************
 * Name: stm32_blit
 * Description:
 *   Entrypoint ioctl FBIOSET_BLIT
 ****************************************************************************/

#  ifdef CONFIG_FB_OVERLAY_BLIT
static int stm32_blit(struct fb_vtable_s *vtable,
                      const struct fb_overlayblit_s *blit)
{
  DEBUGASSERT(vtable != NULL && vtable == &g_vtable.vtable && blit != NULL);
  lcdinfo("vtable = %p, blit = %p\n", vtable, blit);

  if (blit->dest.overlay < LTDC_NOVERLAYS &&
      blit->src.overlay < LTDC_NOVERLAYS)
    {
#    ifdef CONFIG_STM32_DMA2D
      int ret;
      struct fb_area_s sarea;
      const struct fb_area_s *darea = &blit->dest.area;
      struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)
                                         vtable;
      struct stm32_ltdc_s *dlayer = &priv->layer[blit->dest.overlay];
      struct stm32_ltdc_s *slayer = &priv->layer[blit->src.overlay];

      DEBUGASSERT(&dlayer->oinfo == dlayer->dma2dinfo.oinfo &&
                  &slayer->oinfo == slayer->dma2dinfo.oinfo);

      /* DMA2D doesn't support image scale, so set to the smallest area */

      memcpy(&sarea, &blit->src.area, sizeof(struct fb_area_s));

      /* Check if area is within the entire overlay */

      if (!stm32_ltdc_lvalidate(dlayer, darea) ||
          !stm32_ltdc_lvalidate(slayer, &sarea))
        {
          return -EINVAL;
        }

      sarea.w = MIN(darea->w, sarea.w);
      sarea.h = MIN(darea->h, sarea.h);

      nxmutex_lock(dlayer->lock);
      ret = priv->dma2d->blit(&dlayer->dma2dinfo, darea->x, darea->y,
                              &slayer->dma2dinfo, &sarea);
      nxmutex_unlock(dlayer->lock);

      return ret;
#    else
      /* LTDC doesn't support blit transfer */

      return -ENOSYS;
#    endif
    }

  lcderr("ERROR: Returning EINVAL\n");
  return -EINVAL;
}

/****************************************************************************
 * Name: stm32_blend
 * Description:
 *   Entrypoint ioctl FBIOSET_BLEND
 ****************************************************************************/

static int stm32_blend(struct fb_vtable_s *vtable,
                       const struct fb_overlayblend_s *blend)
{
  DEBUGASSERT(vtable != NULL && vtable == &g_vtable.vtable && blend != NULL);
  lcdinfo("vtable = %p, blend = %p\n", vtable, blend);

  if (blend->dest.overlay < LTDC_NOVERLAYS &&
      blend->foreground.overlay < LTDC_NOVERLAYS &&
      blend->background.overlay < LTDC_NOVERLAYS)
    {
#    ifdef CONFIG_STM32_DMA2D
      int ret;
      struct fb_area_s barea;
      const struct fb_area_s *darea = &blend->dest.area;
      const struct fb_area_s *farea = &blend->foreground.area;
      struct stm32_ltdcdev_s *priv = (struct stm32_ltdcdev_s *)
                                         vtable;
      struct stm32_ltdc_s *dlayer = &priv->layer[blend->dest.overlay];
      struct stm32_ltdc_s *flayer =
                                    &priv->layer[blend->foreground.overlay];
      struct stm32_ltdc_s *blayer =
                                    &priv->layer[blend->background.overlay];

      DEBUGASSERT(&dlayer->oinfo == dlayer->dma2dinfo.oinfo &&
                  &flayer->oinfo == flayer->dma2dinfo.oinfo &&
                  &blayer->oinfo == blayer->dma2dinfo.oinfo);

      /* DMA2D doesn't support image scale, so set to the smallest area */

      memcpy(&barea, &blend->background.area, sizeof(struct fb_area_s));

      /* Check if area is within the entire overlay */

      if (!stm32_ltdc_lvalidate(dlayer, darea) ||
          !stm32_ltdc_lvalidate(flayer, farea) ||
          !stm32_ltdc_lvalidate(blayer, &barea))
        {
          lcderr("ERROR: Returning EINVAL\n");
          return -EINVAL;
        }

      barea.w = MIN(darea->w, barea.w);
      barea.h = MIN(darea->h, barea.h);
      barea.w = MIN(farea->w, barea.w);
      barea.h = MIN(farea->h, barea.h);

      nxmutex_lock(dlayer->lock);
      ret = priv->dma2d->blend(&dlayer->dma2dinfo, darea->x, darea->y,
                               &flayer->dma2dinfo, farea->x, farea->y,
                               &blayer->dma2dinfo, &barea);
      nxmutex_unlock(dlayer->lock);

      return ret;
#    else
      /* LTDC doesn't support blend transfer */

      return -ENOSYS;
#    endif
    }

  lcderr("ERROR: Returning EINVAL\n");
  return -EINVAL;
}
#  endif /* CONFIG_FB_OVERLAY_BLIT */
#endif /* CONFIG_FB_OVERLAY */

/****************************************************************************
 * Name: stm32_ltdcreset
 *
 * Description:
 *   Reset LTDC via APB2RSTR
 *
 ****************************************************************************/

void stm32_ltdcreset(void)
{
  uint32_t regval = getreg32(STM32_RCC_APB2RSTR);
  putreg32(regval | RCC_APB2RSTR_LTDCRST, STM32_RCC_APB2RSTR);
  putreg32(regval & ~RCC_APB2RSTR_LTDCRST, STM32_RCC_APB2RSTR);
}

/****************************************************************************
 * Name: stm32_ltdcinitialize
 *
 * Description:
 *   Initialize the ltdc controller
 *
 * Returned Value:
 *   OK
 *
 ****************************************************************************/

int stm32_ltdcinitialize(void)
{
  int ret = OK;

  lcdinfo("Initialize LTDC driver\n");

  if (g_initialized == true)
    {
      return ret;
    }

  /* Disable the LCD */

  stm32_ltdc_enable(false);

  lcdinfo("Configuring the LCD controller\n");

  /* Configure LCD periphery */

  lcdinfo("Configure lcd periphery\n");
  stm32_ltdc_periphconfig();

  /* Configure interrupts */

  lcdinfo("Configure interrupts\n");
  stm32_ltdc_irqconfig();

  /* Configure global ltdc register */

  lcdinfo("Configure global register\n");
  stm32_ltdc_globalconfig();

#ifdef CONFIG_STM32_DMA2D
  /* Initialize the dma2d controller */

  ret = stm32_dma2dinitialize();

  if (ret != OK)
    {
      return ret;
    }

  /* Bind the dma2d interface */

  g_vtable.dma2d = stm32_dma2ddev();
  DEBUGASSERT(g_vtable.dma2d != NULL);
#endif

#ifdef CONFIG_STM32_FB_CMAP
  /* Cleanup clut */

  memset(&g_redclut, 0, STM32_LTDC_NCLUT);
  memset(&g_blueclut, 0, STM32_LTDC_NCLUT);
  memset(&g_greenclut, 0, STM32_LTDC_NCLUT);
#  ifdef CONFIG_STM32_FB_TRANSPARENCY
  memset(&g_transpclut, 0, STM32_LTDC_NCLUT);
#  endif
#endif /* CONFIG_STM32_FB_CMAP */

  /* Initialize ltdc layer */

  lcdinfo("Initialize ltdc layer\n");
  stm32_ltdc_linit(LTDC_LAYER_L1);
#ifdef CONFIG_STM32_LTDC_L2
  stm32_ltdc_linit(LTDC_LAYER_L2);
#endif

#ifdef CONFIG_STM32_DMA2D
  stm32_ltdc_dma2dlinit();
#endif
  /* Enable the backlight */

#ifdef CONFIG_STM32_LCD_BACKLIGHT
  stm32_backlight(true);
#endif

  /* Reload shadow register */

  lcdinfo("Reload shadow register\n");
  stm32_ltdc_reload(LTDC_SRCR_IMR, false);

  /* Turn the LCD on */

  lcdinfo("Enabling the display\n");
  stm32_ltdc_enable(true);

  /* Set initialized state */

  g_initialized = true;
  return ret;
}

/****************************************************************************
 * Name: stm32_ltdcgetvplane
 *
 * Description:
 *   Return a a reference to the framebuffer object for the specified video
 *   plane.
 *
 * Input Parameters:
 *   None
 *
 * Returned Value:
 *   Reference to the framebuffer object (NULL on failure)
 *
 ****************************************************************************/

struct fb_vtable_s *stm32_ltdcgetvplane(int vplane)
{
  lcdinfo("vplane: %d\n", vplane);

  if (vplane == 0)
    {
      return &g_vtable.vtable;
    }

  return NULL;
}

/****************************************************************************
 * Name: stm32_ltdcuninitialize
 *
 * Description:
 *   Uninitialize the framebuffer driver.  Bad things will happen if you
 *   call this without first calling fb_initialize()!
 *
 ****************************************************************************/

void stm32_ltdcuninitialize(void)
{
  /* Disable all ltdc interrupts */

  stm32_ltdc_irqctrl(0, LTDC_IER_RRIE | LTDC_IER_TERRIE |
                        LTDC_IER_FUIE | LTDC_IER_LIE);

  up_disable_irq(g_interrupt.irq);
  irq_detach(g_interrupt.irq);

  /* Disable the LCD controller */

  stm32_ltdc_enable(false);

  /* Set initialized state */

  g_initialized = false;
}

/****************************************************************************
 * Name: stm32_lcd_backlight
 *
 * Description:
 *   Provide this interface to turn the backlight on and off.
 *
 * Input Parameters:
 *   blon - Enable or disable the lcd backlight
 *
 ****************************************************************************/

#ifdef CONFIG_STM32_LCD_BACKLIGHT
void stm32_backlight(bool blon)
{
  /* Set default backlight level CONFIG_STM32_LTDC_DEFBACKLIGHT */

  lcderr("ERROR: Not supported\n");
}
#endif