text: /*
   *
   *    Copyright (c) 2020 Project CHIP Authors
   *    Copyright (c) 2018 Nest Labs, Inc.
   *    All rights reserved.
   *
   *    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.
   */
  
  /**
   * @file Display.cpp
   *
   * This file implements helper APIs for the M5Stack's display
   *
   */
  
  #include <string.h>
  
  #include "driver/ledc.h"
  #include "esp_log.h"
  #include "esp_system.h"
  #include "freertos/FreeRTOS.h"
  #include "freertos/task.h"
  #include "freertos/timers.h"
  
  #include "Display.h"
  
  #if CONFIG_HAVE_DISPLAY
  
  // Brightness picked such that it's easy for cameras to focus on
  #define DEFAULT_BRIGHTNESS_PERCENT 10
  
  // 8MHz is the recommended SPI speed to init the driver with
  // It later gets set to the preconfigured defaults within the driver
  #define TFT_SPI_CLOCK_INIT_HZ 8000000
  
  // The frequency used by the ledc timer
  // value chosen to eliminate flicker
  #define LEDC_PWM_HZ 1000
  
  // with a duty resolution of LEDC_TIMER_8_BIT
  // the highest possible brightness value is 255
  #define BRIGHTNESS_MAX 255
  // The M5Stack's backlight is on Channel 7
  #define BACKLIGHT_CHANNEL LEDC_CHANNEL_7
  
  extern const char * TAG;
  
  uint16_t DisplayHeight = 0;
  uint16_t DisplayWidth  = 0;
  
  bool awake = false;
  
  #if CONFIG_DISPLAY_AUTO_OFF
  // FreeRTOS timer used to turn the display off after a short while
  TimerHandle_t displayTimer = NULL;
  #endif
  
  #if CONFIG_DISPLAY_AUTO_OFF
  static void TimerCallback(TimerHandle_t xTimer);
  #endif
  static void SetupBrightnessControl();
  static void SetBrightness(uint16_t brightness_percent);
  
  esp_err_t InitDisplay()
  {
      esp_err_t err;
      spi_lobo_device_handle_t spi;
  
      // configured based on the display driver's examples
      spi_lobo_bus_config_t buscfg;
      memset((void *) &buscfg, 0, sizeof(buscfg));
      buscfg.miso_io_num   = PIN_NUM_MISO; // set SPI MISO pin
      buscfg.mosi_io_num   = PIN_NUM_MOSI; // set SPI MOSI pin
      buscfg.sclk_io_num   = PIN_NUM_CLK;  // set SPI CLK pin
      buscfg.quadwp_io_num = -1;
      buscfg.quadhd_io_num = -1;
  
      spi_lobo_device_interface_config_t devcfg;
      memset((void *) &devcfg, 0, sizeof(devcfg));
      devcfg.clock_speed_hz   = TFT_SPI_CLOCK_INIT_HZ;
      devcfg.mode             = 0;                        // SPI mode 0
      devcfg.spics_io_num     = -1;                       // we will use external CS pin
      devcfg.spics_ext_io_num = PIN_NUM_CS;               // external CS pin
      devcfg.flags            = LB_SPI_DEVICE_HALFDUPLEX; // ALWAYS SET  to HALF DUPLEX MODE!! for display spi
      tft_max_rdclock         = TFT_SPI_CLOCK_INIT_HZ;
  
      // Initialize all pins used by display driver.
      TFT_PinsInit();
  
      // Initialize SPI bus and add a device for the display.
      err = spi_lobo_bus_add_device(TFT_HSPI_HOST, &buscfg, &devcfg, &spi);
      if (err != ESP_OK)
          return err;
  
      // Configure the display to use the new SPI device.
      tft_disp_spi = spi;
  
      err = spi_lobo_device_select(spi, 1);
      if (err != ESP_OK)
          return err;
      err = spi_lobo_device_deselect(spi);
      if (err != ESP_OK)
          return err;
  
      // Initialize the display driver.
      TFT_display_init();
  
      // Detect maximum read speed and set it.
      tft_max_rdclock = find_rd_speed();
  
      // Set the SPI clock speed overriding the initialized 8MHz speed
      spi_lobo_set_speed(spi, DEFAULT_SPI_CLOCK);
  
      TFT_setGammaCurve(0);
      TFT_setRotation(LANDSCAPE);
      TFT_resetclipwin();
  
      DisplayWidth  = (uint16_t)(1 + tft_dispWin.x2 - tft_dispWin.x1);
      DisplayHeight = (uint16_t)(1 + tft_dispWin.y2 - tft_dispWin.y1);
  
      ESP_LOGI(TAG, "Display initialized (height %u, width %u)", DisplayHeight, DisplayWidth);
  
      // for some reason this is backwards (turns out this is because of a 2019 update to the m5stack hw)
      TFT_invertDisplay(INVERT_ON);
  
      // prepare the display for brightness control
      SetupBrightnessControl();
  
  #if CONFIG_DISPLAY_AUTO_OFF
      displayTimer = xTimerCreate("DisplayTimer", pdMS_TO_TICKS(DISPLAY_TIMEOUT_MS), false, NULL, TimerCallback);
  #endif
      // lower the brightness of the screen
      WakeDisplay();
  
      return err;
  }
  
  void SetBrightness(uint16_t brightness_percent)
  {
      uint16_t brightness = (brightness_percent * BRIGHTNESS_MAX) / 100;
      if (ledc_set_duty(LEDC_HIGH_SPEED_MODE, BACKLIGHT_CHANNEL, brightness) ||
          ledc_update_duty(LEDC_HIGH_SPEED_MODE, BACKLIGHT_CHANNEL))
      {
          ESP_LOGE(TAG, "Failed to set display brightness...");
      }
  }
  
  bool WakeDisplay()
  {
      bool woken = !awake;
      awake      = true;
      SetBrightness(DEFAULT_BRIGHTNESS_PERCENT);
  #if CONFIG_DISPLAY_AUTO_OFF
      xTimerStart(displayTimer, 0);
      ESP_LOGI(TAG, "Display awake but will switch off automatically in %d seconds", DISPLAY_TIMEOUT_MS / 1000);
  #endif
      return woken;
  }
  
  void ClearDisplay()
  {
      ClearRect();
  }
  
  void ClearRect(uint16_t x_percent_start, uint16_t y_percent_start, uint16_t x_percent_end, uint16_t y_percent_end)
  {
      if (x_percent_end < x_percent_start)
      {
          x_percent_end = x_percent_start;
      }
      if (y_percent_end < y_percent_start)
      {
          y_percent_end = y_percent_start;
      }
      uint16_t start_x = (DisplayWidth * x_percent_start) / 100;
      uint16_t start_y = (DisplayHeight * y_percent_start) / 100;
      uint16_t end_x   = (DisplayWidth * x_percent_end) / 100;
      uint16_t end_y   = (DisplayHeight * y_percent_end) / 100;
      TFT_fillRect(start_x, start_y, end_x, end_y, TFT_BLACK);
  }
  
  void DisplayStatusMessage(char * msg, uint16_t vpos)
  {
      TFT_setFont(SMALL_FONT, NULL);
      uint16_t msgX = 0;
      uint16_t msgY = (DisplayHeight * vpos) / 100;
      TFT_print(msg, msgX, msgY);
  }
  
  void TimerCallback(TimerHandle_t xTimer)
  {
      ESP_LOGI(TAG, "Display going to sleep...");
      SetBrightness(0);
      awake = false;
  }
  
  void SetupBrightnessControl()
  {
      ledc_timer_config_t ledc_timer;
      memset(&ledc_timer, 0, sizeof(ledc_timer));
  
      ledc_timer.duty_resolution = LEDC_TIMER_8_BIT;     // resolution of PWM duty
      ledc_timer.freq_hz         = LEDC_PWM_HZ;          // frequency of PWM signal
      ledc_timer.speed_mode      = LEDC_HIGH_SPEED_MODE; // timer mode
      ledc_timer.timer_num       = LEDC_TIMER_0;         // timer index
      ledc_timer_config(&ledc_timer);
  
      ledc_timer_set(LEDC_HIGH_SPEED_MODE, LEDC_TIMER_0, LEDC_PWM_HZ, LEDC_TIMER_8_BIT, LEDC_REF_TICK);
  
      ledc_channel_config_t ledc_channel;
      memset(&ledc_channel, 0, sizeof(ledc_channel));
      ledc_channel.channel    = BACKLIGHT_CHANNEL;
      ledc_channel.duty       = BRIGHTNESS_MAX;
      ledc_channel.gpio_num   = PIN_NUM_BCKL;
      ledc_channel.speed_mode = LEDC_HIGH_SPEED_MODE;
      ledc_channel.timer_sel  = LEDC_TIMER_0;
      ledc_channel_config(&ledc_channel);
  }
  
  #endif // CONFIG_HAVE_DISPLAY
  