/*******************************************************************
 File: audio.c
 Date: 9-September-2020
 Author: Peter Spindler
 Description: Audio driver, including codec via I2C and data stream via SAI

 Based on audio driver from STMicroelectronics: stm32f723e_discovery_audio.c
 STM32Cube Firmware F7 Version 1.16.0, STM32F723E Discovery board

 License from STMicroelectronics:
  Redistribution and use in source and binary forms, with or without modification,
  are permitted provided that the following conditions are met:
    1. Redistributions of source code must retain the above copyright notice,
       this list of conditions and the following disclaimer.
    2. Redistributions in binary form must reproduce the above copyright notice,
       this list of conditions and the following disclaimer in the documentation
       and/or other materials provided with the distribution.
    3. Neither the name of STMicroelectronics nor the names of its contributors
       may be used to endorse or promote products derived from this software
       without specific prior written permission.

  THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
  AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
  IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
  DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
  FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
  DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
********************************************************************/

#include "stm32f7xx_hal.h"
#include "audio.h"
#include <stdio.h>

#define AUDIO_I2C_ADDRESS   0x34
#define AUDIO_I2C_HANDLE    hi2c1
#define AUDIO_SAI_HANDLE    hsai_BlockA2

extern I2C_HandleTypeDef AUDIO_I2C_HANDLE;
extern SAI_HandleTypeDef AUDIO_SAI_HANDLE;

static uint32_t AudioCodec_OutputEnabled = 0;
static uint32_t AudioCodec_InputEnabled = 0;
static uint8_t AudioCodec_ColdStartup = 1;

#define VOLUME_CONVERT(Volume)        (((Volume) > 100)? 100:((uint8_t)(((Volume) * 63) / 100)))
#define VOLUME_IN_CONVERT(Volume)     (((Volume) >= 100)? 239:((uint8_t)(((Volume) * 240) / 100)))

#define CODEC_AUDIOFRAME_SLOT_0123                   SAI_SLOTACTIVE_0 | SAI_SLOTACTIVE_1 | SAI_SLOTACTIVE_2 | SAI_SLOTACTIVE_3
#define CODEC_AUDIOFRAME_SLOT_02                     SAI_SLOTACTIVE_0 | SAI_SLOTACTIVE_2
#define CODEC_AUDIOFRAME_SLOT_13                     SAI_SLOTACTIVE_1 | SAI_SLOTACTIVE_3

//extern void Player_CompleteTransfer_CallBack( void );
//extern void Player_HalfTransfer_CallBack( void );

static uint32_t AudioCodec_SetFrequency( uint32_t AudioFreq );
static uint32_t AudioCodec_SetVolume(uint8_t Volume);
static uint32_t AudioCodec_SetMute( uint32_t Cmd );

static uint16_t AudioCodec_Read( uint16_t Reg ) {
	uint16_t read_value = 0, tmp = 0;
	HAL_I2C_Mem_Read(&AUDIO_I2C_HANDLE, AUDIO_I2C_ADDRESS, Reg, I2C_MEMADD_SIZE_16BIT, (uint8_t*)&read_value, 2, 1000);
	tmp = ((uint16_t)(read_value >> 8) & 0x00FF);
	tmp |= ((uint16_t)(read_value << 8)& 0xFF00);
	read_value = tmp;
	return read_value;
}

static uint8_t AudioCodec_Write( uint16_t Reg, uint16_t Value ) {
	uint32_t result = 0;
	uint16_t tmp = Value;
	Value = ((uint16_t)(tmp >> 8) & 0x00FF);
	Value |= ((uint16_t)(tmp << 8)& 0xFF00);
	HAL_I2C_Mem_Write(&AUDIO_I2C_HANDLE, AUDIO_I2C_ADDRESS, (uint16_t)Reg, I2C_MEMADD_SIZE_16BIT,(uint8_t*)&Value, 2, 1000);
#ifdef VERIFY_WRITTENDATA
	result = (AudioCodec_Read(Reg) == Value)? 0:1;
#endif
	return result;
}

static uint16_t AudioCodec_ReadId( void ) {
	return ((uint16_t)AudioCodec_Read(0x00));
}

static uint32_t AudioCodec_Reset( void ) {
	uint32_t counter = 0;
	// Reset Codec by writing in 0x0000 address register:
	counter = AudioCodec_Write( 0x0000, 0x0000 );
	AudioCodec_OutputEnabled = 0;
	AudioCodec_InputEnabled=0;
	return counter;
}

static uint32_t AudioCodec_Init( uint16_t OutputInputDevice, uint8_t Volume, uint32_t AudioFreq ) {
	uint32_t counter = 0;
	uint16_t output_device = OutputInputDevice & 0xFF;
	uint16_t input_device = OutputInputDevice & 0xFF00;
	uint16_t power_mgnt_reg_1 = 0;

	// wm8994 Errata Work-Arounds
	counter += AudioCodec_Write( 0x102, 0x0003);
	counter += AudioCodec_Write( 0x817, 0x0000);
	counter += AudioCodec_Write( 0x102, 0x0000);

	counter += AudioCodec_Write( 0x39, 0x006C); // Enable VMID soft start (fast), Start-up Bias Current Enabled

	// Enable bias generator, Enable VMID:
	if( input_device > 0 ) {
		counter += AudioCodec_Write( 0x01, 0x0013);
	} else {
		counter += AudioCodec_Write( 0x01, 0x0003);
	}

	HAL_Delay(50);

	// Path Configurations for output:
	if( output_device > 0 ) {
		AudioCodec_OutputEnabled = 1;

		switch (output_device) {
		case OUTPUT_DEVICE_SPEAKER:
			counter += AudioCodec_Write( 0x05, 0x0C0C);  // Enable DAC1 (Left), Enable DAC1 (Right), Disable DAC2 (Left), Disable DAC2 (Right)
			counter += AudioCodec_Write( 0x601, 0x0000); // Enable the AIF1 Timeslot 0 (Left) to DAC 1 (Left) mixer path
			counter += AudioCodec_Write( 0x602, 0x0000); // Enable the AIF1 Timeslot 0 (Right) to DAC 1 (Right) mixer path
			counter += AudioCodec_Write( 0x604, 0x0002); // Disable the AIF1 Timeslot 1 (Left) to DAC 2 (Left) mixer path
			counter += AudioCodec_Write( 0x605, 0x0002); // Disable the AIF1 Timeslot 1 (Right) to DAC 2 (Right) mixer path
			break;

		case OUTPUT_DEVICE_HEADPHONE:
			counter += AudioCodec_Write( 0x05, 0x0303); // Disable DAC1 (Left), Disable DAC1 (Right), Enable DAC2 (Left), Enable DAC2 (Right)
			counter += AudioCodec_Write( 0x601, 0x0001); // Enable the AIF1 Timeslot 0 (Left) to DAC 1 (Left) mixer path
			counter += AudioCodec_Write( 0x602, 0x0001); // Enable the AIF1 Timeslot 0 (Right) to DAC 1 (Right) mixer path
			counter += AudioCodec_Write( 0x604, 0x0000); // Disable the AIF1 Timeslot 1 (Left) to DAC 2 (Left) mixer path
			counter += AudioCodec_Write( 0x605, 0x0000); // Disable the AIF1 Timeslot 1 (Right) to DAC 2 (Right) mixer path
			break;

		case OUTPUT_DEVICE_BOTH:
			if (input_device == INPUT_DEVICE_DIGITAL_MIC1_MIC2) {
				counter += AudioCodec_Write( 0x05, 0x0303 | 0x0C0C);  // Enable DAC1 (Left), Enable DAC1 (Right), also Enable DAC2 (Left), Enable DAC2 (Right)
				counter += AudioCodec_Write( 0x601, 0x0003); // Enable the AIF1 Timeslot 0 (Left) to DAC 1 (Left) mixer path, enable the AIF1 Timeslot 1 (Left) to DAC 1 (Left) mixer path
				counter += AudioCodec_Write( 0x602, 0x0003); // Enable the AIF1 Timeslot 0 (Right) to DAC 1 (Right) mixer path, enable the AIF1 Timeslot 1 (Right) to DAC 1 (Right) mixer path
				counter += AudioCodec_Write( 0x604, 0x0003); // Enable the AIF1 Timeslot 0 (Left) to DAC 2 (Left) mixer path, enable the AIF1 Timeslot 1 (Left) to DAC 2 (Left) mixer path
				counter += AudioCodec_Write( 0x605, 0x0003);  // Enable the AIF1 Timeslot 0 (Right) to DAC 2 (Right) mixer path,eEnable the AIF1 Timeslot 1 (Right) to DAC 2 (Right) mixer path
			} else {
				counter += AudioCodec_Write( 0x05, 0x0303 | 0x0C0C); // Enable DAC1 (Left), Enable DAC1 (Right), also Enable DAC2 (Left), Enable DAC2 (Right)
 				counter += AudioCodec_Write( 0x601, 0x0001); // Enable the AIF1 Timeslot 0 (Left) to DAC 1 (Left) mixer path
				counter += AudioCodec_Write( 0x602, 0x0001); // Enable the AIF1 Timeslot 0 (Right) to DAC 1 (Right) mixer path
				counter += AudioCodec_Write( 0x604, 0x0002); // Enable the AIF1 Timeslot 1 (Left) to DAC 2 (Left) mixer path
				counter += AudioCodec_Write( 0x605, 0x0002); // Enable the AIF1 Timeslot 1 (Right) to DAC 2 (Right) mixer path
			}
			break;

		case OUTPUT_DEVICE_AUTO :
		default:
			counter += AudioCodec_Write( 0x05, 0x0303); // Disable DAC1 (Left), Disable DAC1 (Right), enable DAC2 (Left), Enable DAC2 (Right)
			counter += AudioCodec_Write( 0x601, 0x0001); // Enable the AIF1 Timeslot 0 (Left) to DAC 1 (Left) mixer path
			counter += AudioCodec_Write( 0x602, 0x0001); // Enable the AIF1 Timeslot 0 (Right) to DAC 1 (Right) mixer path
			counter += AudioCodec_Write( 0x604, 0x0000); // Disable the AIF1 Timeslot 1 (Left) to DAC 2 (Left) mixer path
			counter += AudioCodec_Write( 0x605, 0x0000); // Disable the AIF1 Timeslot 1 (Right) to DAC 2 (Right) mixer path
			break;
		}
	} else {
		AudioCodec_OutputEnabled = 0;
	}

	// Path Configurations for input
	if (input_device > 0)
	{
		AudioCodec_InputEnabled = 1;
		switch (input_device)
		{
		case INPUT_DEVICE_DIGITAL_MICROPHONE_2 :
			counter += AudioCodec_Write( 0x04, 0x0C30); // Enable AIF1ADC2 (Left), AIF1ADC2 (Right), DMICDAT2 (Left), DMICDAT2 (Right), Left ADC, Right ADC
			counter += AudioCodec_Write( 0x450, 0x00DB); // Enable AIF1 DRC2 Signal Detect & DRC in AIF1ADC2 Left/Right Timeslot 1
			counter += AudioCodec_Write( 0x02, 0x6000); // Disable IN1L, IN1R, IN2L, IN2R, Enable Thermal sensor & shutdown
			counter += AudioCodec_Write( 0x608, 0x0002); // Enable the DMIC2(Left) to AIF1 Timeslot 1 (Left) mixer path
			counter += AudioCodec_Write( 0x609, 0x0002); // Enable the DMIC2(Right) to AIF1 Timeslot 1 (Right) mixer path
			counter += AudioCodec_Write( 0x700, 0x000E); // GPIO1 pin configuration GP1_DIR = output, GP1_FN = AIF1 DRC2 signal detect
			break;

		case INPUT_DEVICE_INPUT_LINE_1 :
			counter += AudioCodec_Write( 0x28, 0x0011); // IN1LN_TO_IN1L, IN1LP_TO_VMID, IN1RN_TO_IN1R, IN1RP_TO_VMID
			counter += AudioCodec_Write( 0x29, 0x0035); // Disable mute on IN1L_TO_MIXINL and +30dB on IN1L PGA output
			counter += AudioCodec_Write( 0x2A, 0x0035); // Disable mute on IN1R_TO_MIXINL, Gain = +30dB
			counter += AudioCodec_Write( 0x04, 0x0303); // Enable AIF1ADC1 (Left), AIF1ADC1 (Right), Left ADC,  Right ADC
			counter += AudioCodec_Write( 0x440, 0x00DB); // Enable AIF1 DRC1 Signal Detect & DRC in AIF1ADC1 Left/Right Timeslot 0
			counter += AudioCodec_Write( 0x02, 0x6350); // Enable IN1L and IN1R, Disable IN2L and IN2R, Enable Thermal sensor & shutdown
			counter += AudioCodec_Write( 0x606, 0x0002); // Enable the ADCL(Left) to AIF1 Timeslot 0 (Left) mixer path
			counter += AudioCodec_Write( 0x607, 0x0002); // Enable the ADCR(Right) to AIF1 Timeslot 0 (Right) mixer path
			counter += AudioCodec_Write( 0x700, 0x000D); // GPIO1 pin configuration GP1_DIR = output, GP1_FN = AIF1 DRC1 signal detect
			break;

		case INPUT_DEVICE_DIGITAL_MICROPHONE_1 :
			counter += AudioCodec_Write( 0x04, 0x030C); // Enable AIF1ADC1 (Left), AIF1ADC1 (Right), DMICDAT1 (Left), DMICDAT1 (Right), Left ADC, Right ADC
			counter += AudioCodec_Write( 0x440, 0x00DB); // Enable AIF1 DRC2 Signal Detect & DRC in AIF1ADC1 Left/Right Timeslot 0
			counter += AudioCodec_Write( 0x02, 0x6350); // Disable IN1L, IN1R, IN2L, IN2R, Enable Thermal sensor & shutdown
			counter += AudioCodec_Write( 0x606, 0x0002); // Enable the DMIC2(Left) to AIF1 Timeslot 0 (Left) mixer path
			counter += AudioCodec_Write( 0x607, 0x0002); // Enable the DMIC2(Right) to AIF1 Timeslot 0 (Right) mixer path
			counter += AudioCodec_Write( 0x700, 0x000D); // GPIO1 pin configuration GP1_DIR = output, GP1_FN = AIF1 DRC1 signal detect
			break;
		case INPUT_DEVICE_DIGITAL_MIC1_MIC2 :
			counter += AudioCodec_Write( 0x04, 0x0F3C); // Enable AIF1ADC1 (Left), AIF1ADC1 (Right), DMICDAT1 (Left), DMICDAT1 (Right), Left ADC, Right ADC
			counter += AudioCodec_Write( 0x450, 0x00DB); // Enable AIF1 DRC2 Signal Detect & DRC in AIF1ADC2 Left/Right Timeslot 1
			counter += AudioCodec_Write( 0x440, 0x00DB); // Enable AIF1 DRC2 Signal Detect & DRC in AIF1ADC1 Left/Right Timeslot 0
			counter += AudioCodec_Write( 0x02, 0x63A0); // Disable IN1L, IN1R, Enable IN2L, IN2R, Thermal sensor & shutdown
			counter += AudioCodec_Write( 0x606, 0x0002); // Enable the DMIC2(Left) to AIF1 Timeslot 0 (Left) mixer path
			counter += AudioCodec_Write( 0x607, 0x0002); // Enable the DMIC2(Right) to AIF1 Timeslot 0 (Right) mixer path
			counter += AudioCodec_Write( 0x608, 0x0002); // Enable the DMIC2(Left) to AIF1 Timeslot 1 (Left) mixer path
			counter += AudioCodec_Write( 0x609, 0x0002); // Enable the DMIC2(Right) to AIF1 Timeslot 1 (Right) mixer path
			counter += AudioCodec_Write( 0x700, 0x000D); // GPIO1 pin configuration GP1_DIR = output, GP1_FN = AIF1 DRC1 signal detect
			break;
		case INPUT_DEVICE_INPUT_LINE_2 :
		default:
			// Actually, no other input devices supported
			counter++;
			break;
		}
	} else {
		AudioCodec_InputEnabled = 0;
	}

	/*
	//  Clock Configurations
	switch (AudioFreq)
	{
	case  AUDIO_FREQUENCY_8K:  counter += AudioCodec_Write( 0x210, 0x0003); break; // AIF1 Sample Rate = 8 (KHz), ratio=256
	case  AUDIO_FREQUENCY_16K: counter += AudioCodec_Write( 0x210, 0x0033); break; // AIF1 Sample Rate = 16 (KHz), ratio=256
	case  AUDIO_FREQUENCY_32K: counter += AudioCodec_Write( 0x210, 0x0063); break; // AIF1 Sample Rate = 32 (KHz), ratio=256
	case  AUDIO_FREQUENCY_48K: counter += AudioCodec_Write( 0x210, 0x0083); break; // AIF1 Sample Rate = 48 (KHz), ratio=256
	case  AUDIO_FREQUENCY_96K: counter += AudioCodec_Write( 0x210, 0x00A3); break; // AIF1 Sample Rate = 96 (KHz), ratio=256
	case  AUDIO_FREQUENCY_11K: counter += AudioCodec_Write( 0x210, 0x0013); break; // AIF1 Sample Rate = 11.025 (KHz), ratio=256
	case  AUDIO_FREQUENCY_22K: counter += AudioCodec_Write( 0x210, 0x0043); break; // AIF1 Sample Rate = 22.050 (KHz), ratio=256
	case  AUDIO_FREQUENCY_44K: counter += AudioCodec_Write( 0x210, 0x0073); break; // AIF1 Sample Rate = 44.1 (KHz), ratio=256
	default: counter += AudioCodec_Write( 0x210, 0x0083); break;  // AIF1 Sample Rate = 48 (KHz), ratio=256
	}
	*/
	counter += AudioCodec_SetFrequency( AudioFreq );

	if( input_device == INPUT_DEVICE_DIGITAL_MIC1_MIC2 ) {
		counter += AudioCodec_Write( 0x300, 0x4018); // AIF1 Word Length = 16-bits, AIF1 Format = DSP mode
	} else {
		counter += AudioCodec_Write( 0x300, 0x4010); // AIF1 Word Length = 16-bits, AIF1 Format = I2S (Default Register Value)
	}

	counter += AudioCodec_Write( 0x302, 0x0000);  // slave mode
	counter += AudioCodec_Write( 0x208, 0x000A); // Enable the DSP processing clock for AIF1, Enable the core clock
	counter += AudioCodec_Write( 0x200, 0x0001); // Enable AIF1 Clock, AIF1 Clock Source = MCLK1 pin
	if (output_device > 0) {  // Audio output selected
		if (output_device == OUTPUT_DEVICE_HEADPHONE) {
			counter += AudioCodec_Write( 0x2D, 0x0100); // Select DAC1 (Left) to Left Headphone Output PGA (HPOUT1LVOL) path
			counter += AudioCodec_Write( 0x2E, 0x0100); // Select DAC1 (Right) to Right Headphone Output PGA (HPOUT1RVOL) path
			// Startup sequence for Headphone:
			if( AudioCodec_ColdStartup ){
				counter += AudioCodec_Write(0x110,0x8100);
				AudioCodec_ColdStartup=0;
				HAL_Delay(300);
			} else { // Headphone Warm Start-Up
				counter += AudioCodec_Write(0x110,0x8108);
				HAL_Delay(50);
			}
			counter += AudioCodec_Write( 0x420, 0x0000); // Soft un-Mute the AIF1 Timeslot 0 DAC1 path L&R
		}
		// Analog Output Configuration:
		counter += AudioCodec_Write( 0x03, 0x0300);  // Enable SPKRVOL PGA, Enable SPKMIXR, Enable SPKLVOL PGA, Enable SPKMIXL
		counter += AudioCodec_Write( 0x22, 0x0000);  // Left Speaker Mixer Volume = 0dB
		counter += AudioCodec_Write( 0x23, 0x0000);  // Speaker output mode = Class D, Right Speaker Mixer Volume = 0dB ((0x23, 0x0100) = class AB)
		counter += AudioCodec_Write( 0x36, 0x0300);  // Unmute DAC2 (Left) to Left Speaker Mixer (SPKMIXL) path, Unmute DAC2 (Right) to Right Speaker Mixer (SPKMIXR) path
		counter += AudioCodec_Write( 0x01, 0x3003);  // Enable bias generator, Enable VMID, Enable SPKOUTL, Enable SPKOUTR

		// Headphone/Speaker Enable:
		if( input_device == INPUT_DEVICE_DIGITAL_MIC1_MIC2 ) {
			counter += AudioCodec_Write( 0x51, 0x0205); // Enable Class W, Class W Envelope Tracking = AIF1 Timeslots 0 and 1
		} else {
			counter += AudioCodec_Write( 0x51, 0x0005); // Enable Class W, Class W Envelope Tracking = AIF1 Timeslot 0
		}

		// Enable bias generator, Enable VMID, Enable HPOUT1 (Left) and Enable HPOUT1 (Right) input stages idem for Speaker
		power_mgnt_reg_1 |= 0x0303 | 0x3003;
		counter += AudioCodec_Write( 0x01, power_mgnt_reg_1);
		counter += AudioCodec_Write( 0x60, 0x0022); // Enable HPOUT1 (Left) and HPOUT1 (Right) intermediate stages
		counter += AudioCodec_Write( 0x4C, 0x9F25); // Enable Charge Pump

		HAL_Delay(15);

		counter += AudioCodec_Write( 0x2D, 0x0001);  // Select DAC1 (Left) to Left Headphone Output PGA (HPOUT1LVOL) path
		counter += AudioCodec_Write( 0x2E, 0x0001);  // Select DAC1 (Right) to Right Headphone Output PGA (HPOUT1RVOL) path
		counter += AudioCodec_Write( 0x03, 0x0030 | 0x0300);  // Enable Left Output Mixer (MIXOUTL), Enable Right Output Mixer (MIXOUTR) idem for SPKOUTL and SPKOUTR
		counter += AudioCodec_Write( 0x54, 0x0033);  // Enable DC Servo and trigger start-up mode on left and right channels

		HAL_Delay(257);

		counter += AudioCodec_Write( 0x60, 0x00EE);  // Enable HPOUT1 (Left) and HPOUT1 (Right) intermediate and output stages. Remove clamps

		// Unmutes:
		counter += AudioCodec_Write( 0x610, 0x00C0); // Unmute DAC 1 (Left)
		counter += AudioCodec_Write( 0x611, 0x00C0); // Unmute DAC 1 (Right)
		counter += AudioCodec_Write( 0x420, 0x0010); // Unmute the AIF1 Timeslot 0 DAC path
		counter += AudioCodec_Write( 0x612, 0x00C0); // Unmute DAC 2 (Left)
		counter += AudioCodec_Write( 0x613, 0x00C0); // Unmute DAC 2 (Right)
		counter += AudioCodec_Write( 0x422, 0x0010); // Unmute the AIF1 Timeslot 1 DAC2 path

		AudioCodec_SetVolume(Volume);
	}

	if (input_device > 0) { // Audio input selected
		if ((input_device == INPUT_DEVICE_DIGITAL_MICROPHONE_1) || (input_device == INPUT_DEVICE_DIGITAL_MICROPHONE_2)) {
			// Enable Microphone bias 1 generator, Enable VMID
			power_mgnt_reg_1 |= 0x0013;
			counter += AudioCodec_Write( 0x01, power_mgnt_reg_1);
			counter += AudioCodec_Write( 0x620, 0x0002); // ADC oversample enable
			counter += AudioCodec_Write( 0x411, 0x3800); // AIF ADC2 HPF enable, HPF cut = voice mode 1 fc=127Hz at fs=8kHz
		} else if(input_device == INPUT_DEVICE_DIGITAL_MIC1_MIC2) {
			// Enable Microphone bias 1 generator, Enable VMID
			power_mgnt_reg_1 |= 0x0013;
			counter += AudioCodec_Write( 0x01, power_mgnt_reg_1);
			counter += AudioCodec_Write( 0x620, 0x0002); // ADC oversample enable
			counter += AudioCodec_Write( 0x410, 0x1800); // AIF ADC1 HPF enable, HPF cut = voice mode 1 fc=127Hz at fs=8kHz
			counter += AudioCodec_Write( 0x411, 0x1800); // AIF ADC2 HPF enable, HPF cut = voice mode 1 fc=127Hz at fs=8kHz
		} else if ((input_device == INPUT_DEVICE_INPUT_LINE_1) || (input_device == INPUT_DEVICE_INPUT_LINE_2)) {
			counter += AudioCodec_Write( 0x18, 0x000B); // Disable mute on IN1L, IN1L Volume = +0dB
			counter += AudioCodec_Write( 0x1A, 0x000B); // Disable mute on IN1R, IN1R Volume = +0dB
			counter += AudioCodec_Write( 0x410, 0x1800); // AIF ADC1 HPF enable, HPF cut = hifi mode fc=4Hz at fs=48kHz
		}
		AudioCodec_SetVolume(Volume);
	}
	return counter;
}

static uint32_t AudioCodec_SetFrequency( uint32_t AudioFreq ) {
	uint32_t counter = 0;
	switch (AudioFreq) {
	case  AUDIO_FREQUENCY_8K:  counter += AudioCodec_Write( 0x210, 0x0003); break; // AIF1 Sample Rate = 8 (KHz), ratio=256
	case  AUDIO_FREQUENCY_16K: counter += AudioCodec_Write( 0x210, 0x0033); break; // AIF1 Sample Rate = 16 (KHz), ratio=256
	case  AUDIO_FREQUENCY_32K: counter += AudioCodec_Write( 0x210, 0x0063); break; // AIF1 Sample Rate = 32 (KHz), ratio=256
	case  AUDIO_FREQUENCY_48K: counter += AudioCodec_Write( 0x210, 0x0083); break; // AIF1 Sample Rate = 48 (KHz), ratio=256
	case  AUDIO_FREQUENCY_96K: counter += AudioCodec_Write( 0x210, 0x00A3); break; // AIF1 Sample Rate = 96 (KHz), ratio=256
	case  AUDIO_FREQUENCY_11K: counter += AudioCodec_Write( 0x210, 0x0013); break; // AIF1 Sample Rate = 11.025 (KHz), ratio=256
	case  AUDIO_FREQUENCY_22K: counter += AudioCodec_Write( 0x210, 0x0043); break; // AIF1 Sample Rate = 22.050 (KHz), ratio=256
	case  AUDIO_FREQUENCY_44K: counter += AudioCodec_Write( 0x210, 0x0073); break; // AIF1 Sample Rate = 44.1 (KHz), ratio=256
	default: counter += AudioCodec_Write( 0x210, 0x0083); break; // AIF1 Sample Rate = 48 (KHz), ratio=256
	}
	return counter;
}

static uint32_t AudioCodec_SetVolume( uint8_t Volume ) {
	uint32_t counter = 0;
	uint8_t convertedvol = VOLUME_CONVERT(Volume);

	// Output volume
	if( AudioCodec_OutputEnabled != 0 ) {
		if(convertedvol > 0x3E) {
			counter += AudioCodec_SetMute(AUDIO_MUTE_OFF); // Unmute audio codec
			counter += AudioCodec_Write( 0x1C, 0x3F | 0x140); // Left Headphone Volume
			counter += AudioCodec_Write( 0x1D, 0x3F | 0x140); // Right Headphone Volume
			counter += AudioCodec_Write( 0x26, 0x3F | 0x140); // Left Speaker Volume
			counter += AudioCodec_Write( 0x27, 0x3F | 0x140); // Right Speaker Volume
		} else if (Volume == 0) {
			counter += AudioCodec_SetMute(AUDIO_MUTE_ON); // Mute audio codec
		} else {
			counter += AudioCodec_SetMute(AUDIO_MUTE_OFF); // Unmute audio codec
			counter += AudioCodec_Write( 0x1C, convertedvol | 0x140); // Left Headphone Volume
			counter += AudioCodec_Write( 0x1D, convertedvol | 0x140); // Right Headphone Volume
			counter += AudioCodec_Write( 0x26, convertedvol | 0x140); // Left Speaker Volume
			counter += AudioCodec_Write( 0x27, convertedvol | 0x140); // Right Speaker Volume
		}
	}

	// Input volume
	if( AudioCodec_InputEnabled != 0 ) {
		convertedvol = VOLUME_IN_CONVERT(Volume);
		counter += AudioCodec_Write( 0x400, convertedvol | 0x100); // Left AIF1 ADC1 volume
		counter += AudioCodec_Write( 0x401, convertedvol | 0x100); // Right AIF1 ADC1 volume
		counter += AudioCodec_Write( 0x404, convertedvol | 0x100); // Left AIF1 ADC2 volume
		counter += AudioCodec_Write( 0x405, convertedvol | 0x100); // Right AIF1 ADC2 volume
	}
	return counter;
}

static uint32_t AudioCodec_SetMute( uint32_t Cmd ) {
	uint32_t counter = 0;
	if( AudioCodec_OutputEnabled != 0 ) {
		// Set the Mute mode
		if(Cmd == AUDIO_MUTE_ON) {
			counter += AudioCodec_Write( 0x420, 0x0200); // Soft Mute the AIF1 Timeslot 0 DAC1 path L&R
			counter += AudioCodec_Write( 0x422, 0x0200); // Soft Mute the AIF1 Timeslot 1 DAC2 path L&R
		} else { // AUDIO_MUTE_OFF Disable the Mute
			counter += AudioCodec_Write( 0x420, 0x0010); // Unmute the AIF1 Timeslot 0 DAC1 path L&R
			counter += AudioCodec_Write( 0x422, 0x0010); // Unmute the AIF1 Timeslot 1 DAC2 path L&R
		}
	}
	return counter;
}

static uint32_t AudioCodec_Play( void ) {
	uint32_t counter = 0;
	counter += AudioCodec_SetMute(AUDIO_MUTE_OFF);
	return counter;
}

static uint32_t AudioCodec_Pause( void ) {
	uint32_t counter = 0;
	counter += AudioCodec_SetMute(AUDIO_MUTE_ON);  // Mute the output first
	counter += AudioCodec_Write( 0x02, 0x01 );  // Put the Codec in Power save mode
	return counter;
}

static uint32_t AudioCodec_Resume( void ) {
	uint32_t counter = 0;
	counter += AudioCodec_SetMute(AUDIO_MUTE_OFF);
	return counter;
}

static uint32_t AudioCodec_Stop( uint8_t CodecPdwnMode ) {
	uint32_t counter = 0;
	if( AudioCodec_OutputEnabled != 0 ) {
		counter += AudioCodec_SetMute(AUDIO_MUTE_ON); // Mute the output first
		if( CodecPdwnMode == CODEC_PDWN_SW ) { // Only output mute required
		} else { // CODEC_PDWN_HW
			counter += AudioCodec_Write( 0x420, 0x0200); // Mute the AIF1 Timeslot 0 DAC1 path
			counter += AudioCodec_Write( 0x422, 0x0200); // Mute the AIF1 Timeslot 1 DAC2 path
			counter += AudioCodec_Write( 0x2D, 0x0000); // Disable DAC1L_TO_HPOUT1L
			counter += AudioCodec_Write( 0x2E, 0x0000); // Disable DAC1R_TO_HPOUT1R
			counter += AudioCodec_Write( 0x05, 0x0000); // Disable DAC1 and DAC2
			counter += AudioCodec_Write( 0x0000, 0x0000); // Reset Codec by writing in 0x0000 address register
			AudioCodec_OutputEnabled = 0;
		}
	}
	return counter;
}

__weak void Player_CompleteTransfer_CallBack( void ) {
}
__weak void Player_HalfTransfer_CallBack( void ) {
}


void HAL_SAI_TxCpltCallback( SAI_HandleTypeDef *hsai ) {
	Player_CompleteTransfer_CallBack();
}

void HAL_SAI_TxHalfCpltCallback( SAI_HandleTypeDef *hsai ) {
	Player_HalfTransfer_CallBack();
}



static void Audio_ClockConfig(SAI_HandleTypeDef *hsai, uint32_t AudioFreq, void *Params)
{
	RCC_PeriphCLKInitTypeDef rcc_ex_clk_init_struct;

	HAL_RCCEx_GetPeriphCLKConfig(&rcc_ex_clk_init_struct);

	/* Set the PLL configuration according to the audio frequency */
	if((AudioFreq == AUDIO_FREQUENCY_11K) || (AudioFreq == AUDIO_FREQUENCY_22K) || (AudioFreq == AUDIO_FREQUENCY_44K))
	{
		/* Configure PLLSAI prescalers */
		/* PLLSAI_VCO: VCO_429M
    SAI_CLK(first level) = PLLSAI_VCO/PLLSAIQ = 429/2 = 214.5 Mhz
    SAI_CLK_x = SAI_CLK(first level)/PLLSAIDIVQ = 214.5/19 = 11.289 Mhz */
		rcc_ex_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_SAI2;
		rcc_ex_clk_init_struct.Sai2ClockSelection = RCC_SAI2CLKSOURCE_PLLI2S;
		rcc_ex_clk_init_struct.PLLI2S.PLLI2SN = 429;
		rcc_ex_clk_init_struct.PLLI2S.PLLI2SQ = 2;
		rcc_ex_clk_init_struct.PLLI2SDivQ = 19;

		HAL_RCCEx_PeriphCLKConfig(&rcc_ex_clk_init_struct);

	}
	else /* AUDIO_FREQUENCY_8K, AUDIO_FREQUENCY_16K, AUDIO_FREQUENCY_48K, AUDIO_FREQUENCY_96K */
	{
		/* SAI clock config
    PLLSAI_VCO: VCO_344M
    SAI_CLK(first level) = PLLSAI_VCO/PLLSAIQ = 344/7 = 49.142 Mhz
    SAI_CLK_x = SAI_CLK(first level)/PLLSAIDIVQ = 49.142/1 = 49.142 Mhz */
		rcc_ex_clk_init_struct.PeriphClockSelection = RCC_PERIPHCLK_SAI2;
		rcc_ex_clk_init_struct.Sai2ClockSelection = RCC_SAI2CLKSOURCE_PLLI2S;
		rcc_ex_clk_init_struct.PLLI2S.PLLI2SN = 344;
		rcc_ex_clk_init_struct.PLLI2S.PLLI2SQ = 7;
		rcc_ex_clk_init_struct.PLLI2SDivQ = 1;

		HAL_RCCEx_PeriphCLKConfig(&rcc_ex_clk_init_struct);
	}
}

static void Audio_SaiOutInit( SAI_HandleTypeDef *hsai, uint32_t AudioFreq) {

	// Disable SAI peripheral to allow access to SAI internal registers
	__HAL_SAI_DISABLE(hsai);

	// Configure SAI_Block_x: LSBFirst: Disabled, DataSize: 16
	hsai->Init.AudioFrequency = AudioFreq;
	/*
	hsai->Init.MonoStereoMode = SAI_STEREOMODE;
	hsai->Init.AudioFrequency = AudioFreq;
	hsai->Init.AudioMode = SAI_MODEMASTER_TX;
	hsai->Init.NoDivider = SAI_MASTERDIVIDER_ENABLED;
	hsai->Init.Protocol = SAI_FREE_PROTOCOL;
	hsai->Init.DataSize = SAI_DATASIZE_16;
	hsai->Init.FirstBit = SAI_FIRSTBIT_MSB;
	hsai->Init.ClockStrobing = SAI_CLOCKSTROBING_FALLINGEDGE;
	hsai->Init.Synchro = SAI_ASYNCHRONOUS;
	hsai->Init.OutputDrive = SAI_OUTPUTDRIVE_DISABLE;
	hsai->Init.FIFOThreshold = SAI_FIFOTHRESHOLD_1QF;
	hsai->Init.SynchroExt     = SAI_SYNCEXT_DISABLE;
	hsai->Init.CompandingMode = SAI_NOCOMPANDING;
	hsai->Init.TriState       = SAI_OUTPUT_NOTRELEASED;
	hsai->Init.Mckdiv         = 0;
	*/

	/* Configure SAI_Block_x Frame
  	  Frame Length: 64
  	  Frame active Length: 32
  	  FS Definition: Start frame + Channel Side identification
  	  FS Polarity: FS active Low
  	  FS Offset: FS asserted one bit before the first bit of slot 0 */
	hsai->FrameInit.FrameLength = 64;
	hsai->FrameInit.ActiveFrameLength = 32;
	hsai->FrameInit.FSDefinition = SAI_FS_CHANNEL_IDENTIFICATION;
	hsai->FrameInit.FSPolarity = SAI_FS_ACTIVE_LOW;
	hsai->FrameInit.FSOffset = SAI_FS_BEFOREFIRSTBIT;

	/* Configure SAI Block_x Slot
  	  Slot First Bit Offset: 0
  	  Slot Size  : 16
  	  Slot Number: 4
  	  Slot Active: All slot actives */
	hsai->SlotInit.FirstBitOffset = 0;
	hsai->SlotInit.SlotSize = SAI_SLOTSIZE_DATASIZE;
	hsai->SlotInit.SlotNumber = 4;
	hsai->SlotInit.SlotActive = CODEC_AUDIOFRAME_SLOT_0123;

	HAL_SAI_Init(hsai);

	__HAL_SAI_ENABLE(hsai);
}

uint8_t Audio_SetVolume( uint8_t Volume ) {
	if( AudioCodec_SetVolume(Volume) != 0) {
		return AUDIO_ERROR;
	} else {
		return AUDIO_OK;
	}
}

uint8_t Audio_Play( uint8_t *Buffer, uint32_t Size ) {
	if( AudioCodec_Play() != 0) {
		return AUDIO_ERROR;
	} else {
		HAL_SAI_Transmit_DMA( &AUDIO_SAI_HANDLE, Buffer, Size );
		return AUDIO_OK;
	}
}

uint8_t Audio_Pause( void ) {
	if( AudioCodec_Pause() != 0 ) {
		return AUDIO_ERROR;
	} else {
		HAL_SAI_DMAPause(&AUDIO_SAI_HANDLE);
		return AUDIO_OK;
	}
}

uint8_t Audio_Resume( void ) {
	if( AudioCodec_Resume() != 0) {
		return AUDIO_ERROR;
	} else {
		HAL_SAI_DMAResume(&AUDIO_SAI_HANDLE);
		return AUDIO_OK;
	}
}

uint8_t Audio_Stop( void ) {
	HAL_SAI_DMAStop(&hsai_BlockA2);
	uint8_t Option = CODEC_PDWN_SW;
	if( AudioCodec_Stop( Option) != 0) {
		return AUDIO_ERROR;
	} else {
		if(Option == CODEC_PDWN_HW) {
			HAL_Delay(1);
		}
		return AUDIO_OK;
	}
}

uint8_t Audio_SetFrequency( uint32_t AudioFreq ) {
	Audio_ClockConfig(&AUDIO_SAI_HANDLE, AudioFreq/2, NULL);

	__HAL_SAI_DISABLE(&AUDIO_SAI_HANDLE);

	AUDIO_SAI_HANDLE.Init.AudioFrequency = AudioFreq/2;
	HAL_SAI_Init(&AUDIO_SAI_HANDLE);

	__HAL_SAI_ENABLE(&AUDIO_SAI_HANDLE);

	return AUDIO_OK;

	/*
	if( AudioCodec_SetFrequency( AudioFreq ) != 0 ) {
		return AUDIO_ERROR;
	} else {
		return AUDIO_OK;
	}
	*/
}

uint8_t Audio_Init(uint16_t OutputDevice, uint8_t Volume, uint32_t AudioFreq ) {
	uint8_t ret = AUDIO_ERROR; // Default: return error
	uint32_t DeviceId = 0;

	Audio_ClockConfig(&AUDIO_SAI_HANDLE, AudioFreq/2, NULL);
	Audio_SaiOutInit(&AUDIO_SAI_HANDLE, AudioFreq/2);

	DeviceId = AudioCodec_ReadId();
	if( DeviceId == 0x8994 ) {
		AudioCodec_Reset();
		AudioCodec_Init(OutputDevice, Volume, AudioFreq);
		ret = AUDIO_OK;
	}

	return ret;
}
