/****************************************************************************
 * arch/arm/src/stm32/stm32_dma2d.c
 *
 *   Copyright (C) 2014-2015 Marco Krahl. All rights reserved.
 *   Author: Marco Krahl <ocram.lhark@gmail.com>
 *
 * References:
 *   STM32F429 Technical Reference Manual
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in
 *    the documentation and/or other materials provided with the
 *    distribution.
 * 3. Neither the name NuttX nor the names of its contributors may be
 *    used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
 * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
 * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
 * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
 * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
 * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 *
 ****************************************************************************/

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

#include <nuttx/config.h>

#include <stdint.h>
#include <string.h>
#include <errno.h>
#include <debug.h>
#include <semaphore.h>

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

#include <arch/chip/dma2d.h>
#include <arch/board/board.h>

#include "up_arch.h"
#include "up_internal.h"
#include "stm32.h"
#include "chip/stm32_ltdc.h"
#include "chip/stm32_dma2d.h"
#include "chip/stm32_ccm.h"
#include "stm32_dma2d.h"

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

/* output, foreground and background layer */

#define DMA2D_NLAYERS                   3

/* DMA2D PFC value definitions */

#define DMA2D_PF_ARGB8888               0
#define DMA2D_PF_RGB888                 1
#define DMA2D_PF_RGB565                 2
#define DMA2D_PF_ARGB1555               3
#define DMA2D_PF_ARGB14444              4
#define DMA2D_PF_L8                     5
#define DMA2D_PF_AL44                   6
#define DMA2D_PF_AL88                   7
#define DMA2D_PF_L4                     8
#define DMA2D_PF_A8                     9
#define DMA2D_PF_A4                     10

/* DMA2D blender control */

#define STM32_DMA2D_CR_MODE_BLIT        DMA2D_CR_MODE(0)
#define STM32_DMA2D_CR_MODE_BLITPFC     DMA2D_CR_MODE(1)
#define STM32_DMA2D_CR_MODE_BLEND       DMA2D_CR_MODE(2)
#define STM32_DMA2D_CR_MODE_COLOR       DMA2D_CR_MODE(3)
#define STM32_DMA2D_CR_MODE_CLEAR       STM32_DMA2D_CR_MODE_COLOR

/* DMA2D PFC alpha mode */

#define STM32_DMA2D_PFCCR_AM_NONE       0
#define STM32_DMA2D_PFCCR_AM_CONST      1
#define STM32_DMA2D_PFCCR_AM_PIXEL      10

/* Only 8 bit per pixel overal supported */

#define DMA2D_PF_BYPP(n)                ((n) / 8)

#define DMA2D_CLUT_SIZE                 STM32_LTDC_NCLUT - 1

/* Layer argb cmap conversion */

#define DMA2D_CLUT_ALPHA(n)          ((uint32_t)(n) << 24)
#define DMA2D_CLUT_RED(n)            ((uint32_t)(n) << 16)
#define DMA2D_CLUT_GREEN(n)          ((uint32_t)(n) << 8)
#define DMA2D_CLUT_BLUE(n)           ((uint32_t)(n) << 0)

#define DMA2D_CMAP_ALPHA(n)          ((uint32_t)(n) >> 24)
#define DMA2D_CMAP_RED(n)            ((uint32_t)(n) >> 16)
#define DMA2D_CMAP_GREEN(n)          ((uint32_t)(n) >> 8)
#define DMA2D_CMAP_BLUE(n)           ((uint32_t)(n) >> 0)

/* Define shadow layer for ltdc interface */

#ifdef CONFIG_STM32_LTDC_INTERFACE
# ifdef CONFIG_STM32_LTDC_L2
#  define DMA2D_SHADOW_LAYER    2
#  define DMA2D_SHADOW_LAYER_L1 0
#  define DMA2D_SHADOW_LAYER_L2 1
# else
#  define DMA2D_SHADOW_LAYER    1
#  define DMA2D_SHADOW_LAYER_L1 0
# endif
# define DMA2D_LAYER_NSIZE      CONFIG_STM32_DMA2D_NLAYERS + DMA2D_SHADOW_LAYER
#else
# define DMA2D_LAYER_NSIZE      CONFIG_STM32_DMA2D_NLAYERS
# define DMA2D_SHADOW_LAYER     0
#endif

/* Debug option */

#ifdef CONFIG_STM32_DMA2D_REGDEBUG
#  define regdbg       dbg
#  define regvdbg      vdbg
#else
#  define regdbg(x...)
#  define regvdbg(x...)
#endif

/* check clut support */

#ifdef CONFIG_STM32_DMA2D_L8
# ifndef CONFIG_FB_CMAP
#  error "Enable cmap to support the configured layer formats!"
# endif
#endif

/* check ccm heap allocation */

#ifndef CONFIG_STM32_CCMEXCLUDE
# error "Enable CONFIG_STM32_CCMEXCLUDE from the heap allocation"
#endif

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

/* DMA2D General layer information */

struct stm32_dma2d_s
{
  struct dma2d_layer_s dma2d;   /* public dma2d interface */

  /* Fixed settings */

  int lid;                      /* Layer identifier */
  struct fb_videoinfo_s vinfo;  /* Layer videoinfo */
  struct fb_planeinfo_s pinfo;  /* Layer planeinfo */

  /* Blending */

  uint32_t blendmode;           /* the interface blendmode */
  uint8_t  alpha;               /* the alpha value */

  /* Coloring */

#ifdef CONFIG_STM32_DMA2D_L8
  uint32_t *clut;               /* Color lookup table */
#endif

  /* Operation */
  uint8_t  fmt;                 /* the controller pixel format */
  sem_t    *lock;               /* Ensure mutually exclusive access */
};

#ifdef CONFIG_STM32_LTDC_INTERFACE

/* This structures provides the DMA2D layer for each LTDC layer */

struct stm32_ltdc_dma2d_s
{
  struct stm32_dma2d_s dma2ddev;
#ifdef CONFIG_STM32_DMA2D_L8
  FAR struct ltdc_layer_s *ltdc;
#endif
};

struct stm32_ltdc_layer_s
{
  /* Layer state */

  struct stm32_ltdc_dma2d_s layer[DMA2D_SHADOW_LAYER];
};
#endif

/* Interrupt handling */

struct stm32_interrupt_s
{
  bool  wait;       /* Informs that the task is waiting for the irq */
  bool  handled;    /* Informs that an irq was handled */
  int   irq;        /* irq number */
  sem_t *sem;       /* Semaphore for waiting for irq */
};

/* This enumeration foreground and background layer supported by the dma2d
 * controller
 */

enum stm32_layer_e
{
  DMA2D_LAYER_LFORE = 0,       /* Foreground Layer */
  DMA2D_LAYER_LBACK,           /* Background Layer */
  DMA2D_LAYER_LOUT,            /* Output Layer */
};

/* DMA2D memory address register */

static const uintptr_t stm32_mar_layer_t[DMA2D_NLAYERS] =
{
  STM32_DMA2D_FGMAR,
  STM32_DMA2D_BGMAR,
  STM32_DMA2D_OMAR
};

/* DMA2D offset register */

static const uintptr_t stm32_or_layer_t[DMA2D_NLAYERS] =
{
  STM32_DMA2D_FGOR,
  STM32_DMA2D_BGOR,
  STM32_DMA2D_OOR
};

/* DMA2D pfc control register */

static const uintptr_t stm32_pfccr_layer_t[DMA2D_NLAYERS] =
{
  STM32_DMA2D_FGPFCCR,
  STM32_DMA2D_BGPFCCR,
  STM32_DMA2D_OPFCCR
};

/* DMA2D color register */

static const uintptr_t stm32_color_layer_t[DMA2D_NLAYERS] =
{
  STM32_DMA2D_FGCOLR,
  STM32_DMA2D_BGCOLR,
  STM32_DMA2D_OCOLR
};

/* DMA2D clut memory address register */

static const uintptr_t stm32_cmar_layer_t[DMA2D_NLAYERS - 1] =
{
  STM32_DMA2D_FGCMAR,
  STM32_DMA2D_BGCMAR
};

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

/* Private functions */

static int stm32_dma2d_pixelformat(uint8_t fmt, uint8_t *fmtmap);
static int stm32_dma2d_bpp(uint8_t fmt, uint8_t *bpp);
static void stm32_dma2d_control(uint32_t setbits, uint32_t clrbits);
static int stm32_dma2dirq(int irq, void *context);
static int stm32_dma2d_waitforirq(void);
static int stm32_dma2d_start(void);
#ifdef CONFIG_STM32_DMA2D_L8
static int stm32_dma2d_loadclut(uintptr_t reg);
#endif
static uint32_t stm32_dma2d_memaddress(FAR const struct stm32_dma2d_s *layer,
                                        fb_coord_t xpos, fb_coord_t ypos);
static fb_coord_t stm32_dma2d_lineoffset(FAR const struct stm32_dma2d_s *layer,
                                          FAR const struct ltdc_area_s *area);

static int stm32_dma2d_lfreelid(void);
static FAR struct stm32_dma2d_s * stm32_dma2d_lalloc(void);
static void stm32_dma2d_lfree(FAR struct stm32_dma2d_s *layer);
static void stm32_dma2d_llayerscleanup(void);
static bool stm32_dma2d_lvalidate(FAR const struct stm32_dma2d_s *layer);
static bool stm32_dma2d_lvalidatesize(FAR const struct stm32_dma2d_s *layer,
                                      fb_coord_t xpos, fb_coord_t ypos,
                                      FAR const struct ltdc_area_s *area);
static void stm32_dma2d_linit(FAR struct stm32_dma2d_s *layer,
                                int lid, uint8_t fmt);

static void stm32_dma2d_lfifo(FAR const struct stm32_dma2d_s *layer, int lid,
                              fb_coord_t xpos, fb_coord_t ypos,
                              FAR const struct ltdc_area_s *area);
static void stm32_dma2d_lcolor(FAR const struct stm32_dma2d_s *layer,
                               int lid, uint32_t color);
static void stm32_dma2d_llnr(FAR struct stm32_dma2d_s *layer,
                                FAR const struct ltdc_area_s *area);
static int stm32_dma2d_loutpfc(FAR const struct stm32_dma2d_s *layer);
static void stm32_dma2d_lpfc(FAR const struct stm32_dma2d_s *layer,
                              int lid, uint32_t blendmode);
/* Public functions */

static int stm32_dma2dgetvideoinfo(FAR struct dma2d_layer_s *layer,
                                  FAR struct fb_videoinfo_s *vinfo);
static int stm32_dma2dgetplaneinfo(FAR struct dma2d_layer_s *layer, int planeno,
                                  FAR struct fb_planeinfo_s *pinfo);
static int stm32_dma2dgetlid(FAR struct dma2d_layer_s *layer, int *lid);
#ifdef CONFIG_STM32_DMA2D_L8
static int stm32_dma2dsetclut(FAR struct dma2d_layer_s *layer,
                            const FAR struct fb_cmap_s *cmap);
static int stm32_dma2dgetclut(FAR struct dma2d_layer_s *layer,
                            FAR struct fb_cmap_s *cmap);
#endif
static int stm32_dma2dsetalpha(FAR struct dma2d_layer_s *layer, uint8_t alpha);
static int stm32_dma2dgetalpha(FAR struct dma2d_layer_s *layer, uint8_t *alpha);
static int stm32_dma2dsetblendmode(FAR struct dma2d_layer_s *layer,
                                    uint32_t mode);
static int stm32_dma2dgetblendmode(FAR struct dma2d_layer_s *layer,
                                    uint32_t *mode);
static int stm32_dma2dblit(FAR struct dma2d_layer_s *dest,
                            fb_coord_t destxpos, fb_coord_t destypos,
                            FAR const struct dma2d_layer_s *src,
                            FAR const struct ltdc_area_s *srcarea);
static int stm32_dma2dblend(FAR struct dma2d_layer_s *dest,
                            fb_coord_t destxpos, fb_coord_t destypos,
                            FAR const struct dma2d_layer_s *fore,
                            fb_coord_t forexpos, fb_coord_t foreypos,
                            FAR const struct dma2d_layer_s *back,
                            FAR const struct ltdc_area_s *backarea);
static int stm32_dma2dfillarea(FAR struct dma2d_layer_s *layer,
                            FAR const struct ltdc_area_s *area, uint32_t color);

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

/* Remember the layer references for alloc/deallocation */

static struct stm32_dma2d_s *g_layers[DMA2D_LAYER_NSIZE];

/* The DMA2D semaphore that enforces mutually exclusive access */

static sem_t g_lock;

#ifdef CONFIG_STM32_LTDC_INTERFACE
/* This structure provides the DMA2D layer for each LTDC layer */

static struct stm32_ltdc_layer_s g_ltdc_layer;
#endif

/* The initalized state of the driver */

static bool g_initialized;

/* Semaphore for interrupt handling */

static sem_t g_semirq;

/* This structure provides irq handling */

static struct stm32_interrupt_s g_interrupt =
{
  .wait    = false,
  .handled = true,
  .irq     = STM32_IRQ_DMA2D,
  .sem     = &g_semirq
};

/****************************************************************************
 * Public Data
 ****************************************************************************/

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

/****************************************************************************
 * Name: stm32_dma2d_control
 *
 * Description:
 *   Change the DMA2D control register
 *
 * Parameter:
 *   setbits - The bits to set
 *   clrbits - The bits to clear
 *
 ****************************************************************************/

static void stm32_dma2d_control(uint32_t setbits, uint32_t clrbits)
{
  uint32_t   cr;

  gvdbg("setbits=%08x, clrbits=%08x\n", setbits, clrbits);

  cr = getreg32(STM32_DMA2D_CR);
  cr &= ~clrbits;
  cr |= setbits;
  putreg32(cr, STM32_DMA2D_CR);
}

/****************************************************************************
 * Name: stm32_dma2dirq
 *
 * Description:
 *   DMA2D interrupt handler
 *
 ****************************************************************************/

static int stm32_dma2dirq(int irq, void *context)
{
  uint32_t regval = getreg32(STM32_DMA2D_ISR);
  FAR struct stm32_interrupt_s *priv = &g_interrupt;

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

  if (regval & DMA2D_ISR_TCIF)
    {
      /* Transfer complete interrupt */

      /* Clear the interrupt status register */

      putreg32(DMA2D_IFCR_CTCIF, STM32_DMA2D_IFCR);
    }
#ifdef CONFIG_STM32_DMA2D_L8
  else if (regval & DMA2D_ISR_CTCIF)
    {
      /* CLUT transfer complete interrupt */

      /* Clear the interrupt status register */

      putreg32(DMA2D_IFCR_CCTCIF, STM32_DMA2D_IFCR);
    }
#endif
  else
    {
      /* Unknown irq, should not occur */

      return OK;
    }

  /* Update the handled flag */

  priv->handled = true;

  /* Unlock the semaphore if locked */

  if (priv->wait)
    {

      int ret = sem_post(priv->sem);

      if (ret != OK)
        {
          dbg("sem_post() failed\n");
          return ret;
        }
    }

  return OK;
}

/****************************************************************************
 * Name: stm32_dma2d_waitforirq
 *
 * Description:
 *   Helper waits until the dma2d irq occurs. That means that an ongoing clut
 *   loading or dma transfer was completed.
 *   Note! The caller must use this function within irqsave state.
 *
 * Return:
 *   On success OK otherwise ERROR
 *
 ****************************************************************************/

static int stm32_dma2d_waitforirq(void)
{
  FAR struct stm32_interrupt_s *priv = &g_interrupt;

  /* Only waits if last enabled interrupt is currently not handled */

  if (!priv->handled)
    {
      int ret;

      /* Inform the irq handler the task is able to wait for the irq */

      priv->wait = true;

      ret = sem_wait(priv->sem);

      /* irq or an error occurs, reset the wait flag */

      priv->wait = false;

      if (ret != OK)
        {
          dbg("sem_wait() failed\n");
          return ret;
        }
    }

  return OK;
}


#ifdef CONFIG_STM32_DMA2D_L8
/****************************************************************************
 * Name: stm32_dma2d_loadclut
 *
 * Description:
 *   Starts clut loading but doesn't wait until loading is complete!
 *
 * Parameter:
 *   pfcreg - PFC control Register
 *
 * Return:
 *   On success - OK
 *   On error - -EINVAL
 *
 ****************************************************************************/

static int stm32_dma2d_loadclut(uintptr_t pfcreg)
{
  int        ret;
  uint32_t   regval;
  irqstate_t flags;

  flags = irqsave();

  ret = stm32_dma2d_waitforirq();
  if (ret == OK)
    {
      FAR struct stm32_interrupt_s *priv = &g_interrupt;

      /* Reset the handled flag */

      priv->handled = false;

      /* Start clut loading */

      regval  = getreg32(pfcreg);
      regval |= DMA2D_xGPFCCR_START;
      regvdbg("set regval=%08x\n", regval);
      putreg32(regval, pfcreg);
      regvdbg("configured regval=%08x\n", getreg32(pfcreg));
    }

  irqrestore(flags);
  return OK;
}
#endif

/****************************************************************************
 * Name: stm32_dma2d_start
 *
 * Description:
 *   Starts the dma transfer and waits until completed.
 *
 * Parameter:
 *   reg       - Register to set the start
 *   startflag - The related flag to start the dma transfer
 *   irqflag   - The interrupt enable flag in the DMA2D_CR register
 *
 ****************************************************************************/

static int stm32_dma2d_start(void)
{
  int        ret;
  irqstate_t flags;

  flags = irqsave();

  ret = stm32_dma2d_waitforirq();
  if (ret == OK)
    {
      FAR struct stm32_interrupt_s *priv = &g_interrupt;

      /* Reset the handled flag */

      priv->handled = false;

      /* Start clut loading */

      stm32_dma2d_control(DMA2D_CR_START, 0);

      /* wait until transfer is complete */

      ret = stm32_dma2d_waitforirq();
    }

  irqrestore(flags);
  return ret;
}

/****************************************************************************
 * Name: stm32_dma2d_memaddress
 *
 * Description:
 *   Helper to calculate the layer memory address
 *
 * Parameter:
 *   layer - Reference to the common layer state structure
 *
 * Return:
 *   memory address
 *
 ****************************************************************************/

static uint32_t stm32_dma2d_memaddress(FAR const struct stm32_dma2d_s *layer,
                                        fb_coord_t xpos, fb_coord_t ypos)
{
  FAR const struct fb_planeinfo_s *pinfo = &layer->pinfo;
  uint32_t offset;

  offset = xpos * DMA2D_PF_BYPP(layer->pinfo.bpp) + layer->pinfo.stride * ypos;

  gvdbg("%p\n", ((uint32_t) pinfo->fbmem) + offset);
  return ((uint32_t) pinfo->fbmem) + offset;
}

/****************************************************************************
 * Name: stm32_dma2d_lineoffset
 *
 * Description:
 *   Helper to calculate the layer line offset
 *
 * Parameter:
 *   layer - Reference to the common layer state structure
 *
 * Return:
 *   line offset
 *
 ****************************************************************************/

static fb_coord_t stm32_dma2d_lineoffset(FAR const struct stm32_dma2d_s *layer,
                                          FAR const struct ltdc_area_s *area)
{
  /* offset at the end of each line in the context to the area layer */

  gvdbg("%d\n", layer->vinfo.xres - area->xres);
  return layer->vinfo.xres - area->xres;
}

/****************************************************************************
 * Name: stm32_dma2d_pixelformat
 *
 * Description:
 *   Helper to map to dma2d controller pixel format
 *
 * Parameter:
 *   layer - Reference to the common layer state structure
 *   fmt   - Reference to the location to store the pixel format
 *
 * Return:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

static int stm32_dma2d_pixelformat(uint8_t fmt, uint8_t *fmtmap)
{
  gvdbg("fmt=%d, fmtmap=%p\n", fmt, fmtmap);

  /* Map to the controller known format
   *
   * Not supported by NuttX:
   * ARGB8888
   * ARGB1555
   * ARGB4444
   * AL44
   * AL88
   * L8 (non output layer only)
   * L4
   * A8
   * A4
   */

  switch (fmt)
    {
#ifdef CONFIG_STM32_DMA2D_RGB565
      case FB_FMT_RGB16_565:
        *fmtmap = DMA2D_PF_RGB565;
        break;
#endif
#ifdef CONFIG_STM32_DMA2D_RGB888
      case FB_FMT_RGB24:
        *fmtmap = DMA2D_PF_RGB888;
        break;
#endif
#ifdef CONFIG_STM32_DMA2D_L8
      case FB_FMT_RGB8:
        *fmtmap = DMA2D_PF_L8;
        break;
#endif
      default:
        gdbg("ERROR: Returning EINVAL\n");
        return -EINVAL;
    }

  return OK;
}

/****************************************************************************
 * Name: stm32_dma2d_bpp
 *
 * Description:
 *   Helper to get the bits per pixel
 *
 * Parameter:
 *   layer - Reference to the common layer state structure
 *   bpp   - Reference to the location to store the pixel format
 *
 * Return:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

static int stm32_dma2d_bpp(uint8_t fmt, uint8_t *bpp)
{
  gvdbg("fmt=%d, bpp=%p\n", fmt, bpp);

  switch (fmt)
    {
#ifdef CONFIG_STM32_DMA2D_RGB565
      case FB_FMT_RGB16_565:
        *bpp = 16;
        break;
#endif
#ifdef CONFIG_STM32_DMA2D_RGB888
      case FB_FMT_RGB24:
        *bpp = 24;
        break;
#endif
#ifdef CONFIG_STM32_DMA2D_L8
      case FB_FMT_RGB8:
        *bpp = 8;
        break;
#endif
      default:
        gdbg("ERROR: Returning EINVAL\n");
        return -EINVAL;
    }

  return OK;
}

/****************************************************************************
 * Name: stm32_dma2d_lfreelid
 *
 * Description:
 *   Get a free layer id
 *
 * Return:
 *   The number of the free layer
 *   -1 if no free layer is available
 *
 ****************************************************************************/

static int stm32_dma2d_lfreelid(void)
{
  int n;

  for (n = DMA2D_SHADOW_LAYER; n < DMA2D_LAYER_NSIZE; n++)
    {
      if (g_layers[n] == NULL)
        {
          return n;
        }
    }

  return -1;
}

/****************************************************************************
 * Name: stm32_dma2d_lalloc
 *
 * Description:
 *   Allocate a new layer structure
 *
 * Return:
 *   A new allocated layer structure or NULL on error.
 *
 ****************************************************************************/

static FAR struct stm32_dma2d_s * stm32_dma2d_lalloc(void)
{
  FAR struct stm32_dma2d_s *layer;

#ifdef HAVE_CCM_HEAP
  /* First try to allocate from the ccm heap */

  layer = ccm_malloc(sizeof(struct stm32_dma2d_s));

  if (!layer)
    {
      /* Use default allocator */

      layer = kmm_malloc(sizeof(struct stm32_dma2d_s));
    }
#else
  layer = kmm_malloc(sizeof(struct stm32_dma2d_s));
#endif

  return layer;
}

/****************************************************************************
 * Name: stm32_dma2d_lfree
 *
 * Description:
 *   Deallocate the dynamic allocated layer structure
 *
 * Input Parameters:
 *   A previous allocated layer structure
 *
 ****************************************************************************/

static void stm32_dma2d_lfree(FAR struct stm32_dma2d_s *layer)
{
  if (layer)
    {
#ifdef HAVE_CCM_HEAP
      if (((uint32_t)layer & 0xf0000000) == 0x10000000)
        {
          ccm_free(layer);
        }
      else
        {
          kmm_free(layer);
        }
#else
      kmm_free(layer);
#endif
    }
}

/****************************************************************************
 * Name: stm32_dma2d_llayerscleanup
 *
 * Description:
 *   Cleanup all allocated layers
 *
 ****************************************************************************/

static void stm32_dma2d_llayerscleanup(void)
{
  int n;

  /* Do not uninitialize the ltdc related dma2d layer */

  for (n = DMA2D_SHADOW_LAYER; n < DMA2D_LAYER_NSIZE; n++)
    {
      FAR struct stm32_dma2d_s *priv = g_layers[n];
      if (priv)
        {
          kmm_free(priv->pinfo.fbmem);
          stm32_dma2d_lfree(priv);
          g_layers[n] = NULL;
        }
    }
}

/****************************************************************************
 * Name: stm32_dma2d_lvalidate
 *
 * Description:
 *   Helper to validate if the layer is valid
 *
 * Return:
 *   true if validates otherwise false
 *
 ****************************************************************************/

static inline bool stm32_dma2d_lvalidate(FAR const struct stm32_dma2d_s *layer)
{
  return layer && layer->lid < DMA2D_LAYER_NSIZE;
}

/****************************************************************************
 * Name: stm32_dma2d_lvalidatesize
 *
 * Description:
 *   Helper to check if area is outside the whole layer.
 *
 * Parameter:
 *   layer   - Reference to the layer control structure
 *   xpos    - The x position inside the whole layer
 *   ypos    - The y position inside the whole layer
 *   area    - the area inside the whole layer
 *
 * Return:
 *   true if area is inside the whole layer otherwise false
 *
 ****************************************************************************/

static bool stm32_dma2d_lvalidatesize(FAR const struct stm32_dma2d_s *layer,
                                         fb_coord_t xpos, fb_coord_t ypos,
                                         FAR const struct ltdc_area_s *area)
{
  return stm32_dma2d_lvalidate(layer) &&
            ((layer->vinfo.xres - xpos) * (layer->vinfo.yres - ypos) >=
                area->xres * area->yres);
}

/****************************************************************************
 * Name: stm32_dma2d_linit
 *
 * Description:
 *   Initialize the internal layer structure
 *
 * Parameter:
 *
 *
 ****************************************************************************/

static void stm32_dma2d_linit(FAR struct stm32_dma2d_s *layer,
                                int lid, uint8_t fmt)
{
  FAR struct dma2d_layer_s *priv = &layer->dma2d;

  gvdbg("layer=%p, lid=%d, fmt=%02x\n", layer, lid, fmt);

  /* initialize the layer interface */

  priv->getvideoinfo = stm32_dma2dgetvideoinfo;
  priv->getplaneinfo = stm32_dma2dgetplaneinfo;
  priv->getlid       = stm32_dma2dgetlid;
#ifdef CONFIG_STM32_DMA2D_L8
  priv->setclut      = stm32_dma2dsetclut;
  priv->getclut      = stm32_dma2dgetclut;
#endif
  priv->setalpha     = stm32_dma2dsetalpha;
  priv->getalpha     = stm32_dma2dgetalpha;
  priv->setblendmode = stm32_dma2dsetblendmode;
  priv->getblendmode = stm32_dma2dgetblendmode;
  priv->blit         = stm32_dma2dblit;
  priv->blend        = stm32_dma2dblend;
  priv->fillarea     = stm32_dma2dfillarea;

  /* Initialize the layer structure */

  layer->lid          = lid;
#ifdef CONFIG_STM32_DMA2D_L8
  layer->clut         = 0;
#endif
  layer->blendmode    = DMA2D_BLEND_NONE;
  layer->alpha        = 255;
  layer->fmt          = fmt;
  layer->lock         = &g_lock;
}

/****************************************************************************
 * Name: stm32_dma2d_lfifo
 *
 * Description:
 *   Set the fifo for the foreground, background and output layer
 *   Configures the memory address register
 *   Configures the line offset register
 *
 * Parameter:
 *   layer - Reference to the common layer state structure
 *
 ****************************************************************************/

static void stm32_dma2d_lfifo(FAR const struct stm32_dma2d_s *layer, int lid,
                              fb_coord_t xpos, fb_coord_t ypos,
                              FAR const struct ltdc_area_s *area)
{
  gvdbg("layer=%p, lid=%d, xpos=%d, ypos=%d, area=%p\n",
            layer, lid, xpos, ypos, area);

  putreg32(stm32_dma2d_memaddress(layer, xpos, ypos), stm32_mar_layer_t[lid]);
  putreg32(stm32_dma2d_lineoffset(layer, area), stm32_or_layer_t[lid]);
}

/****************************************************************************
 * Name: stm32_dma2d_lcolor
 *
 * Description:
 *   Set the color for the layer
 *
 * Parameter:
 *   layer - Reference to the common layer state structure
 *
 ****************************************************************************/

static void stm32_dma2d_lcolor(FAR const struct stm32_dma2d_s *layer,
                               int lid, uint32_t color)
{
  gvdbg("layer=%p, lid=%d, color=%08x\n", layer, lid, color);
  putreg32(color, stm32_color_layer_t[lid]);
}

/****************************************************************************
 * Name: stm32_dma2d_llnr
 *
 * Description:
 *   Set the number of line register
 *
 * Parameter:
 *   layer   - Reference to the common layer state structure
 *   area    - Reference to the area to copy
 *
 ****************************************************************************/

static void stm32_dma2d_llnr(FAR struct stm32_dma2d_s *layer,
                                FAR const struct ltdc_area_s *area)
{
  uint32_t nlrreg;

  gvdbg("pixel per line: %d, number of lines: %d\n", area->xres, area->yres);

  nlrreg = getreg32(STM32_DMA2D_NLR);
  nlrreg = (DMA2D_NLR_PL(area->xres) | DMA2D_NLR_NL(area->yres));
  putreg32(nlrreg, STM32_DMA2D_NLR);
}

/****************************************************************************
 * Name: stm32_dma2d_loutpfc
 *
 * Description:
 *   Set the output PFC control register
 *
 * Parameter:
 *   layer - Reference to the common layer state structure
 *
 ****************************************************************************/

static int stm32_dma2d_loutpfc(FAR const struct stm32_dma2d_s *layer)
{
  gvdbg("layer=%p\n", layer);

  /* CLUT format isn't supported by the dma2d controller */

  if (layer->fmt == DMA2D_PF_L8)
    {
      /* Destination layer doesn't support CLUT output */

      gdbg("ERROR: Returning ENOSYS, "
            "output to layer with CLUT format not supported.\n");
      return -ENOSYS;
    }

  /* Set the mapped pixel format of source layer */

  putreg32(DMA2D_OPFCCR_CM(layer->fmt), STM32_DMA2D_OPFCCR);

  return OK;
}

/****************************************************************************
 * Name: stm32_dma2d_lpfc
 *
 * Description:
 *   Configure foreground and background layer PFC control register
 *
 * Parameter:
 *   layer - Reference to the common layer state structure
 *
 ****************************************************************************/

static void stm32_dma2d_lpfc(FAR const struct stm32_dma2d_s *layer,
                              int lid, uint32_t blendmode)
{
  uint32_t   pfccrreg;

  gvdbg("layer=%p, lid=%d, blendmode=%08x\n", layer, lid, blendmode);

  /* Set color format */

  pfccrreg = DMA2D_xGPFCCR_CM(layer->fmt);

#ifdef CONFIG_STM32_DMA2D_L8
  if (layer->fmt == DMA2D_PF_L8)
    {
      /* Load CLUT automatically */

      pfccrreg |= DMA2D_xGPFCCR_START;

      /* Set the CLUT color mode */

#ifndef CONFIG_FB_TRANSPARENCY
      pfccrreg |= DMA2D_xGPFCCR_CCM;
#endif

      /* Set CLUT size */

      pfccrreg |= DMA2D_xGPFCCR_CS(DMA2D_CLUT_SIZE);

      /* Set the CLUT memory address */

      putreg32((uint32_t) layer->clut, stm32_cmar_layer_t[lid]);

      /* Start async clut loading */

      stm32_dma2d_loadclut(stm32_pfccr_layer_t[lid]);
    }
#endif

  if (blendmode & DMA2D_BLEND_NONE)
    {
      /* No blend operation */

      pfccrreg |= DMA2D_xGPFCCR_AM(STM32_DMA2D_PFCCR_AM_NONE);
    }
  else
    {
      /* Set alpha value */

      pfccrreg |= DMA2D_xGPFCCR_ALPHA(layer->alpha);

      /* Set alpha mode */

      if (layer->blendmode & DMA2D_BLEND_ALPHA)
        {
          /* Blend with constant alpha */

          pfccrreg |= DMA2D_xGPFCCR_AM(STM32_DMA2D_PFCCR_AM_CONST);
        }
      else if (layer->blendmode & DMA2D_BLEND_PIXELALPHA)
        {
          /* Blend with pixel alpha value */

          pfccrreg |= DMA2D_xGPFCCR_AM(STM32_DMA2D_PFCCR_AM_PIXEL);
        }
    }

  putreg32(pfccrreg, stm32_pfccr_layer_t[lid]);
}

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

/****************************************************************************
 * Name: stm32_dma2dgetvideoinfo
 *
 * Description:
 *   Get video information about the layer
 *
 * Parameter:
 *   layer  - Reference to the layer control structure
 *   vinfo  - Reference to the video info structure
 *
 * Return:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

static int stm32_dma2dgetvideoinfo(FAR struct dma2d_layer_s *layer,
                            FAR struct fb_videoinfo_s *vinfo)
{
  FAR struct stm32_dma2d_s *priv = (FAR struct stm32_dma2d_s *)layer;

  gvdbg("layer=%p, vinfo=%p\n", layer, vinfo);

  if (stm32_dma2d_lvalidate(priv) && vinfo)
    {
      sem_wait(priv->lock);
      memcpy(vinfo, &priv->vinfo, sizeof(struct fb_videoinfo_s));
      sem_post(priv->lock);

      return OK;
    }

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

/****************************************************************************
 * Name: stm32_dma2dgetplaneinfo
 *
 * Description:
 *   Get plane information about the layer
 *
 * Parameter:
 *   layer   - Reference to the layer control structure
 *   planeno - Number of the plane
 *   pinfo   - Reference to the plane info structure
 *
 * Return:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

static int stm32_dma2dgetplaneinfo(FAR struct dma2d_layer_s *layer, int planeno,
                              FAR struct fb_planeinfo_s *pinfo)
{
  FAR struct stm32_dma2d_s *priv = (FAR struct stm32_dma2d_s *)layer;

  gvdbg("layer=%p, planeno=%d, pinfo=%p\n", layer, planeno, pinfo);

  if (stm32_dma2d_lvalidate(priv) && pinfo && planeno == 0)
    {
      sem_wait(priv->lock);
      memcpy(pinfo, &priv->pinfo, sizeof(struct fb_planeinfo_s));
      sem_post(priv->lock);

      return OK;
    }

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

/****************************************************************************
 * Name: stm32_dma2dgetlid
 *
 * Description:
 *   Get a specific layer identifier.
 *
 * Parameter:
 *   layer - Reference to the layer structure
 *   lid   - Reference to store the layer id
 *
 * Return:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

static int stm32_dma2dgetlid(FAR struct dma2d_layer_s *layer, int *lid)
{
  FAR struct stm32_dma2d_s *priv = (FAR struct stm32_dma2d_s *)layer;

  gvdbg("layer=%p, lid=%p\n", layer, lid);

  if (stm32_dma2d_lvalidate(priv) && lid)
    {
      sem_wait(priv->lock);
      *lid = priv->lid;
      sem_post(priv->lock);
      return OK;
    }

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

#ifdef CONFIG_STM32_DMA2D_L8
/****************************************************************************
 * Name: stm32_dma2dsetclut
 *
 * Description:
 *   Configure layer clut (color lookup table).
 *   Non clut is defined during initializing.
 *
 * Parameter:
 *   layer  - Reference to the layer structure
 *   cmap   - color lookup table with up the 256 entries
 *
 * Return:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

static int stm32_dma2dsetclut(FAR struct dma2d_layer_s *layer,
                            const FAR struct fb_cmap_s *cmap)
{
  int   ret;
  FAR struct stm32_dma2d_s *priv = (FAR struct stm32_dma2d_s *)layer;

  gvdbg("layer=%p, cmap=%p\n", layer, cmap);

  if (stm32_dma2d_lvalidate(priv) && cmap)
    {
      sem_wait(priv->lock);

#ifdef CONFIG_STM32_LTDC_INTERFACE
      if (priv->lid < DMA2D_SHADOW_LAYER)
        {
          /* Update the shared color lookup table.
           *
           * Background:
           *
           *  We share the same memory region of the clut table with the LTDC
           *  driver. (see stm32_dma2dinitltdc). This is important because any
           *  changes to the framebuffer and color lookup table by the ltdc
           *  related dma2d layer should also effects to the ltdc visibility,
           *  except operation settings, alpha and blendmode.
           *
           *  But we can not only update the clut memory region. The LTDC driver
           *  also must update they own LTDC clut register to make the changes
           *  visible. Using the LTDC interface to update the clut table will
           *  also update the clut table of the related dma2d layer.
           */

          FAR struct ltdc_layer_s *ltdc =
              g_ltdc_layer.layer[DMA2D_SHADOW_LAYER_L1].ltdc;

          ret = ltdc->setclut(ltdc, cmap);

          sem_post(priv->lock);

          return ret;
        }
#endif

      if (priv->fmt != DMA2D_PF_L8)
        {
          gdbg("Error: CLUT is not supported for the pixel format: %d\n",
                    priv->vinfo.fmt);
          ret = -EINVAL;
        }
      else if (cmap->first >= STM32_DMA2D_NCLUT)
        {
          gdbg("Error: only %d color table entries supported\n",
                    STM32_DMA2D_NCLUT);
          ret = -EINVAL;
        }
      else
        {
          uint32_t *clut;
          int      n;

          clut = priv->clut;

          for (n = cmap->first; n < cmap->len && n < STM32_DMA2D_NCLUT; n++)
            {
              /* Update the layer clut entry */

#ifndef CONFIG_FB_TRANSPARENCY
              uint8_t  *clut888 = (uint8_t *)clut;
              uint16_t offset   = 3 * n;

              clut888[offset]     = cmap->blue[n];
              clut888[offset + 1] = cmap->green[n];
              clut888[offset + 2] = cmap->red[n];

              regvdbg("n=%d, red=%02x, green=%02x, blue=%02x\n", n,
                        clut888[offset], clut888[offset + 1],
                        clut888[offset + 2]);
#else
              clut[n] = (uint32_t)DMA2D_CLUT_RED(cmap->transp[n]) |
                        (uint32_t)DMA2D_CLUT_GREEN(cmap->red[n]) |
                        (uint32_t)DMA2D_CLUT_GREEN(cmap->green[n]) |
                        (uint32_t)DMA2D_CLUT_BLUE(cmap->blue[n]);

              regvdbg("n=%d, alpha=%02x, red=%02x, green=%02x, blue=%02x\n", n,
                        DMA2D_CLUT_ALPHA(cmap->alpha[n]),
                        DMA2D_CLUT_RED(cmap->red[n]),
                        DMA2D_CLUT_GREEN(cmap->green[n]),
                        DMA2D_CLUT_BLUE(cmap->blue[n]));
#endif
            }


          ret = OK;
        }

      sem_post(priv->lock);
      return ret;
    }

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

/****************************************************************************
 * Name: stm32_dma2dgetclut
 *
 * Description:
 *   Get configured layer clut (color lookup table).
 *
 * Parameter:
 *   layer - Reference to the layer structure
 *   cmap  - Reference to valid color lookup table accept up the 256 color
 *           entries
 *
 * Return:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

static int stm32_dma2dgetclut(FAR struct dma2d_layer_s *layer,
                            FAR struct fb_cmap_s *cmap)
{
  int ret;
  FAR struct stm32_dma2d_s *priv = (FAR struct stm32_dma2d_s *)layer;

  gvdbg("layer=%p, cmap=%p\n", layer, cmap);

  if (stm32_dma2d_lvalidate(priv) && cmap)
    {
      sem_wait(priv->lock);

      if (priv->fmt != DMA2D_PF_L8)
        {
          gdbg("Error: CLUT is not supported for the pixel format: %d\n",
                    priv->vinfo.fmt);
          ret = -EINVAL;
        }
      else if (cmap->first >= STM32_DMA2D_NCLUT)
        {
          gdbg("Error: only %d color table entries supported\n",
                    STM32_DMA2D_NCLUT);
          ret = -EINVAL;
        }
      else
        {
          /* Copy from the layer clut */

          uint32_t *clut;
          int      n;

          clut = priv->clut;

          for (n = cmap->first; n < cmap->len && n < STM32_DMA2D_NCLUT; n++)
            {
#ifndef CONFIG_FB_TRANSPARENCY
              uint8_t  *clut888 = (uint8_t *)clut;
              uint16_t offset   = 3 * n;

              cmap->blue[n]   = clut888[offset];
              cmap->green[n]  = clut888[offset + 1];
              cmap->red[n]    = clut888[offset + 2];

              regvdbg("n=%d, red=%02x, green=%02x, blue=%02x\n", n,
                        clut888[offset], clut888[offset + 1],
                        clut888[offset + 2]);
#else
              cmap->transp[n] = (uint8_t)DMA2D_CMAP_ALPHA(clut[n]);
              cmap->red[n]    = (uint8_t)DMA2D_CMAP_RED(clut[n]);
              cmap->green[n]  = (uint8_t)DMA2D_CMAP_GREEN(clut[n]);
              cmap->blue[n]   = (uint8_t)DMA2D_CMAP_BLUE(clut[n]);

              regvdbg("n=%d, alpha=%02x, red=%02x, green=%02x, blue=%02x\n", n,
                        DMA2D_CMAP_ALPHA(clut[n]), DMA2D_CMAP_RED(clut[n]),
                        DMA2D_CMAP_GREEN(clut[n]), DMA2D_CMAP_BLUE(clut[n]));
#endif
            }

          ret = OK;
        }

      sem_post(priv->lock);

      return ret;
    }

  gdbg("ERROR: Returning EINVAL\n");
  return -EINVAL;
}
#endif

/****************************************************************************
 * Name: stm32_dma2dsetalpha
 *
 * Description:
 *   Configure layer alpha value factor into blend operation.
 *   During the layer blend operation the source alpha value is multiplied
 *   with this alpha value. If the source color format doesn't support alpha
 *   channel (e.g. non ARGB8888) this alpha value will be used as constant
 *   alpha value for blend operation.
 *   Default value during initializing: 0xff
 *
 * Parameter:
 *   layer - Reference to the layer structure
 *   alpha - Alpha value
 *
 * Return:
 *   On success - OK
 *   On error - -EINVAL
 *
 ****************************************************************************/

static int stm32_dma2dsetalpha(FAR struct dma2d_layer_s *layer, uint8_t alpha)
{
  FAR struct stm32_dma2d_s *priv = (FAR struct stm32_dma2d_s *)layer;

  gvdbg("layer=%p, alpha=%02x\n", layer, alpha);

  if (stm32_dma2d_lvalidate(priv))
    {
      sem_wait(priv->lock);
      priv->alpha = alpha;
      sem_post(priv->lock);

      return OK;
    }

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

/****************************************************************************
 * Name: stm32_dma2dgetalpha
 *
 * Description:
 *   Get configured layer alpha value factor for blend operation.
 *
 * Parameter:
 *   layer - Reference to the layer structure
 *   alpha - Reference to store the alpha value
 *
 * Return:
 *   On success - OK
 *   On error - -EINVAL
 *
 ****************************************************************************/

static int stm32_dma2dgetalpha(FAR struct dma2d_layer_s *layer, uint8_t *alpha)
{
  FAR struct stm32_dma2d_s *priv = (FAR struct stm32_dma2d_s *)layer;

  gvdbg("layer=%p, alpha=%p\n", layer, alpha);

  if (stm32_dma2d_lvalidate(priv))
    {
      sem_wait(priv->lock);
      *alpha = priv->alpha;
      sem_post(priv->lock);

      return OK;
    }

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

/****************************************************************************
 * Name: stm32_dma2dsetblendmode
 *
 * Description:
 *   Configure blend mode of the layer.
 *   Default mode during initializing: DMA2D_BLEND_NONE
 *   Blendmode is active after next update.
 *
 * Parameter:
 *   layer - Reference to the layer structure
 *   mode  - Blend mode (see DMA2D_BLEND_*)
 *
 * Return:
 *   On success - OK
 *   On error - -EINVAL
 *
 * Procedure information:
 *   DMA2D_BLEND_NONE:
 *     Informs the driver to disable all blend operation for the given layer.
 *     That means the layer is opaque.
 *
 *   DMA2D_BLEND_ALPHA:
 *     Informs the driver to enable alpha blending for the given layer.
 *
 *   DMA2D_BLEND_PIXELALPHA:
 *     Informs the driver to use the pixel alpha value of the layer instead
 *     the constant alpha value. This is only useful for ARGB8888
 *     color format.
 *
 ****************************************************************************/

static int stm32_dma2dsetblendmode(FAR struct dma2d_layer_s *layer,
                                    uint32_t mode)
{
  FAR struct stm32_dma2d_s *priv = (FAR struct stm32_dma2d_s *)layer;

  gvdbg("layer=%p, mode=%08x\n", layer, mode);

  if (stm32_dma2d_lvalidate(priv))
    {
      sem_wait(priv->lock);
      priv->blendmode = mode;
      sem_post(priv->lock);

      return OK;
    }

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

/****************************************************************************
 * Name: stm32_getblendmode
 *
 * Description:
 *   Get configured blend mode of the layer.
 *
 * Parameter:
 *   layer - Reference to the layer structure
 *   mode  - Reference to store the blend mode
 *
 * Return:
 *   On success - OK
 *   On error - -EINVAL
 *
 ****************************************************************************/

static int stm32_dma2dgetblendmode(FAR struct dma2d_layer_s *layer,
                                    uint32_t *mode)
{
  FAR struct stm32_dma2d_s *priv = (FAR struct stm32_dma2d_s *)layer;

  gvdbg("layer=%p, mode=%p\n", layer, mode);

  if (stm32_dma2d_lvalidate(priv) && mode)
    {
      sem_wait(priv->lock);
      *mode = priv->blendmode;
      sem_post(priv->lock);

      return OK;
    }

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

/****************************************************************************
 * Name: stm32_dma2dblit
 *
 * Description:
 *   Copy selected area from a source layer to selected position of the
 *   destination layer.
 *
 * Parameter:
 *   dest     - Valid reference to the destination layer
 *   destxpos - Valid selected x position of the destination layer
 *   destypos - Valid selected y position of the destination layer
 *   src      - Valid reference to the source layer
 *   srcarea  - Valid reference to the selected area of the source layer
 *
 * Return:
 *    OK        - On success
 *   -EINVAL    - If one of the parameter invalid or if the size of the selected
 *                source area outside the visible area of the destination layer.
 *                (The visible area usually represents the display size)
 *   -ECANCELED - Operation cancelled, something goes wrong.
 *
 ****************************************************************************/

static int stm32_dma2dblit(FAR struct dma2d_layer_s *dest,
                            fb_coord_t destxpos, fb_coord_t destypos,
                            FAR const struct dma2d_layer_s *src,
                            FAR const struct ltdc_area_s *srcarea)
{
  uint32_t   mode;
  int        ret;
  FAR struct stm32_dma2d_s * destlayer = (FAR struct stm32_dma2d_s *)dest;
  FAR struct stm32_dma2d_s * srclayer = (FAR struct stm32_dma2d_s *)src;

  gvdbg("dest=%p, destxpos=%d, destypos=%d, src=%p, srcarea=%p\n",
            dest, destxpos, destypos, src, srcarea);

  if (stm32_dma2d_lvalidatesize(destlayer, destxpos, destypos, srcarea) &&
        stm32_dma2d_lvalidatesize(srclayer, srcarea->xpos,
                                    srcarea->ypos, srcarea))
    {
      sem_wait(destlayer->lock);

      /* Set output pfc */

      ret = stm32_dma2d_loutpfc(destlayer);

      if (ret == OK)
        {
          /* Set foreground pfc */

          stm32_dma2d_lpfc(srclayer, DMA2D_LAYER_LFORE, DMA2D_BLEND_NONE);

          /* Set foreground fifo */

          stm32_dma2d_lfifo(srclayer, DMA2D_LAYER_LFORE,
                            srcarea->xpos, srcarea->ypos, srcarea);

          /* Set output fifo */

          stm32_dma2d_lfifo(destlayer, DMA2D_LAYER_LOUT,
                            destxpos, destypos, srcarea);

          /* Set number of lines and pixel per line */

          stm32_dma2d_llnr(destlayer, srcarea);

          /* Set dma2d mode for blit operation */

          if (destlayer->fmt == srclayer->fmt)
            {
              /* Blit without pfc */

              mode = STM32_DMA2D_CR_MODE_BLIT;
            }
          else
            {
              /* Blit with pfc */

              mode = STM32_DMA2D_CR_MODE_BLITPFC;
            }

          stm32_dma2d_control(mode, STM32_DMA2D_CR_MODE_CLEAR);

          /* Start DMA2D and wait until completed */

          ret = stm32_dma2d_start();

          if (ret != OK)
            {
              ret = -ECANCELED;
              gdbg("ERROR: Returning ECANCELED\n");
            }
        }

      sem_post(destlayer->lock);
    }
  else
    {
      ret = -EINVAL;
      gdbg("ERROR: Returning EINVAL\n");
    }

  return ret;
}

/****************************************************************************
 * Name: stm32_dma2dblend
 *
 * Description:
 *   Blends the selected area from a background layer with selected position
 *   of the foreground layer. Copies the result to the selected position of
 *   the destination layer. Note! The content of the foreground and background
 *   layer keeps unchanged as long destination layer is unequal to the
 *   foreground and background layer.
 *
 * Parameter:
 *   dest     - Reference to the destination layer
 *   fore     - Reference to the foreground layer
 *   forexpos - Selected x target position of the foreground layer
 *   foreypos - Selected y target position of the foreground layer
 *   back     - Reference to the background layer
 *   backarea - Reference to the selected area of the background layer
 *
 * Return:
 *    OK        - On success
 *   -EINVAL    - If one of the parameter invalid or if the size of the selected
 *                source area outside the visible area of the destination layer.
 *                (The visible area usually represents the display size)
 *   -ECANCELED - Operation cancelled, something goes wrong.
 *
 ****************************************************************************/

static int stm32_dma2dblend(FAR struct dma2d_layer_s *dest,
                            fb_coord_t destxpos, fb_coord_t destypos,
                            FAR const struct dma2d_layer_s *fore,
                            fb_coord_t forexpos, fb_coord_t foreypos,
                            FAR const struct dma2d_layer_s *back,
                            FAR const struct ltdc_area_s *backarea)
{
  int    ret;
  FAR struct stm32_dma2d_s * destlayer = (FAR struct stm32_dma2d_s *)dest;
  FAR struct stm32_dma2d_s * forelayer = (FAR struct stm32_dma2d_s *)fore;
  FAR struct stm32_dma2d_s * backlayer = (FAR struct stm32_dma2d_s *)back;

  gvdbg("dest=%p, destxpos=%d, destypos=%d, "
        "fore=%p, forexpos=%d, foreypos=%d, "
        "back=%p, backarea=%p\n",
        dest, destxpos, destypos, fore, forexpos, foreypos, back, backarea);

  if (stm32_dma2d_lvalidatesize(destlayer, destxpos, destypos, backarea) &&
        stm32_dma2d_lvalidatesize(forelayer, forexpos, foreypos, backarea) &&
            stm32_dma2d_lvalidatesize(backlayer, backarea->xpos,
                                        backarea->ypos, backarea))
    {

      sem_wait(destlayer->lock);

      /* Set output pfc */

      ret = stm32_dma2d_loutpfc(destlayer);

      if (ret == OK)
        {
          /* Set background pfc */

          stm32_dma2d_lpfc(backlayer, DMA2D_LAYER_LBACK, backlayer->blendmode);

          /* Set foreground pfc */

          stm32_dma2d_lpfc(forelayer, DMA2D_LAYER_LFORE, forelayer->blendmode);

          /* Set background fifo */

          stm32_dma2d_lfifo(backlayer, DMA2D_LAYER_LBACK,
                                backarea->xpos, backarea->ypos, backarea);

          /* Set foreground fifo */

          stm32_dma2d_lfifo(forelayer, DMA2D_LAYER_LFORE,
                                forexpos, foreypos, backarea);

          /* Set output fifo */

          stm32_dma2d_lfifo(destlayer, DMA2D_LAYER_LOUT,
                                destxpos, destypos, backarea);

          /* Set number of lines and pixel per line */

          stm32_dma2d_llnr(destlayer, backarea);

          /* Set watermark */

          /* Enable DMA2D blender */

          stm32_dma2d_control(STM32_DMA2D_CR_MODE_BLEND,
                                STM32_DMA2D_CR_MODE_CLEAR);

          /* Start DMA2D and wait until completed */

          ret = stm32_dma2d_start();

          if (ret != OK)
            {
              ret = -ECANCELED;
              gdbg("ERROR: Returning ECANCELED\n");
            }
        }

      sem_post(destlayer->lock);
    }
  else
    {
      ret = -EINVAL;
      gdbg("ERROR: Returning EINVAL\n");
    }

  return ret;
}

/****************************************************************************
 * Name: stm32_dma2dfillarea
 *
 * Description:
 *   Fill the selected area of the whole layer with a specific color.
 *
 * Parameter:
 *   layer    - Reference to the layer structure
 *   area     - Reference to the valid area structure select the area
 *   color    - Color to fill the selected area. Color must be formatted
 *              according to the layer pixel format.
 *
 * Return:
 *    OK        - On success
 *   -EINVAL    - If one of the parameter invalid or if the size of the selected
 *                area outside the visible area of the layer.
 *   -ECANCELED - Operation cancelled, something goes wrong.
 *
 ****************************************************************************/

static int stm32_dma2dfillarea(FAR struct dma2d_layer_s *layer,
                               FAR const struct ltdc_area_s *area,
                               uint32_t color)
{
  int ret;
  FAR struct stm32_dma2d_s *priv = (FAR struct stm32_dma2d_s *)layer;

  gvdbg("layer=%p, area=%p, color=%08x\n", layer, area, color);

  if (stm32_dma2d_lvalidatesize(priv, area->xpos, area->ypos, area))
    {

      sem_wait(priv->lock);

      /* Set output pfc */

      ret = stm32_dma2d_loutpfc(priv);

      if (ret == OK)
        {
          /* Set output fifo */

          stm32_dma2d_lfifo(priv, DMA2D_LAYER_LOUT,
                                area->xpos, area->ypos, area);

          /* Set the output color register */

          stm32_dma2d_lcolor(priv, DMA2D_LAYER_LOUT, color);

          /* Set number of lines and pixel per line */

          stm32_dma2d_llnr(priv, area);

          /* Set register to memory transfer */

          stm32_dma2d_control(STM32_DMA2D_CR_MODE_COLOR,
                                STM32_DMA2D_CR_MODE_CLEAR);

          /* Start DMA2D and wait until completed */

          ret = stm32_dma2d_start();

          if (ret != OK)
            {
              ret = -ECANCELED;
              gdbg("ERROR: Returning ECANCELED\n");
            }
        }

      sem_post(priv->lock);
    }
  else
    {
      ret = -EINVAL;
      gdbg("ERROR: Returning EINVAL\n");
    }

  return ret;
}

/****************************************************************************
 * Name: up_dma2dgetlayer
 *
 * Description:
 *   Get a dma2d layer structure by the layer identifier
 *
 * Parameter:
 *   lid - Layer identifier
 *
 * Return:
 *   Reference to the dma2d layer control structure on success or Null if no
 *   related exist.
 *
 ****************************************************************************/

FAR struct dma2d_layer_s * up_dma2dgetlayer(int lid)
{
  if (lid < DMA2D_LAYER_NSIZE)
    {
      FAR struct stm32_dma2d_s *priv;
      sem_wait(&g_lock);
      priv = g_layers[lid];
      sem_post(&g_lock);

      return &priv->dma2d;
    }

  gdbg("ERROR: EINVAL, Unknown layer identifier\n");
  errno = EINVAL;
  return NULL;
}

/****************************************************************************
 * Name: up_dma2dcreatelayer
 *
 * Description:
 *   Create a new dma2d layer object to interact with the dma2d controller
 *
 * Parameter:
 *   width  - Layer width
 *   height - Layer height
 *   fmt    - Pixel format of the layer
 *
 * Return:
 *   On success - A valid dma2d layer reference
 *   On error   - NULL and errno is set to
 *                -EINVAL if one of the parameter is invalid
 *                -ENOMEM if no memory available or exceeds
 *                 CONFIG_STM32_DMA2D_NLAYERS
 *
 ****************************************************************************/

FAR struct dma2d_layer_s *up_dma2dcreatelayer(fb_coord_t width,
                                              fb_coord_t height,
                                              uint8_t fmt)
{
  int        ret;
  int        lid;
  uint8_t    fmtmap;
  uint8_t    bpp = 0;
  FAR struct stm32_dma2d_s *layer = NULL;

  gvdbg("width=%d, height=%d, fmt=%02x \n", width, height, fmt);

  /* Validate if pixel format supported */

  ret = stm32_dma2d_pixelformat(fmt, &fmtmap);

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

  ret = stm32_dma2d_bpp(fmt, &bpp);

  sem_wait(&g_lock);

  /* Get a free layer identifier */

  lid = stm32_dma2d_lfreelid();

  if (lid >= 0)
    {
      layer = stm32_dma2d_lalloc();

      if (layer)
        {
          uint32_t   fblen;
          void       *fbmem;
          fb_coord_t stride;

          /* Stride calculation for the supported formats */

          stride = width * bpp / 8;

          /* Calculate buffer size */

          fblen = stride * height;

          /* Allocate 32-bit aligned memory for the layer buffer. As reported in
           * mm_memalign 8-byte alignment is guaranteed by normal malloc calls.
           * We have also ensure memory is allocated from the SRAM1/2/3 block.
           * The CCM block is only accessible through the D-BUS but not by
           * the AHB-BUS. Ensure that CONFIG_STM32_CCMEXCLUDE is set!
           */

          fbmem = kmm_zalloc(fblen);

          if (fbmem)
            {
              FAR struct fb_videoinfo_s *vinfo = &layer->vinfo;
              FAR struct fb_planeinfo_s *pinfo = &layer->pinfo;

              /* Initialize dma2d structure */

              stm32_dma2d_linit(layer, lid, fmtmap);

              /* Initialize the videoinfo structure */

              vinfo->fmt     = fmt;
              vinfo->xres    = width;
              vinfo->yres    = height;
              vinfo->nplanes = 1;

              /* Initialize the planeinfo structure */

              pinfo->fbmem  = fbmem;
              pinfo->fblen  = fblen;
              pinfo->stride = stride;
              pinfo->bpp    = bpp;

              /* Bind the layer to the identifier */

              g_layers[lid] = layer;
            }
          else
            {
              /* free the layer struture */

              kmm_free(layer);
              gdbg("ERROR: ENOMEM, Unable to allocate layer buffer\n");
              errno = ENOMEM;
            }
        }
      else
        {
          gdbg("ERROR: ENOMEM, unable to allocate layer structure\n");
          errno = ENOMEM;
        }
    }
  else
    {
      gdbg("ERROR: EINVAL, no free layer available\n");
      errno = EINVAL;
    }

  sem_post(&g_lock);
  return (FAR struct dma2d_layer_s *)layer;
}

/****************************************************************************
 * Name: up_dma2dremovelayer
 *
 * Description:
 *  Remove and deallocate the dma2d layer
 *
 * Parameter:
 *   layer  - Reference to the layer to remove
 *
 * Return:
 *   On success - OK
 *   On error   - -EINVAL
 *
 ****************************************************************************/

int up_dma2dremovelayer(FAR struct dma2d_layer_s *layer)
{
  int ret = -EINVAL;
  FAR struct stm32_dma2d_s *priv = (FAR struct stm32_dma2d_s *)layer;

  /* Check if the layer is valid and unlike a ltdc related layer */

  if (stm32_dma2d_lvalidate(priv) && priv->lid >= DMA2D_SHADOW_LAYER)
    {
      sem_wait(priv->lock);

      /* Check also if the layer id is valid to the layer reference */

      if (priv == g_layers[priv->lid])
        {
          int lid = priv->lid;

          kmm_free(priv->pinfo.fbmem);
          stm32_dma2d_lfree(priv);

          g_layers[lid] = NULL;
          ret = OK;
        }

      sem_post(priv->lock);
    }

  return ret;
}

/****************************************************************************
 * Name: up_dma2dinitialize
 *
 * Description:
 *   Initialize the dma2d controller
 *
 * Return:
 *   OK - On success
 *   An error if initializing failed.
 *
 ****************************************************************************/

int up_dma2dinitialize(void)
{
  dbg("Initialize DMA2D driver\n");

  if (g_initialized == false)
    {
      /* Abort current dma2d data transfer */

      up_dma2duninitialize();

      /* Enable dma2d is done in rcc_enableahb1, see
       * arch/arm/src/stm32/stm32f40xxx_rcc.c
       */

      /* Initialize the DMA2D semaphore that enforces mutually exclusive access
       * to the driver
       */

      sem_init(&g_lock, 0, 1);

      /* Initialize the semaphore for interrupt handling */

      sem_init(g_interrupt.sem, 0, 0);

#ifdef CONFIG_STM32_DMA2D_L8
      /* Enable dma2d transfer and clut loading interrupts only */

      stm32_dma2d_control(DMA2D_CR_TCIE | DMA2D_CR_CTCIE, DMA2D_CR_TEIE |
                          DMA2D_CR_TWIE | DMA2D_CR_CAEIE | DMA2D_CR_CEIE);
#else
      /* Enable dma transfer interrupt only */

      stm32_dma2d_control(DMA2D_CR_TCIE, DMA2D_CR_TEIE | DMA2D_CR_TWIE |
                          DMA2D_CR_CAEIE | DMA2D_CR_CTCIE | DMA2D_CR_CEIE);
#endif

      /* Attach DMA2D interrupt vector */

      (void)irq_attach(g_interrupt.irq, stm32_dma2dirq);

      /* Enable the IRQ at the NVIC */

      up_enable_irq(g_interrupt.irq);

      /* Initialize the dma2d layer for ltdc binding */

#ifdef DMA2D_SHADOW_LAYER_L1
      g_layers[DMA2D_SHADOW_LAYER_L1] =
        &g_ltdc_layer.layer[DMA2D_SHADOW_LAYER_L1].dma2ddev;
#endif
#ifdef DMA2D_SHADOW_LAYER_L2
      g_layers[DMA2D_SHADOW_LAYER_L2] =
        &g_ltdc_layer.layer[DMA2D_SHADOW_LAYER_L2].dma2ddev;
#endif
      /* Set initialized state */

      g_initialized = true;
    }

  return OK;
}

/****************************************************************************
 * Name: up_dma2duninitialize
 *
 * Description:
 *   Uninitialize the dma2d controller
 *
 ****************************************************************************/

void up_dma2duninitialize(void)
{
  /* Disable DMA2D interrupts */

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

  /* Cleanup all layers */

  stm32_dma2d_llayerscleanup();

  /* Abort current dma2d transfer */

  stm32_dma2d_control(DMA2D_CR_ABORT, 0);

  /* Set initialized state */

  g_initialized = false;
}

#ifdef CONFIG_STM32_LTDC_INTERFACE
/****************************************************************************
 * Name: stm32_dma2dinitltdc
 *
 * Description:
 *   Get a reference to the dma2d layer coupled with the ltdc layer.
 *   It not intends to use this by user space applications.
 *   It resolves the following requirements:
 *   1. Share the color lookup table
 *   2. Share the planeinfo information
 *   3. Share the videoinfo information
 *
 * Parameter:
 *   layer  - a valid reference to the low level ltdc layer structure
 *   clut   - a pointer to a valid memory region to hold 256 clut colors
 *
 * Return:
 *   On success - A valid dma2d layer reference
 *   On error   - NULL and errno is set to
 *                -EINVAL if one of the parameter is invalid
 *
 ****************************************************************************/

FAR struct dma2d_layer_s * stm32_dma2dinitltdc(FAR struct stm32_ltdc_s *layer)
{
  int        ret;
  uint8_t    fmt = 0;
  FAR struct stm32_ltdc_dma2d_s *priv;

  gvdbg("layer=%p\n", layer);
  DEBUGASSERT(layer && layer->lid >= 0 && layer->lid < DMA2D_SHADOW_LAYER);

  ret = stm32_dma2d_pixelformat(layer->vinfo.fmt, &fmt);

  if (ret != OK)
    {
      dbg("Returning -EINVAL, unsupported pixel format: %d\n",
            layer->vinfo.fmt);
      errno = -EINVAL;
      return NULL;
    }

  priv = &g_ltdc_layer.layer[layer->lid];

  stm32_dma2d_linit(&priv->dma2ddev, layer->lid, fmt);

  memcpy(&priv->dma2ddev.vinfo, &layer->vinfo, sizeof(struct fb_videoinfo_s));
  memcpy(&priv->dma2ddev.pinfo, &layer->pinfo, sizeof(struct fb_planeinfo_s));

#ifdef CONFIG_STM32_DMA2D_L8
  /* Verifies that the ltdc layer has a clut. This ensures that DMA2D driver can
   * support clut format but the LTDC driver does not and vice versa.
   */

  if (layer->vinfo.fmt == FB_FMT_RGB8)
    {
      priv->dma2ddev.clut = layer->clut;
      priv->ltdc = stm32_ltdcgetlayer(layer->lid);
      DEBUGASSERT(priv->ltdc != NULL);
    }
#endif

  return &priv->dma2ddev.dma2d;
}
#endif /* CONFIG_STM32_LTDC_INTERFACE */
