/**
  *********************************************************************************
  *
  * @file    hal_i2s.c
  * @brief   I2S module driver.
  *          This file provides firmware functions to manage the following
  *          functionalities of I2S peripheral:
  *           + Initialization functions
  *           + IO operation functions
  *           + Peripheral Control functions
  *           + Peripheral State functions
  *
  * @version V1.0
  * @date    08 Jan 2018
  * @author  AE Team
  * @note
  *
  * Copyright (C) Shanghai Eastsoft Microelectronics Co. Ltd. All rights reserved.
  *
  *********************************************************************************
  @verbatim
  ==============================================================================
                        ##### How to use this driver #####
  ==============================================================================
    [..]
      The I2S driver can be used as follows:

      (#) Declare a i2s_handle_t structure, for example:
          i2s_handle_t hperh;

      (#) Initialize the I2S low level resources:
          (##) Enable the I2Sx interface clock
          (##) I2S pins configuration
              (+++) Enable the clock for the I2S GPIOs
              (+++) Configure these I2S pins as push-pull
          (##) NVIC configuration if you need to use interrupt process
               by implementing the mcu_irq_config() API.
	       Invoked i2s_irq_handle() function in I2S-IRQ function
          (##) DMA Configuration if you need to use DMA process
              (+++) Define HAL_DMA in hal_conf.h
 	      (+++) Enable the DMAx clock

      (#) Program the Mode, Standard, Direction, Data length, Channel length, Baudrate Prescaler, 
          Clock polarity.

      (#) Initialize the I2S module by invoking the i2s_init() API.

     [..]
       Circular mode restriction:
      (#) When the I2S DMA Pause/Stop features are used, we must use the following APIs
          the i2s_dma_pause()/ i2s_dma_stop().

  * @endverbatim
  */

#include "hal_i2s.h"


/** @addtogroup ES32FXXX_HAL
  * @{
  */

/** @defgroup I2S I2S
  * @brief I2S module driver
  * @{
  */
#ifdef HAL_I2S

/** @addtogroup I2S_Private_Functions   I2S Private Functions
  * @{
  */
static hal_status_t i2s_wait_flag(i2s_handle_t *hperh, i2s_flag_t flag, flag_status_t status, uint32_t timeout);
static void __i2s_send_by_it(i2s_handle_t *hperh);
static void __i2s_recv_by_it(i2s_handle_t *hperh);
#ifdef HAL_DMA
static void i2s_dma_send_cplt(void *arg);
static void i2s_dma_recv_cplt(void *arg);
static void i2s_dma_error(void *arg);
#endif
/**
  * @}
  */

/** @defgroup I2S_Public_Functions I2S Public Functions
  * @{
  */

/** @defgroup I2S_Public_Functions_Group1 Initialization functions
  * @brief Initialization and Configuration functions
  *
  * @verbatim
 ===============================================================================
              ##### Initialization and de-initialization functions #####
 ===============================================================================
    [..]  This subsection provides a set of functions allowing to initialize and
          reset the I2Sx peripheral:

      (+) User must configure all related peripherals resources
          (CLOCK, GPIO, DMA, NVIC).

      (+) Call the function i2s_init() to configure the selected device with
          the selected configuration:
        (++) Mode
        (++) Direction
        (++) Data Size
        (++) Clock Polarity and Phase
        (++) NSS Management
        (++) BaudRate Prescaler
        (++) FirstBit
        (++) TIMode
        (++) CRC Calculation
        (++) CRC Polynomial if CRC enabled

      (+) Call the function i2s_reset() to reset the selected I2Sx periperal.

    @endverbatim
  * @{
  */

/**
  * @brief  Reset the I2S peripheral.
  * @param  hperh: Pointer to a i2s_handle_t structure that contains
  *         the configuration information for the specified I2S module.
  * @retval None
  */
void i2s_reset(i2s_handle_t *hperh)
{
	hperh->perh->I2SCFGR.Word = 0x0;
	hperh->perh->I2SPR.Word   = 0x2;

	I2S_RESET_HANDLE_STATE(hperh);
	__UNLOCK(hperh);

	return;
}

/**
  * @brief  Initializes the I2S mode according to the specified parameters in
  *         the i2s_init_t and create the associated handle.
  * @param  hperh: Pointer to a i2s_handle_t structure that contains
  *         the configuration information for the specified I2S module.
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_init(i2s_handle_t *hperh)
{
	if (hperh == NULL)
		return ERROR;

	assert_param(IS_I2S(hperh->perh));
	assert_param(IS_I2S_CH_LEN(hperh->init.ch_len));
	assert_param(IS_I2S_DATE_LEN(hperh->init.data_len));
	assert_param(IS_I2S_CPOL(hperh->init.polarity));
	assert_param(IS_I2S_STANDARD(hperh->init.standard));
	assert_param(IS_FUNC_STATE(hperh->init.ext_ck));
	assert_param(IS_FUNC_STATE(hperh->init.mck_en));
	assert_param(IS_I2S_PCMS(hperh->init.pcm_frame));
	assert_param(IS_I2S_ODD(hperh->init.odd));
	assert_param(IS_I2S_DIV(hperh->init.div));

	i2s_reset(hperh);
	
	hperh->perh->I2SPR.EXTCKEN = hperh->init.ext_ck;
	hperh->perh->I2SPR.ODD = hperh->init.odd;
	hperh->perh->I2SPR.I2SDIV = hperh->init.div;
	hperh->perh->I2SCFGR.CHLEN = hperh->init.ch_len;
	hperh->perh->I2SCFGR.DATLEN = hperh->init.data_len;
	hperh->perh->I2SCFGR.CKPOL = hperh->init.polarity;
	hperh->perh->I2SCFGR.I2SSTD = hperh->init.standard;
	hperh->perh->I2SCFGR.I2SMOD = 1;
	
	if(hperh->init.standard == I2S_PCM_STANDARD)
		hperh->perh->I2SCFGR.PCMSYNC = hperh->init.pcm_frame;


	hperh->err_code = I2S_ERROR_NONE;
	hperh->state    = I2S_STATE_READY;

	return OK;
}

/**
  * @}
  */

/** @defgroup I2S_Public_Functions_Group2 IO operation functions
  * @brief I2S Transmit and Receive functions
  *
  * @verbatim
  ==============================================================================
                      ##### IO operation functions #####
 ===============================================================================
    This subsection provides a set of functions allowing to manage the I2S
    data transfers.

    [..] The I2S supports master or slave mode:

    (#) There are two modes of transfer:
       (++) Blocking mode: The communication is performed in polling mode.
            The HAL status of all data processing is returned by the same function
            after finishing transfer.
       (++) No-Blocking mode: The communication is performed using Interrupts
            or DMA, These APIs return the HAL status.
            The end of the data processing will be indicated through the
            dedicated I2S IRQ when using Interrupt mode or the DMA IRQ when
            using DMA mode.
            The hperh->tx_cplt_cbk(), hperh->rx_cplt_cbk() and hperh->tx_rx_cplt_cbk() user callbacks
            will be executed respectivelly at the end of the transmit or Receive process
            The hperh->err_cbk() user callback will be executed when a communication error is detected

    (#) APIs provided for these 2 transfer modes (Blocking mode or Non blocking mode using either Interrupt or DMA)
        exist for 1Line (simplex) and 2Lines (full duplex) modes.

  * @endverbatim
  * @{
  */

/**
  * @brief  Master mode transmit an amount of data in blocking mode.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data buffer
  * @param  size: Amount of data to be sent
  * @param  timeout: Timeout duration
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_master_send(i2s_handle_t *hperh, uint16_t *buf, uint16_t size, uint32_t timeout)
{
	assert_param(IS_I2S(hperh->perh));

	if (hperh->state != I2S_STATE_READY)
		return BUSY;

	if (buf == NULL || size == 0)
		return ERROR;

	__LOCK(hperh);

	hperh->state    = I2S_STATE_BUSY_TX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->tx_buf   = buf;
	hperh->tx_size  = size;
	hperh->tx_count = size;
	hperh->rx_buf   = NULL;
	hperh->rx_size  = 0;
	hperh->rx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_MASTER_TRANSMIT;

	if (hperh->init.mck_en)
		hperh->perh->I2SPR.MCKOE = hperh->init.mck_en;

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);

	while (hperh->tx_count > 0) {
		if (i2s_wait_flag(hperh, I2S_FLAG_TXE, SET, timeout) != OK) {
			I2S_DISABLE(hperh);

			hperh->state = I2S_STATE_READY;
			__UNLOCK(hperh);
			return TIMEOUT;
		}

		hperh->side = hperh->perh->SR.CHSIDE;
		hperh->perh->DR.DR = *hperh->tx_buf;
		hperh->tx_buf++;
		--hperh->tx_count;		
	}

 	if ((i2s_wait_flag(hperh, I2S_FLAG_TXE, SET, timeout) != OK)
			|| (i2s_wait_flag(hperh, I2S_FLAG_BSY, RESET, timeout) != OK)) {
 		I2S_DISABLE(hperh);
 		hperh->state = I2S_STATE_READY;
 		__UNLOCK(hperh);
 		return TIMEOUT;
 	}

 	I2S_DISABLE(hperh);
	hperh->state = I2S_STATE_READY;
	__UNLOCK(hperh);

	return OK;
}

/**
  * @brief  Master mode receive an amount of data in blocking mode.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data buffer
  * @param  size: Amount of data to be received
  * @param  timeout: Timeout duration
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_master_recv(i2s_handle_t *hperh, uint16_t *buf, uint16_t size, uint32_t timeout)
{
	assert_param(IS_I2S(hperh->perh));

	if(hperh->state != I2S_STATE_READY)
		return BUSY;
	if(buf == NULL || size == 0)
		return ERROR;

	__LOCK(hperh);
	hperh->state    = I2S_STATE_BUSY_RX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->rx_buf   = buf;
	hperh->rx_size  = size;
	hperh->rx_count = size;
	hperh->tx_buf   = NULL;
	hperh->tx_size  = 0;
	hperh->tx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_MASTER_RECEIVE;

	if (hperh->init.mck_en)
		hperh->perh->I2SPR.MCKOE = hperh->init.mck_en;

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);

	while (hperh->rx_count > 0) {
		*((uint16_t *)&(hperh->perh->DR)) = 0xffff;
		if (i2s_wait_flag(hperh, I2S_FLAG_RXE, RESET, timeout) != OK) {
			I2S_DISABLE(hperh);
			hperh->state = I2S_STATE_READY;
			__UNLOCK(hperh);
			return TIMEOUT;
		}

		*hperh->rx_buf = hperh->perh->DR.DR;
		hperh->rx_buf++;
		--hperh->rx_count;

	}

	hperh->state = I2S_STATE_READY;
	__UNLOCK(hperh);

	return OK;
}

/**
  * @brief  Slave mode transmit an amount of data in blocking mode.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data buffer
  * @param  size: Amount of data to be sent
  * @param  timeout: Timeout duration
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_slave_send(i2s_handle_t *hperh, uint16_t *buf, uint16_t size, uint32_t timeout)
{
	assert_param(IS_I2S(hperh->perh));

	if (hperh->state != I2S_STATE_READY)
		return BUSY;

	if (buf == NULL || size == 0)
		return ERROR;

	__LOCK(hperh);

	hperh->state    = I2S_STATE_BUSY_TX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->tx_buf   = buf;
	hperh->tx_size  = size;
	hperh->tx_count = size;
	hperh->rx_buf   = NULL;
	hperh->rx_size  = 0;
	hperh->rx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_SLAVE_TRANSMIT;

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);

	while (hperh->tx_count > 0) {
		if (i2s_wait_flag(hperh, I2S_FLAG_TXE, SET, timeout) != OK) {
			I2S_DISABLE(hperh);

			hperh->state = I2S_STATE_READY;
			__UNLOCK(hperh);
			return TIMEOUT;
		}

		hperh->side = hperh->perh->SR.CHSIDE;
		hperh->perh->DR.DR = *hperh->tx_buf;
		hperh->tx_buf++;
		--hperh->tx_count;		
	}

 	if ((i2s_wait_flag(hperh, I2S_FLAG_TXE, SET, timeout) != OK)
			|| (i2s_wait_flag(hperh, I2S_FLAG_BSY, RESET, timeout) != OK)) {
 		I2S_DISABLE(hperh);
 		hperh->state = I2S_STATE_READY;
 		__UNLOCK(hperh);
 		return TIMEOUT;
 	}

 	I2S_DISABLE(hperh);
	hperh->state = I2S_STATE_READY;
	__UNLOCK(hperh);

	return OK;
}

/**
  * @brief  Slave mode receive an amount of data in blocking mode.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data buffer
  * @param  size: Amount of data to be received
  * @param  timeout: Timeout duration
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_slave_recv(i2s_handle_t *hperh, uint16_t *buf, uint16_t size, uint32_t timeout)
{
	assert_param(IS_I2S(hperh->perh));

	if(hperh->state != I2S_STATE_READY)
		return BUSY;
	if(buf == NULL || size == 0)
		return ERROR;

	__LOCK(hperh);
	hperh->state    = I2S_STATE_BUSY_RX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->rx_buf   = buf;
	hperh->rx_size  = size;
	hperh->rx_count = size;
	hperh->tx_buf   = NULL;
	hperh->tx_size  = 0;
	hperh->tx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_SLAVE_RECEIVE;

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);

	while (hperh->rx_count > 0) {
		if (i2s_wait_flag(hperh, I2S_FLAG_RXE, RESET, timeout) != OK) {
			I2S_DISABLE(hperh);
			hperh->state = I2S_STATE_READY;
			__UNLOCK(hperh);
			return TIMEOUT;
		}
		*hperh->rx_buf = hperh->perh->DR.DR;
		hperh->rx_buf++;
		--hperh->rx_count;	
	}

	hperh->state = I2S_STATE_READY;
	__UNLOCK(hperh);

	return OK;
}

/**
  * @brief  Wraps up master mode transmission in non blocking mode.
  * @param  hperh: pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data transmitted buffer
  * @param  size: Amount of data to be sent
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_master_send_by_it(i2s_handle_t *hperh, uint16_t *buf, uint16_t size)
{
	assert_param(IS_I2S(hperh->perh));

	if(hperh->state != I2S_STATE_READY)
		return BUSY;
	if(buf == NULL || size == 0)
		return ERROR;

	__LOCK(hperh);
	hperh->state    = I2S_STATE_BUSY_TX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->tx_buf   = buf;
	hperh->tx_size  = size;
	hperh->tx_count = size;
	hperh->rx_buf   = NULL;
	hperh->rx_size  = 0;
	hperh->rx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_MASTER_TRANSMIT;

	if (hperh->init.mck_en)
		hperh->perh->I2SPR.MCKOE = hperh->init.mck_en;

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);

	i2s_interrupt_config(hperh, I2S_IT_TXE, ENABLE);

	__UNLOCK(hperh);
	return OK;
}

/**
  * @brief  Master mode receives an amount of data in non blocking mode
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data received buffer
  * @param  size: Amount of data to be sent
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_master_recv_by_it(i2s_handle_t *hperh, uint16_t *buf, uint16_t size)
{
	assert_param(IS_I2S(hperh->perh));

	if (hperh->state != I2S_STATE_READY)
		return BUSY;
	if (buf == NULL || size == 0)
		return ERROR;

	__LOCK(hperh);
	hperh->state    = I2S_STATE_BUSY_RX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->rx_buf   = buf;
	hperh->rx_size  = size;
	hperh->rx_count = size;
	hperh->tx_buf   = NULL;
	hperh->tx_size  = 0;
	hperh->tx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_MASTER_RECEIVE;

	if (hperh->init.mck_en)
		hperh->perh->I2SPR.MCKOE = hperh->init.mck_en;

	i2s_interrupt_config(hperh, I2S_IT_RXF, ENABLE);

	__UNLOCK(hperh);

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);

	return OK;
}

/**
  * @brief  Wraps up slave mode transmission in non blocking mode.
  * @param  hperh: pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data transmitted buffer
  * @param  size: Amount of data to be sent
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_slave_send_by_it(i2s_handle_t *hperh, uint16_t *buf, uint16_t size)
{
	assert_param(IS_I2S(hperh->perh));

	if(hperh->state != I2S_STATE_READY)
		return BUSY;
	if(buf == NULL || size == 0)
		return ERROR;

	__LOCK(hperh);
	hperh->state    = I2S_STATE_BUSY_TX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->tx_buf   = buf;
	hperh->tx_size  = size;
	hperh->tx_count = size - 1;
	hperh->rx_buf   = NULL;
	hperh->rx_size  = 0;
	hperh->rx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_SLAVE_TRANSMIT;

	i2s_interrupt_config(hperh, I2S_IT_TXE, ENABLE);

	__UNLOCK(hperh);

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);

	return OK;
}

/**
  * @brief  Slave mode receives an amount of data in non blocking mode
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data received buffer
  * @param  size: Amount of data to be sent
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_slave_recv_by_it(i2s_handle_t *hperh, uint16_t *buf, uint16_t size)
{
	assert_param(IS_I2S(hperh->perh));

	if (hperh->state != I2S_STATE_READY)
		return BUSY;
	if (buf == NULL || size == 0)
		return ERROR;

	__LOCK(hperh);
	hperh->state    = I2S_STATE_BUSY_RX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->rx_buf   = buf;
	hperh->rx_size  = size;
	hperh->rx_count = size;
	hperh->tx_buf   = NULL;
	hperh->tx_size  = 0;
	hperh->tx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_SLAVE_RECEIVE;


	__UNLOCK(hperh);

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);

	i2s_interrupt_config(hperh, I2S_IT_RXTH, ENABLE);

	return OK;
}

#ifdef HAL_DMA
/**
  * @brief  Master mode transmit an amount of data used dma channel
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data buffer
  * @param  size: Amount of data to be sent
  * @param  channel: DMA channel as I2S transmit
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_master_send_by_dma(i2s_handle_t *hperh, uint16_t *buf, uint16_t size, uint8_t channel)
{
	assert_param(IS_I2S(hperh->perh));

	if (hperh->state != I2S_STATE_READY)
		return BUSY;
	if (buf == NULL|| size == 0)
		return ERROR;

	__LOCK(hperh);
	hperh->state    = I2S_STATE_BUSY_TX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->tx_buf   = buf;
	hperh->tx_size  = size;
	hperh->tx_count = size;
	hperh->rx_buf   = NULL;
	hperh->rx_size  = 0;
	hperh->rx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_MASTER_TRANSMIT;

	if (hperh->init.mck_en)
		hperh->perh->I2SPR.MCKOE = hperh->init.mck_en;

	hperh->hdma.cplt_arg = (void *)hperh;
	hperh->hdma.cplt_cbk = i2s_dma_send_cplt;
	hperh->hdma.err_arg  = (void *)hperh;
	hperh->hdma.err_cbk  = i2s_dma_error;

	/* Configure I2S DMA transmit */
	dma_config_struct(&(hperh->hdma.config));

	hperh->hdma.config.data_width = DMA_DATA_SIZE_HALFWORD;
	hperh->hdma.config.src        = (void *)buf;
	hperh->hdma.config.dst        = (void *)&hperh->perh->DR.Word;
	hperh->hdma.config.size       = size;
	hperh->hdma.config.src_inc    = DMA_DATA_INC_HALFWORD;
	hperh->hdma.config.dst_inc    = DMA_DATA_INC_NONE;
	hperh->hdma.config.msel       = hperh->perh == I2S0 ? DMA_MSEL_SPI0 : (hperh->perh == I2S1 ? DMA_MSEL_SPI1 : DMA_MSEL_SPI2);
	hperh->hdma.config.msigsel    = DMA_MSIGSEL_SPI_TXEMPTY;
	hperh->hdma.config.channel    = channel;
	hperh->hdma.config.burst      = ENABLE;
	dma_config_basic(&(hperh->hdma));

	i2s_dma_req_config(hperh, I2S_DMA_REQ_TX, ENABLE);
	__UNLOCK(hperh);

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);

	return OK;
}

/**
  * @brief  Master mode receive an amount of data used dma channel
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data buffer
  * @param  size: Amount of data to be sent
  * @param  channel: DMA channel as I2S transmit
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_master_recv_by_dma(i2s_handle_t *hperh, uint16_t *buf, uint16_t size, uint8_t channel)
{
	assert_param(IS_I2S(hperh->perh));

	if (hperh->state != I2S_STATE_READY)
		return BUSY;
	if (buf == NULL|| size == 0)
		return ERROR;

	__LOCK(hperh);
	hperh->state    = I2S_STATE_BUSY_RX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->rx_buf   = buf;
	hperh->rx_size  = size;
	hperh->rx_count = size;
	hperh->tx_buf   = NULL;
	hperh->tx_size  = 0;
	hperh->tx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_MASTER_RECEIVE;

	if (hperh->init.mck_en)
		hperh->perh->I2SPR.MCKOE = hperh->init.mck_en;

	hperh->hdma.cplt_arg = (void *)hperh;
	hperh->hdma.cplt_cbk = i2s_dma_recv_cplt;
	hperh->hdma.err_arg  = (void *)hperh;
	hperh->hdma.err_cbk  = i2s_dma_error;

	/* Configure DMA Receive */
	dma_config_struct(&(hperh->hdma.config));

	hperh->hdma.config.data_width = DMA_DATA_SIZE_HALFWORD;
	hperh->hdma.config.src        = (void *)&hperh->perh->DR.Word;
	hperh->hdma.config.dst        = (void *)buf;
	hperh->hdma.config.size       = size;
	hperh->hdma.config.src_inc    = DMA_DATA_INC_NONE;
	hperh->hdma.config.dst_inc    = DMA_DATA_INC_HALFWORD;
	hperh->hdma.config.msel       = hperh->perh == I2S0 ? DMA_MSEL_SPI0 : (hperh->perh == I2S1 ? DMA_MSEL_SPI1 : DMA_MSEL_SPI2);
	hperh->hdma.config.msigsel    = DMA_MSIGSEL_SPI_RNR;
	hperh->hdma.config.channel    = channel;
	hperh->hdma.config.burst      = ENABLE;
	dma_config_basic(&(hperh->hdma));

	i2s_dma_req_config(hperh, I2S_DMA_REQ_RX, ENABLE);
	__UNLOCK(hperh);

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);

	return OK;
}

/**
  * @brief  Slave mode transmit an amount of data used dma channel
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data buffer
  * @param  size: Amount of data to be sent
  * @param  channel: DMA channel as I2S transmit
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_slave_send_by_dma(i2s_handle_t *hperh, uint16_t *buf, uint16_t size, uint8_t channel)
{
	assert_param(IS_I2S(hperh->perh));

	if (hperh->state != I2S_STATE_READY)
		return BUSY;
	if (buf == NULL|| size == 0)
		return ERROR;

	__LOCK(hperh);
	hperh->state    = I2S_STATE_BUSY_TX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->tx_buf   = buf;
	hperh->tx_size  = size;
	hperh->tx_count = size;
	hperh->rx_buf   = NULL;
	hperh->rx_size  = 0;
	hperh->rx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_SLAVE_TRANSMIT;

	hperh->hdma.cplt_arg = (void *)hperh;
	hperh->hdma.cplt_cbk = i2s_dma_send_cplt;
	hperh->hdma.err_arg  = (void *)hperh;
	hperh->hdma.err_cbk  = i2s_dma_error;

	/* Configure I2S DMA transmit */
	dma_config_struct(&(hperh->hdma.config));

	hperh->hdma.config.data_width = DMA_DATA_SIZE_HALFWORD;
	hperh->hdma.config.src        = (void *)buf;
	hperh->hdma.config.dst        = (void *)&hperh->perh->DR.Word;
	hperh->hdma.config.size       = size;
	hperh->hdma.config.src_inc    = DMA_DATA_INC_HALFWORD;
	hperh->hdma.config.dst_inc    = DMA_DATA_INC_NONE;
	hperh->hdma.config.msel       = hperh->perh == I2S0 ? DMA_MSEL_SPI0 : (hperh->perh == I2S1 ? DMA_MSEL_SPI1 : DMA_MSEL_SPI2);
	hperh->hdma.config.msigsel    = DMA_MSIGSEL_SPI_TXEMPTY;
	hperh->hdma.config.channel    = channel;
	hperh->hdma.config.burst      = ENABLE;
	dma_config_basic(&(hperh->hdma));

	i2s_dma_req_config(hperh, I2S_DMA_REQ_TX, ENABLE);
	__UNLOCK(hperh);

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);

	return OK;
}

/**
  * @brief  Slave mode receive an amount of data used dma channel
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  buf: Pointer to data buffer
  * @param  size: Amount of data to be sent
  * @param  channel: DMA channel as I2S transmit
  * @retval Status, see @ref hal_status_t.
  */
hal_status_t i2s_slave_recv_by_dma(i2s_handle_t *hperh, uint16_t *buf, uint16_t size, uint8_t channel)
{
	assert_param(IS_I2S(hperh->perh));

	if (hperh->state != I2S_STATE_READY)
		return BUSY;
	if (buf == NULL|| size == 0)
		return ERROR;

	__LOCK(hperh);
	hperh->state    = I2S_STATE_BUSY_RX;
	hperh->err_code = I2S_ERROR_NONE;

	hperh->rx_buf   = buf;
	hperh->rx_size  = size;
	hperh->rx_count = size;
	hperh->tx_buf   = NULL;
	hperh->tx_size  = 0;
	hperh->tx_count = 0;

	hperh->perh->I2SCFGR.I2SCFG = I2S_SLAVE_RECEIVE;

	hperh->hdma.cplt_arg = (void *)hperh;
	hperh->hdma.cplt_cbk = i2s_dma_recv_cplt;
	hperh->hdma.err_arg  = (void *)hperh;
	hperh->hdma.err_cbk  = i2s_dma_error;

	/* Configure DMA Receive */
	dma_config_struct(&(hperh->hdma.config));

	hperh->hdma.perh              = DMA0;
	hperh->hdma.config.data_width = DMA_DATA_SIZE_HALFWORD;
	hperh->hdma.config.src        = (void *)&hperh->perh->DR.Word;
	hperh->hdma.config.dst        = (void *)buf;
	hperh->hdma.config.size       = size;
	hperh->hdma.config.src_inc    = DMA_DATA_INC_NONE;
	hperh->hdma.config.dst_inc    = DMA_DATA_INC_HALFWORD;
	hperh->hdma.config.msel       = hperh->perh == I2S0 ? DMA_MSEL_SPI0 : (hperh->perh == I2S1 ? DMA_MSEL_SPI1 : DMA_MSEL_SPI2);
	hperh->hdma.config.msigsel    = DMA_MSIGSEL_SPI_RNR;
	hperh->hdma.config.channel    = channel;
	hperh->hdma.config.burst      = ENABLE;
	dma_config_basic(&(hperh->hdma));

	i2s_dma_req_config(hperh, I2S_DMA_REQ_RX, ENABLE);
	__UNLOCK(hperh);

	if (hperh->perh->I2SCFGR.I2SE != ENABLE)
		I2S_ENABLE(hperh);
	return OK;
}

/**
  * @brief  Pauses the DMA Transfer.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @retval Status
  */
hal_status_t i2s_dma_pause(i2s_handle_t *hperh)
{
	assert_param(IS_I2S(hperh->perh));

	__LOCK(hperh);
	i2s_dma_req_config(hperh, I2S_DMA_REQ_TX, DISABLE);
	i2s_dma_req_config(hperh, I2S_DMA_REQ_RX, DISABLE);
	__UNLOCK(hperh);

	return OK;
}

/**
  * @brief  Resumes the DMA Transfer.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @retval Status
  */
hal_status_t i2s_dma_resume(i2s_handle_t *hperh)
{
	assert_param(IS_I2S(hperh->perh));

	__LOCK(hperh);
	i2s_dma_req_config(hperh, I2S_DMA_REQ_TX, ENABLE);
	i2s_dma_req_config(hperh, I2S_DMA_REQ_RX, ENABLE);
	__UNLOCK(hperh);

	return OK;
}

/**
  * @brief  Stops the DMA Transfer.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @retval Status
  */
hal_status_t i2s_dma_stop(i2s_handle_t *hperh)
{
	assert_param(IS_I2S(hperh->perh));

	__LOCK(hperh);
	i2s_dma_req_config(hperh, I2S_DMA_REQ_TX, DISABLE);
	i2s_dma_req_config(hperh, I2S_DMA_REQ_RX, DISABLE);
	__UNLOCK(hperh);

	hperh->state = I2S_STATE_READY;
	return OK;
}
#endif
/**
  * @}
  */

/** @defgroup I2S_Public_Functions_Group3 Control functions
  * @brief I2S Control functions
  *
  * @verbatim
   ===============================================================================
                      ##### Peripheral Control functions #####
   ===============================================================================
    [..]
    This subsection provides a set of functions allowing to control the I2S.
     (+) Handle interrupt about I2S module. The i2s_irq_handle() function must
         be invoked by I2S-IRQ function.
     (+) Configure the interrupt DISABLE/ENABLE.
     (+) Configure the DMA request.
     (+) Get interrupt source status.
     (+) Get interrupt flag status.
     (+) Clear interrupt flag

    @endverbatim
  * @{
  */

/**
  * @brief  This function handles I2S interrupt request.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @retval None
  */
void i2s_irq_handle(i2s_handle_t *hperh)
{
	if ((i2s_get_it_status(hperh, I2S_IT_RXTH) != RESET) && (i2s_get_it_flag_status(hperh, I2S_IF_RXTH) != RESET)) {
		i2s_clear_it_flag_status(hperh, I2S_IF_RXTH);
		if (hperh->state == I2S_STATE_BUSY_RX)
			__i2s_recv_by_it(hperh);
	}
	if ((i2s_get_it_status(hperh, I2S_IT_TXE) != RESET) && (i2s_get_it_flag_status(hperh, I2S_IF_TXE) != RESET)) {
		i2s_clear_it_flag_status(hperh, I2S_IF_TXE);
		if (hperh->state == I2S_STATE_BUSY_TX)
			__i2s_send_by_it(hperh);
	}

	
	if (hperh->err_code != I2S_ERROR_NONE) {
		i2s_interrupt_config(hperh, I2S_IT_RXF, DISABLE);
		i2s_interrupt_config(hperh, I2S_IT_TXE, DISABLE);
		hperh->state = I2S_STATE_READY;

		if (hperh->err_cbk)
			hperh->err_cbk(hperh);
	}


	return;
}

/**
  * @brief  Enables or disables the specified I2S interrupts.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  it: Specifies the I2S interrupt sources to be enabled or disabled.
  *         This parameter can be one of the @ref i2s_it_t.
  * @param  state: New status
  *           - ENABLE
  *           - DISABLE
  * @retval None
  */
void i2s_interrupt_config(i2s_handle_t *hperh, i2s_it_t it, type_func_t state)
{
	assert_param(IS_I2S(hperh->perh));
	assert_param(IS_I2S_IT(it));
	assert_param(IS_FUNC_STATE(state));

	if (state == ENABLE)
		hperh->perh->IER.Word |= (uint32_t)it;
	else
		hperh->perh->IDR.Word |= (uint32_t)it;

	return;
}

/**
  * @brief  Enables or disables the dma request.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  req: Specifies the I2S dma request sources to be enabled or disabled.
  *         This parameter can be one of the @ref i2s_dma_req_t.
  * @param  state: New status
  *           - ENABLE
  *           - DISABLE
  * @retval None
  */
void i2s_dma_req_config(i2s_handle_t *hperh, i2s_dma_req_t req, type_func_t state)
{
	assert_param(IS_I2S(hperh->perh));
	assert_param(IS_I2S_DMA_REQ(req));
	assert_param(IS_FUNC_STATE(state));

	if (state == ENABLE) {
		if (req == I2S_DMA_REQ_TX)
			hperh->perh->CR2.TXDMAEN = 1;
		else
			hperh->perh->CR2.RXDMAEN = 1;
	}
	else {
		if (req == I2S_DMA_REQ_TX)
			hperh->perh->CR2.TXDMAEN = 0;
		else
			hperh->perh->CR2.RXDMAEN = 0;
	}

	return;
}

/** @brief  Check whether the specified I2S flag is set or not.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  flag: specifies the flag to check.
  *         This parameter can be one of the @ref i2s_flag_t.
  * @retval Status
  *           - SET
  *           - RESET
  */
flag_status_t i2s_get_flag_status(i2s_handle_t *hperh, i2s_flag_t flag)
{
	assert_param(IS_I2S(hperh->perh));
	assert_param(IS_I2S_FLAG(flag));

	if (hperh->perh->SR.Word & flag)
		return SET;

	return RESET;
}

/**
  * @brief  Checks whether the specified I2S interrupt has occurred or not.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  it: Specifies the I2S interrupt source to check.
  *         This parameter can be one of the @ref i2s_it_t.
  * @retval Status
  *           - SET
  *           - RESET
  */
it_status_t i2s_get_it_status(i2s_handle_t *hperh, i2s_it_t it)
{
	assert_param(IS_I2S(hperh->perh));
	assert_param(IS_I2S_IT(it));

	if (hperh->perh->IVS.Word & it)
		return SET;

	return RESET;
}

/** @brief  Check whether the specified I2S interrupt flag is set or not.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  flag: specifies the flag to check.
  *         This parameter can be one of the @ref i2s_it_flag_t.
  * @retval Status
  *           - SET
  *           - RESET
  */
flag_status_t i2s_get_it_flag_status(i2s_handle_t *hperh, i2s_it_flag_t flag)
{
	assert_param(IS_I2S(hperh->perh));
	assert_param(IS_I2S_IF(flag));

	if (hperh->perh->RIF.Word & flag)
		return SET;

	return RESET;
}

/** @brief  Clear the specified I2S interrupt flags.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  flag: specifies the flag to check.
  *         This parameter can be one of the @ref i2s_it_flag_t.
  * @retval None
  */
void i2s_clear_it_flag_status(i2s_handle_t *hperh, i2s_it_flag_t flag)
{
	assert_param(IS_I2S(hperh->perh));
	assert_param(IS_I2S_IF(flag));

	hperh->perh->ICR.Word |= flag;
	return;
}

/**
  * @brief  This function handles I2S Communication Timeout.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @param  flag: specifies the I2S flag to check.
  * @param  status: The new Flag status (SET or RESET).
  * @param  timeout: Timeout duration
  * @retval Status, see @ref hal_status_t.
  */
static hal_status_t i2s_wait_flag(i2s_handle_t *hperh, i2s_flag_t flag, flag_status_t status, uint32_t timeout)
{
	uint32_t tick = __get_tick();

	assert_param(timeout > 0);

	while ((i2s_get_flag_status(hperh, flag)) != status) {
		if (((__get_tick()) - tick) > timeout) {
			i2s_interrupt_config(hperh, I2S_IT_TXE, DISABLE);
			i2s_interrupt_config(hperh, I2S_IT_RXF, DISABLE);
			return TIMEOUT;
		}
	}

	return OK;
}

static hal_status_t i2s_wait_bsy_flag(i2s_handle_t *hperh,flag_status_t status, uint32_t timeout)
{
	uint32_t tick = __get_tick();

	assert_param(timeout > 0);

	while (hperh->perh->SR.BSY != status) {
		if (((__get_tick()) - tick) > timeout) {
			i2s_interrupt_config(hperh, I2S_IT_TXE, DISABLE);
			i2s_interrupt_config(hperh, I2S_IT_RXF, DISABLE);
			return TIMEOUT;
		}
	}

	return OK;
}
/**
  * @}
  */

/** @defgroup I2S_Public_Functions_Group4 Peripheral State and Errors functions
  *  @brief   I2S State and Errors functions
  *
  * @verbatim
 ===============================================================================
                      ##### Peripheral State and Errors functions #####
 ===============================================================================
    [..]
    This subsection provides a set of functions allowing to control the I2S.
     (+) i2s_get_state() API can check in run-time the state of the I2S peripheral
     (+) i2s_get_error() check in run-time Errors occurring during communication

    @endverbatim
  * @{
  */

/**
  * @brief  Returns the I2S state.
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @retval HAL state
  */
i2s_state_t i2s_get_state(i2s_handle_t *hperh)
{
	assert_param(IS_I2S(hperh->perh));
	return hperh->state;
}

/**
  * @brief  Return the I2S error code
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @retval I2S Error Code
  */
uint32_t i2s_get_error(i2s_handle_t *hperh)
{
	assert_param(IS_I2S(hperh->perh));
	return hperh->err_code;
}
/**
  * @}
  */

/**
  * @}
  */

/** @defgroup I2S_Private_Functions I2S Private Functions
  * @brief   I2S Private functions
  * @{
  */

/**
  * @brief  handle program when an tx empty interrupt flag arrived in non block mode
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @retval Status, see @ref hal_status_t.
  */
static void __i2s_send_by_it(i2s_handle_t *hperh)
{
	if (hperh->tx_count == 0) {
		i2s_interrupt_config(hperh, I2S_IT_TXE, DISABLE);
		hperh->state = I2S_STATE_READY;

		if ((i2s_wait_bsy_flag(hperh, RESET, 1000)) != OK) {
			if (hperh->err_cbk)
				hperh->err_cbk(hperh);

			return;
		}

		if (hperh->tx_cplt_cbk)
			hperh->tx_cplt_cbk(hperh);

		return;
	}

	hperh->side = hperh->perh->SR.CHSIDE;
	hperh->perh->DR.DR = *hperh->tx_buf;
	hperh->tx_buf++;
	--hperh->tx_count;

	return;
}

/**
  * @brief  handle program when an rx no empty interrupt flag arrived in non block mode
  * @param  hperh: Pointer to a i2s_handle_t structure.
  * @retval Status, see @ref hal_status_t.
  */
static void __i2s_recv_by_it(i2s_handle_t *hperh)
{
	*hperh->rx_buf = hperh->perh->DR.DR;
	hperh->rx_buf++;
	--hperh->rx_count;

	i2s_clear_it_flag_status(hperh, I2S_IF_RXTH);

	if (hperh->rx_count == 0) {
		i2s_interrupt_config(hperh, I2S_IT_RXTH, DISABLE);
		hperh->state = I2S_STATE_READY;

		if (hperh->rx_cplt_cbk)
			hperh->rx_cplt_cbk(hperh);
	}

	return;
}

#ifdef HAL_DMA
/**
  * @brief  DMA I2S transmit process complete callback.
  * @param  arg: Pointer to a i2s_handle_t structure.
  * @retval None
  */
static void i2s_dma_send_cplt(void *arg)
{
	i2s_handle_t *hperh = (i2s_handle_t *)arg;

	hperh->tx_count = 0;
	i2s_dma_req_config(hperh, I2S_DMA_REQ_TX, DISABLE);
	hperh->state = I2S_STATE_READY;

	if ((i2s_wait_bsy_flag(hperh, RESET, 1000)) != OK)
		hperh->err_code |= I2S_ERROR_FLAG;

	if (hperh->err_code == I2S_ERROR_NONE) {
		if (hperh->tx_cplt_cbk)
			hperh->tx_cplt_cbk(hperh);
	}
	else {
		if (hperh->err_cbk)
			hperh->err_cbk(hperh);
	}

	return;
}

/**
  * @brief  DMA I2S receive process complete callback.
  * @param  arg: Pointer to a i2s_handle_t structure.
  * @retval None
  */
static void i2s_dma_recv_cplt(void *arg)
{
	i2s_handle_t *hperh = (i2s_handle_t *)arg;

	hperh->rx_count = 0;
	i2s_dma_req_config(hperh, I2S_DMA_REQ_RX, DISABLE);
	hperh->state = I2S_STATE_READY;

	if (hperh->err_code == I2S_ERROR_NONE) {
		if (hperh->rx_cplt_cbk)
			hperh->rx_cplt_cbk(hperh);
	}
	else {
		if (hperh->err_cbk)
			hperh->err_cbk(hperh);
	}

	return;
}

/**
  * @brief  DMA I2S communication error callback.
  * @param  arg: Pointer to a i2s_handle_t structure that contains
  *         the configuration information for the specified I2S module.
  * @retval None
  */
static void i2s_dma_error(void *arg)
{
	i2s_handle_t *hperh = (i2s_handle_t *)arg;

	i2s_dma_req_config(hperh, I2S_DMA_REQ_TX, DISABLE);
	i2s_dma_req_config(hperh, I2S_DMA_REQ_RX, DISABLE);
	SET_BIT(hperh->err_code, I2S_ERROR_DMA);

 	hperh->tx_count = 0;
 	hperh->rx_count = 0;
	hperh->state    = I2S_STATE_READY;

	if (hperh->err_cbk)
		hperh->err_cbk(hperh);

	return;
}
#endif /* HAL_DMA */
/**
  * @}
  */
#endif /* HAL_I2S */
/**
  * @}
  */

/**
  * @}
  */
