#include <touchgfx/hal/OSWrappers.hpp>
#include <touchgfx/hal/HAL.hpp>
#include <touchgfx/lcd/LCD.hpp>
#include <touchgfx/Color.hpp>
#include <cassert>
#include <ChromArtDMA.hpp>
#include "stm32l4xx_hal.h"
#include "stm32l4xx_hal_dma2d.h"
#include "string.h"

#if USEBPP==8
#include "LCD8bpp_LUT8.h"
#endif

#include "FreeRTOS.h"
#include "task.h"

volatile bool disableChromArt = false;

extern "C" {
    DMA2D_HandleTypeDef hdma2d;

    static void DMA2D_XferCpltCallback(DMA2D_HandleTypeDef* handle)
    {
        touchgfx::HAL::getInstance()->signalDMAInterrupt();
    }

    static void DMA2D_XferErrorCallback(DMA2D_HandleTypeDef* handle)
    {
        assert(0);
    }
}

static HAL_StatusTypeDef HAL_DMA2D_SetMode(DMA2D_HandleTypeDef* handle, uint32_t mode, uint32_t color, uint32_t offset)
{
    assert_param(IS_DMA2D_ALL_INSTANCE(handle->Instance));

    MODIFY_REG(handle->Instance->CR, DMA2D_CR_MODE, mode);
    MODIFY_REG(handle->Instance->OPFCCR, DMA2D_OPFCCR_CM, color);
    MODIFY_REG(handle->Instance->OOR, DMA2D_OOR_LO, offset);

    return HAL_OK;
}

ChromArtDMA::ChromArtDMA()
    : DMA_Interface(dma_queue), dma_queue(queue_storage, sizeof(queue_storage) / sizeof(queue_storage[0])), blitCaps(0)
{
    assert((HAL::FRAME_BUFFER_WIDTH < TILE_RAM_SIZE));
    TileRamLineWidth = HAL::FRAME_BUFFER_WIDTH;

    blitCaps = ( BLIT_OP_FILL
               | BLIT_OP_FILL_WITH_ALPHA
               | BLIT_OP_COPY
               | BLIT_OP_COPY_WITH_ALPHA
               | BLIT_OP_COPY_ARGB8888
               | BLIT_OP_COPY_ARGB8888_WITH_ALPHA
               | BLIT_OP_COPY_A4
               | BLIT_OP_COPY_A8);
}

ChromArtDMA::~ChromArtDMA()
{
    HAL_DMA2D_DeInit(&hdma2d);
    NVIC_DisableIRQ(DMA2D_IRQn);
}

void ChromArtDMA::initialize()
{
    hdma2d.Instance = DMA2D;
    HAL_DMA2D_DeInit(&hdma2d);
    HAL_DMA2D_Init(&hdma2d);

    hdma2d.XferCpltCallback = DMA2D_XferCpltCallback;
    hdma2d.XferErrorCallback = DMA2D_XferErrorCallback;

    if(HAL::lcd().bitDepth() == 8)
    {
#if USEBPP==8
        /* Load DMA2D Foreground CLUT */
        DMA2D_CLUTCfgTypeDef CLUTCfg;
        CLUTCfg.CLUTColorMode = DMA2D_CCM_ARGB8888;
        CLUTCfg.pCLUT = (uint32_t*)LCD8bpp_LUT8;
        CLUTCfg.Size = 255;

        HAL_DMA2D_CLUTLoad(&hdma2d, CLUTCfg, 1);

        while (HAL_DMA2D_PollForTransfer(&hdma2d, 1) == HAL_TIMEOUT)
        {
            /* wait for the end of the CLUT loading*/
            taskYIELD();
        }

        /* Load DMA2D Background CLUT */
        CLUTCfg.CLUTColorMode = DMA2D_CCM_ARGB8888;
        CLUTCfg.pCLUT = (uint32_t*)LCD8bpp_LUT8;
        CLUTCfg.Size = 255;

        HAL_DMA2D_CLUTLoad(&hdma2d, CLUTCfg, 0);

        while (HAL_DMA2D_PollForTransfer(&hdma2d, 1) == HAL_TIMEOUT)
        {
            /* wait for the end of the CLUT loading*/
            taskYIELD();
        }
#endif
        // Disable fill hw acceleration for LCD8bpp
        blitCaps &= ~(BLIT_OP_FILL | BLIT_OP_FILL_WITH_ALPHA); // turn hw fill acceleration off
    }
}

DMAType ChromArtDMA::getDMAType(void)
{
    if (disableChromArt)
    {
        return DMA_TYPE_GENERIC;
    }
    else
    {
        return DMA_TYPE_CHROMART;
    }
}

BlitOperations ChromArtDMA::getBlitCaps()
{
    if (disableChromArt)
    {
        return static_cast<BlitOperations>(0);
    }
    else
    {
        return static_cast<BlitOperations>(blitCaps);
    }
}

uint32_t ChromArtDMA::getChromARTOutputFormat(Bitmap::BitmapFormat format)
{
    uint32_t dma2dColorMode = DMA2D_OUTPUT_ARGB8888;

    switch (format)
    {
    case Bitmap::ARGB8888:
        dma2dColorMode = DMA2D_OUTPUT_ARGB8888;
        break;
    case Bitmap::RGB888:
    case Bitmap::ARGB2222:
    case Bitmap::ABGR2222:
    case Bitmap::RGBA2222:
    case Bitmap::BGRA2222:
        dma2dColorMode = DMA2D_OUTPUT_RGB888;
        break;
    case Bitmap::RGB565:
        dma2dColorMode = DMA2D_OUTPUT_RGB565;
        break;
    case Bitmap::L8:
    case Bitmap::BW:
    case Bitmap::BW_RLE:
    case Bitmap::GRAY4:
    case Bitmap::GRAY2:
    default:
        assert(0 && "Unsupported Format!");
        break;
    }

    return dma2dColorMode;
}

uint32_t ChromArtDMA::getChromARTInputFormat(Bitmap::BitmapFormat format)
{
    uint32_t dma2dColorMode = DMA2D_INPUT_ARGB8888;

    switch (format)
    {
    case Bitmap::ARGB8888:
        dma2dColorMode = DMA2D_INPUT_ARGB8888;
        break;
    case Bitmap::RGB888:
        dma2dColorMode = DMA2D_INPUT_RGB888;
        break;
    case Bitmap::RGB565:
        dma2dColorMode = DMA2D_INPUT_RGB565;
        break;
    case Bitmap::ARGB2222:
    case Bitmap::ABGR2222:
    case Bitmap::RGBA2222:
    case Bitmap::BGRA2222:
    case Bitmap::L8:
        dma2dColorMode = DMA2D_INPUT_L8;
        break;
    case Bitmap::BW:
    case Bitmap::BW_RLE:
    case Bitmap::GRAY4:
    case Bitmap::GRAY2:
    default:
        assert(0 && "Unsupported Format!");
        break;
    }

    return dma2dColorMode;
}

void ChromArtDMA::setupDummyFillOp(void)
{
    HAL_StatusTypeDef hal_status = HAL_OK;

    // Do dummy 4 bytes fill using ChromART to fire DMA2D IT
    /* Configure the DMA2D Mode, Color Mode and output offset */
    hdma2d.Init.Mode          = DMA2D_R2M      ;                           /* DMA2D mode Register to Memory */
    hdma2d.Init.ColorMode     = DMA2D_OUTPUT_ARGB8888;                     /* output format of DMA2D */
    hdma2d.Init.OutputOffset  = 0x0;                                       /* No offset in output */

    /* Foreground layer Configuration */
    hdma2d.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA;
    hdma2d.LayerCfg[1].InputAlpha = 0xFF;                                  /* Opaque color */
    hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_ARGB8888;              /* Input Color mode is ARGB8888 */
    hdma2d.LayerCfg[1].InputOffset = 0x0;                                  /* No offset in input */

    /* DMA2D Mode Configuration */
    hal_status = HAL_DMA2D_SetMode(&hdma2d, hdma2d.Init.Mode, hdma2d.Init.ColorMode, hdma2d.Init.OutputOffset);
    assert(hal_status == HAL_OK);

    /* Apply DMA2D Foreground configuration */
    HAL_DMA2D_ConfigLayer(&hdma2d, 1);

    /* Do the Dummy fill */
    HAL_DMA2D_Start_IT(&hdma2d, 0x00000000 /* color */, (unsigned int)_tile_ram, 1, 1);
}

void ChromArtDMA::convertRGB565ToARGB8888(const uint32_t* Src, uint32_t* Dst, uint32_t Pixels)
{
    HAL_StatusTypeDef hal_status = HAL_OK;

    assert((Pixels < TILE_RAM_SIZE));

    /* Configure the DMA2D Mode, Color Mode and output offset */
    hdma2d.Init.Mode          = DMA2D_M2M_PFC;                             /* DMA2D mode memory to memory with pixel format conversion transfer mode */
    hdma2d.Init.ColorMode     = DMA2D_OUTPUT_ARGB8888;                     /* output format of DMA2D */
    hdma2d.Init.OutputOffset  = 0x0;                                       /* No offset in output */

    /* Foreground layer Configuration */
    hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
    hdma2d.LayerCfg[1].InputAlpha = 0xFF;                                  /* Opaque color */
    hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;                /* Input Color mode is ARGB8888 */
    hdma2d.LayerCfg[1].InputOffset = 0x0;                                  /* No offset in input */

    /* DMA2D Mode Configuration */
    hal_status = HAL_DMA2D_SetMode(&hdma2d, hdma2d.Init.Mode, hdma2d.Init.ColorMode, hdma2d.Init.OutputOffset);
    assert(hal_status == HAL_OK);

    /* Apply DMA2D Foreground configuration */
    HAL_DMA2D_ConfigLayer(&hdma2d, 1);

    /*##- Start DMA2D transfer in interrupt mode ###########################*/
    hal_status = HAL_DMA2D_BlendingStart(&hdma2d,
                                         (uint32_t)Src,
                                         (uint32_t)Dst,
                                         (uint32_t)Dst,
                                         Pixels,
                                         1);
    assert(hal_status == HAL_OK);

    while (HAL_DMA2D_PollForTransfer(&hdma2d, 1) == HAL_TIMEOUT)
    {
        /* wait for Transfer complete*/
        taskYIELD();
    }
}

void ChromArtDMA::convertRGB565ToRGB888(const uint32_t* Src, uint32_t* Dst, uint32_t Pixels)
{
    HAL_StatusTypeDef hal_status = HAL_OK;

    assert((Pixels < TILE_RAM_SIZE));

    /* Configure the DMA2D Mode, Color Mode and output offset */
    hdma2d.Init.Mode          = DMA2D_M2M_PFC;                             /* DMA2D mode memory to memory with pixel format conversion transfer mode */
    hdma2d.Init.ColorMode     = DMA2D_OUTPUT_RGB888;                       /* output format of DMA2D */
    hdma2d.Init.OutputOffset  = 0x0;                                       /* No offset in output */

    /* Foreground layer Configuration */
    hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
    hdma2d.LayerCfg[1].InputAlpha = 0xFF;                                  /* Opaque color */
    hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;                /* Input Color mode is ARGB8888 */
    hdma2d.LayerCfg[1].InputOffset = 0x0;                                  /* No offset in input */

    /* DMA2D Mode Configuration */
    hal_status = HAL_DMA2D_SetMode(&hdma2d, hdma2d.Init.Mode, hdma2d.Init.ColorMode, hdma2d.Init.OutputOffset);
    assert(hal_status == HAL_OK);

    /* Apply DMA2D Foreground configuration */
    HAL_DMA2D_ConfigLayer(&hdma2d, 1);

    /*##- Start DMA2D transfer in interrupt mode ###########################*/
    hal_status = HAL_DMA2D_BlendingStart(&hdma2d,
                                         (uint32_t)Src,
                                         (uint32_t)Dst,
                                         (uint32_t)Dst,
                                         Pixels,
                                         1);
    assert(hal_status == HAL_OK);

    while (HAL_DMA2D_PollForTransfer(&hdma2d, 1) == HAL_TIMEOUT)
    {
        /* wait for Transfer complete*/
        taskYIELD();
    }
}

void ChromArtDMA::convertARGB8888ToLCD8bpp(const uint32_t* Src, uint8_t* Dst, uint32_t Pixels)
{
    // The alpha channel is considered
    switch (HAL::lcd().framebufferFormat())
    {
    case Bitmap::ARGB2222:
        {
            for (uint32_t i = 0; i < Pixels; i++)
            {
                uint32_t pix32 = *Src++;
                uint8_t pix8 = (pix32 >> 30) & 0x3; // = highest bit alpha in ARGB
                pix8 = pix8 << 2;
                pix8 = (pix32 >> 22) & 0x3; // = highest bit red in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 14) & 0x3; // = highest bit blue in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 6) & 0x3; // = highest bit green in ARGB

                *Dst++ = pix8;
            }
        }
        break;
    case Bitmap::ABGR2222:
        {
            for (uint32_t i = 0; i < Pixels; i++)
            {
                uint32_t pix32 = *Src++;
                uint8_t pix8 = (pix32 >> 30) & 0x3; // = highest bit alpha in ARGB
                pix8 = pix8 << 2;
                pix8 = (pix32 >> 14) & 0x3; // = highest bit blue in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 6) & 0x3; // = highest bit green in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 22) & 0x3; // = highest bit red in ARGB

                *Dst++ = pix8;
            }
        }
        break;
    case Bitmap::BGRA2222:
        {
            for (uint32_t i = 0; i < Pixels; i++)
            {
                uint32_t pix32 = *Src++;
                uint8_t pix8 = (pix32 >> 14) & 0x3; // = highest bit blue in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 6) & 0x3; // = highest bit green in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 22) & 0x3; // = highest bit red in ARGB
                pix8 = pix8 << 2;
                pix8 |= pix32 & 0x3; // = 0x3  opaque alpha

                *Dst++ = pix8;
            }
        }
        break;
    case Bitmap::RGBA2222:
        {
            for (uint32_t i = 0; i < Pixels; i++)
            {
                uint32_t pix32 = *Src++;
                uint8_t pix8 = (pix32 >> 22) & 0x3; // = highest bit red in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 14) & 0x3; // = highest bit blue in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 6) & 0x3; // = highest bit green in ARGB
                pix8 = pix8 << 2;
                pix8 |= pix32 & 0x3; // = 0x3  opaque alpha

                *Dst++ = pix8;
            }
        }
        break;
    default:
        assert(0 && "Unsupported Format!");
        break;
    }
}

void ChromArtDMA::convertARGB8888ToLCD8bpp_NoAlpha(const uint32_t* Src, uint8_t* Dst, uint32_t Pixels)
{
    // The alpha channel is being ignored (for LCD8bpp)
    switch (HAL::lcd().framebufferFormat())
    {
    case Bitmap::ARGB2222:
        {
            for (uint32_t i = 0; i < Pixels; i++)
            {
                uint32_t pix32 = *Src++;
                uint8_t pix8 = (pix32 >> 22) & 0x3; // = highest bit red in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 14) & 0x3; // = highest bit blue in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 6) & 0x3; // = highest bit green in ARGB

                *Dst++ = pix8;
            }
        }
        break;
    case Bitmap::ABGR2222:
        {
            for (uint32_t i = 0; i < Pixels; i++)
            {
                uint32_t pix32 = *Src++;
                uint8_t pix8 = (pix32 >> 14) & 0x3; // = highest bit blue in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 6) & 0x3; // = highest bit green in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 22) & 0x3; // = highest bit red in ARGB

                *Dst++ = pix8;
            }
        }
        break;
    case Bitmap::BGRA2222:
        {
            for (uint32_t i = 0; i < Pixels; i++)
            {
                uint32_t pix32 = *Src++;
                uint8_t pix8 = (pix32 >> 14) & 0x3; // = highest bit blue in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 6) & 0x3; // = highest bit green in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 22) & 0x3; // = highest bit red in ARGB
                pix8 = pix8 << 2;
                pix8 |= 0x3; // = 0x3  opaque alpha

                *Dst++ = pix8;
            }
        }
        break;
    case Bitmap::RGBA2222:
        {
            for (uint32_t i = 0; i < Pixels; i++)
            {
                uint32_t pix32 = *Src++;
                uint8_t pix8 = (pix32 >> 22) & 0x3; // = highest bit red in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 14) & 0x3; // = highest bit blue in ARGB
                pix8 = pix8 << 2;
                pix8 |= (pix32 >> 6) & 0x3; // = highest bit green in ARGB
                pix8 = pix8 << 2;
                pix8 |= 0x3; // = 0x3  opaque alpha

                *Dst++ = pix8;
            }
        }
        break;
    default:
        assert(0 && "Unsupported Format!");
        break;
    }
}

void ChromArtDMA::blitBlend_8Bits(const BlitOp& blitOp)
{
    uint32_t dma2dColorMode = getChromARTInputFormat(static_cast<Bitmap::BitmapFormat>(blitOp.dstFormat)); // only destination format

    uint8_t* Src         = (uint8_t*)blitOp.pSrc;  // The source memory Buffer address for the foreground layer
    uint8_t* Dst         = (uint8_t*)blitOp.pDst;  // The destination memory Buffer address for the background layer

    /*##- Configure DMA2D : Configure foreground and background
        To display blended ARGB2222 (Source) and ARGB2222 (Framebuffer)
        64 color images ##*/
    HAL_StatusTypeDef hal_status = HAL_OK;

    /* Configure the DMA2D Mode, Color Mode and output offset */
    hdma2d.Init.Mode          = DMA2D_M2M_BLEND;                           /* DMA2D mode Memory to Memory with Blending */
    hdma2d.Init.ColorMode     = DMA2D_OUTPUT_ARGB8888;                     /* output format of DMA2D */
    hdma2d.Init.OutputOffset  = 0x0;                                       /* No offset in output */

    /* Foreground layer Configuration */
    if (blitOp.alpha < 255)
    {
        hdma2d.LayerCfg[1].AlphaMode = DMA2D_COMBINE_ALPHA; // Blending with Global Alpha value
        hdma2d.LayerCfg[1].InputAlpha = blitOp.alpha;
    }
    else
    {
        hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA; // Blending with Per Pixel Alpha value
    }
    hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_L8;                    /* Input Color mode is L8 : indexed 256 color image */
    hdma2d.LayerCfg[1].InputOffset = blitOp.srcLoopStride - blitOp.nSteps; /* No offset in input */

    /* Background layer Configuration */
    hdma2d.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA; // Use 'DMA2D_NO_MODIF_ALPHA' in case background has transparent pixels
    hdma2d.LayerCfg[0].InputAlpha = 0xFF;                                  /* Fully opaque */
    hdma2d.LayerCfg[0].InputColorMode = DMA2D_INPUT_L8;                    /* Input Color mode is L8 : indexed 256 color image */
    hdma2d.LayerCfg[0].InputOffset = blitOp.dstLoopStride - blitOp.nSteps; /* No offset in input */

    /* DMA2D Mode Configuration */
    hal_status = HAL_DMA2D_SetMode(&hdma2d, hdma2d.Init.Mode, hdma2d.Init.ColorMode, hdma2d.Init.OutputOffset);
    assert(hal_status == HAL_OK);

    /* Apply DMA2D Foreground configuration */
    HAL_DMA2D_ConfigLayer(&hdma2d, 1);

    /* Apply DMA2D Background configuration */
    HAL_DMA2D_ConfigLayer(&hdma2d, 0);

    /* DMA2D Foreground and Background CLUTs should be already loaded! */
    uint32_t alignedTileSize = blitOp.nSteps; // Assuming Tile RAM width is bigger than the source width
    uint32_t nbLines = blitOp.nLoops;

    for (uint32_t line = 0; line < nbLines; line++)
    {
        /*##- Start DMA2D transfer in interrupt mode ###########################*/
        hal_status = HAL_DMA2D_BlendingStart(&hdma2d,
                                             (uint32_t)Src,
                                             (uint32_t)Dst,
                                             (uint32_t)_tile_ram,
                                             blitOp.nSteps,
                                             (blitOp.nLoops) / nbLines);
        assert(hal_status == HAL_OK);

        while (HAL_DMA2D_PollForTransfer(&hdma2d, 1) == HAL_TIMEOUT)
        {
            /* wait for Transfer complete*/
            taskYIELD();
        }

        convertARGB8888ToLCD8bpp_NoAlpha(_tile_ram, Dst, alignedTileSize);

        Src += (blitOp.srcLoopStride * blitOp.nLoops) / nbLines;
        Dst += (blitOp.dstLoopStride * blitOp.nLoops) / nbLines;
    }

    /* Dummy fill ops to rise DMA interrunpt */
    setupDummyFillOp();
}

/* This is not yet working ! */
void ChromArtDMA::textBlend_8Bits(const BlitOp& blitOp)
{
    uint32_t dma2dColorMode = (blitOp.operation == BLIT_OP_COPY_A4) ? DMA2D_INPUT_A4 : DMA2D_INPUT_A8;

    uint8_t* Src         = (uint8_t*)blitOp.pSrc;  // The source memory Buffer address for the foreground layer
    uint8_t* Dst         = (uint8_t*)blitOp.pDst;  // The destination memory Buffer address for the background layer

    uint32_t color = 0;

    uint32_t red = touchgfx::Color::getRedColor(blitOp.color);
    uint32_t green = touchgfx::Color::getGreenColor(blitOp.color);
    uint32_t blue = touchgfx::Color::getBlueColor(blitOp.color);
    uint32_t alpha = blitOp.alpha;
    color = (alpha << 24) | (red << 16) | (green << 8) | blue;

    /*##- Configure DMA2D : Configure foreground and background
        To display blended ARGB2222 (Source) and ARGB2222 (Framebuffer)
        64 color images ##*/
    HAL_StatusTypeDef hal_status = HAL_OK;

    /* Configure the DMA2D Mode, Color Mode and output offset */
    hdma2d.Init.Mode          = DMA2D_M2M_BLEND;                           /* DMA2D mode Memory to Memory with Blending */
    hdma2d.Init.ColorMode     = DMA2D_OUTPUT_ARGB8888;                     /* output format of DMA2D */
    hdma2d.Init.OutputOffset  = 0x0;                                       /* No offset in output */

    /* Foreground layer Configuration */
    if (blitOp.alpha < 255)
    {
        hdma2d.LayerCfg[1].AlphaMode = DMA2D_COMBINE_ALPHA; // Blending with Global Alpha value
    }
    else
    {
        hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA; // Blending with Per Pixel Alpha value
    }
    hdma2d.LayerCfg[1].InputAlpha = color;
    hdma2d.LayerCfg[1].InputColorMode = dma2dColorMode;                    /* Input Color mode is L8 : indexed 256 color image */
    hdma2d.LayerCfg[1].InputOffset = blitOp.srcLoopStride - blitOp.nSteps; /* No offset in input */

    /* Background layer Configuration */
    hdma2d.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA; // Use 'DMA2D_NO_MODIF_ALPHA' in case background has transparent pixels
    hdma2d.LayerCfg[0].InputAlpha = 0xFF;                                  /* Fully opaque */
    hdma2d.LayerCfg[0].InputColorMode = DMA2D_INPUT_L8;                    /* Input Color mode is L8 : indexed 256 color image */
    hdma2d.LayerCfg[0].InputOffset = blitOp.dstLoopStride - blitOp.nSteps; /* No offset in input */

    /* DMA2D Mode Configuration */
    hal_status = HAL_DMA2D_SetMode(&hdma2d, hdma2d.Init.Mode, hdma2d.Init.ColorMode, hdma2d.Init.OutputOffset);
    assert(hal_status == HAL_OK);

    /* Apply DMA2D Foreground configuration */
    HAL_DMA2D_ConfigLayer(&hdma2d, 1);

    /* Apply DMA2D Background configuration */
    HAL_DMA2D_ConfigLayer(&hdma2d, 0);

    /* DMA2D Foreground and Background CLUTs should be already loaded! */
    uint32_t stride = (blitOp.operation == BLIT_OP_COPY_A4) ? (blitOp.srcLoopStride >> 1) : blitOp.srcLoopStride; // in bytes

    /* DMA2D Foreground and Background CLUTs should be already loaded! */
    uint32_t alignedTileSize = blitOp.nSteps; // Assuming Tile RAM width is bigger than the source width
    uint32_t nbLines = blitOp.nLoops;

    for (uint32_t line = 0; line < nbLines; line++)
    {
        /*##- Start DMA2D transfer in interrupt mode ###########################*/
        hal_status = HAL_DMA2D_BlendingStart(&hdma2d,
                                             (uint32_t)Src,
                                             (uint32_t)Dst,
                                             (uint32_t)_tile_ram,
                                             blitOp.nSteps,
                                             (blitOp.nLoops) / nbLines);
        assert(hal_status == HAL_OK);

        while (HAL_DMA2D_PollForTransfer(&hdma2d, 1) == HAL_TIMEOUT)
        {
            /* wait for Transfer complete*/
            taskYIELD();
        }

        convertARGB8888ToLCD8bpp_NoAlpha(_tile_ram, Dst, alignedTileSize);

        Src += (stride * blitOp.nLoops) / nbLines;
        Dst += (blitOp.dstLoopStride * blitOp.nLoops) / nbLines;
    }

    /* Dummy fill ops to rise DMA interrunpt */
    setupDummyFillOp();
}

void ChromArtDMA::fillBlend_8Bits(const BlitOp& blitOp)
{
    uint32_t dma2dColorMode = getChromARTInputFormat(static_cast<Bitmap::BitmapFormat>(blitOp.dstFormat)); // only destination format;
    uint32_t color = 0;

    uint32_t red = touchgfx::Color::getRedColor(blitOp.color);
    uint32_t green = touchgfx::Color::getGreenColor(blitOp.color);
    uint32_t blue = touchgfx::Color::getBlueColor(blitOp.color);
    uint32_t alpha = blitOp.alpha;
    color = (alpha << 24) | (red << 16) | (green << 8) | blue;

    uint8_t* Dst         = (uint8_t*)blitOp.pDst;  // The destination memory Buffer address for the background layer

    /*##- Configure DMA2D : Configure foreground and background
        To display blended ARGB2222 (Color) and ARGB2222 (Framebuffer)
        64 color images ##*/
    HAL_StatusTypeDef hal_status = HAL_OK;

    /* Configure the DMA2D Mode, Color Mode and output offset */
    hdma2d.Init.Mode          = DMA2D_M2M_BLEND;                           /* DMA2D mode Memory to Memory with Blending */
    hdma2d.Init.ColorMode     = DMA2D_OUTPUT_ARGB8888;                     /* output format of DMA2D */
    hdma2d.Init.OutputOffset  = 0x0;                                       /* No offset in output */

    /* Foreground layer Configuration */
    hdma2d.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA; // Blending with Color value
    hdma2d.LayerCfg[1].InputAlpha = color;
    hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_A8;                    /* Input Color mode is A8 : indexed 256 alpha image */
    hdma2d.LayerCfg[1].InputOffset = blitOp.dstLoopStride - blitOp.nSteps; /* No offset in input */

    /* Background layer Configuration */
    hdma2d.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA; // Use 'DMA2D_NO_MODIF_ALPHA' in case background has transparent pixels
    hdma2d.LayerCfg[0].InputAlpha = 0xFF;                                  /* Fully opaque */
    hdma2d.LayerCfg[0].InputColorMode = DMA2D_INPUT_L8;                    /* Input Color mode is A8 : indexed 256 color image */
    hdma2d.LayerCfg[0].InputOffset = blitOp.dstLoopStride - blitOp.nSteps; /* No offset in input */

    /* DMA2D Mode Configuration */
    hal_status = HAL_DMA2D_SetMode(&hdma2d, hdma2d.Init.Mode, hdma2d.Init.ColorMode, hdma2d.Init.OutputOffset);
    assert(hal_status == HAL_OK);

    /* Apply DMA2D Foreground configuration */
    HAL_DMA2D_ConfigLayer(&hdma2d, 1);

    /* Apply DMA2D Background configuration */
    HAL_DMA2D_ConfigLayer(&hdma2d, 0);

    /* DMA2D Foreground and Background CLUTs should be already loaded! */
    uint32_t alignedTileSize = blitOp.nSteps; // Assuming Tile RAM width is bigger than the source width
    uint32_t nbLines = blitOp.nLoops;

    for (uint32_t line = 0; line < nbLines; line++) // draw line by line
    {
        /*##- Start DMA2D transfer in pooling mode #############################*/
        hal_status = HAL_DMA2D_BlendingStart(&hdma2d,
                                             (uint32_t)Dst,
                                             (uint32_t)Dst,
                                             (uint32_t)_tile_ram,
                                             blitOp.nSteps,
                                             blitOp.nLoops / nbLines);
        assert(hal_status == HAL_OK);

        while (HAL_DMA2D_PollForTransfer(&hdma2d, 1) == HAL_TIMEOUT)
        {
            /* wait for Transfer complete*/
            taskYIELD();
        }

        convertARGB8888ToLCD8bpp_NoAlpha(_tile_ram, Dst, alignedTileSize);

        Dst  += (blitOp.dstLoopStride * blitOp.nLoops) / nbLines;
    }

    /* Dummy fill ops to rise DMA interrunpt */
    setupDummyFillOp();
}

void ChromArtDMA::setupDataCopyL8(const BlitOp& blitOp, bool blendingImage)
{
    DMA2D_CLUTCfgTypeDef CLUTCfg;

    /* Extract CLUT type and size */
    clutData_t *clutData = (clutData_t *)blitOp.pClut;
    Bitmap::ClutFormat clutFormat = static_cast<Bitmap::ClutFormat>(clutData->format);
    uint16_t clutSize = clutData->size;
    uint32_t* pClut = clutData->data;  // The source memory Buffer address for the foreground layer

    if (clutFormat == Bitmap::CLUT_FORMAT_L8_RGB565)
    {
        // Convert CLUT using DMA2D
        // Select either RGB888 or ARGB8888 depending on the framebuffer format.
        switch (HAL::lcd().framebufferFormat())
        {
        case Bitmap::ARGB8888:
        case Bitmap::RGB565:
            convertRGB565ToARGB8888(clutData->data, _tile_ram, clutData->size);
            clutFormat = Bitmap::CLUT_FORMAT_L8_ARGB8888;
            break;
        case Bitmap::RGB888:
            convertRGB565ToRGB888(clutData->data, _tile_ram, clutData->size);
            clutFormat = Bitmap::CLUT_FORMAT_L8_RGB888;
            break;
        case Bitmap::ARGB2222:
        case Bitmap::ABGR2222:
        case Bitmap::RGBA2222:
        case Bitmap::BGRA2222:
        case Bitmap::L8:
        case Bitmap::BW:
        case Bitmap::BW_RLE:
        case Bitmap::GRAY4:
        case Bitmap::GRAY2:
        default:
            assert(0 && "Unsupported Format!");
            break;
        }
        pClut = _tile_ram;
    }

    /* Load DMA2D Foreground CLUT */
    CLUTCfg.CLUTColorMode = (clutFormat == Bitmap::CLUT_FORMAT_L8_RGB888) ? DMA2D_CCM_RGB888 : DMA2D_CCM_ARGB8888;
    CLUTCfg.pCLUT = (uint32_t*)pClut;
    CLUTCfg.Size = clutSize - 1;

    HAL_DMA2D_CLUTLoad(&hdma2d, CLUTCfg, 1);
    while (HAL_DMA2D_PollForTransfer(&hdma2d, 1) == HAL_TIMEOUT)
    {
        /* wait for the end of the CLUT loading*/
        taskYIELD();
    }
}

void ChromArtDMA::setupDataCopy(const BlitOp& blitOp)
{
    uint32_t dma2dTransferMode    = DMA2D_M2M_BLEND;

    uint32_t dma2dForegroundColorMode  = getChromARTInputFormat(static_cast<Bitmap::BitmapFormat>(blitOp.srcFormat));
    uint32_t dma2dBackgroundColorMode  = getChromARTInputFormat(static_cast<Bitmap::BitmapFormat>(blitOp.dstFormat));
    uint32_t dma2dOutputColorMode      = getChromARTOutputFormat(static_cast<Bitmap::BitmapFormat>(blitOp.dstFormat));

    bool blendingImage = (blitOp.operation == BLIT_OP_COPY_ARGB8888
                          || blitOp.operation == BLIT_OP_COPY_ARGB8888_WITH_ALPHA
                          || blitOp.operation == BLIT_OP_COPY_WITH_ALPHA);

    bool blendingText = (blitOp.operation == BLIT_OP_COPY_A4
                         || blitOp.operation == BLIT_OP_COPY_A8);

    bool isIndexedImage = (blitOp.srcFormat == static_cast<uint32_t>(Bitmap::L8));

    if (dma2dForegroundColorMode == DMA2D_INPUT_L8)
    {
        if (isIndexedImage)
        {
            setupDataCopyL8(blitOp, blendingImage);
        }
        else if (blendingImage)
        {
            blitBlend_8Bits(blitOp);
            return;
        }
        else if (blendingText)
        {
            textBlend_8Bits(blitOp);
            return;
        }
    }

    switch (blitOp.operation)
    {
    case BLIT_OP_COPY_A4:
        dma2dForegroundColorMode = DMA2D_INPUT_A4;   // update dma2dForegroundColorMode value and use A4 format for Text
        break;
    case BLIT_OP_COPY_A8:
        dma2dForegroundColorMode = DMA2D_INPUT_A8;   // update dma2dForegroundColorMode value and use A8 format for Text
        break;
    case BLIT_OP_COPY_WITH_ALPHA:
    case BLIT_OP_COPY_ARGB8888:
    case BLIT_OP_COPY_ARGB8888_WITH_ALPHA:
        break;
    default:
        dma2dTransferMode = DMA2D_M2M;          // update dma2dTransferMode value and disable blending
        break;
    }

    if (dma2dForegroundColorMode != dma2dBackgroundColorMode && !blendingImage && !blendingText)
    {
        dma2dTransferMode = DMA2D_M2M_PFC;
    }

    /* HAL_DMA2D_ConfigLayer() depends on hdma2d.Init */
    hdma2d.Init.Mode = dma2dTransferMode;
    hdma2d.Init.ColorMode = dma2dOutputColorMode;
    hdma2d.Init.OutputOffset = blitOp.dstLoopStride - blitOp.nSteps;

    HAL_DMA2D_SetMode(&hdma2d, hdma2d.Init.Mode, hdma2d.Init.ColorMode, hdma2d.Init.OutputOffset);

    hdma2d.LayerCfg[1].InputColorMode = dma2dForegroundColorMode;
    hdma2d.LayerCfg[1].InputOffset = blitOp.srcLoopStride - blitOp.nSteps;

    if (blendingImage || blendingText)
    {
        if (blitOp.alpha < 255)
        {
            hdma2d.LayerCfg[1].AlphaMode = DMA2D_COMBINE_ALPHA;
            hdma2d.LayerCfg[1].InputAlpha = blitOp.alpha;
        }
        else
        {
            hdma2d.LayerCfg[1].AlphaMode = DMA2D_NO_MODIF_ALPHA;
        }

        if (blendingText)
        {
            if (blitOp.dstFormat == Bitmap::RGB565)
            {
                uint32_t red = (((blitOp.color & 0xF800) >> 11) * 255) / 31;
                uint32_t green = (((blitOp.color & 0x7E0) >> 5) * 255) / 63;
                uint32_t blue = (((blitOp.color & 0x1F)) * 255) / 31;
                uint32_t alpha = blitOp.alpha;
                hdma2d.LayerCfg[1].InputAlpha = (alpha << 24) | (red << 16) | (green << 8) | blue;
            }
            else
            {
                hdma2d.LayerCfg[1].InputAlpha = blitOp.color.getColor32() | (blitOp.alpha << 24);
            }
        }

        hdma2d.LayerCfg[0].InputOffset = blitOp.dstLoopStride - blitOp.nSteps;
        hdma2d.LayerCfg[0].InputColorMode = dma2dBackgroundColorMode;

        HAL_DMA2D_ConfigLayer(&hdma2d, 0);
    }

    HAL_DMA2D_ConfigLayer(&hdma2d, 1);

    if (blendingImage || blendingText)
    {
        HAL_DMA2D_BlendingStart_IT(&hdma2d,
                                   (unsigned int)blitOp.pSrc,
                                   (unsigned int)blitOp.pDst,
                                   (unsigned int)blitOp.pDst,
                                   blitOp.nSteps, blitOp.nLoops);
    }
    else
    {
        HAL_DMA2D_Start_IT(&hdma2d,
                           (unsigned int)blitOp.pSrc,
                           (unsigned int)blitOp.pDst,
                           blitOp.nSteps, blitOp.nLoops);
    }
}

void ChromArtDMA::setupDataFill(const BlitOp& blitOp)
{
    uint32_t dma2dTransferMode    = DMA2D_R2M; // solid fill
    uint32_t dma2dOutputColorMode = getChromARTInputFormat(static_cast<Bitmap::BitmapFormat>(blitOp.dstFormat));
    uint32_t color = 0;

    if ((dma2dOutputColorMode == DMA2D_INPUT_L8) /*&& (blitOp.operation == BLIT_OP_FILL_WITH_ALPHA)*/)
    {
        fillBlend_8Bits(blitOp);
        return;
    }

    if (blitOp.dstFormat == Bitmap::RGB565)
    {
        uint32_t red = (((blitOp.color & 0xF800) >> 11) * 255) / 31;
        uint32_t green = (((blitOp.color & 0x7E0) >> 5) * 255) / 63;
        uint32_t blue = (((blitOp.color & 0x1F)) * 255) / 31;
        uint32_t alpha = blitOp.alpha;
        color = (alpha << 24) | (red << 16) | (green << 8) | blue;
    }
    else
    {
        color = (blitOp.alpha << 24) | blitOp.color.getColor32();
    }

    if (blitOp.operation == BLIT_OP_FILL_WITH_ALPHA)
    {
        dma2dTransferMode = DMA2D_M2M_BLEND;
    }

    hdma2d.Init.Mode = dma2dTransferMode;
    hdma2d.Init.ColorMode = dma2dOutputColorMode;
    hdma2d.Init.OutputOffset = blitOp.dstLoopStride - blitOp.nSteps;

    HAL_DMA2D_SetMode(&hdma2d, hdma2d.Init.Mode, hdma2d.Init.ColorMode, hdma2d.Init.OutputOffset);

    if (dma2dTransferMode == DMA2D_M2M_BLEND)
    {
        hdma2d.LayerCfg[1].AlphaMode = DMA2D_REPLACE_ALPHA;
        hdma2d.LayerCfg[1].InputAlpha = color;
        hdma2d.LayerCfg[1].InputColorMode = DMA2D_INPUT_A8;

        hdma2d.LayerCfg[0].InputOffset = blitOp.dstLoopStride - blitOp.nSteps;
        hdma2d.LayerCfg[0].InputColorMode = dma2dOutputColorMode;
        HAL_DMA2D_ConfigLayer(&hdma2d, 0);
    }
    else
    {
        hdma2d.LayerCfg[1].InputColorMode = dma2dOutputColorMode;
        hdma2d.LayerCfg[1].InputOffset = 0;
    }

    HAL_DMA2D_ConfigLayer(&hdma2d, 1);

    if (dma2dTransferMode == DMA2D_M2M_BLEND)
    {
        HAL_DMA2D_BlendingStart_IT(&hdma2d,
                                   (unsigned int)blitOp.pDst,
                                   (unsigned int)blitOp.pDst,
                                   (unsigned int)blitOp.pDst,
                                   blitOp.nSteps, blitOp.nLoops);
    }
    else
    {
        HAL_DMA2D_Start_IT(&hdma2d, color, (unsigned int)blitOp.pDst,
                           blitOp.nSteps, blitOp.nLoops);
    }
}

void ChromArtDMA::CleanInvalidateCache()
{
#ifdef CHROMART_CACHE_ENABLED
    SCB_CleanDCache();
#endif
}

void ChromArtDMA::start()
{
    CleanInvalidateCache();
    DMA_Interface::start();
}
