 /* *****************************************************************************
 * File:      lcd_ili9341.c
 * Date:      21-03-2026
 * Author:    Andrew Levido
 *
 * Copyright 2026 Imbrius Pty Ltd - All Rights Reserved
 *
 ******************************************************************************/

/* Includes ------------------------------------------------------------------*/
#include "lcd_ili9341.h"
#include <stdbool.h>

/* Defines -------------------------------------------------------------------*/
#define SOFTWARE_RESET                  0x01
#define READ_DISPLAY_PIXEL_FORMAT       0x0C
#define SLEEP_OUT                       0x11
#define GAMMA_SET                       0x26
#define DISPLAY_OFF                     0x28
#define DISPLAY_ON                      0x29
#define SET_COLUMN_ADDRESS              0x2A
#define SET_PAGE_ADDRESS                0x2B
#define WRITE_MEMORY                    0x2C
#define READ_MEMORY                     0x2E
#define MEMORY_ACCESS_CONTROL           0x36
#define WRITE_MEMORY_CONTINUE           0x3C
#define READ_MEMORY_CONTINUE            0x3E
#define PIXEL_FORMAT_SET                0x3A
#define FRAME_RATE_CONTROL              0xB1
#define DISPLAY_FUNCTION_CONTROL        0xB6
#define POWER_CONTROL_1                 0xC0
#define POWER_CONTROL_2                 0xC1
#define VCOM_CONTROL_1                  0xC5
#define VCOM_CONTROL_2                  0xC7
#define POWER_CONTROL_A                 0xCB
#define POWER_CONTROL_B                 0xCF
#define POSITIVE_GAMMA_CORRECTION       0xE0
#define NEGATIVE_GAMMA_CORRECTION       0xE1
#define DRIVER_TIMING_CONTROL_A         0xE8
#define DRIVER_TIMING_CONTROL_B         0xEA
#define POWER_ON_SEQUENCE_CONTROL       0xED
#define UNDOCUMENTED_0xEF               0xEF
#define ENABLE_3G                       0xF2
#define INTERFACE_CONTROL               0xF6

#define LCD_BYTE_TIMEOUT 10
#define LCD_BLOCK_TIMEOUT 100

/* Typedefs & Enumerations ---------------------------------------------------*/

/* Global Variables ----------------------------------------------------------*/
static struct {
	uint16_t xmax;
	uint16_t ymax;
  volatile bool writing;
} lcd;

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


// Send a one-byte command
void lcd_send_command(uint8_t cmd) 
{
  if(lcd.writing == true) return;
  HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_RESET);
  HAL_SPI_Transmit(LCD_SPI, &cmd, 1, LCD_BYTE_TIMEOUT);
  HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET);
}

// Send one byte of data
void lcd_send_byte(uint8_t data) 
{ 
  if(lcd.writing == true) return;
  HAL_SPI_Transmit(LCD_SPI, &data, 1, LCD_BYTE_TIMEOUT);
}




void lcd_set_x(uint16_t start, uint16_t end) 
{
  lcd_send_command(SET_COLUMN_ADDRESS);
  lcd_send_byte(start >> 8);
  lcd_send_byte(start & 0xff);
  lcd_send_byte(end >> 8);
  lcd_send_byte(end & 0xff); 
}
void lcd_set_y(uint16_t start, uint16_t end) 
{
  lcd_send_command(SET_PAGE_ADDRESS);
  lcd_send_byte(start >> 8);
  lcd_send_byte(start & 0xff);
  lcd_send_byte(end >> 8);
  lcd_send_byte(end & 0xff);
}


void lcd_init(void)
{
 
	// Initialise data structure
  #if LCD_ORIENTATION == 0 || LCD_ORIENTATION == 180
    lcd.xmax = 240;
    lcd.ymax = 320;
    lcd.writing = false;
  #elif LCD_ORIENTATION == 90 || LCD_ORIENTATION == 270
    lcdData.xmax = 320;
    lcdData.ymax = 240;	
  #else 
    #warning Macro LCD_ORIENTATION must be 0, 90, 180 or 270
  #endif

  //Initialise LCD (set CS and DC default to high)

  HAL_GPIO_WritePin(LCD_DC_PORT, LCD_DC_PIN, GPIO_PIN_SET);
  HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
  lcd_send_command(SOFTWARE_RESET);

  HAL_Delay(100);

  lcd_send_command(SLEEP_OUT);
  HAL_Delay(5);

  lcd_send_command(INTERFACE_CONTROL);
  lcd_send_byte(0x01);  /* Data transfer overrun to GRAM  wraps */
  lcd_send_byte(0x00);  /* Default - used for RGB/VSYNC mode */
  lcd_send_byte(0x00);  /* Default - used for RGB/VSYNC mode */
  
  lcd_send_command(UNDOCUMENTED_0xEF);
  lcd_send_byte(0x03);  /* god only knows... */
  lcd_send_byte(0x80);
  lcd_send_byte(0x02);
  
  lcd_send_command(POWER_CONTROL_B);
  lcd_send_byte(0x00);  /* Does Nothing */
  lcd_send_byte(0xF2);  /* VCOM Enhancement, Power Saving */
  lcd_send_byte(0xA0);  /* Use Display manufacturer defaults */

  lcd_send_command(POWER_ON_SEQUENCE_CONTROL);
  lcd_send_byte(0x64);  /* Soft Start parameters - leave at  manufacturer*/
  lcd_send_byte(0x03);  /* defaults */
  lcd_send_byte(0x12);
  lcd_send_byte(0x81);
  
  lcd_send_command(POWER_CONTROL_A);
  lcd_send_byte(0x39);  /* Does nothing */
  lcd_send_byte(0x2c);  /* Does nothing */
  lcd_send_byte(0x00);  /* Does nothing */
  lcd_send_byte(0x34);  /* Default Values Vcore = 1.6 DDVDH = 5.6 */
  lcd_send_byte(0x02);

  lcd_send_command(DRIVER_TIMING_CONTROL_B);
  lcd_send_byte(0x00);  /* All set to 0 (defaults are 2 units) */
  lcd_send_byte(0x00);  /* Does nothing */
  
  lcd_send_command(DRIVER_TIMING_CONTROL_A);
  lcd_send_byte(0x85);  /* Non-overlap time = default + 1 unit */
  lcd_send_byte(0x10);  /* CRE time = 1 unit, EQE timing = 1default */
  lcd_send_byte(0x7a);  /* No documentation - this is default value */

  lcd_send_command(POWER_CONTROL_1);
  lcd_send_byte(0x21);  /* Default Value = 4.50V */
  
  lcd_send_command(POWER_CONTROL_2);
  lcd_send_byte(0x11);  /* VGH = VCI x 7, VGL = -VCI x 3 */

  lcd_send_command(VCOM_CONTROL_1);
  lcd_send_byte(0x3f);  /* Default (max) values */
  lcd_send_byte(0x3c);

  lcd_send_command(VCOM_CONTROL_2);
  lcd_send_byte(0xc6);  /* VCOM Offset +12 */

  lcd_send_command(PIXEL_FORMAT_SET);
  lcd_send_byte(0x55); /* 16 bits per pixel */

  lcd_send_command(MEMORY_ACCESS_CONTROL);
    #if LCD_ORIENTATION == 0
      lcd_send_byte(0x08);
    #elif LCD_ORIENTATION == 90
      lcd_send_data(0xa8);
    #elif LCD_ORIENTATION == 180
      lcd_send_data(0xc8);
    #elif LCD_ORIENTATION == 270
      lcd_send_data(0x60);
    #else
        #warning Macro LCD_ORIENTATION must be 0, 90, 180 or 270
    #endif
        
  lcd_send_command(FRAME_RATE_CONTROL);
  lcd_send_byte(0x00); /* Default Values  */
  lcd_send_byte(0x1b);
  
  lcd_send_command(DISPLAY_FUNCTION_CONTROL);
  lcd_send_byte(0x0a);  /* default values */
  lcd_send_byte(0xa2);  /* REV = 1, reverse gate & src scan */

  lcd_send_command(ENABLE_3G);
  lcd_send_byte(0x00);   /* default value - no 3 Gamma */
  
  lcd_send_command(GAMMA_SET);
  lcd_send_byte(0x01);    
  
  lcd_send_command(POSITIVE_GAMMA_CORRECTION);
  lcd_send_byte(0x0f);
  lcd_send_byte(0x24);
  lcd_send_byte(0x21);
  lcd_send_byte(0x0f);
  lcd_send_byte(0x13);
  lcd_send_byte(0x0a);
  lcd_send_byte(0x52);
  lcd_send_byte(0xc9);
  lcd_send_byte(0x3b);
  lcd_send_byte(0x05);
  lcd_send_byte(0x00);
  lcd_send_byte(0x00);
  lcd_send_byte(0x00);
  lcd_send_byte(0x00);
  lcd_send_byte(0x00);
  
  lcd_send_command(NEGATIVE_GAMMA_CORRECTION);
  lcd_send_byte(0x00);
  lcd_send_byte(0x1b);
  lcd_send_byte(0x1e);
  lcd_send_byte(0x00);
  lcd_send_byte(0x0c);
  lcd_send_byte(0x04);
  lcd_send_byte(0x2f);
  lcd_send_byte(0x36);
  lcd_send_byte(0x44);
  lcd_send_byte(0x0a);
  lcd_send_byte(0x1f);
  lcd_send_byte(0x0f);
  lcd_send_byte(0x3f);
  lcd_send_byte(0x3f);
  lcd_send_byte(0x0f);

  HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
 
}


void lcd_on(void)
{
  HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
  lcd_send_command(DISPLAY_ON);
  HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
}

void lcd_off(void)
{
  HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
  lcd_send_command(DISPLAY_OFF );              
  HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
}

void lcd_start_write(int16_t x, uint16_t y, uint16_t w, uint16_t h)
{
  if(w == 0) return;
  if(h == 0) return;
  if(x >= lcd.xmax) return;
  if(y >= lcd.ymax) return;

  HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_RESET);
  lcd_set_x(x, x + w - 1);
  lcd_set_y(y, y + h - 1);
  lcd_send_command(WRITE_MEMORY);

  // switch to 16 bit mode
  hspi1.Init.DataSize = SPI_DATASIZE_16BIT;
  // switch to LSB first
  hspi1.Init.FirstBit = SPI_FIRSTBIT_LSB;
  HAL_SPI_Init(&hspi1);
  lcd.writing = true;
}

void lcd_write_pixel(uint16_t colour)
{
  HAL_SPI_Transmit(LCD_SPI, (uint8_t*)&colour , 1, LCD_BYTE_TIMEOUT);
}

void lcd_stop_write(void)
{
  HAL_GPIO_WritePin(LCD_CS_PORT, LCD_CS_PIN, GPIO_PIN_SET);
  // restore SPI for commands
  hspi1.Init.DataSize = SPI_DATASIZE_8BIT;
  hspi1.Init.FirstBit = SPI_FIRSTBIT_MSB;
  HAL_SPI_Init(&hspi1);
  lcd.writing = false;
}

void lcd_write_block(int16_t x, uint16_t y, uint16_t width, uint16_t height, uint16_t colour)
{
  lcd_start_write(x, y, width, height);
  for (uint32_t i = 0; i < width * height; i++) {
    lcd_write_pixel(colour);
  }
  lcd_stop_write();
}



