From e0314e637e9787c95d3d09bc0869226ad10d631f Mon Sep 17 00:00:00 2001 From: xx Date: Wed, 28 Mar 2018 12:32:18 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0iis=E9=A9=B1=E5=8A=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- bsp/imxrt1052-evk/SConstruct | 8 +- bsp/imxrt1052-evk/drivers/SConscript | 3 + bsp/imxrt1052-evk/drivers/drv_codec.c | 387 +++++++++++++++ bsp/imxrt1052-evk/drivers/drv_codec.h | 228 +++++++++ bsp/imxrt1052-evk/drivers/fsl_wm8960.c | 621 +++++++++++++++++++++++++ bsp/imxrt1052-evk/drivers/fsl_wm8960.h | 426 +++++++++++++++++ 6 files changed, 1669 insertions(+), 4 deletions(-) create mode 100644 bsp/imxrt1052-evk/drivers/drv_codec.c create mode 100644 bsp/imxrt1052-evk/drivers/drv_codec.h create mode 100644 bsp/imxrt1052-evk/drivers/fsl_wm8960.c create mode 100644 bsp/imxrt1052-evk/drivers/fsl_wm8960.h diff --git a/bsp/imxrt1052-evk/SConstruct b/bsp/imxrt1052-evk/SConstruct index 23d4762514..6017040eed 100644 --- a/bsp/imxrt1052-evk/SConstruct +++ b/bsp/imxrt1052-evk/SConstruct @@ -3,10 +3,10 @@ import sys import rtconfig -if os.getenv('RTT_ROOT'): - RTT_ROOT = os.getenv('RTT_ROOT') -else: - RTT_ROOT = os.path.normpath(os.getcwd() + '/../..') +#if os.getenv('RTT_ROOT'): +# RTT_ROOT = os.getenv('RTT_ROOT') +#else: +RTT_ROOT = os.path.normpath(os.getcwd() + '/../..') sys.path = sys.path + [os.path.join(RTT_ROOT, 'tools')] from building import * diff --git a/bsp/imxrt1052-evk/drivers/SConscript b/bsp/imxrt1052-evk/drivers/SConscript index 6ca466f3c8..cf656eda91 100644 --- a/bsp/imxrt1052-evk/drivers/SConscript +++ b/bsp/imxrt1052-evk/drivers/SConscript @@ -38,6 +38,9 @@ if GetDepend('RT_USING_USB_DEVICE'): if GetDepend('RT_USING_RTGUI') or GetDepend('PKG_USING_GUIENGINE'): src += ['drv_lcd.c', 'drv_ft5406.c', 'drv_i2c.c'] +if GetDepend('RT_USING_AUDIO'): + src += ['drv_codec.c', 'fsl_wm8960.c'] + group = DefineGroup('Drivers', src, depend = [''], CPPPATH = CPPPATH, CPPDEFINES=CPPDEFINES) Return('group') diff --git a/bsp/imxrt1052-evk/drivers/drv_codec.c b/bsp/imxrt1052-evk/drivers/drv_codec.c new file mode 100644 index 0000000000..eb219ec0ac --- /dev/null +++ b/bsp/imxrt1052-evk/drivers/drv_codec.c @@ -0,0 +1,387 @@ +#include +#include +#include + +#include "board.h" +#include "drv_codec.h" +#include "fsl_wm8960.h" + +#include +#include +#include +#include + +#define DEMO_CODEC_WM8960 +#define DEMO_SAI SAI1 +#define DEMO_SAI_IRQ SAI1_IRQn +#define SAI_TxIRQHandler SAI1_IRQHandler + +/* Select Audio/Video PLL (786.48 MHz) as sai1 clock source */ +#define DEMO_SAI1_CLOCK_SOURCE_SELECT (2U) +/* Clock pre divider for sai1 clock source */ +#define DEMO_SAI1_CLOCK_SOURCE_PRE_DIVIDER (1U) +/* Clock divider for sai1 clock source */ +#define DEMO_SAI1_CLOCK_SOURCE_DIVIDER (63U) +/* Get frequency of sai1 clock */ +#define DEMO_SAI_CLK_FREQ (CLOCK_GetFreq(kCLOCK_AudioPllClk) / (DEMO_SAI1_CLOCK_SOURCE_DIVIDER + 1U) / (DEMO_SAI1_CLOCK_SOURCE_PRE_DIVIDER + 1U)) + +/* I2C instance and clock */ +#define DEMO_I2C LPI2C1 + +/* Select USB1 PLL (480 MHz) as master lpi2c clock source */ +#define DEMO_LPI2C_CLOCK_SOURCE_SELECT (0U) +/* Clock divider for master lpi2c clock source */ +#define DEMO_LPI2C_CLOCK_SOURCE_DIVIDER (5U) +/* Get frequency of lpi2c clock */ +#define DEMO_I2C_CLK_FREQ ((CLOCK_GetFreq(kCLOCK_Usb1PllClk) / 8) / (DEMO_LPI2C_CLOCK_SOURCE_DIVIDER + 1U)) + +/* DMA */ +#define DMAMUX0 DMAMUX +#define EXAMPLE_DMA DMA0 +#define EXAMPLE_CHANNEL (0U) +#define EXAMPLE_SAI_TX_SOURCE kDmaRequestMuxSai1Tx + +struct imxcodec +{ + I2S_Type *sai; + sai_edma_handle_t txHandle; + wm8960_handle_t codecHandle; + edma_handle_t dmaHandle; + lpi2c_master_handle_t i2cHandle; + sai_transfer_format_t format; +}; + +static void _InitPins(void) +{ + CLOCK_EnableClock(kCLOCK_Iomuxc); + + IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_00_LPI2C1_SCL, 1); + IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_01_LPI2C1_SDA, 1); + + IOMUXC_SetPinConfig( IOMUXC_GPIO_AD_B1_00_LPI2C1_SCL, 0xD8B0u); + IOMUXC_SetPinConfig( IOMUXC_GPIO_AD_B1_01_LPI2C1_SDA, 0xD8B0u); + + IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_09_SAI1_MCLK, 1U); + IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00, 1U); + IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK, 1U); + IOMUXC_SetPinMux(IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC, 1U); + + IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_09_SAI1_MCLK, 0x10B0u); + IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_13_SAI1_TX_DATA00, 0x10B0u); + IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_14_SAI1_TX_BCLK, 0x10B0u); + IOMUXC_SetPinConfig(IOMUXC_GPIO_AD_B1_15_SAI1_TX_SYNC, 0x10B0u); +} + +static void BOARD_EnableSaiMclkOutput(bool enable) +{ + if (enable) + { + IOMUXC_GPR->GPR1 |= IOMUXC_GPR_GPR1_SAI1_MCLK_DIR_MASK; + } + else + { + IOMUXC_GPR->GPR1 &= (~IOMUXC_GPR_GPR1_SAI1_MCLK_DIR_MASK); + } +} + +static void saidma_callback(I2S_Type *base, sai_edma_handle_t *handle, status_t status, void *userData) +{ + int ind = 0; + rt_uint8_t *saddr; + + ind = handle->queueDriver; + saddr = (rt_uint8_t*)handle->saiQueue[ind].data; + rt_audio_tx_complete(userData, saddr); +} + +/********************************************************************************************************* +** Audio device +*********************************************************************************************************/ + +static rt_err_t icodec_getcaps(struct rt_audio_device *audio,struct rt_audio_caps *caps) +{ + rt_err_t result = RT_EOK; + struct imxcodec *icodec = (struct imxcodec *)audio->parent.user_data; + + switch (caps->main_type) + { + case AUDIO_TYPE_QUERY: /* qurey the types of hw_codec device */ + { + switch (caps->sub_type) + { + case AUDIO_TYPE_QUERY: + caps->udata.mask = AUDIO_TYPE_OUTPUT | AUDIO_TYPE_MIXER; + break; + default: + result = -RT_ERROR; + break; + } + + break; + } + case AUDIO_TYPE_OUTPUT: /* Provide capabilities of OUTPUT unit */ + switch (caps->sub_type) + { + case AUDIO_DSP_PARAM: + if (audio->replay == NULL) + { + result = -RT_ERROR; + break; + } + caps->udata.config.channels = 1; + caps->udata.config.samplefmt = 1; + caps->udata.config.samplerate = 1; + caps->udata.config.samplefmts = 1; + break; + default: + result = -RT_ERROR; + break; + } + break; + case AUDIO_TYPE_MIXER: /* report the Mixer Units */ + switch (caps->sub_type) + { + case AUDIO_MIXER_QUERY: + caps->udata.mask = AUDIO_MIXER_VOLUME | AUDIO_MIXER_DIGITAL | AUDIO_MIXER_LINE; + break; + case AUDIO_MIXER_VOLUME: + caps->udata.value = WM8960_GetVolume(&icodec->codecHandle, kWM8960_ModuleDAC); + break; + case AUDIO_MIXER_DIGITAL: + + break; + case AUDIO_MIXER_LINE: + + break; + default: + result = -RT_ERROR; + break; + } + break; + default: + result = -RT_ERROR; + break; + } + + return result; +} + +static rt_err_t icodec_configure(struct rt_audio_device *audio,struct rt_audio_caps *caps) +{ + rt_err_t result = RT_EOK; + struct imxcodec *icodec = (struct imxcodec *)audio->parent.user_data; + + switch (caps->main_type) + { + case AUDIO_TYPE_MIXER: + { + switch (caps->sub_type) + { + case AUDIO_MIXER_VOLUME: + { + WM8960_SetVolume(&icodec->codecHandle, kWM8960_ModuleDAC, + caps->udata.value); + } + break; + default: + { + result = -RT_ERROR; + } + break; + } + } + break; + case AUDIO_TYPE_OUTPUT: + { + switch (caps->sub_type) + { + case AUDIO_DSP_PARAM: + { + + } break; + case AUDIO_DSP_SAMPLERATE: + { + int rate = caps->udata.value; + + icodec->format.sampleRate_Hz = rate; + SAI_TxSetFormat(icodec->sai, &icodec->format, icodec->format.masterClockHz, icodec->format.masterClockHz); + } + break; + default: + { + result = -RT_ERROR; + } + break; + } + } + break; + default: + result = -RT_ERROR; + break; + } + + return result; +} + +static rt_err_t icodec_init(struct rt_audio_device *audio) +{ + sai_config_t config; + uint32_t mclkSourceClockHz = 0U; + edma_config_t dmaConfig = {0}; + lpi2c_master_config_t i2cConfig = {0}; + uint32_t i2cSourceClock; + clock_audio_pll_config_t audioPllConfig = {32, 1, 77, 100}; + struct imxcodec *icodec = audio->parent.user_data; + sai_transfer_format_t *format; + + icodec->sai = DEMO_SAI; + format = &icodec->format; + + _InitPins(); + CLOCK_InitAudioPll(&audioPllConfig); + + /*Clock setting for LPI2C*/ + CLOCK_SetMux(kCLOCK_Lpi2cMux, DEMO_LPI2C_CLOCK_SOURCE_SELECT); + CLOCK_SetDiv(kCLOCK_Lpi2cDiv, DEMO_LPI2C_CLOCK_SOURCE_DIVIDER); + + /*Clock setting for SAI1*/ + CLOCK_SetMux(kCLOCK_Sai1Mux, DEMO_SAI1_CLOCK_SOURCE_SELECT); + CLOCK_SetDiv(kCLOCK_Sai1PreDiv, DEMO_SAI1_CLOCK_SOURCE_PRE_DIVIDER); + CLOCK_SetDiv(kCLOCK_Sai1Div, DEMO_SAI1_CLOCK_SOURCE_DIVIDER); + + /*Enable MCLK clock*/ + BOARD_EnableSaiMclkOutput(true); + + /* Create EDMA handle */ + EDMA_GetDefaultConfig(&dmaConfig); + EDMA_Init(EXAMPLE_DMA, &dmaConfig); + EDMA_CreateHandle(&icodec->dmaHandle, EXAMPLE_DMA, EXAMPLE_CHANNEL); + + DMAMUX_Init(DMAMUX0); + DMAMUX_SetSource(DMAMUX0, EXAMPLE_CHANNEL, EXAMPLE_SAI_TX_SOURCE); + DMAMUX_EnableChannel(DMAMUX0, EXAMPLE_CHANNEL); + + /* Init SAI module */ + SAI_TxGetDefaultConfig(&config); + SAI_TxInit(DEMO_SAI, &config); + + /* Configure the audio format */ + format->bitWidth = kSAI_WordWidth16bits; + format->channel = 0U; + format->sampleRate_Hz = kSAI_SampleRate48KHz; + format->masterClockHz = DEMO_SAI_CLK_FREQ; + format->protocol = config.protocol; + format->stereo = kSAI_Stereo; + format->isFrameSyncCompact = 0; + format->watermark = FSL_FEATURE_SAI_FIFO_COUNT / 2U; + + /* Configure Sgtl5000 I2C */ + icodec->codecHandle.base = DEMO_I2C; + icodec->codecHandle.i2cHandle = &icodec->i2cHandle; + i2cSourceClock = DEMO_I2C_CLK_FREQ; + + LPI2C_MasterGetDefaultConfig(&i2cConfig); + LPI2C_MasterInit(DEMO_I2C, &i2cConfig, i2cSourceClock); + LPI2C_MasterTransferCreateHandle(DEMO_I2C, &icodec->i2cHandle, NULL, NULL); + + WM8960_Init(&icodec->codecHandle, NULL); + WM8960_ConfigDataFormat(&icodec->codecHandle, format->masterClockHz, format->sampleRate_Hz, format->bitWidth); + + SAI_TransferTxCreateHandleEDMA(icodec->sai, &icodec->txHandle, saidma_callback, audio, &icodec->dmaHandle); + + mclkSourceClockHz = DEMO_SAI_CLK_FREQ; + SAI_TransferTxSetFormatEDMA(icodec->sai, &icodec->txHandle, format, mclkSourceClockHz, format->masterClockHz); + + return RT_EOK; +} + +static rt_err_t icodec_shutdown(struct rt_audio_device *audio) +{ + return RT_EOK; +} + +rt_err_t icodec_start(struct rt_audio_device *audio,int stream) +{ + return RT_EOK; +} + +rt_err_t icodec_stop(struct rt_audio_device *audio,int stream) +{ + return RT_EOK; +} + +static rt_err_t icodec_suspend(struct rt_audio_device *audio,int stream) +{ + return RT_EOK; +} + +static rt_err_t icodec_resume(struct rt_audio_device *audio,int stream) +{ + return RT_EOK; +} + +static rt_err_t icodec_control (struct rt_audio_device *audio, int cmd, void *args) +{ + rt_err_t result = RT_EOK; + + switch (cmd) + { + case AUDIO_CTL_HWRESET: + + break; + default: + result = -RT_ERROR; + break; + } + + return result; +} + +static rt_size_t icodec_transmit(struct rt_audio_device *audio, const void *writeBuf, void *readBuf, rt_size_t size) +{ + struct imxcodec *icodec = (struct imxcodec *)audio->parent.user_data; + + if(writeBuf != RT_NULL) + { + sai_transfer_t xfer; + + xfer.data = (uint8_t *)writeBuf; + xfer.dataSize = size; + SAI_TransferSendEDMA(icodec->sai, &icodec->txHandle, &xfer); + + return size; + } + + return 0; +} + +static struct imxcodec _g_imxcodec; +static struct rt_audio_device _g_audio_device; +const struct rt_audio_ops _g_audio_ops = +{ + .getcaps = icodec_getcaps, + .configure = icodec_configure, + + .init = icodec_init, + .shutdown = icodec_shutdown, + .start = icodec_start, + .stop = icodec_stop, + .suspend = icodec_suspend, + .resume = icodec_resume, + .control = icodec_control, + + .transmit = icodec_transmit, +}; + +int rt_hw_codec_init(void) +{ + int result; + struct rt_audio_device *audio = &_g_audio_device; + + audio->ops = (struct rt_audio_ops*)&_g_audio_ops; + _g_imxcodec.sai = DEMO_SAI; + result = rt_audio_register(audio,"sound0", RT_DEVICE_FLAG_WRONLY, &_g_imxcodec); + + return result; +} +INIT_DEVICE_EXPORT(rt_hw_codec_init); diff --git a/bsp/imxrt1052-evk/drivers/drv_codec.h b/bsp/imxrt1052-evk/drivers/drv_codec.h new file mode 100644 index 0000000000..6ee6c354e9 --- /dev/null +++ b/bsp/imxrt1052-evk/drivers/drv_codec.h @@ -0,0 +1,228 @@ +#ifndef __DRV_CODEC_H__ +#define __DRV_CODEC_H__ + +#define SUNXI_DAC_DPC 0x00 +#define SUNXI_DAC_FIFOC 0x04 +#define SUNXI_DAC_FIFOS 0x08 +#define SUNXI_DAC_TXDATA 0x0c +#define SUNXI_ADC_FIFOC 0x10 +#define SUNXI_ADC_FIFOS 0x14 +#define SUNXI_ADC_RXDATA 0x18 +#define DAC_MIXER_CTRL 0x20 +#define ADC_MIXER_CTRL 0x24 +#define ADDA_TUNE 0x28 +#define BIAS_DA16_CAL_CTRL0 0x2C +#define BIAS_DA16_CAL_CTRL1 0x34 + +#define SUNXI_DAC_CNT 0x40 +#define SUNXI_ADC_CNT 0x44 +#define SUNXI_DAC_DG 0x48 +#define SUNXI_ADC_DG 0x4c + +#define AC_PR_CFG 0x400 + +/*AC_DAC_DPC:0x00*/ +#define EN_DAC 31 +#define MODQU 25 +#define DWA 24 +#define HPF_EN 18 +#define DVOL 12 +/*#define HUB_EN 0 */ + +/*AC_DAC_FIFOC:0x04*/ +#define DAC_FS 29 +#define FIR_VER 28 +#define SEND_LASAT 26 +#define FIFO_MODE 24 +#define DAC_DRQ_CLR_CNT 21 +#define TX_TRIG_LEVEL 8 +#define ADDA_LOOP_EN 7 +#define DAC_MONO_EN 6 +#define TX_SAMPLE_BITS 5 +#define DAC_DRQ_EN 4 +#define DAC_IRQ_EN 3 +#define FIFO_UNDERRUN_IRQ_EN 2 +#define FIFO_OVERRUN_IRQ_EN 1 +#define FIFO_FLUSH 0 + +/*AC_ADC_FIFOC:0x10*/ +#define ADFS 29 +#define EN_AD 28 +#define RX_FIFO_MODE 24 +#define ADCDFEN 16 +#define RX_FIFO_TRG_LEVEL 8 +#define ADC_MONO_EN 7 +#define RX_SAMPLE_BITS 6 +#define ADC_DRQ_EN 4 +#define ADC_IRQ_EN 3 +#define ADC_OVERRUN_IRQ_EN 1 +#define ADC_FIFO_FLUSH 0 + + +/*DAC_MIXER_CTRL: 0x20*/ +#define DAC_AG_R_EN 31 /* dac right enable bit */ +#define DAC_AG_L_EN 30 /* dac left enable bit */ +#define R_MIXER_EN 29 /* right output mixer */ +#define L_MIXER_EN 28 /* left output mixer */ +#define PH_R_MUTE 27 /* headphone right mute */ +#define PH_L_MUTE 26 /* headphone left mute */ +#define PH_R_PWR_SLT 25 +#define PH_L_PWR_SLT 24 +#define PH_COM_FC 22 +#define PH_COM_PROTEC 21 +#define R_MIXER_MUTE_MIC 20 +#define R_MIXER_MUTE_LINEIN 19 +#define R_MIXER_MUTE_FM 18 +#define R_MIXER_MUTE_R_DAC 17 +#define R_MIXER_MUTE_L_DAC 16 +#define R_MIXER_MUTE 16 +#define HP_POWER_EN 15 +#define L_MIXER_MUTE_MIC 12 +#define L_MIXER_MUTE_LINEIN 11 +#define L_MIXER_MUTE_FM 10 +#define L_MIXER_MUTE_R_DAC 9 +#define L_MIXER_MUTE_L_DAC 8 +#define L_MIXER_MUTE 8 +#define L_HP_TO_R_HP_MUTE 7 +#define R_HP_TO_L_HP_MUTE 6 +#define HP_VOL 0 + +/*ADC_MIXER_CTRL: 0x24*/ +#define ADC_EN 31 /* adc enable bit */ +/* mic in boost stage to L or R output mixer gain control */ +#define MIC_GAIN_CTL 24 +#define LINEIN_VOL 21 /* right output mixer */ +#define ADC_IN_GAIN_CTL 16 /* adc input gain control */ +#define COS_SLOP_TM 14 /* COS slop time control for Anti-pop */ +#define ADC_MIX_MUTE_MIC 13 +#define ADC_MIX_MUTE_FML 12 +#define ADC_MIX_MUTE_FMR 11 +#define ADC_MIX_MUTE_LINEIN 10 +#define ADC_MIX_MUTE_L 9 +#define ADC_MIX_MUTE_R 8 +#define ADC_MIX_MUTE 8 /* ADC mixer mute control */ +#define PA_SPEED_SLT 7 /* PA speed select->0: normal 1: fast */ +#define FM_TO_MIX_GAIN 4 /* FMin to mixer gain control */ +#define MIC_BST_AMP_EN 3 /* MIC boost AMP enable */ +#define MIC_BOST_GAIN 0 /* MIC boast AMP gain control */ + +/*AC_ADC_TXDATA:0x20*/ +#define TX_DATA 0 + +/*AC_DAC_CNT:0x40*/ +#define TX_CNT 0 + +/*AC_ADC_CNT:0x44*/ +#define RX_CNT 0 + +/*AC_DAC_DG:0x48*/ +/* +* DAC Modulator Debug +* 0:DAC Modulator Normal Mode +* 1:DAC Modulator Debug Mode +*/ +#define DAC_MODU_SELECT 11 +/* +* DAC Pattern Select +* 00:Normal(Audio sample from TX fifo) +* 01: -6 dB sin wave +* 10: -60 dB sin wave +* 11: silent wave +*/ +#define DAC_PATTERN_SELECT 9 +/* +* CODEC Clock Source Select +* 0:codec clock from PLL +* 1:codec clock from OSC(for debug) +*/ +#define CODEC_CLK_SELECT 8 +/* +* DAC output channel swap enable +* 0:disable +* 1:enable +*/ +#define DA_SWP 6 + +/*AC_ADC_DG:0x4c*/ +#define AD_SWP 24 + +/*AC_PR_CFG:0x400*/ +#define AC_PR_RST 28 +#define AC_PR_RW 24 +#define AC_PR_ADDR 16 +#define ADDA_PR_WDAT 8 +#define ADDA_PR_RDAT 0 + + + +/* 时钟配置相关寄存器 */ +#define R6_REG_CCU_BASE 0x01c20000 +#define R6_REG_PLL_AUDIO_CTRL (R6_REG_CCU_BASE + 0x008) +#define R6_REG_BUS_CLK_GATING_0 (R6_REG_CCU_BASE + 0x060) +#define R6_REG_BUS_CLK_GATING_1 (R6_REG_CCU_BASE + 0x064) +#define R6_REG_BUS_CLK_GATING_2 (R6_REG_CCU_BASE + 0x068) +#define R6_REG_AUDIO_CODEC_CLK (R6_REG_CCU_BASE + 0x140) +#define R6_REG_BUS_SOFT_RST_0 (R6_REG_CCU_BASE + 0x02C0) +#define R6_REG_BUS_SOFT_RST_1 (R6_REG_CCU_BASE + 0x02C4) +#define R6_REG_BUS_SOFT_RST_2 (R6_REG_CCU_BASE + 0x02D0) + +/* GPIO配置相关寄存器 */ +#define R6_REG_PIO_BASE 0x01c20800 +#define R6_REG_PD_CFG0 (R6_REG_PIO_BASE + (3 * 0x24 + 0X00)) +#define R6_REG_PD_CFG1 (R6_REG_PIO_BASE + (3 * 0x24 + 0X04)) +#define R6_REG_PD_CFG2 (R6_REG_PIO_BASE + (3 * 0x24 + 0X08)) +#define R6_REG_PD_CFG3 (R6_REG_PIO_BASE + (3 * 0x24 + 0X0c)) +#define R6_REG_PD_DATA (R6_REG_PIO_BASE + (3 * 0x24 + 0X10)) +#define R6_REG_PD_DRV0 (R6_REG_PIO_BASE + (3 * 0x24 + 0X14)) +#define R6_REG_PD_DRV1 (R6_REG_PIO_BASE + (3 * 0x24 + 0X18)) +#define R6_REG_PD_PUL0 (R6_REG_PIO_BASE + (3 * 0x24 + 0X1c)) +#define R6_REG_PD_PUL1 (R6_REG_PIO_BASE + (3 * 0x24 + 0X20)) + + +/* AUDIO配置相关寄存器 */ +#define R6_REG_AC_BASE 0x01c23c00 +#define R6_REG_AC_DAC_DPC (R6_REG_AC_BASE + 0x00) +#define R6_REG_AC_DAC_FIFOC (R6_REG_AC_BASE + 0x04) +#define R6_REG_AC_DAC_FIFOS (R6_REG_AC_BASE + 0x08) +#define R6_REG_AC_DAC_TXDADA (R6_REG_AC_BASE + 0x0c) +#define R6_REG_AC_ADC_FIFOC (R6_REG_AC_BASE + 0x10) +#define R6_REG_AC_ADC_FIFOS (R6_REG_AC_BASE + 0x14) +#define R6_REG_AC_ADC_RXDADA (R6_REG_AC_BASE + 0x18) +#define R6_REG_DAC_MIXER_CTRL (R6_REG_AC_BASE + 0x20) +#define R6_REG_ADC_MIXER_CTRL (R6_REG_AC_BASE + 0x24) +#define R6_REG_AC_DAC_CNT (R6_REG_AC_BASE + 0x40) + +/* DMA配置相关寄存器 */ +#define R6_REG_NDMA_0_BASE (0x01c02000 + 0x100 + 0 * 0x20) + +#define R6_REG_DMA_INT_CTRL (0x01c02000 + 0x00) +#define R6_REG_DMA_INT_STA (0x01c02000 + 0x04) +#define R6_REG_DMA_PTY_CFG (0x01c02000 + 0x08) +#define REG_NDMA_CFG (0x0) +#define REG_NDMA_SRC_ADR (0x4) +#define REG_NDMA_DES_ADR (0x8) +#define REG_NDMA_BYTE_CNT (0xc) +// #define REG_NDMA_PAR (0x300 + 0x1c) + + +#define NDMA_CFG_SRC_DRQ_IR_RX (0x00 << 0) +#define NDMA_CFG_SRC_DRQ_NONE (0x01 << 0) +#define NDMA_CFG_SRC_DRQ_SDRAM (0x11 << 0) + +#define NDMA_CFG_DST_LINEAR (0x00 << 21) + +#define NDMA_CFG_DST_DRQ_IR_RX (0x00 << 16) +#define NDMA_CFG_DST_DRQ_NONE (0x01 << 16) +#define NDMA_CFG_DST_DRQ_SRAM (0x10 << 16) +#define NDMA_CFG_DST_DRQ_SDRAM (0x11 << 16) + +#define NDMA_CFG_SRC_DRQ_SRAM (0x10 << 0) +#define NDMA_CFG_SRC_LINEAR (0x00 << 5) +#define NDMA_CFG_SRC_BST4_WIDTH32 ((0x1 << 7) | (0x2 << 8)) +#define NDMA_CFG_DST_DRQ_CODEC (0x0c << 16) +#define NDMA_CFG_DST_IO (0x1 << 21) +#define NDMA_CFG_DST_BST4_WIDTH32 ((0x1 << 23) | (0x2 << 24)) + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0])) + +#endif diff --git a/bsp/imxrt1052-evk/drivers/fsl_wm8960.c b/bsp/imxrt1052-evk/drivers/fsl_wm8960.c new file mode 100644 index 0000000000..11c21742ec --- /dev/null +++ b/bsp/imxrt1052-evk/drivers/fsl_wm8960.c @@ -0,0 +1,621 @@ +/* + * Copyright (c) 2016, Freescale Semiconductor, Inc. + * Copyright 2016-2017 NXP + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * o Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * o 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. + * + * o Neither the name of the copyright holder 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 "fsl_wm8960.h" +#include "fsl_common.h" + +/******************************************************************************* + * Definitations + ******************************************************************************/ + +/******************************************************************************* + * Prototypes + ******************************************************************************/ + +/******************************************************************************* + * Variables + ******************************************************************************/ +/* + * wm8960 register cache + * We can't read the WM8960 register space when we are + * using 2 wire for device control, so we cache them instead. + */ +static const uint16_t wm8960_reg[WM8960_CACHEREGNUM] = { + 0x0097, 0x0097, 0x0000, 0x0000, 0x0000, 0x0008, 0x0000, 0x000a, 0x01c0, 0x0000, 0x00ff, 0x00ff, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x007b, 0x0100, 0x0032, 0x0000, 0x00c3, 0x00c3, 0x01c0, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0000, 0x0000, 0x0000, 0x0000, 0x0100, 0x0100, 0x0050, 0x0050, 0x0050, 0x0050, 0x0000, 0x0000, 0x0000, 0x0000, + 0x0040, 0x0000, 0x0000, 0x0050, 0x0050, 0x0000, 0x0002, 0x0037, 0x004d, 0x0080, 0x0008, 0x0031, 0x0026, 0x00e9, +}; + +static uint16_t reg_cache[WM8960_CACHEREGNUM]; +/******************************************************************************* + * Code + ******************************************************************************/ + +void WM8960_Init(wm8960_handle_t *handle, wm8960_config_t *config) +{ + uint32_t i = 4000000; + + memcpy(reg_cache, wm8960_reg, sizeof(wm8960_reg)); + + /* Set WM8960 I2C address */ + handle->xfer.slaveAddress = WM8960_I2C_ADDR; + + /* NULL pointer means default setting. */ + if (config == NULL) + { + /* + * Reset all registers + */ + WM8960_WriteReg(handle, WM8960_RESET, 0x00); + + WM8960_WriteReg(handle, WM8960_IFACE2, 0x40); + /* + * VMID=50K, Enable VREF, AINL, AINR, ADCL and ADCR + * I2S_IN (bit 0), I2S_OUT (bit 1), DAP (bit 4), DAC (bit 5), ADC (bit 6) are powered on + */ + WM8960_WriteReg(handle, WM8960_POWER1, 0xCA); + + /* + * Enable DACL, DACR, LOUT1, ROUT1, PLL down + */ + WM8960_WriteReg(handle, WM8960_POWER2, 0x1E0); + + /* + * Enable left and right channel input PGA, left and right output mixer + */ + WM8960_WriteReg(handle, WM8960_POWER3, 0xC); + + /* Configure SYS_FS clock to 44.1kHz, MCLK_FREQ to 256*Fs, SYSCLK derived from MCLK input */ + WM8960_WriteReg(handle, WM8960_CLOCK1, 0x00); + + /* + * Audio data length = 32bit, Left justified data format + */ + WM8960_WriteReg(handle, WM8960_IFACE1, 0x0D); + + /* + * LMICBOOST = 0dB, Connect left and right PGA to left and right Input Boost Mixer + */ + WM8960_WriteReg(handle, WM8960_LINPATH, 0x18); + WM8960_WriteReg(handle, WM8960_RINPATH, 0x18); + + /* + * Left and right input boost, LIN3BOOST and RIN3BOOST = 0dB + */ + WM8960_WriteReg(handle, WM8960_INBMIX1, 0x70); + WM8960_WriteReg(handle, WM8960_INBMIX2, 0x70); + + /* + * Left DAC and LINPUT3 to left output mixer, LINPUT3 left output mixer volume = 0dB + */ + WM8960_WriteReg(handle, WM8960_LOUTMIX, 0x100); + + /* + * Right DAC and RINPUT3 to right output mixer, RINPUT3 right output mixer volume = 0dB + */ + WM8960_WriteReg(handle, WM8960_ROUTMIX, 0x100); + + WM8960_WriteReg(handle, WM8960_BYPASS1, 0x0); + + WM8960_WriteReg(handle, WM8960_BYPASS2, 0x0); + + WM8960_WriteReg(handle, WM8960_MONOMIX1, 0x00); + WM8960_WriteReg(handle, WM8960_MONOMIX2, 0x00); + } + else + { + WM8960_SetDataRoute(handle, config->route); + WM8960_SetProtocol(handle, config->bus); + WM8960_SetMasterSlave(handle, config->master_slave); + } + WM8960_WriteReg(handle, WM8960_ADDCTL1, 0x0C4); + WM8960_WriteReg(handle, WM8960_ADDCTL4, 0x40); + + /* + * ADC volume, 0dB + */ + WM8960_WriteReg(handle, WM8960_LADC, 0x1F3); + WM8960_WriteReg(handle, WM8960_RADC, 0x1F3); + + /* + * Digital DAC volume, 0dB + */ + WM8960_WriteReg(handle, WM8960_LDAC, 0x1E0); + WM8960_WriteReg(handle, WM8960_RDAC, 0x1E0); + + /* + * Headphone volume, LOUT1 and ROUT1, 0dB + */ + WM8960_WriteReg(handle, WM8960_LOUT1, 0x16F); + WM8960_WriteReg(handle, WM8960_ROUT1, 0x16F); + + /* Delay for some while */ + while (i) + { + __ASM("nop"); + i--; + } + + /* Unmute DAC. */ + WM8960_WriteReg(handle, WM8960_DACCTL1, 0x0000); +} + +void WM8960_Deinit(wm8960_handle_t *handle) +{ + WM8960_SetModule(handle, kWM8960_ModuleADC, false); + WM8960_SetModule(handle, kWM8960_ModuleDAC, false); + WM8960_SetModule(handle, kWM8960_ModuleVREF, false); + WM8960_SetModule(handle, kWM8960_ModuleLineIn, false); + WM8960_SetModule(handle, kWM8960_ModuleLineOut, false); + WM8960_SetModule(handle, kWM8960_ModuleSpeaker, false); +} + +void WM8960_SetMasterSlave(wm8960_handle_t *handle, bool master) +{ + if (master == 1) + { + WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_MS_MASK, WM8960_IFACE1_MS(WM8960_IFACE1_MASTER)); + } + else + { + WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_MS_MASK, WM8960_IFACE1_MS(WM8960_IFACE1_SLAVE)); + } +} + +status_t WM8960_SetModule(wm8960_handle_t *handle, wm8960_module_t module, bool isEnabled) +{ + status_t ret = kStatus_Success; + switch (module) + { + case kWM8960_ModuleADC: + WM8960_ModifyReg(handle, WM8960_POWER1, WM8960_POWER1_ADCL_MASK, + ((uint16_t)isEnabled << WM8960_POWER1_ADCL_SHIFT)); + WM8960_ModifyReg(handle, WM8960_POWER1, WM8960_POWER1_ADCR_MASK, + ((uint16_t)isEnabled << WM8960_POWER1_ADCR_SHIFT)); + break; + case kWM8960_ModuleDAC: + WM8960_ModifyReg(handle, WM8960_POWER2, WM8960_POWER2_DACL_MASK, + ((uint16_t)isEnabled << WM8960_POWER2_DACL_SHIFT)); + WM8960_ModifyReg(handle, WM8960_POWER2, WM8960_POWER2_DACR_MASK, + ((uint16_t)isEnabled << WM8960_POWER2_DACR_SHIFT)); + break; + case kWM8960_ModuleVREF: + WM8960_ModifyReg(handle, WM8960_POWER1, WM8960_POWER1_VREF_MASK, + ((uint16_t)isEnabled << WM8960_POWER1_VREF_SHIFT)); + break; + case kWM8960_ModuleLineIn: + WM8960_ModifyReg(handle, WM8960_POWER1, WM8960_POWER1_AINL_MASK, + ((uint16_t)isEnabled << WM8960_POWER1_AINL_SHIFT)); + WM8960_ModifyReg(handle, WM8960_POWER1, WM8960_POWER1_AINR_MASK, + ((uint16_t)isEnabled << WM8960_POWER1_AINR_SHIFT)); + break; + case kWM8960_ModuleLineOut: + WM8960_ModifyReg(handle, WM8960_POWER2, WM8960_POWER2_LOUT1_MASK, + ((uint16_t)isEnabled << WM8960_POWER2_LOUT1_SHIFT)); + WM8960_ModifyReg(handle, WM8960_POWER2, WM8960_POWER2_ROUT1_MASK, + ((uint16_t)isEnabled << WM8960_POWER2_ROUT1_SHIFT)); + break; + case kWM8960_ModuleSpeaker: + WM8960_ModifyReg(handle, WM8960_POWER2, WM8960_POWER2_SPKL_MASK, + ((uint16_t)isEnabled << WM8960_POWER2_SPKL_SHIFT)); + WM8960_ModifyReg(handle, WM8960_POWER2, WM8960_POWER2_SPKR_MASK, + ((uint16_t)isEnabled << WM8960_POWER2_SPKR_SHIFT)); + WM8960_WriteReg(handle, WM8960_CLASSD1, 0xF7); + break; + default: + ret = kStatus_InvalidArgument; + break; + } + return ret; +} + +status_t WM8960_SetDataRoute(wm8960_handle_t *handle, wm8960_route_t route) +{ + status_t ret = kStatus_Success; + switch (route) + { + case kWM8960_RouteBypass: + /* Bypass means from line-in to HP*/ + /* + * Left LINPUT3 to left output mixer, LINPUT3 left output mixer volume = 0dB + */ + WM8960_WriteReg(handle, WM8960_LOUTMIX, 0x80); + + /* + * Right RINPUT3 to right output mixer, RINPUT3 right output mixer volume = 0dB + */ + WM8960_WriteReg(handle, WM8960_ROUTMIX, 0x80); + break; + case kWM8960_RoutePlayback: + /* Data route I2S_IN-> DAC-> HP */ + /* + * Left DAC to left output mixer, LINPUT3 left output mixer volume = 0dB + */ + WM8960_WriteReg(handle, WM8960_LOUTMIX, 0x100); + + /* + * Right DAC to right output mixer, RINPUT3 right output mixer volume = 0dB + */ + WM8960_WriteReg(handle, WM8960_ROUTMIX, 0x100); + break; + case kWM8960_RoutePlaybackandRecord: + /* I2S IN->DAC->HP LINE_IN->ADC->I2S_OUT */ + /* + * Left and right input boost, LIN3BOOST and RIN3BOOST = 0dB + */ + WM8960_WriteReg(handle, WM8960_INBMIX1, 0x50); + WM8960_WriteReg(handle, WM8960_INBMIX2, 0x50); + /* + * Left DAC to left output mixer, LINPUT3 left output mixer volume = 0dB + */ + WM8960_WriteReg(handle, WM8960_LOUTMIX, 0x100); + + /* + * Right DAC to right output mixer, RINPUT3 right output mixer volume = 0dB + */ + WM8960_WriteReg(handle, WM8960_ROUTMIX, 0x100); + break; + case kWM8960_RoutePlaybackwithDAP: + /* I2S_IN->DAP->DAC->HP */ + break; + case kWM8960_RoutePlaybackwithDAPandRecord: + /* I2S_IN->DAP->DAC->HP, LINE_IN->ADC->I2S_OUT */ + break; + case kWM8960_RouteRecord: + /* LINE_IN->ADC->I2S_OUT */ + /* + * Left and right input boost, LIN3BOOST and RIN3BOOST = 0dB + */ + WM8960_WriteReg(handle, WM8960_INBMIX1, 0x50); + WM8960_WriteReg(handle, WM8960_INBMIX2, 0x50); + break; + default: + ret = kStatus_InvalidArgument; + break; + } + return ret; +} + +status_t WM8960_SetProtocol(wm8960_handle_t *handle, wm8960_protocol_t protocol) +{ + status_t ret = kStatus_Success; + switch (protocol) + { + case kWM8960_BusI2S: + WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_FORMAT_MASK, + WM8960_IFACE1_FORMAT(WM8960_IFACE1_FORMAT_I2S)); + break; + case kWM8960_BusLeftJustified: + WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_FORMAT_MASK, + WM8960_IFACE1_FORMAT(WM8960_IFACE1_FORMAT_LJ)); + break; + case kWM8960_BusRightJustified: + WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_FORMAT_MASK, + WM8960_IFACE1_FORMAT(WM8960_IFACE1_FORMAT_RJ)); + break; + case kWM8960_BusPCMA: + WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_FORMAT_MASK, + WM8960_IFACE1_FORMAT(WM8960_IFACE1_FORMAT_DSP)); + WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_LRP_MASK, WM8960_IFACE1_LRP(WM8960_IFACE1_DSP_MODEA)); + break; + case kWM8960_BusPCMB: + WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_FORMAT_MASK, + WM8960_IFACE1_FORMAT(WM8960_IFACE1_FORMAT_DSP)); + WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_LRP_MASK, WM8960_IFACE1_LRP(WM8960_IFACE1_DSP_MODEB)); + break; + default: + ret = kStatus_InvalidArgument; + break; + } + WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_WL_MASK, WM8960_IFACE1_WL(WM8960_IFACE1_WL_32BITS)); + return ret; +} + +status_t WM8960_SetVolume(wm8960_handle_t *handle, wm8960_module_t module, uint32_t volume) +{ + uint16_t vol = 0; + status_t ret = kStatus_Success; + switch (module) + { + case kWM8960_ModuleADC: + vol = 0x100 | volume; + ret = WM8960_WriteReg(handle, WM8960_LADC, vol); + ret = WM8960_WriteReg(handle, WM8960_RADC, vol); + break; + case kWM8960_ModuleDAC: + vol = 0x100 | volume; + ret = WM8960_WriteReg(handle, WM8960_LDAC, vol); + ret = WM8960_WriteReg(handle, WM8960_RDAC, vol); + break; + case kWM8960_ModuleHP: + vol = 0x100 | volume; + ret = WM8960_WriteReg(handle, WM8960_LOUT1, vol); + ret = WM8960_WriteReg(handle, WM8960_ROUT1, vol); + break; + case kWM8960_ModuleLineIn: + vol = 0x100 | volume; + ret = WM8960_WriteReg(handle, WM8960_LINVOL, vol); + ret = WM8960_WriteReg(handle, WM8960_RINVOL, vol); + break; + case kWM8960_ModuleSpeaker: + vol = 0x100 | volume; + ret = WM8960_WriteReg(handle, WM8960_LOUT2, vol); + ret = WM8960_WriteReg(handle, WM8960_ROUT2, vol); + break; + default: + ret = kStatus_InvalidArgument; + break; + } + return ret; +} + +uint32_t WM8960_GetVolume(wm8960_handle_t *handle, wm8960_module_t module) +{ + uint16_t vol = 0; + switch (module) + { + case kWM8960_ModuleADC: + WM8960_ReadReg(WM8960_LADC, &vol); + vol &= 0xFF; + break; + case kWM8960_ModuleDAC: + WM8960_ReadReg(WM8960_LDAC, &vol); + vol &= 0xFF; + break; + case kWM8960_ModuleHP: + WM8960_ReadReg(WM8960_LOUT1, &vol); + vol &= 0x7F; + break; + case kWM8960_ModuleLineOut: + WM8960_ReadReg(WM8960_LINVOL, &vol); + vol &= 0x3F; + break; + default: + vol = 0; + break; + } + return vol; +} + +status_t WM8960_SetMute(wm8960_handle_t *handle, wm8960_module_t module, bool isEnabled) +{ + status_t ret = kStatus_Success; + switch (module) + { + case kWM8960_ModuleADC: + /* + * Digital Mute + */ + if (isEnabled) + { + ret = WM8960_WriteReg(handle, WM8960_LADC, 0x100); + ret = WM8960_WriteReg(handle, WM8960_RADC, 0x100); + } + else + { + ret = WM8960_WriteReg(handle, WM8960_LADC, 0x1C3); + ret = WM8960_WriteReg(handle, WM8960_RADC, 0x1C3); + } + break; + case kWM8960_ModuleDAC: + /* + * Digital mute + */ + if (isEnabled) + { + ret = WM8960_WriteReg(handle, WM8960_LDAC, 0x100); + ret = WM8960_WriteReg(handle, WM8960_RDAC, 0x100); + } + else + { + ret = WM8960_WriteReg(handle, WM8960_LDAC, 0x1FF); + ret = WM8960_WriteReg(handle, WM8960_RDAC, 0x1FF); + } + break; + case kWM8960_ModuleHP: + /* + * Analog mute + */ + if (isEnabled) + { + ret = WM8960_WriteReg(handle, WM8960_LOUT1, 0x100); + ret = WM8960_WriteReg(handle, WM8960_ROUT1, 0x100); + } + else + { + ret = WM8960_WriteReg(handle, WM8960_LOUT1, 0x179); + ret = WM8960_WriteReg(handle, WM8960_ROUT1, 0x179); + } + break; + case kWM8960_ModuleLineOut: + break; + default: + ret = kStatus_InvalidArgument; + break; + } + return ret; +} + +status_t WM8960_ConfigDataFormat(wm8960_handle_t *handle, uint32_t mclk, uint32_t sample_rate, uint8_t bits) +{ + status_t retval = kStatus_Success; + + switch (sample_rate) + { + case 8000: + retval = WM8960_WriteReg(handle, WM8960_CLOCK1, 0x1B0); + break; + case 11025: + retval = WM8960_WriteReg(handle, WM8960_CLOCK1, 0xD8); + break; + case 12000: + retval = WM8960_WriteReg(handle, WM8960_CLOCK1, 0x120); + break; + case 16000: + retval = WM8960_WriteReg(handle, WM8960_CLOCK1, 0xD8); + break; + case 22050: + retval = WM8960_WriteReg(handle, WM8960_CLOCK1, 0xD8); + break; + case 24000: + retval = WM8960_WriteReg(handle, WM8960_CLOCK1, 0x90); + break; + case 32000: + retval = WM8960_WriteReg(handle, WM8960_CLOCK1, 0x48); + break; + case 44100: + retval = WM8960_WriteReg(handle, WM8960_CLOCK1, 0xD8); + break; + case 48000: + retval = WM8960_WriteReg(handle, WM8960_CLOCK1, 0x00); + break; + default: + retval = kStatus_InvalidArgument; + break; + } + + /* + * Slave mode (MS = 0), LRP = 0, 32bit WL, left justified (FORMAT[1:0]=0b01) + */ + switch (bits) + { + case 16: + retval = WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_WL_MASK, + WM8960_IFACE1_WL(WM8960_IFACE1_WL_16BITS)); + break; + case 20: + retval = WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_WL_MASK, + WM8960_IFACE1_WL(WM8960_IFACE1_WL_20BITS)); + break; + case 24: + retval = WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_WL_MASK, + WM8960_IFACE1_WL(WM8960_IFACE1_WL_24BITS)); + break; + case 32: + retval = WM8960_ModifyReg(handle, WM8960_IFACE1, WM8960_IFACE1_WL_MASK, + WM8960_IFACE1_WL(WM8960_IFACE1_WL_32BITS)); + break; + default: + retval = kStatus_InvalidArgument; + break; + } + + return retval; +} + +status_t WM8960_SetJackDetect(wm8960_handle_t *handle, bool isEnabled) +{ + uint8_t retval = 0; + uint16_t val = 0; + + WM8960_ReadReg(WM8960_ADDCTL2, &val); + + if (isEnabled) + { + val |= 0x40U; + } + else + { + val &= 0xCF; + } + + retval = WM8960_WriteReg(handle, WM8960_ADDCTL2, val); + + return retval; +} + +status_t WM8960_WriteReg(wm8960_handle_t *handle, uint8_t reg, uint16_t val) +{ + uint8_t cmd, buff; + uint8_t retval = 0; + + /* The register address */ + cmd = (reg << 1) | ((val >> 8U) & 0x0001U); + /* Data */ + buff = val & 0xFF; + + /* Copy data to cache */ + reg_cache[reg] = val; + +#if defined(FSL_FEATURE_SOC_LPI2C_COUNT) && (FSL_FEATURE_SOC_LPI2C_COUNT) + uint8_t data[2]; + data[0] = cmd; + data[1] = buff; + retval = LPI2C_MasterStart(handle->base, WM8960_I2C_ADDR, kLPI2C_Write); + retval = LPI2C_MasterSend(handle->base, data, 2); + retval = LPI2C_MasterStop(handle->base); +#else + /* Config the I2C xfer */ + handle->xfer.direction = kI2C_Write; + handle->xfer.subaddress = cmd; + handle->xfer.subaddressSize = 1U; + handle->xfer.data = &buff; + handle->xfer.dataSize = 1U; + + retval = I2C_MasterTransferBlocking(handle->base, &handle->xfer); +#endif + + if (retval != kStatus_Success) + { + return kStatus_Fail; + } + return kStatus_Success; +} + +status_t WM8960_ReadReg(uint8_t reg, uint16_t *val) +{ + if (reg >= WM8960_CACHEREGNUM) + { + return kStatus_InvalidArgument; + } + + *val = reg_cache[reg]; + + return kStatus_Success; +} + +status_t WM8960_ModifyReg(wm8960_handle_t *handle, uint8_t reg, uint16_t mask, uint16_t val) +{ + uint8_t retval = 0; + uint16_t reg_val = 0; + retval = WM8960_ReadReg(reg, ®_val); + if (retval != kStatus_Success) + { + return kStatus_Fail; + } + reg_val &= (uint16_t)~mask; + reg_val |= val; + retval = WM8960_WriteReg(handle, reg, reg_val); + if (retval != kStatus_Success) + { + return kStatus_Fail; + } + return kStatus_Success; +} diff --git a/bsp/imxrt1052-evk/drivers/fsl_wm8960.h b/bsp/imxrt1052-evk/drivers/fsl_wm8960.h new file mode 100644 index 0000000000..848e0332f9 --- /dev/null +++ b/bsp/imxrt1052-evk/drivers/fsl_wm8960.h @@ -0,0 +1,426 @@ +/* + * Copyright (c) 2015, Freescale Semiconductor, Inc. + * Copyright 2016-2017 NXP + * + * Redistribution and use in source and binary forms, with or without modification, + * are permitted provided that the following conditions are met: + * + * o Redistributions of source code must retain the above copyright notice, this list + * of conditions and the following disclaimer. + * + * o 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. + * + * o Neither the name of the copyright holder 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. + */ + +#ifndef _FSL_WM8960_H_ +#define _FSL_WM8960_H_ + +#include "fsl_common.h" +#if defined(FSL_FEATURE_SOC_LPI2C_COUNT) && (FSL_FEATURE_SOC_LPI2C_COUNT) +#include "fsl_lpi2c.h" +#else +#include "fsl_i2c.h" +#endif + +/*! + * @addtogroup wm8960 + * @{ + */ + +/******************************************************************************* + * Definitions + ******************************************************************************/ + +/*! @brief Define the register address of WM8960. */ +#define WM8960_LINVOL 0x0 +#define WM8960_RINVOL 0x1 +#define WM8960_LOUT1 0x2 +#define WM8960_ROUT1 0x3 +#define WM8960_CLOCK1 0x4 +#define WM8960_DACCTL1 0x5 +#define WM8960_DACCTL2 0x6 +#define WM8960_IFACE1 0x7 +#define WM8960_CLOCK2 0x8 +#define WM8960_IFACE2 0x9 +#define WM8960_LDAC 0xa +#define WM8960_RDAC 0xb + +#define WM8960_RESET 0xf +#define WM8960_3D 0x10 +#define WM8960_ALC1 0x11 +#define WM8960_ALC2 0x12 +#define WM8960_ALC3 0x13 +#define WM8960_NOISEG 0x14 +#define WM8960_LADC 0x15 +#define WM8960_RADC 0x16 +#define WM8960_ADDCTL1 0x17 +#define WM8960_ADDCTL2 0x18 +#define WM8960_POWER1 0x19 +#define WM8960_POWER2 0x1a +#define WM8960_ADDCTL3 0x1b +#define WM8960_APOP1 0x1c +#define WM8960_APOP2 0x1d + +#define WM8960_LINPATH 0x20 +#define WM8960_RINPATH 0x21 +#define WM8960_LOUTMIX 0x22 + +#define WM8960_ROUTMIX 0x25 +#define WM8960_MONOMIX1 0x26 +#define WM8960_MONOMIX2 0x27 +#define WM8960_LOUT2 0x28 +#define WM8960_ROUT2 0x29 +#define WM8960_MONO 0x2a +#define WM8960_INBMIX1 0x2b +#define WM8960_INBMIX2 0x2c +#define WM8960_BYPASS1 0x2d +#define WM8960_BYPASS2 0x2e +#define WM8960_POWER3 0x2f +#define WM8960_ADDCTL4 0x30 +#define WM8960_CLASSD1 0x31 + +#define WM8960_CLASSD3 0x33 +#define WM8960_PLL1 0x34 +#define WM8960_PLL2 0x35 +#define WM8960_PLL3 0x36 +#define WM8960_PLL4 0x37 + +/*! @brief Cache register number */ +#define WM8960_CACHEREGNUM 56 + +/*! @brief WM8960_IFACE1 FORMAT bits */ +#define WM8960_IFACE1_FORMAT_MASK 0x03 +#define WM8960_IFACE1_FORMAT_SHIFT 0x00 +#define WM8960_IFACE1_FORMAT_RJ 0x00 +#define WM8960_IFACE1_FORMAT_LJ 0x01 +#define WM8960_IFACE1_FORMAT_I2S 0x02 +#define WM8960_IFACE1_FORMAT_DSP 0x03 +#define WM8960_IFACE1_FORMAT(x) ((x << WM8960_IFACE1_FORMAT_SHIFT) & WM8960_IFACE1_FORMAT_MASK) + +/*! @brief WM8960_IFACE1 WL bits */ +#define WM8960_IFACE1_WL_MASK 0x0C +#define WM8960_IFACE1_WL_SHIFT 0x02 +#define WM8960_IFACE1_WL_16BITS 0x00 +#define WM8960_IFACE1_WL_20BITS 0x01 +#define WM8960_IFACE1_WL_24BITS 0x02 +#define WM8960_IFACE1_WL_32BITS 0x03 +#define WM8960_IFACE1_WL(x) ((x << WM8960_IFACE1_WL_SHIFT) & WM8960_IFACE1_WL_MASK) + +/*! @brief WM8960_IFACE1 LRP bit */ +#define WM8960_IFACE1_LRP_MASK 0x10 +#define WM8960_IFACE1_LRP_SHIFT 0x04 +#define WM8960_IFACE1_LRCLK_NORMAL_POL 0x00 +#define WM8960_IFACE1_LRCLK_INVERT_POL 0x01 +#define WM8960_IFACE1_DSP_MODEA 0x00 +#define WM8960_IFACE1_DSP_MODEB 0x01 +#define WM8960_IFACE1_LRP(x) ((x << WM8960_IFACE1_LRP_SHIFT) & WM8960_IFACE1_LRP_MASK) + +/*! @brief WM8960_IFACE1 DLRSWAP bit */ +#define WM8960_IFACE1_DLRSWAP_MASK 0x20 +#define WM8960_IFACE1_DLRSWAP_SHIFT 0x05 +#define WM8960_IFACE1_DACCH_NORMAL 0x00 +#define WM8960_IFACE1_DACCH_SWAP 0x01 +#define WM8960_IFACE1_DLRSWAP(x) ((x << WM8960_IFACE1_DLRSWAP_SHIFT) & WM8960_IFACE1_DLRSWAP_MASK) + +/*! @brief WM8960_IFACE1 MS bit */ +#define WM8960_IFACE1_MS_MASK 0x40 +#define WM8960_IFACE1_MS_SHIFT 0x06 +#define WM8960_IFACE1_SLAVE 0x00 +#define WM8960_IFACE1_MASTER 0x01 +#define WM8960_IFACE1_MS(x) ((x << WM8960_IFACE1_MS_SHIFT) & WM8960_IFACE1_MS_MASK) + +/*! @brief WM8960_IFACE1 BCLKINV bit */ +#define WM8960_IFACE1_BCLKINV_MASK 0x80 +#define WM8960_IFACE1_BCLKINV_SHIFT 0x07 +#define WM8960_IFACE1_BCLK_NONINVERT 0x00 +#define WM8960_IFACE1_BCLK_INVERT 0x01 +#define WM8960_IFACE1_BCLKINV(x) ((x << WM8960_IFACE1_BCLKINV_SHIFT) & WM8960_IFACE1_BCLKINV_MASK) + +/*! @brief WM8960_IFACE1 ALRSWAP bit */ +#define WM8960_IFACE1_ALRSWAP_MASK 0x100 +#define WM8960_IFACE1_ALRSWAP_SHIFT 0x08 +#define WM8960_IFACE1_ADCCH_NORMAL 0x00 +#define WM8960_IFACE1_ADCCH_SWAP 0x01 +#define WM8960_IFACE1_ALRSWAP(x) ((x << WM8960_IFACE1_ALRSWAP_SHIFT) & WM8960_IFACE1_ALRSWAP_MASK) + +/*! @brief WM8960_POWER1 */ +#define WM8960_POWER1_VREF_MASK 0x40 +#define WM8960_POWER1_VREF_SHIFT 0x06 + +#define WM8960_POWER1_AINL_MASK 0x20 +#define WM8960_POWER1_AINL_SHIFT 0x05 + +#define WM8960_POWER1_AINR_MASK 0x10 +#define WM8960_POWER1_AINR_SHIFT 0x04 + +#define WM8960_POWER1_ADCL_MASK 0x08 +#define WM8960_POWER1_ADCL_SHIFT 0x03 + +#define WM8960_POWER1_ADCR_MASK 0x04 +#define WM8960_POWER1_ADCR_SHIFT 0x02 + +/*! @brief WM8960_POWER2 */ +#define WM8960_POWER2_DACL_MASK 0x100 +#define WM8960_POWER2_DACL_SHIFT 0x08 + +#define WM8960_POWER2_DACR_MASK 0x80 +#define WM8960_POWER2_DACR_SHIFT 0x07 + +#define WM8960_POWER2_LOUT1_MASK 0x40 +#define WM8960_POWER2_LOUT1_SHIFT 0x06 + +#define WM8960_POWER2_ROUT1_MASK 0x20 +#define WM8960_POWER2_ROUT1_SHIFT 0x05 + +#define WM8960_POWER2_SPKL_MASK 0x10 +#define WM8960_POWER2_SPKL_SHIFT 0x04 + +#define WM8960_POWER2_SPKR_MASK 0x08 +#define WM8960_POWER2_SPKR_SHIFT 0x03 + +/*! @brief WM8960 I2C address. */ +#define WM8960_I2C_ADDR 0x1A + +/*! @brief Modules in WM8960 board. */ +typedef enum _WM8960_module +{ + kWM8960_ModuleADC = 0x0, /*!< ADC module in WM8960 */ + kWM8960_ModuleDAC = 0x1, /*!< DAC module in WM8960 */ + kWM8960_ModuleVREF = 0x2, /*!< VREF module */ + kWM8960_ModuleHP = 0x03, /*!< Headphone module */ + kWM8960_ModuleLineIn = 0x6, /*!< Line-in module */ + kWM8960_ModuleLineOut = 0x7, /*!< Line out module */ + kWM8960_ModuleSpeaker = 0x8 /*!< Speaker module */ +} wm8960_module_t; + +/*! +* @brief WM8960 data route. +* Only provide some typical data route, not all route listed. +* Note: Users cannot combine any routes, once a new route is set, the previous one would be replaced. +*/ +typedef enum _wm8960_route +{ + kWM8960_RouteBypass = 0x0, /*!< LINEIN->Headphone. */ + kWM8960_RoutePlayback = 0x1, /*!< I2SIN->DAC->Headphone. */ + kWM8960_RoutePlaybackandRecord = 0x2, /*!< I2SIN->DAC->Headphone, LINEIN->ADC->I2SOUT. */ + kWM8960_RoutePlaybackwithDAP = 0x3, /*!< I2SIN->DAP->DAC->Headphone. */ + kWM8960_RoutePlaybackwithDAPandRecord = 0x4, /*!< I2SIN->DAP->DAC->HP, LINEIN->ADC->I2SOUT. */ + kWM8960_RouteRecord = 0x5 /*!< LINEIN->ADC->I2SOUT. */ +} wm8960_route_t; + +/*! +* @brief The audio data transfer protocol choice. +* WM8960 only supports I2S format and PCM format. +*/ +typedef enum _wm8960_protocol +{ + kWM8960_BusI2S = 0x0, /*!< I2S type */ + kWM8960_BusLeftJustified = 0x1, /*!< Left justified mode */ + kWM8960_BusRightJustified = 0x2, /*!< Right justified mode */ + kWM8960_BusPCMA = 0x3, /*!< PCM A mode */ + kWM8960_BusPCMB = 0x4 /*!< PCM B mode */ +} wm8960_protocol_t; + +/*! @brief WM8960 configure definition. */ +typedef struct wm8960_handle +{ +#if defined(FSL_FEATURE_SOC_LPI2C_COUNT) && (FSL_FEATURE_SOC_LPI2C_COUNT) + LPI2C_Type *base; + lpi2c_master_transfer_t xfer; + lpi2c_master_handle_t *i2cHandle; +#else + /* I2C relevant definition. */ + I2C_Type *base; /*!< I2C instance. */ + i2c_master_transfer_t xfer; /*!< I2C device setting */ + i2c_master_handle_t *i2cHandle; /*!< I2C internal state space. */ +#endif +} wm8960_handle_t; + +/*! @brief Initialize structure of WM8960 */ +typedef struct wm8960_config +{ + wm8960_route_t route; /*!< Audio data route.*/ + wm8960_protocol_t bus; /*!< Audio transfer protocol */ + bool master_slave; /*!< Master or slave. */ +} wm8960_config_t; + +/******************************************************************************* + * API + ******************************************************************************/ +#if defined(__cplusplus) +extern "C" { +#endif + +/*! + * @brief WM8960 initialize function. + * + * The second parameter is NULL to WM8960 in this version. If users want + * to change the settings, they have to use wm8960_write_reg() or wm8960_modify_reg() + * to set the register value of WM8960. + * Note: If the codec_config is NULL, it would initialize WM8960 using default settings. + * The default setting: + * codec_config->route = kWM8960_RoutePlaybackandRecord + * codec_config->bus = kWM8960_BusI2S + * codec_config->master = slave + * + * @param handle WM8960 handle structure. + * @param codec_config WM8960 configuration structure. + */ +void WM8960_Init(wm8960_handle_t *handle, wm8960_config_t *config); + +/*! + * @brief Deinit the WM8960 codec. + * + * This function close all modules in WM8960 to save power. + * + * @param handle WM8960 handle structure pointer. + */ +void WM8960_Deinit(wm8960_handle_t *handle); + +/*! + * @brief Set audio data route in WM8960. + * + * This function would set the data route according to route. The route cannot be combined, + * as all route would enable different modules. + * Note: If a new route is set, the previous route would not work. + * + * @param handle WM8960 handle structure. + * @param route Audio data route in WM8960. + */ +status_t WM8960_SetDataRoute(wm8960_handle_t *handle, wm8960_route_t route); + +/*! + * @brief Set the audio transfer protocol. + * + * WM8960 only supports I2S, left justified, right justified, PCM A, PCM B format. + * + * @param handle WM8960 handle structure. + * @param bus Audio data transfer protocol. + */ +status_t WM8960_SetProtocol(wm8960_handle_t *handle, wm8960_protocol_t protocol); + +/*! + * @brief Set WM8960 as master or slave. + * + * @param handle WM8960 handle structure. + * @param master 1 represent master, 0 represent slave. + */ +void WM8960_SetMasterSlave(wm8960_handle_t *handle, bool master); + +/*! + * @brief Set the volume of different modules in WM8960. + * + * This function would set the volume of WM8960 modules. Uses need to appoint the module. + * The function assume that left channel and right channel has the same volume. + * + * @param handle WM8960 handle structure. + * @param module Module to set volume, it can be ADC, DAC, Headphone and so on. + * @param volume Volume value need to be set. + */ +status_t WM8960_SetVolume(wm8960_handle_t *handle, wm8960_module_t module, uint32_t volume); + +/*! + * @brief Get the volume of different modules in WM8960. + * + * This function gets the volume of WM8960 modules. Uses need to appoint the module. + * The function assume that left channel and right channel has the same volume. + * + * @param handle WM8960 handle structure. + * @param module Module to set volume, it can be ADC, DAC, Headphone and so on. + * @return Volume value of the module. + */ +uint32_t WM8960_GetVolume(wm8960_handle_t *handle, wm8960_module_t module); + +/*! + * @brief Mute modules in WM8960. + * + * @param handle WM8960 handle structure. + * @param module Modules need to be mute. + * @param isEnabled Mute or unmute, 1 represent mute. + */ +status_t WM8960_SetMute(wm8960_handle_t *handle, wm8960_module_t module, bool isEnabled); + +/*! + * @brief Enable/disable expected devices. + * + * @param handle WM8960 handle structure. + * @param module Module expected to enable. + * @param isEnabled Enable or disable moudles. + */ +status_t WM8960_SetModule(wm8960_handle_t *handle, wm8960_module_t module, bool isEnabled); + +/*! + * @brief Configure the data format of audio data. + * + * This function would configure the registers about the sample rate, bit depths. + * + * @param handle WM8960 handle structure pointer. + * @param mclk Master clock frequency of I2S. + * @param sample_rate Sample rate of audio file running in WM8960. WM8960 now + * supports 8k, 11.025k, 12k, 16k, 22.05k, 24k, 32k, 44.1k, 48k and 96k sample rate. + * @param bits Bit depth of audio file (WM8960 only supports 16bit, 20bit, 24bit + * and 32 bit in HW). + */ +status_t WM8960_ConfigDataFormat(wm8960_handle_t *handle, uint32_t mclk, uint32_t sample_rate, uint8_t bits); + +/*! + * @brief Enable/disable jack detect feature. + * + * @param handle WM8960 handle structure. + * @param isEnabled Enable or disable moudles. + */ +status_t WM8960_SetJackDetect(wm8960_handle_t *handle, bool isEnabled); + +/*! + * @brief Write register to WM8960 using I2C. + * + * @param handle WM8960 handle structure. + * @param reg The register address in WM8960. + * @param val Value needs to write into the register. + */ +status_t WM8960_WriteReg(wm8960_handle_t *handle, uint8_t reg, uint16_t val); + +/*! + * @brief Read register from WM8960 using I2C. + * @param handle WM8960 handle structure. + * @param reg The register address in WM8960. + * @param val Value written to. + */ +status_t WM8960_ReadReg(uint8_t reg, uint16_t *val); + +/*! + * @brief Modify some bits in the register using I2C. + * @param handle WM8960 handle structure. + * @param reg The register address in WM8960. + * @param mask The mask code for the bits want to write. The bit you want to write should be 0. + * @param val Value needs to write into the register. + */ +status_t WM8960_ModifyReg(wm8960_handle_t *handle, uint8_t reg, uint16_t mask, uint16_t val); + +#if defined(__cplusplus) +} +#endif + +/*! @} */ + +#endif /* _FSL_WM8960_H_ */ + +/******************************************************************************* + * API + ******************************************************************************/ -- GitLab