/** ****************************************************************************** * @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); } /** * @} */ /** * @} */ /** * @} */ /** * @} */