
/*
  Copyright (C) 2020 Ein Terakawa

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/

#include "hal.h"
#include "ssd1306.h"

static const uint8_t logo_bits[] = {
  0x00, 0x00, 0x00, 0x80, 0xF0, 0xF8, 0xFC, 0xFC,
  0xFE, 0x3E, 0x1E, 0x1E, 0x7E, 0x7C, 0x7C, 0x78,
  0x70, 0x40, 0x00, 0x00, 0xFE, 0xFE, 0xFE, 0xFE,
  0xFE, 0xE0, 0xE0, 0xE0, 0xE0, 0xE0, 0xC0, 0x00,
  0x00, 0xEE, 0xEE, 0xEE, 0xEE, 0xEE, 0x00, 0x00,
  0xFE, 0xFE, 0xFE, 0xFE, 0xFE, 0xC0, 0xE0, 0xE0,
  0xE0, 0xC0, 0x00, 0x00, 0xEE, 0xEE, 0xEE, 0xEE,
  0xEE, 0x00, 0x80, 0xF0, 0xF8, 0xFC, 0xFC, 0xFE,
  0x1E, 0x1E, 0x1E, 0xFE, 0xFC, 0xFC, 0xF8, 0xF0,
  0x80, 0x60, 0xF8, 0xFC, 0xFC, 0xFE, 0xFE, 0xDE,
  0x9E, 0xBE, 0xBC, 0xBC, 0x38, 0x20, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x01, 0x0F, 0x1F, 0x3F, 0x3F,
  0x7F, 0x7C, 0x78, 0x78, 0x7E, 0x3E, 0x3E, 0x3E,
  0x0E, 0x02, 0x00, 0x00, 0x7F, 0x7F, 0x7F, 0x7F,
  0x7F, 0x00, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x00,
  0x00, 0x7F, 0x7F, 0x7F, 0x7F, 0x7F, 0x00, 0x00,
  0x7F, 0x7F, 0x7F, 0x7F, 0x1F, 0x79, 0x7F, 0x7F,
  0x7F, 0x3F, 0x0F, 0x00, 0x7F, 0x7F, 0x7F, 0x7F,
  0x7F, 0x00, 0x01, 0x0F, 0x1F, 0x3F, 0x3F, 0x7F,
  0x78, 0x78, 0x78, 0x7F, 0x3F, 0x3F, 0x1F, 0x0F,
  0x01, 0x04, 0x1C, 0x3D, 0x3D, 0x7D, 0x79, 0x7B,
  0x7B, 0x7F, 0x7F, 0x3F, 0x1F, 0x0C, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0xFE, 0xFE, 0xFE, 0xFE,
  0xFE, 0xFE, 0xF8, 0xC0, 0xFE, 0xFE, 0xFE, 0xFE,
  0xFE, 0x00, 0x00, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
  0xFE, 0x00, 0x00, 0xFE, 0xFE, 0xFE, 0xFE, 0xFE,
  0xFE, 0x00, 0x80, 0xF0, 0xF8, 0xFC, 0xFC, 0xFE,
  0x3E, 0x1E, 0x1E, 0x7E, 0x7C, 0x7C, 0x78, 0x70,
  0x40, 0x00, 0x38, 0x38, 0x38, 0xFE, 0xFE, 0xFE,
  0xFE, 0xFE, 0xFE, 0x00, 0x00, 0x20, 0x38, 0x3C,
  0x3C, 0x3E, 0x9E, 0x9E, 0xFE, 0xFE, 0xFC, 0xF8,
  0x70, 0x00, 0x20, 0x38, 0x3C, 0xFC, 0xFE, 0xDE,
  0xDE, 0xFE, 0xFE, 0x7C, 0x78, 0x20, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
  0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x7F, 0x7F,
  0x7F, 0x01, 0x07, 0x1F, 0x7F, 0x7F, 0x7F, 0x7F,
  0x7F, 0x00, 0x00, 0x03, 0x0F, 0x1F, 0x3F, 0x7F,
  0x7F, 0x7C, 0x7C, 0x7F, 0x7F, 0x3F, 0x3F, 0x1F,
  0x03, 0x00, 0x01, 0x0F, 0x1F, 0x3F, 0x3F, 0x7F,
  0x7C, 0x78, 0x78, 0x7E, 0x3E, 0x3E, 0x3E, 0x0E,
  0x02, 0x00, 0x00, 0x00, 0x00, 0x7F, 0x7F, 0x7F,
  0x7F, 0x7F, 0x7F, 0x00, 0x00, 0x70, 0x7C, 0x7E,
  0x7F, 0x7F, 0x7F, 0x7F, 0x7B, 0x7B, 0x79, 0x79,
  0x78, 0x00, 0x04, 0x1C, 0x3C, 0x7D, 0x79, 0x79,
  0x79, 0x7F, 0x7F, 0x3F, 0x3F, 0x0E, 0x00, 0x00,
};

static void draw_logo(SSD1306_DRIVER *ssd1306, bool invert, char xx) {
  uint8_t width = ssd1306->width;
  uint8_t height = ssd1306->height;
  uint8_t num_pages = height / 8;
  uint8_t invert_bits = invert ? 0xff : 0x00;
  uint8_t *buf = SSD1306_GET_FRAMEBUFFER(ssd1306);

  for (uint8_t row = 0; row < num_pages; ++row) {
    for (uint8_t x = 0; x < width; ++x) {
      uint8_t dat = logo_bits[(128 - xx + x) % 128 + (row % 4) * width];
      if (row / 2 % 2 == 0) {
        dat ^= 0xff;
      }
      dat ^= invert_bits; /* Invert Imeage Bits */
      if (true) {
        /* Horizontal Addressing Mode */
        buf[row * width + x] = dat;
      } else {
        /* Vertical Addressing Mode */
        buf[x * num_pages + row] = dat;
      }
    }
  }
}

#define I2C_CLK_FREQ 1000000
static const I2CConfig i2ccfg = { I2C_CLK_FREQ };
#if NUC123_I2C_USE_I2C0
DEFINE_SSD1306_DRIVER(ssd1306_128x64, &I2CD0, SSD1306_ADDRESS, 128, 64, false);
#endif
#if NUC123_I2C_USE_I2C1
DEFINE_SSD1306_DRIVER(ssd1306_128x32, &I2CD1, SSD1306_ADDRESS, 128, 32, true);
#endif

#if I2C_USE_MUTUAL_EXCLUSION
#define ACQUIRE_BUS(oled_driver) i2cAcquireBus((oled_driver)->i2cd)
#define RELEASE_BUS(oled_driver) i2cReleaseBus((oled_driver)->i2cd)
#else
#define ACQUIRE_BUS(oled_driver)
#define RELEASE_BUS(oled_driver)
#endif

thread_reference_t thread1_ref = NULL;

static THD_WORKING_AREA(waThread1, 128);
static THD_FUNCTION(Thread1, arg) {
  chRegSetThreadName("blinker");

  (void)arg;
  while (true) {
    if(osalThreadSuspendS(&thread1_ref) == MSG_OK) {
      OnboardLED_On();
    }else{
      OnboardLED_Off();
    }
  }
}

#define I2C_THREAD_STACK_SIZE 256
#if NUC123_I2C_USE_I2C0
static THD_WORKING_AREA(waThread2, I2C_THREAD_STACK_SIZE);
static THD_FUNCTION(Thread2, arg) {
  chRegSetThreadName("oled1");

  SSD1306_DRIVER *ssd1306 = (SSD1306_DRIVER *)arg;
  ssd1306->i2ccfg = &i2ccfg;
  ACQUIRE_BUS(ssd1306);
  ssd1306_init(ssd1306);
  RELEASE_BUS(ssd1306);
  char x = 0;
  while (true) {
    osalThreadSleepMilliseconds(127);
    bool invert = false;
    draw_logo(ssd1306, invert, x);
    ACQUIRE_BUS(ssd1306);
    ssd1306_data(ssd1306);
    /* RELEASE_BUS(ssd1306); */
    /* ACQUIRE_BUS(ssd1306); */
    if (x % 16 == 12) {
      ssd1306_display_off(ssd1306);
    } else if (x % 16 == 0) {
      ssd1306_display_on(ssd1306);
    }
    RELEASE_BUS(ssd1306);
    x = (x + 1) % 128;
  }
}
#endif

#if NUC123_I2C_USE_I2C1
static THD_WORKING_AREA(waThread3, I2C_THREAD_STACK_SIZE);
static THD_FUNCTION(Thread3, arg) {
  chRegSetThreadName("oled2");

  SSD1306_DRIVER *ssd1306 = (SSD1306_DRIVER *)arg;
  ssd1306->i2ccfg = &i2ccfg;
  ACQUIRE_BUS(ssd1306);
#if true
  /* Workaround for some 128x32 OLED modules typically with black PCB. */
  while (!ssd1306_init(ssd1306)) {
    RELEASE_BUS(ssd1306);
    osalThreadSleepMilliseconds(100);
    ACQUIRE_BUS(ssd1306);
  }
#endif
  ssd1306_init(ssd1306);
  RELEASE_BUS(ssd1306);
  char x = 0;
  while (true) {
    osalThreadSleepMilliseconds(131);
    bool invert = true;
    draw_logo(ssd1306, invert, x);
    ACQUIRE_BUS(ssd1306);
    ssd1306_data(ssd1306);
    RELEASE_BUS(ssd1306);
    x = (x - 1) % 128;
  }
}
#endif

/*
 * Application entry point.
 */
int main(void) {

  /*
   * System initializations.
   * - HAL initialization, this also initializes the configured device drivers
   *   and performs the board-specific initializations.
   */
  halInit();

  /*
   * chSysInit() will also enable interrupts.
   */
  chSysInit();

  chThdCreateStatic(waThread1, sizeof(waThread1), NORMALPRIO + 2, Thread1, NULL);
#if NUC123_I2C_USE_I2C0
  chThdCreateStatic(waThread2, sizeof(waThread2), NORMALPRIO + 1, Thread2, &ssd1306_128x64);
#endif
#if NUC123_I2C_USE_I2C1
  chThdCreateStatic(waThread3, sizeof(waThread3), NORMALPRIO + 1, Thread3, &ssd1306_128x32);
#endif

  while (true) {
    osalThreadResumeS(&thread1_ref, MSG_OK);
    osalThreadSleepMilliseconds(100);
    osalThreadResumeS(&thread1_ref, MSG_RESET);
    osalThreadSleepMilliseconds(400);
  }
}
