/* ****************************************************************************
 * File:      lcd_X30654.c
 * Date:      28-12-2024
 * Author:    Andrew Levido
 *
 * Copyright 2025 Imbrius Pty Ltd - All Rights Reserved
 *
 *****************************************************************************/

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

/* Defines -----------------------------------------------------------------*/
#define LCD_CMD_SETMUX    (uint8_t)0xA8 // Set multiplex ratio (N, number of lines active on display)
#define LCD_CMD_SETOFFS   (uint8_t)0xD3 // Set display offset
#define LCD_CMD_STARTLINE (uint8_t)0x40 // Set display start line
#define LCD_CMD_SEG_NORM  (uint8_t)0xA0 // Column 0 is mapped to SEG0 (X coordinate normal)
#define LCD_CMD_SEG_INV   (uint8_t)0xA1 // Column 127 is mapped to SEG0 (X coordinate inverted)
#define LCD_CMD_COM_NORM  (uint8_t)0xC0 // Scan from COM0 to COM[N-1] (N - mux ratio, Y coordinate normal)
#define LCD_CMD_COM_INV   (uint8_t)0xC8 // Scan from COM[N-1] to COM0 (N - mux ratio, Y coordinate inverted)
#define LCD_CMD_COM_HW    (uint8_t)0xDA // Set COM pins hardware configuration
#define LCD_CMD_CONTRAST  (uint8_t)0x81 // Contrast control
#define LCD_CMD_EDON      (uint8_t)0xA5 // Entire display ON enabled (all pixels on, RAM content ignored)
#define LCD_CMD_EDOFF     (uint8_t)0xA4 // Entire display ON disabled (output follows RAM content)
#define LCD_CMD_INV_OFF   (uint8_t)0xA6 // Entire display inversion OFF (normal display)
#define LCD_CMD_INV_ON    (uint8_t)0xA7 // Entire display inversion ON (all pixels inverted)
#define LCD_CMD_CLOCKDIV  (uint8_t)0xD5 // Set display clock divide ratio/oscillator frequency
#define LCD_CMD_DISP_ON   (uint8_t)0xAF // Display ON
#define LCD_CMD_DISP_OFF  (uint8_t)0xAE // Display OFF (sleep mode)
#define LCD_CMD_CHARGE    (uint8_t)0xDB // 
#define LCD_CMD_COL_LOW   (uint8_t)0x00 // Set Lower Column Address
#define LCD_CMD_COL_HIGH  (uint8_t)0x10 // Set Higher Column Address
#define LCD_CMD_PAGE      (uint8_t)0xB0 // Set Page Address
#define LCD_CMD_SETPUMP   (uint8_t)0x30 // DC-DC output voltage

#define OLED_PAGE_LEN     128
#define OLED_PAGE_NUM     8
#define OLED_HEIGHT       64

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

/* Global Variables --------------------------------------------------------*/
static struct {
  uint8_t buffer[OLED_PAGE_LEN * OLED_PAGE_NUM];
} oled;

/* Private Functions -------------------------------------------------------*/
oled_result_t oled_write_cmd(uint8_t cmd)
{ 
  if(i2c_write(OLED_I2C_DEVADDR, 0x00, &cmd, 1) != I2C_OK) {
    return(OLED_ERR_COMMS);
  }
  return(OLED_OK);
}

int16_t oled_write_double_cmd(uint8_t cmd, uint8_t data)
{ 
  uint8_t byte[2];

  byte[0] = cmd;
  byte[1] = data;

 if(i2c_write(OLED_I2C_DEVADDR, 0x00, byte, 2) != I2C_OK) {
   return(OLED_ERR_COMMS);
 }
 return(OLED_OK);
}

int16_t oled_write_data(uint8_t * data, uint8_t len)
{
  if(i2c_write(OLED_I2C_DEVADDR, 0x40, data, len) != I2C_OK) {
    return(OLED_ERR_COMMS);
  }

  return(OLED_OK);
}

/* Public Functions --------------------------------------------------------*/
oled_result_t oled_init(oled_orientation_t orient)
{
  oled_result_t result;

  /* Hard reset */
  LL_GPIO_SetOutputPin(DSP_RST_GPIO_Port, DSP_RST_Pin);
  LL_mDelay(10);
  LL_GPIO_ResetOutputPin(DSP_RST_GPIO_Port, DSP_RST_Pin);
  LL_mDelay(10);
  LL_GPIO_SetOutputPin(DSP_RST_GPIO_Port, DSP_RST_Pin);
  LL_mDelay(10);
  
  /* Initialise display */
  result = oled_write_cmd(LCD_CMD_DISP_OFF);               // Display off
  result |= oled_write_double_cmd(LCD_CMD_CLOCKDIV, 0x80); // Clock divider
  result |= oled_write_double_cmd(LCD_CMD_SETMUX, 0x3f);   // set lines to 64
  result |= oled_write_double_cmd(LCD_CMD_SETOFFS, 0x00);  // first line at top
  result |= oled_write_cmd(LCD_CMD_STARTLINE | 0x00);      // Start line 0
  if(orient == OLED_NORMAL) {
    result |= oled_write_cmd(LCD_CMD_SEG_INV);               // X-direction
    result |= oled_write_cmd(LCD_CMD_COM_INV);               // Y-direction
  }
  else {
    result |= oled_write_cmd(LCD_CMD_SEG_NORM);               // X-direction
    result |= oled_write_cmd(LCD_CMD_COM_NORM);               // Y-direction
  }
  result |= oled_write_double_cmd(LCD_CMD_COM_HW, 0x12);   //
  result |= oled_write_double_cmd(LCD_CMD_CHARGE, 0xf1);   //
  result |= oled_write_cmd(LCD_CMD_EDOFF);                 // display follows ram
  result |= oled_write_cmd(LCD_CMD_DISP_ON);               // On
 
  oled_clear_buffer();
  oled_update();
 
  return(result);
}

void oled_off(void)
{
  LL_GPIO_ResetOutputPin(DSP_RST_GPIO_Port, DSP_RST_Pin);
  oled_write_cmd(LCD_CMD_DISP_OFF);
}

void oled_on(void)
{

  oled_write_cmd(LCD_CMD_DISP_ON);
}

oled_result_t oled_flip(oled_orientation_t orient)
{
  oled_result_t result;

   if(orient == OLED_NORMAL) {
    result = oled_write_cmd(LCD_CMD_SEG_INV);               // X-direction
    result |= oled_write_cmd(LCD_CMD_COM_INV);               // Y-direction
  }
  else {
    result = oled_write_cmd(LCD_CMD_SEG_NORM);               // X-direction
    result |= oled_write_cmd(LCD_CMD_COM_NORM);               // Y-direction
  }
  return(result);
}

oled_result_t oled_update(void)
{
  uint8_t page;
  uint8_t * x_ptr = oled.buffer;
  oled_result_t result = OLED_OK;

  for(page = 0; page < OLED_PAGE_NUM; page++) {
    result |= oled_write_cmd(LCD_CMD_PAGE | (page & 0x0f) );
    result |= oled_write_cmd(LCD_CMD_COL_LOW | 2);
    result |= oled_write_cmd(LCD_CMD_COL_HIGH | 0);
    result |= oled_write_data(x_ptr, OLED_PAGE_LEN);
    x_ptr += OLED_PAGE_LEN;
  }
  return(result);
}

void oled_put_pixel(uint8_t x0, uint8_t y0, uint8_t value)
{
  x0 &= 0x7f;          // limit to 127
  y0 &= 0x3f;          // limit to 63

  if(value !=0) { oled.buffer[128 * y0/8 + x0] |= (1 << (y0 % 8)); }
  else { oled.buffer[128 * y0/8 + x0] &= ~(1 << (y0 % 8)); }
}

void oled_put_buffer(uint8_t x0, uint8_t page, uint8_t width, uint8_t height, uint8_t * buf)
{
  uint8_t p, i;

  /* Off screen */
  if(x0 >= OLED_PAGE_LEN) { return; }
  if(page >= OLED_PAGE_NUM) { return; }
  
  /* Crop */
  if(x0 + width > OLED_PAGE_LEN) { width = OLED_PAGE_LEN - x0; }
  if(page + height > OLED_PAGE_NUM) { height = OLED_PAGE_NUM - page; }

  /* Write */
  for(i = x0; i < x0 + width; i++) {
    for(p = page; p < page + height; p++) {
      oled.buffer[128 * p + i] = *buf++;
    }
  }
}

void oled_clear_buffer(void)
{
  uint16_t index;

  for(index = 0; index < OLED_PAGE_LEN * OLED_PAGE_NUM; index++) {
    oled.buffer[index] = 0;
  }
}

void oled_clear(uint8_t x0, uint8_t page, uint8_t width, uint8_t height)
{
  uint8_t p, i;

  /* Off screen */
  if(x0 >= OLED_PAGE_LEN) { return; }
  if(page >= OLED_PAGE_NUM) { return; }
  
  /* Crop */
  if(x0 + width > OLED_PAGE_LEN) { width = OLED_PAGE_LEN - x0; }
  if(page + height > OLED_PAGE_NUM) { height = OLED_PAGE_NUM - page; }
  
  /* Clear */
  for(i = x0; i < x0 + width; i++) {
    for(p = page; p < page + height; p++) {
      oled.buffer[128 * p + i] = 0;
    }
  }
}

/* Interrupt Service Routines ----------------------------------------------*/

/* End lcd_X30654.c */