/*
 * This file is part of the Trezor project, https://trezor.io/
 *
 * Copyright (c) SatoshiLabs
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

#include "dma2d.h"
#include "colors.h"
#include STM32_HAL_H

typedef enum {
  DMA2D_LAYER_FG = 1,
  DMA2D_LAYER_BG = 0,
} dma2d_layer_t;

static DMA2D_HandleTypeDef dma2d_handle = {0};

void dma2d_init(void) {
  __HAL_RCC_DMA2D_CLK_ENABLE();

  dma2d_handle.Instance = (DMA2D_TypeDef*)DMA2D_BASE;
  dma2d_handle.Init.ColorMode = DMA2D_OUTPUT_RGB565;
  dma2d_handle.Init.OutputOffset = 0;
}

static void dma2d_init_clut(uint16_t fg, uint16_t bg, dma2d_layer_t layer) {
  volatile uint32_t* table = NULL;
  if (layer == DMA2D_LAYER_BG) {
    table = dma2d_handle.Instance->BGCLUT;
  } else {
    table = dma2d_handle.Instance->FGCLUT;
  }

  uint32_t fg32 = rgb565_to_rgb888(fg);
  uint32_t bg32 = rgb565_to_rgb888(bg);

  for (uint8_t i = 0; i < 16; i++) {
    table[i] = interpolate_rgb888_color(fg32, bg32, i);
  }

  DMA2D_CLUTCfgTypeDef clut;
  clut.CLUTColorMode = DMA2D_CCM_ARGB8888;
  clut.Size = 0xf;
  clut.pCLUT = 0;  // loading directly

  HAL_DMA2D_ConfigCLUT(&dma2d_handle, clut, layer);
}

void dma2d_setup_const(void) {
  dma2d_handle.Init.Mode = DMA2D_R2M;
  HAL_DMA2D_Init(&dma2d_handle);
}

void dma2d_setup_4bpp(uint16_t fg_color, uint16_t bg_color) {
  dma2d_handle.Init.Mode = DMA2D_M2M_PFC;
  dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_L4;
  dma2d_handle.LayerCfg[1].InputOffset = 0;
  dma2d_handle.LayerCfg[1].AlphaMode = 0;
  dma2d_handle.LayerCfg[1].InputAlpha = 0;

  dma2d_init_clut(fg_color, bg_color, DMA2D_LAYER_FG);

  HAL_DMA2D_Init(&dma2d_handle);
  HAL_DMA2D_ConfigLayer(&dma2d_handle, 1);
}

void dma2d_setup_16bpp(void) {
  dma2d_handle.Init.Mode = DMA2D_M2M_PFC;
  dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_RGB565;
  dma2d_handle.LayerCfg[1].InputOffset = 0;
  dma2d_handle.LayerCfg[1].AlphaMode = 0;
  dma2d_handle.LayerCfg[1].InputAlpha = 0;

  HAL_DMA2D_Init(&dma2d_handle);
  HAL_DMA2D_ConfigLayer(&dma2d_handle, 1);
}

void dma2d_setup_4bpp_over_16bpp(uint16_t overlay_color) {
  dma2d_handle.Init.Mode = DMA2D_M2M_BLEND;
  dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A4;
  dma2d_handle.LayerCfg[1].InputOffset = 0;
  dma2d_handle.LayerCfg[1].AlphaMode = 0;
  dma2d_handle.LayerCfg[1].InputAlpha =
      0xFF000000 | rgb565_to_rgb888(overlay_color);

  dma2d_handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_RGB565;
  dma2d_handle.LayerCfg[0].InputOffset = 0;
  dma2d_handle.LayerCfg[0].AlphaMode = 0;
  dma2d_handle.LayerCfg[0].InputAlpha = 0;

  HAL_DMA2D_Init(&dma2d_handle);
  HAL_DMA2D_ConfigLayer(&dma2d_handle, 1);
  HAL_DMA2D_ConfigLayer(&dma2d_handle, 0);
}

void dma2d_setup_4bpp_over_4bpp(uint16_t fg_color, uint16_t bg_color,
                                uint16_t overlay_color) {
  dma2d_handle.Init.Mode = DMA2D_M2M_BLEND;
  dma2d_handle.LayerCfg[1].InputColorMode = DMA2D_INPUT_A4;
  dma2d_handle.LayerCfg[1].InputOffset = 0;
  dma2d_handle.LayerCfg[1].AlphaMode = 0;
  dma2d_handle.LayerCfg[1].InputAlpha = rgb565_to_rgb888(overlay_color);

  dma2d_handle.LayerCfg[0].InputColorMode = DMA2D_INPUT_L4;
  dma2d_handle.LayerCfg[0].InputOffset = 0;
  dma2d_handle.LayerCfg[0].AlphaMode = DMA2D_REPLACE_ALPHA;
  dma2d_handle.LayerCfg[0].InputAlpha = 0xFF;

  dma2d_init_clut(fg_color, bg_color, DMA2D_LAYER_BG);

  HAL_DMA2D_Init(&dma2d_handle);
  HAL_DMA2D_ConfigLayer(&dma2d_handle, 1);
  HAL_DMA2D_ConfigLayer(&dma2d_handle, 0);
}

void dma2d_start(uint8_t* in_addr, uint8_t* out_addr, int32_t pixels) {
  HAL_DMA2D_Start(&dma2d_handle, (uint32_t)in_addr, (uint32_t)out_addr, pixels,
                  1);
}

void dma2d_start_const(uint16_t color, uint8_t* out_addr, int32_t pixels) {
  HAL_DMA2D_Start(&dma2d_handle, rgb565_to_rgb888(color), (uint32_t)out_addr,
                  pixels, 1);
}

void dma2d_start_blend(uint8_t* overlay_addr, uint8_t* bg_addr,
                       uint8_t* out_addr, int32_t pixels) {
  HAL_DMA2D_BlendingStart(&dma2d_handle, (uint32_t)overlay_addr,
                          (uint32_t)bg_addr, (uint32_t)out_addr, pixels, 1);
}

void dma2d_wait_for_transfer(void) {
  while (HAL_DMA2D_PollForTransfer(&dma2d_handle, 10) != HAL_OK)
    ;
}