/***************************************************************************
 *
 * Copyright 2015-2019 BES.
 * All rights reserved. All unpublished rights reserved.
 *
 * No part of this work may be used or reproduced in any form or by any
 * means, or stored in a database or retrieval system, without prior written
 * permission of BES.
 *
 * Use of this work is governed by a license granted by BES.
 * This work contains confidential and proprietary information of
 * BES. which is protected by copyright, trade secret,
 * trademark and other intellectual property rights.
 *
 ****************************************************************************/
#ifdef CHIP_HAS_SPDIF

#include "hal_spdif.h"
#include "analog.h"
#include "cmsis.h"
#include "hal_iomux.h"
#include "hal_spdifip.h"
#include "hal_timer.h"
#include "hal_trace.h"
#include "plat_addr_map.h"
#include "plat_types.h"
#include "reg_spdifip.h"

// #define SPDIF_CLOCK_SOURCE 240000000
// #define SPDIF_CLOCK_SOURCE 22579200
// #define SPDIF_CLOCK_SOURCE 48000000
#define SPDIF_CLOCK_SOURCE 3072000

// #define SPDIF_CLOCK_SOURCE 76800000
// #define SPDIF_CLOCK_SOURCE 84672000

// Trigger DMA request when TX-FIFO count <= threshold
#define HAL_SPDIF_TX_FIFO_TRIGGER_LEVEL (SPDIFIP_FIFO_DEPTH / 2)
// Trigger DMA request when RX-FIFO count >= threshold
#define HAL_SPDIF_RX_FIFO_TRIGGER_LEVEL (SPDIFIP_FIFO_DEPTH / 2)

#define HAL_SPDIF_YES 1
#define HAL_SPDIF_NO 0

#ifdef CHIP_BEST2000
#define SPDIF_CMU_DIV CODEC_CMU_DIV
#else
#define SPDIF_CMU_DIV CODEC_PLL_DIV
#endif

enum HAL_SPDIF_STATUS_T {
  HAL_SPDIF_STATUS_NULL,
  HAL_SPDIF_STATUS_OPENED,
  HAL_SPDIF_STATUS_STARTED,
};

struct HAL_SPDIF_MOD_NAME_T {
  enum HAL_CMU_MOD_ID_T mod;
  enum HAL_CMU_MOD_ID_T apb;
};

struct HAL_SPDIF_MOD_NAME_T spdif_mod[HAL_SPDIF_ID_QTY] = {
    {
        .mod = HAL_CMU_MOD_O_SPDIF0,
        .apb = HAL_CMU_MOD_P_SPDIF0,
    },
#if (CHIP_HAS_SPDIF > 1)
    {
        .mod = HAL_CMU_MOD_O_SPDIF1,
        .apb = HAL_CMU_MOD_P_SPDIF1,
    },
#endif
};

static const char *const invalid_id = "Invalid SPDIF ID: %d\n";
// static const char * const invalid_ch = "Invalid SPDIF CH: %d\n";

struct SPDIF_SAMPLE_RATE_T {
  enum AUD_SAMPRATE_T sample_rate;
  uint32_t codec_freq;
  uint8_t codec_div;
  uint8_t pcm_div;
  uint8_t tx_ratio;
};

// SAMPLE_RATE * 128 = PLL_nominal / PCM_DIV / TX_RATIO
static const struct SPDIF_SAMPLE_RATE_T spdif_sample_rate[] = {
#if defined(CHIP_BEST1000) && defined(AUD_PLL_DOUBLE)
    {AUD_SAMPRATE_96000, CODEC_FREQ_48K_SERIES * 2, CODEC_PLL_DIV,
     SPDIF_CMU_DIV, 4},
    {AUD_SAMPRATE_192000, CODEC_FREQ_48K_SERIES * 2, CODEC_PLL_DIV,
     SPDIF_CMU_DIV, 2},
    {AUD_SAMPRATE_384000, CODEC_FREQ_48K_SERIES * 2, CODEC_PLL_DIV,
     SPDIF_CMU_DIV, 1},
#else
    {AUD_SAMPRATE_8000, CODEC_FREQ_48K_SERIES, CODEC_PLL_DIV, SPDIF_CMU_DIV,
     24},
    {AUD_SAMPRATE_16000, CODEC_FREQ_48K_SERIES, CODEC_PLL_DIV, SPDIF_CMU_DIV,
     12},
    {AUD_SAMPRATE_22050, CODEC_FREQ_44_1K_SERIES, CODEC_PLL_DIV, SPDIF_CMU_DIV,
     8},
    {AUD_SAMPRATE_24000, CODEC_FREQ_48K_SERIES, CODEC_PLL_DIV, SPDIF_CMU_DIV,
     8},
    {AUD_SAMPRATE_44100, CODEC_FREQ_44_1K_SERIES, CODEC_PLL_DIV, SPDIF_CMU_DIV,
     4},
    {AUD_SAMPRATE_48000, CODEC_FREQ_48K_SERIES, CODEC_PLL_DIV, SPDIF_CMU_DIV,
     4},
    {AUD_SAMPRATE_96000, CODEC_FREQ_48K_SERIES, CODEC_PLL_DIV, SPDIF_CMU_DIV,
     2},
    {AUD_SAMPRATE_192000, CODEC_FREQ_48K_SERIES, CODEC_PLL_DIV, SPDIF_CMU_DIV,
     1},
#endif
};

#if (CHIP_HAS_SPDIF > 1)
static uint8_t spdif_map;
STATIC_ASSERT(HAL_SPDIF_ID_QTY <= sizeof(spdif_map) * 8, "Too many SPDIF IDs");
#endif

static enum HAL_SPDIF_STATUS_T spdif_status[HAL_SPDIF_ID_QTY][AUD_STREAM_NUM];
static bool spdif_dma[HAL_SPDIF_ID_QTY][AUD_STREAM_NUM];

static inline uint32_t _spdif_get_reg_base(enum HAL_SPDIF_ID_T id) {
  ASSERT(id < HAL_SPDIF_ID_QTY, invalid_id, id);

  switch (id) {
  case HAL_SPDIF_ID_0:
  default:
    return SPDIF0_BASE;
    break;
#if (CHIP_HAS_SPDIF > 1)
  case HAL_SPDIF_ID_1:
    return SPDIF1_BASE;
    break;
#endif
  }
  return 0;
}

int hal_spdif_open(enum HAL_SPDIF_ID_T id, enum AUD_STREAM_T stream) {
  uint32_t reg_base;

  reg_base = _spdif_get_reg_base(id);

  if (spdif_status[id][stream] != HAL_SPDIF_STATUS_NULL) {
    TRACE(2, "Invalid SPDIF opening status for stream %d: %d", stream,
          spdif_status[id][stream]);
    return 1;
  }

  if (spdif_status[id][AUD_STREAM_PLAYBACK] == HAL_SPDIF_STATUS_NULL &&
      spdif_status[id][AUD_STREAM_CAPTURE] == HAL_SPDIF_STATUS_NULL) {
#ifndef SIMU
    bool cfg_pll = true;
    int i;

    for (i = HAL_SPDIF_ID_0; i < HAL_SPDIF_ID_QTY; i++) {
      if (spdif_status[i][AUD_STREAM_PLAYBACK] != HAL_SPDIF_STATUS_NULL ||
          spdif_status[i][AUD_STREAM_CAPTURE] != HAL_SPDIF_STATUS_NULL) {
        cfg_pll = false;
        break;
      }
    }
    if (cfg_pll) {
      analog_aud_pll_open(ANA_AUD_PLL_USER_SPDIF);
    }
#endif

#if (CHIP_HAS_SPDIF > 1)
    if (id == HAL_SPDIF_ID_1) {
      hal_iomux_set_spdif1();
    } else
#endif
    {
      hal_iomux_set_spdif0();
    }
    hal_cmu_spdif_clock_enable(id);
    hal_cmu_clock_enable(spdif_mod[id].mod);
    hal_cmu_clock_enable(spdif_mod[id].apb);
    hal_cmu_reset_clear(spdif_mod[id].mod);
    hal_cmu_reset_clear(spdif_mod[id].apb);

    spdifip_w_enable_spdifip(reg_base, HAL_SPDIF_YES);
  }

  spdif_dma[id][stream] = false;
  spdif_status[id][stream] = HAL_SPDIF_STATUS_OPENED;

  return 0;
}

int hal_spdif_close(enum HAL_SPDIF_ID_T id, enum AUD_STREAM_T stream) {
  uint32_t reg_base;

  if (id >= HAL_SPDIF_ID_QTY) {
    return 1;
  }

  if (spdif_status[id][stream] != HAL_SPDIF_STATUS_OPENED) {
    TRACE(2, "Invalid SPDIF closing status for stream %d: %d", stream,
          spdif_status[id][stream]);
    return 1;
  }

  spdif_status[id][stream] = HAL_SPDIF_STATUS_NULL;

  if (spdif_status[id][AUD_STREAM_PLAYBACK] == HAL_SPDIF_STATUS_NULL &&
      spdif_status[id][AUD_STREAM_CAPTURE] == HAL_SPDIF_STATUS_NULL) {
    reg_base = _spdif_get_reg_base(id);

    spdifip_w_enable_spdifip(reg_base, HAL_SPDIF_NO);

    hal_cmu_spdif_set_div(id, 0x1FFF + 2);
    hal_cmu_reset_set(spdif_mod[id].apb);
    hal_cmu_reset_set(spdif_mod[id].mod);
    hal_cmu_clock_disable(spdif_mod[id].apb);
    hal_cmu_clock_disable(spdif_mod[id].mod);
    hal_cmu_spdif_clock_disable(id);

#ifndef SIMU
    bool cfg_pll = true;
    int i;

    for (i = HAL_SPDIF_ID_0; i < HAL_SPDIF_ID_QTY; i++) {
      if (spdif_status[i][AUD_STREAM_PLAYBACK] != HAL_SPDIF_STATUS_NULL ||
          spdif_status[i][AUD_STREAM_CAPTURE] != HAL_SPDIF_STATUS_NULL) {
        cfg_pll = false;
        break;
      }
    }
    if (cfg_pll) {
      analog_aud_pll_close(ANA_AUD_PLL_USER_SPDIF);
    }
#endif
  }

  return 0;
}

int hal_spdif_start_stream(enum HAL_SPDIF_ID_T id, enum AUD_STREAM_T stream) {
  uint32_t reg_base;
  uint32_t lock;

  reg_base = _spdif_get_reg_base(id);

  if (spdif_status[id][stream] != HAL_SPDIF_STATUS_OPENED) {
    TRACE(2, "Invalid SPDIF starting status for stream %d: %d", stream,
          spdif_status[id][stream]);
    return 1;
  }

  if (stream == AUD_STREAM_PLAYBACK) {
    lock = int_lock();
    spdifip_w_enable_tx_channel0(reg_base, HAL_SPDIF_YES);
    spdifip_w_enable_tx(reg_base, HAL_SPDIF_YES);
    spdifip_w_tx_valid(reg_base, HAL_SPDIF_YES);
    if (spdif_dma[id][stream]) {
      spdifip_w_enable_tx_dma(reg_base, HAL_SPDIF_YES);
    }
    int_unlock(lock);
  } else {
    if (spdif_dma[id][stream]) {
      spdifip_w_enable_rx_dma(reg_base, HAL_SPDIF_YES);
    }
    spdifip_w_enable_rx_channel0(reg_base, HAL_SPDIF_YES);
    spdifip_w_enable_rx(reg_base, HAL_SPDIF_YES);
    spdifip_w_sample_en(reg_base, HAL_SPDIF_YES);
  }

  spdif_status[id][stream] = HAL_SPDIF_STATUS_STARTED;

  return 0;
}

int hal_spdif_stop_stream(enum HAL_SPDIF_ID_T id, enum AUD_STREAM_T stream) {
  uint32_t reg_base;

  reg_base = _spdif_get_reg_base(id);

  if (spdif_status[id][stream] != HAL_SPDIF_STATUS_STARTED) {
    TRACE(2, "Invalid SPDIF stopping status for stream %d: %d", stream,
          spdif_status[id][stream]);
    return 1;
  }

  spdif_status[id][stream] = HAL_SPDIF_STATUS_OPENED;

  if (stream == AUD_STREAM_PLAYBACK) {
    spdifip_w_enable_tx(reg_base, HAL_SPDIF_NO);
    spdifip_w_enable_tx_channel0(reg_base, HAL_SPDIF_NO);
    spdifip_w_enable_tx_dma(reg_base, HAL_SPDIF_NO);
    spdifip_w_tx_fifo_reset(reg_base);
  } else {
    spdifip_w_enable_rx(reg_base, HAL_SPDIF_NO);
    spdifip_w_enable_rx_channel0(reg_base, HAL_SPDIF_NO);
    spdifip_w_enable_rx_dma(reg_base, HAL_SPDIF_NO);
    spdifip_w_rx_fifo_reset(reg_base);
  }

  return 0;
}

int hal_spdif_setup_stream(enum HAL_SPDIF_ID_T id, enum AUD_STREAM_T stream,
                           struct HAL_SPDIF_CONFIG_T *cfg) {
  uint8_t i;
  uint32_t reg_base;
  uint8_t fmt;

  reg_base = _spdif_get_reg_base(id);

  if (spdif_status[id][stream] != HAL_SPDIF_STATUS_OPENED) {
    TRACE(2, "Invalid SPDIF setup status for stream %d: %d", stream,
          spdif_status[id][stream]);
    return 1;
  }

  for (i = 0; i < ARRAY_SIZE(spdif_sample_rate); i++) {
    if (spdif_sample_rate[i].sample_rate == cfg->sample_rate) {
      break;
    }
  }
  ASSERT(i < ARRAY_SIZE(spdif_sample_rate), "%s: Invalid spdif sample rate: %d",
         __func__, cfg->sample_rate);

  TRACE(3, "[%s] stream=%d sample_rate=%d", __func__, stream, cfg->sample_rate);

#ifndef SIMU
  analog_aud_freq_pll_config(spdif_sample_rate[i].codec_freq,
                             spdif_sample_rate[i].codec_div);
#ifdef CHIP_BEST2000
  analog_aud_pll_set_dig_div(spdif_sample_rate[i].codec_div /
                             spdif_sample_rate[i].pcm_div);
#endif
#endif
  // SPDIF module is working on 24.576M or 22.5792M
  hal_cmu_spdif_set_div(id, spdif_sample_rate[i].pcm_div);

  if ((stream == AUD_STREAM_PLAYBACK &&
       spdif_status[id][AUD_STREAM_CAPTURE] == HAL_SPDIF_STATUS_NULL) ||
      (stream == AUD_STREAM_CAPTURE &&
       spdif_status[id][AUD_STREAM_PLAYBACK] == HAL_SPDIF_STATUS_NULL)) {
    hal_cmu_reset_pulse(spdif_mod[id].mod);
  }

  spdif_dma[id][stream] = cfg->use_dma;

  fmt = 0;
  switch (cfg->bits) {
  case AUD_BITS_16:
    fmt = 0;
    break;
  // here, 32-bit is treated as 24-bit
  case AUD_BITS_32:
  case AUD_BITS_24:
    fmt = 8;
    break;
  default:
    ASSERT(0, "%s: invalid bits[%d]", __func__, cfg->bits);
  }

  if (stream == AUD_STREAM_PLAYBACK) {
    spdifip_w_tx_ratio(reg_base, spdif_sample_rate[i].tx_ratio - 1);
    spdifip_w_tx_format_cfg_reg(reg_base, fmt);
    spdifip_w_tx_fifo_threshold(reg_base, HAL_SPDIF_TX_FIFO_TRIGGER_LEVEL);
  } else {
    spdifip_w_rx_format_cfg_reg(reg_base, fmt);
    spdifip_w_rx_fifo_threshold(reg_base, HAL_SPDIF_RX_FIFO_TRIGGER_LEVEL);
  }

  return 0;
}

int hal_spdif_send(enum HAL_SPDIF_ID_T id, uint8_t *value, uint32_t value_len) {
  uint32_t i = 0;
  uint32_t reg_base;

  reg_base = _spdif_get_reg_base(id);

  for (i = 0; i < value_len; i += 4) {
    while (!(spdifip_r_int_status(reg_base) &
             SPDIFIP_INT_STATUS_TX_FIFO_EMPTY_MASK))
      ;

    spdifip_w_tx_left_fifo(reg_base, value[i + 1] << 8 | value[i]);
    spdifip_w_tx_right_fifo(reg_base, value[i + 3] << 8 | value[i + 2]);
  }

  return 0;
}

uint8_t hal_spdif_recv(enum HAL_SPDIF_ID_T id, uint8_t *value,
                       uint32_t value_len) {
  // uint32_t reg_base;

  // reg_base = _spdif_get_reg_base(id);
  return 0;
}

int hal_spdif_clock_out_enable(enum HAL_SPDIF_ID_T id, uint32_t div) {
  if (id >= HAL_SPDIF_ID_QTY) {
    return 1;
  }

  hal_cmu_spdif_clock_enable(id);
  hal_cmu_spdif_set_div(id, div);

  return 0;
}

int hal_spdif_clock_out_disable(enum HAL_SPDIF_ID_T id) {
  if (id >= HAL_SPDIF_ID_QTY) {
    return 1;
  }

  hal_cmu_spdif_set_div(id, 0x1FFF + 2);
  hal_cmu_spdif_clock_disable(id);

  return 0;
}

#endif
