/**
 * @file spi_drv.c
 * @author June6 (https://github.com/Junedoubling6)
 * @brief
 * @version 0.1
 * @date 2023-08-16
 *
 * @copyright Copyright (c) 2022 - , All rights reserved.
 *
 */
#include "spi_drv.h"

#include "driver/gpio.h"
#include "driver/spi_master.h"
#include "log.h"

#ifdef CONFIG_IDF_TARGET_ESP32
#define SPI_MASTER_HOST HSPI_HOST
#elif defined CONFIG_IDF_TARGET_ESP32S2 || CONFIG_IDF_TARGET_ESP32S3
#define SPI_MASTER_HOST SPI2_HOST
#elif defined CONFIG_IDF_TARGET_ESP32C3 || CONFIG_IDF_TARGET_ESP32C2 || \
    CONFIG_IDF_TARGET_ESP32C6
#define SPI_MASTER_HOST SPI2_HOST
#elif defined CONFIG_IDF_TARGET_ESP32H2
#define SPI_MASTER_HOST SPI2_HOST
#endif

#define SPI_MASTER_MODE 0
#if CONFIG_SPI_OVERCLOCK
#define SPI_MASTER_FREQ_HZ SPI_MASTER_FREQ_26M
#else
#define SPI_MASTER_FREQ_HZ SPI_MASTER_FREQ_10M
#endif

/* 希望能够一次对7个事务进行排队处理 */
#define SPI_MASTER_QUEUE_SIZE 7

static const char *TAG = "spi_drv";
static int lcd_pin_num_dc = GPIO_NUM_NC;
static bool spi_config_flag = false;
static spi_device_handle_t spi_lcd_handler;

/**
 * @brief SPI prepare transfer callback
 *
 * @param spi_data
 * @details This function is called (in irq context!) just before a transmission
 *          starts. It will set the D/C line to the value indicated in the user
 *          field.
 */
void spi_pre_transfer_callback(spi_transaction_t *spi_data) {
  int dc = (int)spi_data->user;

  gpio_set_level((gpio_num_t)lcd_pin_num_dc, dc);
}

/**
 * @brief Config spi lcd master.
 *
 * @param pin_sclk
 * @param pin_mosi
 * @param pin_miso
 * @param pin_cs
 * @param pin_dc
 * @return esp_err_t
 */
esp_err_t spi_lcd_master_config(int pin_sclk, int pin_mosi, int pin_miso,
                                int pin_cs, int pin_dc) {
  esp_err_t ret = ESP_OK;
  if (spi_config_flag) {
    USER_LOGD(TAG, "SPI master is configured");
    goto End;
  }
  transaction_cb_t cb = NULL;
  spi_bus_config_t buscfg = {.sclk_io_num = pin_sclk,
                             .mosi_io_num = pin_mosi,
                             .miso_io_num = pin_miso,
                             .quadwp_io_num = GPIO_NUM_NC,
                             .quadhd_io_num = GPIO_NUM_NC,
                             .max_transfer_sz = SPI_MASTER_TRANSFER_SIZE};

  /* 初始化SPI */
  ret = spi_bus_initialize(SPI_MASTER_HOST, &buscfg, SPI_DMA_CH_AUTO);
  if (ret != ESP_OK) {
    USER_LOGE(TAG, "spi_lcd_master_config error");
    goto End;
  }

  if (pin_dc >= 0) {
    cb = spi_pre_transfer_callback;
    lcd_pin_num_dc = pin_dc;
  }
  spi_device_interface_config_t devcfg = {
      .clock_speed_hz = SPI_MASTER_FREQ_HZ,
      .mode = SPI_MASTER_MODE,
      .spics_io_num = pin_cs,
      .flags = SPI_DEVICE_3WIRE,
      /* 希望能够一次对SPI_MASTER_QUEUE_SIZE个事务进行排队处理 */
      .queue_size = SPI_MASTER_QUEUE_SIZE,
      /* 指定预传输回调以处理D/C行 */
      .pre_cb = cb,
  };
  /* 将LCD连接到SPI总线 */
  ret = spi_bus_add_device(SPI_MASTER_HOST, &devcfg, &spi_lcd_handler);
  if (ret != ESP_OK) {
    USER_LOGE(TAG, "spi_lcd_master_config error");
    goto End;
  }
  spi_config_flag = true;
  USER_LOGD(TAG, "Initialize SPI master");

End:
  return ret;
}

/**
 * @brief Deconfig spi lcd master.
 *
 * @return esp_err_t
 */
esp_err_t spi_lcd_master_deconfig(void) {
  esp_err_t ret = ESP_OK;
  if (!spi_config_flag) goto End;
  ret = spi_bus_remove_device(spi_lcd_handler);
  if (ret != ESP_OK) {
    USER_LOGE(TAG, "spi_bus_remove_device error");
    goto End;
  }
  ret = spi_bus_free(SPI_MASTER_HOST);
  if (ret != ESP_OK) {
    USER_LOGE(TAG, "spi_bus_free error");
    goto End;
  }
  spi_config_flag = false;

End:
  return ret;
}

/**
 * @brief SPI lcd master write.
 *
 * @param cmd_flag
 * @param data
 * @param len
 * @param keep_cs_active_flag
 * @return esp_err_t
 */
esp_err_t spi_lcd_write(bool cmd_flag, const uint8_t *data, size_t len,
                        bool keep_cs_active_flag) {
  esp_err_t ret = ESP_FAIL;
  if (len <= 0) {
    ret = ESP_FAIL;
    goto End;
  }
  /* FIXME: June6 修复 spi_device_polling_transmit 支持问题 */
#if 0
  spi_transaction_t spi_data;
  memset(&spi_data, 0, sizeof(spi_data));
  /* 长度以字节为单位, 事务长度以位为单位 */
  spi_data.length = len * 8;
  /* 数据是cmd本身 */
  spi_data.tx_buffer = data;
  if (lcd_pin_num_dc >= 0) {
    /* 当传输命令D/C需要设置为0时; 否则需要设置为1 */
    spi_data.user = (void *)0;
    if (!cmd_flag) spi_data.user = (void *)1;
  }

  if (keep_cs_active_flag) /* 数据传输后保持CS活动 */
    spi_data.flags = SPI_TRANS_CS_KEEP_ACTIVE;

  /* 传输SPI数据! */
  ret = spi_device_polling_transmit(spi_lcd_handler, &spi_data);
#else

  if (cmd_flag) {
    ret = spi_send_cmd(data[0]);
  } else {
    for (size_t i = 0; i < len; i++) ret = spi_send_data(data[i]);
  }
#endif

End:
  return ret;
}

/* TODO: June6 增加spi_read */

/**
 * @brief Spi send cmd.
 *
 * @param cmd
 */
esp_err_t spi_send_cmd(uint8_t cmd) {
  uint16_t tmp_cmd = (cmd | 0x0000);

  spi_transaction_ext_t trans = (spi_transaction_ext_t){
      .base =
          {
              .flags = SPI_TRANS_VARIABLE_CMD,
              .cmd = tmp_cmd,
          },
      .command_bits = 9,
  };

  return spi_device_transmit(spi_lcd_handler, (spi_transaction_t *)&trans);
}

/**
 * @brief Spi send data.
 *
 * @param data
 */
esp_err_t spi_send_data(uint8_t data) {
  uint16_t tmp_data = (data | 0x0100);

  spi_transaction_ext_t trans = (spi_transaction_ext_t){
      .base =
          {
              .flags = SPI_TRANS_VARIABLE_CMD,
              .cmd = tmp_data,
          },
      .command_bits = 9,
  };

  return spi_device_transmit(spi_lcd_handler, (spi_transaction_t *)&trans);
}

/**
 * @brief Spi send data.
 *
 * @param data
 */
esp_err_t spi_re_data(uint8_t data) {
  uint16_t tmp_data = (data | 0x0000);

  spi_transaction_ext_t trans = (spi_transaction_ext_t){
      .base =
          {
              .flags = SPI_TRANS_VARIABLE_CMD,
              .cmd = tmp_data,
          },
      .command_bits = 9,
  };

  return spi_device_transmit(spi_lcd_handler, (spi_transaction_t *)&trans);
}
