/*
 * Copyright (C) 2023 Daniele Lacamera <root@danielinux.net>
 *
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU Lesser 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 Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
 */

/* Includes ------------------------------------------------------------------*/
#include <string.h>
#include "malloc.h"
#include "ui.h"
#include <unicore-mx/stm32/rcc.h>
#include <unicore-mx/stm32/gpio.h>
#include <unicore-mx/stm32/ltdc.h>
#include "stm32f7_ltdc.h"


/* Private function prototypes -----------------------------------------------*/
static void ltdc_config(void); 
static void ltdc_pinmux(void); 
static void ltdc_clock(void); 
static int ltdc_config_layer(void);

static uint32_t Screen_size = 0;

static int ltdc_config_layer(void)
{
  uint32_t format = 0; 
  Screen_size = xres * yres * (bits_per_pixel/8);
  
  /* Screen configuration */ 
  ltdc_setup_windowing(LTDC_LAYER_1, xres, xres);
  ltdc_set_pixel_format(LTDC_LAYER_1, pixel_format);
  ltdc_set_default_colors(LTDC_LAYER_1, 0, 0, 0, 0);
  ltdc_set_constant_alpha(LTDC_LAYER_1, 255);
  ltdc_set_blending_factors(LTDC_LAYER_1, LTDC_LxBFCR_BF1_CONST_ALPHA, LTDC_LxBFCR_BF2_CONST_ALPHA);
  ltdc_set_fbuffer_address(LTDC_LAYER_1, Screen_address);
  ltdc_set_fb_line_length(LTDC_LAYER_1, xres * (bits_per_pixel/8), xres * (bits_per_pixel/8));
  ltdc_set_fb_line_count(LTDC_LAYER_1, yres);

#if (OVERLAY)
  /* Overlay configuration */ 
  ltdc_setup_windowing(LTDC_LAYER_2, xres, xres);
  ltdc_set_pixel_format(LTDC_LAYER_2, LTDC_LxPFCR_AL44);
  ltdc_set_default_colors(LTDC_LAYER_2, 255, 0, 0, 0);
  ltdc_set_constant_alpha(LTDC_LAYER_2, 0);
//  ltdc_set_blending_factors(LTDC_LAYER_2, LTDC_LxBFCR_BF1_CONST_ALPHA, LTDC_LxBFCR_BF2_CONST_ALPHA);
  ltdc_set_fbuffer_address(LTDC_LAYER_2, Overlay_address);
  ltdc_set_fb_line_length(LTDC_LAYER_2, xres * (bits_per_pixel/8), xres * (bits_per_pixel/8));
  ltdc_set_fb_line_count(LTDC_LAYER_2, yres);
  /* Enable layer 2 */
  ltdc_layer_ctrl_enable(LTDC_LAYER_2, LTDC_LxCR_LAYER_ENABLE);
#endif

  /* Enable layer 1 */
  ltdc_layer_ctrl_enable(LTDC_LAYER_1, LTDC_LxCR_LAYER_ENABLE);
  

  /* Sets the Reload type */
  ltdc_reload(LTDC_SRCR_IMR);

  return 0;
}

/**
  * @brief LCD Configuration.
  * @note  This function Configure tha LTDC peripheral :
  *        1) Configure the Pixel Clock for the LCD
  *        2) Configure the LTDC Timing and Polarity
  *        3) Configure the LTDC Layer 1 :
  *           - The frame buffer is located at FLASH memory
  *           - The Layer size configuration : 480x272                      
  * @retval
  *  None
  */
static void ltdc_config(void)
{ 
  /* LTDC Initialization */
  ltdc_ctrl_disable(LTDC_GCR_HSPOL_ACTIVE_HIGH); /* Active Low Horizontal Sync */
  ltdc_ctrl_disable(LTDC_GCR_VSPOL_ACTIVE_HIGH); /* Active Low Vertical Sync */
  ltdc_ctrl_disable(LTDC_GCR_DEPOL_ACTIVE_HIGH); /* Active Low Date Enable */
  ltdc_ctrl_disable(LTDC_GCR_PCPOL_ACTIVE_HIGH); /* Active Low Pixel Clock */
  
  /* Configure the LTDC */  
  ltdc_set_tft_sync_timings(RK043FN48H_HSYNC, RK043FN48H_VSYNC,
			                RK043FN48H_HBP,   RK043FN48H_VBP,
			                RK043FN48H_WIDTH, RK043FN48H_HEIGHT,
			                RK043FN48H_HFP,   RK043FN48H_VFP);

  ltdc_set_background_color(0, 0, 0);
  ltdc_ctrl_enable(LTDC_GCR_LTDC_ENABLE);
}


static void ltdc_clock(void)
{
  /* LCD clock configuration */
  /* PLLSAI_VCO Input = HSE_VALUE/PLL_M = 1 Mhz */
  /* PLLSAI_VCO Output = PLLSAI_VCO Input * PLLSAIN = 192 Mhz */
  /* PLLLCDCLK = PLLSAI_VCO Output/PLLSAIR = 192/5 = 38.4 Mhz */
  /* LTDC clock frequency = PLLLCDCLK / LTDC_PLLSAI_DIVR_4 = 38.4/4 = 9.6Mhz */

  /* Disable PLLSAI */
  RCC_CR &= ~RCC_CR_PLLSAION;
  while((RCC_CR & (RCC_CR_PLLSAIRDY))) {};

  /* N and R are needed,
   * P and Q are not needed for LTDC */
  RCC_PLLSAICFGR &= ~RCC_PLLSAICFGR_PLLSAIN_MASK;
  RCC_PLLSAICFGR |= 192 << RCC_PLLSAICFGR_PLLSAIN_SHIFT;
  RCC_PLLSAICFGR &= ~RCC_PLLSAICFGR_PLLSAIR_MASK;
  RCC_PLLSAICFGR |= 5 << RCC_PLLSAICFGR_PLLSAIR_SHIFT;
  RCC_DCKCFGR1 &= ~RCC_DCKCFGR1_PLLSAIDIVR_MASK;
  RCC_DCKCFGR1 |= RCC_DCKCFGR1_PLLSAIDIVR_DIVR_4;

  /* Enable PLLSAI */
  RCC_CR |= RCC_CR_PLLSAION;
  while(!(RCC_CR & (RCC_CR_PLLSAIRDY))) {};
}

int ltdc_blank(void);

static void ltdc_screen_on(void)
{
    /* Assert display enable LCD_DISP pin */
    gpio_set(GPIOI, GPIO12);
    /* Assert backlight LCD_BL_CTRL pin */
    gpio_set(GPIOK, GPIO3);
}

void ltdc_enable_clut(void)
{
  /* Disable LTDC color lookup table by setting CLUTEN bit */
  ltdc_layer_ctrl_enable(LTDC_LAYER_1, LTDC_LxCR_CLUT_ENABLE);

  /* Sets the Reload type */
  ltdc_reload(LTDC_SRCR_IMR);
}

/* Only L8 CLUTs supported for now */
void ltdc_config_clut(uint32_t *CLUT, uint32_t size)
{
    uint32_t i = 0;

    for(i = 0; (i < size); i++)
    {
        /* Specifies the C-LUT address and RGB value */
        LTDC_LxCLUTWR(LTDC_LAYER_1) = ((i << 24) | ((uint32_t)(*CLUT) & 0xFF) | ((uint32_t)(*CLUT) & 0xFF00) | ((uint32_t)(*CLUT) & 0xFF0000));
        CLUT++;
    }
}

int ltdc_set_cmap(uint32_t *cmap)
{
    ltdc_config_clut(cmap, 256);
    ltdc_enable_clut();
    return 0;
}

static void lcd_pinmux(void)
{
    int i;
    uint32_t base, pin, af;

    rcc_periph_clock_enable(RCC_GPIOE);
    rcc_periph_clock_enable(RCC_GPIOG);
    rcc_periph_clock_enable(RCC_GPIOI);
    rcc_periph_clock_enable(RCC_GPIOJ);
    rcc_periph_clock_enable(RCC_GPIOK);

    /* Enable the LTDC Clock */
    rcc_periph_clock_enable(RCC_LTDC);

    /* PE4 */
    base = GPIOE;
    pin = GPIO4;
    af = GPIO_AF14;
    gpio_mode_setup(base, GPIO_MODE_AF, GPIO_PUPD_NONE, pin);
    gpio_set_af(base, af, pin);

    /* PG12 */
    base = GPIOG;
    pin = GPIO12;
    af = GPIO_AF9;
    gpio_mode_setup(base, GPIO_MODE_AF, GPIO_PUPD_NONE, pin);
    gpio_set_af(base, af, pin);

    /* LTDC PI8:PI10*/
    for (i = 8; i < 11; i++) {
        base = GPIOI;
        pin = (1 << i);
        af = GPIO_AF14;
    gpio_mode_setup(base, GPIO_MODE_AF, GPIO_PUPD_NONE, pin);
    gpio_set_af(base, af, pin);
    }

    /* LTDC PI14:PI15 */
    for (i = 14; i < 16; i++) {
        base = GPIOI;
        pin = (1 << i);
        af = GPIO_AF14;
        gpio_mode_setup(base, GPIO_MODE_AF, GPIO_PUPD_NONE, pin);
        gpio_set_af(base, af, pin);
    }

    /* LTDC PJ0:PJ15 (excl. PJ12) */
    for (i = 0; i < 16; i++) {
        if (i != 12) {
            base = GPIOJ;
            pin = (1 << i);
            af = GPIO_AF14;
            gpio_mode_setup(base, GPIO_MODE_AF, GPIO_PUPD_NONE, pin);
            gpio_set_af(base, af, pin);
        }
    }
    
    /* LTDC PK0:PK7 (excl. PK3) */
    for (i = 0; i < 8; i++) {
        if (i != 3) {
            base = GPIOK;
            pin = (1 << i);
            af = GPIO_AF14;
            gpio_mode_setup(base, GPIO_MODE_AF, GPIO_PUPD_NONE, pin);
            gpio_set_af(base, af, pin);
        }
    }
    
    /* LCD_DISP control PI12 (output) */
    base = GPIOI;
    pin = GPIO12;
    gpio_mode_setup(base, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, pin);

    /* LCD_BL control PK3 (output) */
    base = GPIOK;
    pin = GPIO3;
    gpio_mode_setup(base, GPIO_MODE_OUTPUT, GPIO_PUPD_NONE, pin);
    ltdc_screen_on();

}


/* DRIVER INIT */
void ltdc_init(void)
{
    extern const uint32_t xterm_cmap[256];
    lcd_pinmux();
    ltdc_clock(); 
    ltdc_config(); /* Configure LCD : Only one layer is used */
    ltdc_config_layer();
    ltdc_set_cmap(xterm_cmap);

    
}




int ltdc_blank(void)
{
    uint32_t pixels = (xres * yres * (bits_per_pixel/8));
    int i;
    uint8_t *screen = (uint8_t *)Screen_address;
    for (i = 0; i < pixels; i++) {
        screen[i] = 0x00;
    }
#if OVERLAY
    screen = (uint8_t *)Overlay_address;
    for (i = 0; i < pixels; i++) {
        screen[i] = 0xFF;
    }
#endif

#ifdef GTEST
    draw_h_segment(BRIGHT(RED), 20, 20, 30);
    draw_h_segment(BRIGHT(RED), 50, 20, 30);
    draw_v_segment(BRIGHT(PURPLE), 20, 20, 30);
    draw_v_segment(BRIGHT(BLUE), 50, 20, 30);
    text_at(BRIGHT(YELLOW), 50,50,"hello world");
    fill_area(BRIGHT(CYAN), 200, 200, 270, 400);
#endif
    return 0;
}
