Files
2026-10-18 14:06:40 +02:00

1046 lines
29 KiB
C

/**
******************************************************************************
* @file wm8904.c
* @author MCD Application Team
* @brief This file provides the WM8904 Audio Codec driver.
******************************************************************************
* @attention
*
* Copyright (c) 2023 STMicroelectronics.
* All rights reserved.
*
* This software is licensed under terms that can be found in the LICENSE file
* in the root directory of this software component.
* If no LICENSE file comes with this software, it is provided AS-IS.
*
******************************************************************************
*/
/* Includes ------------------------------------------------------------------*/
#include "wm8904.h"
/** @addtogroup BSP
* @{
*/
/** @addtogroup Components
* @{
*/
/** @defgroup WM8904 WM8904
* @brief This file provides a set of functions needed to drive the
* WM8904 audio codec.
* @{
*/
/** @defgroup WM8904_Exported_Variables Exported Variables
* @{
*/
/* Audio codec driver structure initialization */
WM8904_Drv_t WM8904_Driver =
{
WM8904_Init,
WM8904_DeInit,
WM8904_ReadID,
WM8904_Play,
WM8904_Pause,
WM8904_Resume,
WM8904_Stop,
WM8904_SetFrequency,
WM8904_GetFrequency,
WM8904_SetVolume,
WM8904_GetVolume,
WM8904_SetMute,
WM8904_SetOutputMode,
WM8904_SetResolution,
WM8904_GetResolution,
WM8904_SetProtocol,
WM8904_GetProtocol,
WM8904_Reset
};
/**
* @}
*/
/** @defgroup WM8904_Private_Macros Private Macros
* @{
*/
#define VOLUME_OUT_CONVERT(Volume) (((Volume) >= 100U) ? 63U : ((uint8_t)((((Volume) / 2U) + 13U))))
#define VOLUME_OUT_INVERT(Volume) ((uint8_t)(((Volume) - 13U) * 2U))
/**
* @}
*/
/** @defgroup WM8904_Private_Variables Private Variables
* @{
*/
static uint32_t WM8904_CurrentDevices = WM8904_OUT_NONE;
/**
* @}
*/
/** @defgroup WM8904_Private_Functions Private Functions
* @{
*/
static int32_t WM8904_ReadRegWrap(void *handle, uint16_t Reg, uint8_t* Data, uint16_t Length);
static int32_t WM8904_WriteRegWrap(void *handle, uint16_t Reg, uint8_t* Data, uint16_t Length);
static void WM8904_Delay(const WM8904_Object_t *pObj, uint32_t Delay);
/**
* @}
*/
/** @defgroup WM8904_Exported_Functions Exported Functions
* @{
*/
/**
* @brief Initializes the audio codec and the control interface.
* @param pObj pointer to component object
* @param pInit pointer de component init structure
* @retval 0 if correct communication, else wrong communication
*/
int32_t WM8904_Init(WM8904_Object_t *pObj, WM8904_Init_t *pInit)
{
int32_t ret = WM8904_OK;
uint16_t tmp;
uint8_t iter_count;
if (pObj->IsInitialized == 1U)
{
ret += WM8904_DeInit(pObj);
}
/*********/
/* Reset */
/*********/
tmp = 0x0U; /* Reset registers */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_SW_RESET, &tmp, 2U);
/**************/
/* Clock rate */
/**************/
tmp = 0x0004U; /* CLK_SYS_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_CLOCK_RATES2, &tmp, 2U);
/**************************************/
/* Reference voltages and master bias */
/**************************************/
tmp = 0x0018U; /* ISEL[1:0] = 10b, BIAS_EN = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_BIAS_CONTROL0, &tmp, 2U);
tmp = 0x0047U; /* VMID_BUF_ENA = 1, VMID_RES[1:0] = 11b, VMID_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_VMID_CONTROL0, &tmp, 2U);
WM8904_Delay(pObj, 100U);
tmp = 0x0043U; /* VMID_BUF_ENA = 1, VMID_RES[1:0] = 01b, VMID_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_VMID_CONTROL0, &tmp, 2U);
tmp = 0x0019U; /* ISEL[1:0] = 10b, BIAS_EN = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_BIAS_CONTROL0, &tmp, 2U);
/********************/
/* Mic BIAS control */
/********************/
tmp = 0x0001U; /* MICBIAS_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_MIC_BIAS_CONTROL0, &tmp, 2U);
/**********************/
/* Power management */
/**********************/
if (pInit->InputDevice == WM8904_IN_LINE2)
{
tmp = 0x0003U; /* INL_ENA = INR_ENA = 1 */
}
else if (pInit->InputDevice == WM8904_IN_MIC1)
{
tmp = 0x0002U; /* INL_ENA = 1 */
}
else /* WM8904_IN_DIGITAL_MIC2 */
{
tmp = 0x0000U; /* INx_ENA = 0 */
}
ret += wm8904_write_reg(&pObj->Ctx, WM8904_PWR_MANAGEMENT0, &tmp, 2U);
tmp = 0x0003U; /* HPL_PGA_ENA = HPR_PGA_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_PWR_MANAGEMENT2, &tmp, 2U);
/**************/
/* Clock rate */
/**************/
tmp = 0x0006U; /* CLK_DSP_ENA = CLK_SYS_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_CLOCK_RATES2, &tmp, 2U);
/**************/
/* DAC and ADC*/
/**************/
if (pInit->InputDevice == WM8904_IN_LINE2)
{
tmp = 0x000FU; /* ADCR_ENA = ADCL_ENA = DACL_ENA = DACR_ENA = 1 */
}
else if (pInit->InputDevice == WM8904_IN_MIC1)
{
tmp = 0x000EU; /* ADCL_ENA = DACL_ENA = DACR_ENA = 1, ADCR_ENA = 0 */
}
else /* WM8904_IN_DIGITAL_MIC2 */
{
tmp = 0x000EU; /* ADCL_ENA = DACL_ENA = DACR_ENA = 1, ADCR_ENA = 0 */
}
ret += wm8904_write_reg(&pObj->Ctx, WM8904_PWR_MANAGEMENT6, &tmp, 2U);
if (pInit->InputDevice == WM8904_IN_DIGITAL_MIC2)
{
tmp = 0x1800U; /* DMIC_ENA = 1, DMIC_SRC = IN1R/DMICDAT2 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DIGITAL_MICROPHONE0, &tmp, 2U);
tmp = 0x0008U; /* GPIO1_PU = GPIO1_PD = 0, GPIO1_SEL = 8 (DMIC clock out) */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_GPIO_CONTROL1, &tmp, 2U);
tmp = 0x81AFU; /* DRC_ENA = 1, DRC_DAC_PATH = ADC */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DRC0, &tmp, 2U);
tmp = 0x32C1U; /* DRC_MINGAIN = 0dB, DRC_MAXGAIN = 18dB */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DRC1, &tmp, 2U);
tmp = 0x002CU; /* DRC_HIGH_COMP = DRC_LO_COMP = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DRC2, &tmp, 2U);
if (pInit->Frequency >= WM8904_FREQUENCY_32K)
{
tmp = 0x0000U; /* ADC_OSR128 = 0 (64xfs) */
}
else
{
tmp = 0x0001U; /* ADC_OSR128 = 1 (128xfs) */
}
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_ADC0, &tmp, 2U);
}
else
{
tmp = 0x0000U; /* DMIC_ENA = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DIGITAL_MICROPHONE0, &tmp, 2U);
tmp = 0x0014U; /* GPIO1_PU = GPIO1_PD = 1, GPIO1_SEL = 4 (IRQ, default value) */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_GPIO_CONTROL1, &tmp, 2U);
tmp = 0x01AFU; /* DRC_ENA = 0, DRC_DAC_PATH = ADC */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DRC0, &tmp, 2U);
tmp = 0x0001U; /* ADC_OSR128 = 1 (128xfs) */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_ADC0, &tmp, 2U);
}
tmp = 0x0648U; /* DAC_MUTERATE = DAC_UNMUTE_RAMP = DAC_OSR128 = DAC_MUTE = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DAC_DIGITAL1, &tmp, 2U);
if (pInit->InputDevice == WM8904_IN_LINE2)
{
tmp = 0x0050U; /* AIFADCL_SRC = 0 (Left), AIFADCR_SRC = 1 (Right) */
}
else if (pInit->InputDevice == WM8904_IN_MIC1)
{
tmp = 0x0010U; /* AIFADCL_SRC = 0 (Left), AIFADCR_SRC = 0 (left) */
}
else /* WM8904_IN_DIGITAL_MIC2 */
{
tmp = 0x0010U; /* AIFADCL_SRC = 0 (Left), AIFADCR_SRC = 0 (Left) */
}
ret += wm8904_write_reg(&pObj->Ctx, WM8904_AUDIO_INTERFACE0, &tmp, 2U);
/**********************************************/
/* Resolution, protocol, frequency and volume */
/**********************************************/
tmp = 0x0002U; /* Set resolution to 16B and protocol to I2S*/
ret += wm8904_write_reg(&pObj->Ctx, WM8904_AUDIO_INTERFACE1, &tmp, 2U);
ret += WM8904_SetFrequency(pObj, pInit->Frequency);
if (pInit->OutputDevice == WM8904_OUT_HEADPHONE)
{
ret += WM8904_SetVolume(pObj, VOLUME_OUTPUT, (uint8_t)pInit->Volume);
}
if (pInit->InputDevice == WM8904_IN_LINE2)
{
tmp = 0x01C0U; /* ADC_VU = 1, ADC_VOL = +0dB */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ADC_DIGITAL_VOL_LEFT, &tmp, 2U);
tmp = 0x01C0U; /* ADC_VU = 1, ADC_VOL = +0dB */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ADC_DIGITAL_VOL_RIGHT, &tmp, 2U);
tmp = 0x0080U; /* LINMUTE = 1, LIN_VOL = -1.5dB */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_LEFT_INPUT0, &tmp, 2U);
tmp = 0x0080U; /* LINMUTE = 1, LIN_VOL = -1.5dB */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_RIGHT_INPUT0, &tmp, 2U);
tmp = 0x0014U; /* INL_CM_ENA = 0, L_IP_SEL_N[1:0] = IN2L (01b) */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_LEFT_INPUT1, &tmp, 2U);
tmp = 0x0014U; /* INR_CM_ENA = 0, R_IP_SEL_N[1:0] = IN2R (01b) */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_RIGHT_INPUT1, &tmp, 2U);
}
else if (pInit->InputDevice == WM8904_IN_MIC1)
{
tmp = 0x01FFU; /* ADC_VU = 1, ADC_VOL = +17dB */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ADC_DIGITAL_VOL_LEFT, &tmp, 2U);
tmp = 0x009AU; /* LINMUTE = 1, LIN_VOL = +14dB */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_LEFT_INPUT0, &tmp, 2U);
tmp = 0x0044U; /* INL_CM_ENA = 1, L_IP_SEL_N[1:0] = IN1L (00b) */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_LEFT_INPUT1, &tmp, 2U);
}
else /* WM8904_IN_DIGITAL_MIC2 */
{
tmp = 0x0100U; /* ADC_VU = 1, ADC_VOL = Mute */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ADC_DIGITAL_VOL_LEFT, &tmp, 2U);
}
/***************/
/* Charge pump */
/***************/
tmp = 0x0001U; /* CP_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_CHARGE_PUMP0, &tmp, 2U);
/**********************/
/* Output signal path */
/**********************/
tmp = 0x0011U; /* HPL_ENA = HPR_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_HP0, &tmp, 2U);
tmp = 0x0033U; /* HPL_ENA_DLY = HPR_ENA_DLY = HPL_ENA = HPR_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_HP0, &tmp, 2U);
/************/
/* DC Servo */
/************/
tmp = 0x0003U; /* Enable channels 0 and 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DC_SERVO0, &tmp, 2U);
tmp = 0x0030U; /* Startup 0 and 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DC_SERVO1, &tmp, 2U);
iter_count = 0;
tmp = 0x0000U;
while(((tmp & 0x0003U) != 0x0003U) && (iter_count < 30U)) /* Wait DC Servo start-up completion */
{
iter_count ++;
WM8904_Delay(pObj, 10U);
ret += wm8904_read_reg(&pObj->Ctx, WM8904_DC_SERVO_READBACK0, &tmp, 2U);
}
/**********************/
/* Output signal path */
/**********************/
tmp = 0x0077U; /* HPL_ENA_OUTP= HPL_ENA_OUTR = HPL_ENA_DLY = HPR_ENA_DLY = HPL_ENA = HPR_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_HP0, &tmp, 2U);
tmp = 0x00FFU; /* HPL_RMV_SHORT = HPR_RMV_SHORT = HPL_ENA_OUTP= HPL_ENA_OUTR = HPL_ENA_DLY = HPR_ENA_DLY = HPL_ENA = HPR_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_HP0, &tmp, 2U);
/***************/
/* Charge pump */
/***************/
tmp = 0x01U; /* CP_DYN_PWR = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_CLASS_W0, &tmp, 2U);
/* Store current devices */
WM8904_CurrentDevices = (pInit->OutputDevice | pInit->InputDevice);
pObj->IsInitialized = 1U;
return ret;
}
/**
* @brief Deinitializes the audio codec.
* @param pObj pointer to component object
* @retval Component status
*/
int32_t WM8904_DeInit(WM8904_Object_t *pObj)
{
int32_t ret;
uint16_t tmp;
/**********************/
/* Output signal path */
/**********************/
tmp = 0x0077U; /* HPL_RMV_SHORT = HPR_RMV_SHORT = 0 */
ret = wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_HP0, &tmp, 2U);
tmp = 0x0000U; /* HPL_ENA_OUTP= HPL_ENA_OUTR = HPL_ENA_DLY = HPR_ENA_DLY = HPL_ENA = HPR_ENA = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_HP0, &tmp, 2U);
/************/
/* DC Servo */
/************/
tmp = 0x0000U; /* Disable channels 0 and 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DC_SERVO0, &tmp, 2U);
/***************/
/* Charge pump */
/***************/
tmp = 0x0000U; /* CP_ENA = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_CHARGE_PUMP0, &tmp, 2U);
/*******/
/* DAC */
/*******/
tmp = 0x0000U; /* ADCL_ENA = DACL_ENA = DACR_ENA = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_PWR_MANAGEMENT6, &tmp, 2U);
/**************/
/* Clock rate */
/**************/
tmp = 0x0004U; /* CLK_DSP_ENA = 0, CLK_SYS_ENA = 1 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_CLOCK_RATES2, &tmp, 2U);
/**********************/
/* Output signal path */
/**********************/
tmp = 0x0000U; /* HPL_PGA_ENA = HPR_PGA_ENA = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_PWR_MANAGEMENT2, &tmp, 2U);
tmp = 0x0000U; /* INL_ENA = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_PWR_MANAGEMENT0, &tmp, 2U);
/********************/
/* Mic BIAS control */
/********************/
tmp = 0x0000U; /* MICBIAS_ENA = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_MIC_BIAS_CONTROL0, &tmp, 2U);
/**************************************/
/* Reference voltages and master bias */
/**************************************/
tmp = 0x0018U; /* ISEL[1:0] = 10b, BIAS_EN = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_BIAS_CONTROL0, &tmp, 2U);
tmp = 0x0042U; /* VMID_BUF_ENA = 1, VMID_RES[1:0] = 01b, VMID_ENA = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_VMID_CONTROL0, &tmp, 2U);
WM8904_Delay(pObj, 300U);
tmp = 0x0042U; /* VMID_BUF_ENA = 1, VMID_RES[1:0] = 01b, VMID_ENA = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_VMID_CONTROL0, &tmp, 2U);
tmp = 0x0000U; /* VMID_BUF_ENA = 0, VMID_RES[1:0] = 00b, VMID_ENA = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_VMID_CONTROL0, &tmp, 2U);
tmp = 0x0018U; /* ISEL[1:0] = 10b, BIAS_EN = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_BIAS_CONTROL0, &tmp, 2U);
/**************/
/* Clock rate */
/**************/
tmp = 0x0000U; /* CLK_SYS_ENA = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_CLOCK_RATES2, &tmp, 2U);
if (pObj->IsInitialized == 1U)
{
pObj->IsInitialized = 0U;
}
return ret;
}
/**
* @brief Get the WM8904 ID.
* @param pObj pointer to component object
* @param Id component ID
* @retval Component status
*/
int32_t WM8904_ReadID(WM8904_Object_t *pObj, uint32_t *Id)
{
int32_t ret;
uint16_t wm8904_id;
/* Initialize the Control interface of the Audio Codec */
pObj->IO.Init();
/* Get ID from component */
ret = wm8904_read_reg(&pObj->Ctx, WM8904_SW_RESET, &wm8904_id, 2U);
*Id = wm8904_id;
return ret;
}
/**
* @brief Start the audio Codec play feature.
* @note For this codec no Play options are required.
* @param pObj pointer to component object
* @retval Component status
*/
int32_t WM8904_Play(WM8904_Object_t *pObj)
{
int32_t ret = WM8904_OK;
uint16_t tmp;
if ((WM8904_CurrentDevices & WM8904_OUT_HEADPHONE) == WM8904_OUT_HEADPHONE)
{
tmp = 0x0640U; /* DAC_MUTE= 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DAC_DIGITAL1, &tmp, 2U);
}
if ((WM8904_CurrentDevices & WM8904_IN_MIC1) == WM8904_IN_MIC1)
{
tmp = 0x001AU; /* LINMUTE = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_LEFT_INPUT0, &tmp, 2U);
}
if ((WM8904_CurrentDevices & WM8904_IN_LINE2) == WM8904_IN_LINE2)
{
tmp = 0x0000U; /* LINMUTE = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_LEFT_INPUT0, &tmp, 2U);
tmp = 0x0000U; /* LINMUTE = 0 */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_RIGHT_INPUT0, &tmp, 2U);
}
if ((WM8904_CurrentDevices & WM8904_IN_DIGITAL_MIC2) == WM8904_IN_DIGITAL_MIC2)
{
tmp = 0x01F0U; /* ADC_VU = 1, ADC_VOL = +17,6dB */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ADC_DIGITAL_VOL_LEFT, &tmp, 2U);
}
return ret;
}
/**
* @brief Pauses playing on the audio codec.
* @param pObj pointer to component object
* @retval Component status
*/
int32_t WM8904_Pause(WM8904_Object_t *pObj)
{
/* Pause the audio file playing */
return WM8904_SetMute(pObj, WM8904_MUTE_ON);
}
/**
* @brief Resumes playing on the audio codec.
* @param pObj pointer to component object
* @retval Component status
*/
int32_t WM8904_Resume(WM8904_Object_t *pObj)
{
/* Resumes the audio file playing */
return WM8904_SetMute(pObj, WM8904_MUTE_OFF);
}
/**
* @brief Stops audio Codec playing. It powers down the codec.
* @param pObj pointer to component object
* @param CodecPdwnMode selects the power down mode.
* - WM8904_PDWN_SW: only mutes the audio codec. When resuming from this
* mode the codec keeps the previous initialization
* (no need to re-Initialize the codec registers).
* - WM8904_PDWN_HW: Physically power down the codec. When resuming from this
* mode, the codec is set to default configuration
* (user should re-Initialize the codec in order to
* play again the audio stream).
* @retval 0 if correct communication, else wrong communication
*/
int32_t WM8904_Stop(WM8904_Object_t *pObj, uint32_t CodecPdwnMode)
{
int32_t ret;
/* Mute the output */
ret = WM8904_SetMute(pObj, WM8904_MUTE_ON);
if (CodecPdwnMode == WM8904_PDWN_HW)
{
ret += WM8904_DeInit(pObj);
}
return ret;
}
/**
* @brief Set higher or lower the codec volume level.
* @param pObj pointer to component object
* @param InputOutput Input or Output volume
* @param Volume a byte value from 0 to 100.
* (refer to codec registers description for more details).
* @retval Component status
*/
int32_t WM8904_SetVolume(WM8904_Object_t *pObj, uint32_t InputOutput, uint8_t Volume)
{
uint8_t convertedvol;
int32_t ret;
uint16_t tmp;
/* Only output volume supported */
if (InputOutput == VOLUME_OUTPUT)
{
convertedvol = VOLUME_OUT_CONVERT(Volume);
tmp = (uint16_t) convertedvol;
/* Left Headphone Volume */
ret = wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_OUTPUT1_LEFT, &tmp, 2U);
/* Right Headphone Volume */
tmp |= 0x80U; /* Volume update */
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_OUTPUT1_RIGHT, &tmp, 2U);
}
else /* Input volume */
{
ret = WM8904_ERROR;
}
return ret;
}
/**
* @brief Get higher or lower the codec volume level.
* @param pObj pointer to component object
* @param InputOutput Input or Output volume
* @param Volume audio volume
* @retval Component status
*/
int32_t WM8904_GetVolume(WM8904_Object_t *pObj, uint32_t InputOutput, uint8_t *Volume)
{
int32_t ret;
uint16_t tmp;
/* only output volume supported */
if (InputOutput == VOLUME_OUTPUT)
{
/* Left Headphone Volume */
ret = wm8904_read_reg(&pObj->Ctx, WM8904_ANALOG_OUTPUT1_LEFT, &tmp, 2U);
tmp &= 0x3FU;
if (ret == WM8904_OK)
{
*Volume = (uint8_t) VOLUME_OUT_INVERT(tmp);
}
}
else /* Input volume */
{
ret = WM8904_ERROR;
}
return ret;
}
/**
* @brief Enables or disables the mute feature on the audio codec.
* @param pObj pointer to component object
* @param Cmd WM8904_MUTE_ON to enable the mute or WM8904_MUTE_OFF to disable the
* mute mode.
* @retval 0 if correct communication, else wrong communication
*/
int32_t WM8904_SetMute(WM8904_Object_t *pObj, uint32_t Cmd)
{
int32_t ret = WM8904_OK;
uint16_t tmp;
if ((WM8904_CurrentDevices & WM8904_OUT_HEADPHONE) == WM8904_OUT_HEADPHONE)
{
/* Read DAC digital 1 */
ret += wm8904_read_reg(&pObj->Ctx, WM8904_DAC_DIGITAL1, &tmp, 2U);
if(Cmd == WM8904_MUTE_ON)
{
/* Mute the DAC Digital 1 */
tmp |= 0x0008U;
}
else
{
/* Unmute the the DAC Digital 1 */
tmp &= ~(0x0008U);
}
ret += wm8904_write_reg(&pObj->Ctx, WM8904_DAC_DIGITAL1, &tmp, 2U);
}
if ((WM8904_CurrentDevices & WM8904_IN_MIC1) == WM8904_IN_MIC1)
{
if(Cmd == WM8904_MUTE_ON)
{
tmp = 0x009AU; /* LINMUTE = 1 */
}
else
{
tmp = 0x001AU; /* LINMUTE = 0 */
}
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_LEFT_INPUT0, &tmp, 2U);
}
if ((WM8904_CurrentDevices & WM8904_IN_LINE2) == WM8904_IN_LINE2)
{
if(Cmd == WM8904_MUTE_ON)
{
tmp = 0x0080U; /* LINMUTE = 1 */
}
else
{
tmp = 0x0000U; /* LINMUTE = 0 */
}
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_LEFT_INPUT0, &tmp, 2U);
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ANALOG_RIGHT_INPUT0, &tmp, 2U);
}
if ((WM8904_CurrentDevices & WM8904_IN_DIGITAL_MIC2) == WM8904_IN_DIGITAL_MIC2)
{
if(Cmd == WM8904_MUTE_ON)
{
tmp = 0x0100U; /* ADC_VU = 1, ADC_VOL = Mute */
}
else
{
tmp = 0x01F0U; /* ADC_VU = 1, ADC_VOL = +17,6dB */
}
ret += wm8904_write_reg(&pObj->Ctx, WM8904_ADC_DIGITAL_VOL_RIGHT, &tmp, 2U);
}
return ret;
}
/**
* @brief Switch dynamically (while audio file is played) the output target
* (speaker or headphone).
* @param pObj pointer to component object
* @param Output specifies the audio output target: WM8904_OUT_HEADPHONE,
* WM8904_OUT_LINE or WM8904_OUT_BOTH.
* @retval 0 if correct communication, else wrong communication
*/
int32_t WM8904_SetOutputMode(WM8904_Object_t *pObj, uint32_t Output)
{
(void)(pObj);
(void)(Output);
/* This feature is currently not supported */
return WM8904_ERROR;
}
/**
* @brief Set Audio resolution.
* @param pObj pointer to component object
* @param Resolution Audio resolution. Can be:
* WM8904_RESOLUTION_16b, WM8904_RESOLUTION_20b,
* WM8904_RESOLUTION_24b or WM8904_RESOLUTION_32b
* @retval Component status
*/
int32_t WM8904_SetResolution(WM8904_Object_t *pObj, uint32_t Resolution)
{
(void)(pObj);
(void)(Resolution);
/* This feature is currently not supported */
return WM8904_ERROR;
}
/**
* @brief Get Audio resolution.
* @param pObj pointer to component object
* @retval Audio resolution
*/
int32_t WM8904_GetResolution(WM8904_Object_t *pObj, uint32_t *Resolution)
{
int32_t ret;
uint16_t tmp;
ret = wm8904_read_reg(&pObj->Ctx, WM8904_AUDIO_INTERFACE1, &tmp, 2U);
switch((tmp >> 2U) & 0x0003U)
{
case 1:
*Resolution = WM8904_RESOLUTION_20B;
break;
case 2:
*Resolution = WM8904_RESOLUTION_24B;
break;
case 3:
*Resolution = WM8904_RESOLUTION_32B;
break;
case 0:
default:
*Resolution = WM8904_RESOLUTION_16B;
break;
}
return ret;
}
/**
* @brief Set Audio Protocol.
* @param pObj pointer to component object
* @param Protocol Audio Protocol. Can be:
* WM8904_PROTOCOL_R_JUSTIFIED, WM8904_PROTOCOL_L_JUSTIFIED,
* WM8904_PROTOCOL_I2S or WM8904_PROTOCOL_DSP
* @retval Component status
*/
int32_t WM8904_SetProtocol(WM8904_Object_t *pObj, uint32_t Protocol)
{
(void)(pObj);
(void)(Protocol);
/* This feature is currently not supported */
return WM8904_ERROR;
}
/**
* @brief Get Audio Protocol.
* @param pObj pointer to component object
* @retval Component status
*/
int32_t WM8904_GetProtocol(WM8904_Object_t *pObj, uint32_t *Protocol)
{
int32_t ret;
uint16_t tmp;
ret = wm8904_read_reg(&pObj->Ctx, WM8904_AUDIO_INTERFACE1, &tmp, 2U);
*Protocol = (((uint32_t) tmp) & 0x0003U);
return ret;
}
/**
* @brief Sets new frequency.
* @param pObj pointer to component object
* @param AudioFreq Audio frequency
* @retval Component status
*/
int32_t WM8904_SetFrequency(WM8904_Object_t *pObj, uint32_t AudioFreq)
{
int32_t ret;
uint16_t tmp;
switch (AudioFreq)
{
case WM8904_FREQUENCY_8K:
/* Sample Rate = 8 (kHz), ratio=256 */
tmp = 0x0C00U;
ret = wm8904_write_reg(&pObj->Ctx, WM8904_CLOCK_RATES1, &tmp, 2U);
break;
case WM8904_FREQUENCY_11K:
/* Sample Rate = 11.025 (kHz), ratio=256 */
tmp = 0x0C01U;
ret = wm8904_write_reg(&pObj->Ctx, WM8904_CLOCK_RATES1, &tmp, 2U);
break;
case WM8904_FREQUENCY_16K:
/* Sample Rate = 16 (kHz), ratio=256 */
tmp = 0x0C02U;
ret = wm8904_write_reg(&pObj->Ctx, WM8904_CLOCK_RATES1, &tmp, 2U);
break;
case WM8904_FREQUENCY_22K:
case WM8904_FREQUENCY_24K:
/* Sample Rate = 22.050 (kHz) or 24 (kHz), ratio=256 */
tmp = 0x0C03U;
ret = wm8904_write_reg(&pObj->Ctx, WM8904_CLOCK_RATES1, &tmp, 2U);
break;
case WM8904_FREQUENCY_32K:
/* Sample Rate = 32 (kHz), ratio=256 */
tmp = 0x0C04U;
ret = wm8904_write_reg(&pObj->Ctx, WM8904_CLOCK_RATES1, &tmp, 2U);
break;
case WM8904_FREQUENCY_44K:
case WM8904_FREQUENCY_48K:
/* Sample Rate = 44.1 (kHz) or 48 (kHz), ratio=256 */
tmp = 0x0C05U;
ret = wm8904_write_reg(&pObj->Ctx, WM8904_CLOCK_RATES1, &tmp, 2U);
break;
default:
ret = WM8904_ERROR;
break;
}
return ret;
}
/**
* @brief Get frequency.
* @param pObj pointer to component object
* @param AudioFreq Audio frequency
* @retval Component status
*/
int32_t WM8904_GetFrequency(WM8904_Object_t *pObj, uint32_t *AudioFreq)
{
int32_t ret;
uint16_t tmp;
ret = wm8904_read_reg(&pObj->Ctx, WM8904_CLOCK_RATES1, &tmp, 2U);
switch(tmp & 0x0007U)
{
case 0:
*AudioFreq = WM8904_FREQUENCY_8K;
break;
case 1:
/* 11.025k or 12k */
*AudioFreq = WM8904_FREQUENCY_11K;
break;
case 2:
*AudioFreq = WM8904_FREQUENCY_16K;
break;
case 3:
/* 22.05k or 24k */
*AudioFreq = WM8904_FREQUENCY_22K;
break;
case 4:
*AudioFreq = WM8904_FREQUENCY_32K;
break;
case 5:
/* 44.1k or 48k */
*AudioFreq = WM8904_FREQUENCY_44K;
break;
default:
ret = WM8904_ERROR;
break;
}
return ret;
}
/**
* @brief Resets wm8904 registers.
* @param pObj pointer to component object
* @retval Component status if correct communication, else wrong communication
*/
int32_t WM8904_Reset(WM8904_Object_t *pObj)
{
uint16_t tmp;
int32_t ret;
/* Reset Codec by writing in 0x0000 address register */
tmp = 0x0U; /* Reset registers */
ret = wm8904_write_reg(&pObj->Ctx, WM8904_SW_RESET, &tmp, 2U);
return ret;
}
/**
* @brief Function
* @param Component object pointer
* @retval error status
*/
int32_t WM8904_RegisterBusIO(WM8904_Object_t *pObj, const WM8904_IO_t *pIO)
{
int32_t ret;
if (pObj == NULL)
{
ret = WM8904_ERROR;
}
else
{
pObj->IO.Init = pIO->Init;
pObj->IO.DeInit = pIO->DeInit;
pObj->IO.Address = pIO->Address;
pObj->IO.WriteReg = pIO->WriteReg;
pObj->IO.ReadReg = pIO->ReadReg;
pObj->IO.GetTick = pIO->GetTick;
pObj->Ctx.ReadReg = WM8904_ReadRegWrap;
pObj->Ctx.WriteReg = WM8904_WriteRegWrap;
pObj->Ctx.handle = pObj;
if(pObj->IO.Init != NULL)
{
ret = pObj->IO.Init();
}
else
{
ret = WM8904_ERROR;
}
}
return ret;
}
/**
* @}
*/
/******************** Static functions ****************************************/
/** @addtogroup WM8904_Private_Functions
* @{
*/
/**
* @brief This function provides accurate delay (in milliseconds)
* @param pObj pointer to component object
* @param Delay: specifies the delay time length, in milliseconds
*/
static void WM8904_Delay(const WM8904_Object_t *pObj, uint32_t Delay)
{
uint32_t tickstart;
tickstart = pObj->IO.GetTick();
while((pObj->IO.GetTick() - tickstart) < Delay)
{
}
}
/**
* @brief Function
* @param handle Component object handle
* @param Reg The target register address to write
* @param pData The target register value to be written
* @param Length buffer size to be written
* @retval error status
*/
static int32_t WM8904_ReadRegWrap(void *handle, uint16_t Reg, uint8_t* pData, uint16_t Length)
{
const WM8904_Object_t *pObj = (WM8904_Object_t *)handle;
return pObj->IO.ReadReg(pObj->IO.Address, Reg, pData, Length);
}
/**
* @brief Function
* @param handle Component object handle
* @param Reg The target register address to write
* @param pData The target register value to be written
* @param Length buffer size to be written
* @retval error status
*/
static int32_t WM8904_WriteRegWrap(void *handle, uint16_t Reg, uint8_t* pData, uint16_t Length)
{
const WM8904_Object_t *pObj = (WM8904_Object_t *)handle;
return pObj->IO.WriteReg(pObj->IO.Address, Reg, pData, Length);
}
/**
* @}
*/
/**
* @}
*/
/**
* @}
*/
/**
* @}
*/