diff --git a/drivers/audio/Kconfig b/drivers/audio/Kconfig index 6437d7e317..cbd2bbbab0 100644 --- a/drivers/audio/Kconfig +++ b/drivers/audio/Kconfig @@ -206,6 +206,63 @@ config CS4344_WORKER_STACKSIZE endif # AUDIO_CS4344 +config AUDIO_ES8311 + bool "ES8311 codec chip" + default n + depends on AUDIO + ---help--- + Select to enable support for the ES8311 Audio codec by Everest + Semiconductor. + + NOTE: This driver also depends on both I2C and I2S support although + that dependency is not explicit here. + +if AUDIO_ES8311 + +config ES8311_INPUT_INITVOLUME + int "ES8311 initial input volume setting" + default 1000 + +config ES8311_OUTPUT_INITVOLUME + int "ES8311 initial output volume setting" + default 400 + +choice + prompt "MCLK source" + default ES8311_SRC_MCLK + ---help--- + Select the source of the MCLK signal. + +config ES8311_SRC_MCLK + bool "From MCLK Pin" + +config ES8311_SRC_BCLK + bool "From BCLK Pin" + +endchoice + +config ES8311_INFLIGHT + int "ES8311 maximum in-flight audio buffers" + default 2 + +config ES8311_MSG_PRIO + int "ES8311 message priority" + default 1 + +config ES8311_BUFFER_SIZE + int "ES8311 preferred buffer size" + default 8192 + +config ES8311_NUM_BUFFERS + int "ES8311 preferred number of buffers" + default 4 + +config ES8311_WORKER_STACKSIZE + int "ES8311 worker thread stack size" + default 2048 + +endif # AUDIO_ES8388 + config AUDIO_ES8388 bool "ES8388 codec chip" default n diff --git a/drivers/audio/Make.defs b/drivers/audio/Make.defs index 31de28713c..e1c67cdad1 100644 --- a/drivers/audio/Make.defs +++ b/drivers/audio/Make.defs @@ -48,6 +48,13 @@ ifeq ($(CONFIG_AUDIO_CS4344),y) CSRCS += cs4344.c endif +ifeq ($(CONFIG_AUDIO_ES8311),y) +CSRCS += es8311.c +ifeq ($(CONFIG_ES8311_REGDUMP),y) +CSRCS += es8311_debug.c +endif +endif + ifeq ($(CONFIG_AUDIO_ES8388),y) CSRCS += es8388.c ifeq ($(CONFIG_ES8388_REGDUMP),y) diff --git a/drivers/audio/es8311.c b/drivers/audio/es8311.c new file mode 100644 index 0000000000..a3616fda6e --- /dev/null +++ b/drivers/audio/es8311.c @@ -0,0 +1,2292 @@ +/**************************************************************************** + * drivers/audio/es8311.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "es8311.h" + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +#if !defined(CONFIG_ES8311_REGDUMP) +static +#endif /* !CONFIG_ES8311_REGDUMP */ +uint8_t es8311_readreg(FAR struct es8311_dev_s *priv, uint8_t regaddr); +static int es8311_writereg(FAR struct es8311_dev_s *priv, + uint8_t regaddr, + uint16_t regval); +#ifndef CONFIG_AUDIO_EXCLUDE_VOLUME +static int es8311_setvolume(FAR struct es8311_dev_s *priv, + es_module_e module, + uint16_t volume); +#endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */ +static int es8311_getcoeff(FAR struct es8311_dev_s *priv, + uint32_t samplerate); +#ifndef CONFIG_AUDIO_EXCLUDE_MUTE +static int es8311_setmute(FAR struct es8311_dev_s *priv, + es_module_e module, + bool enable); +#endif /* CONFIG_AUDIO_EXCLUDE_MUTE */ +static int es8311_setmicgain(FAR struct es8311_dev_s *priv, uint32_t gain); +static int es8311_setbitspersample(FAR struct es8311_dev_s *priv); +static int es8311_setsamplerate(FAR struct es8311_dev_s *priv); +static int es8311_getcaps(FAR struct audio_lowerhalf_s *dev, + int type, + FAR struct audio_caps_s *caps); +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_configure(FAR struct audio_lowerhalf_s *dev, + FAR void *session, + FAR const struct audio_caps_s *caps); +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_configure(FAR struct audio_lowerhalf_s *dev, + FAR const struct audio_caps_s *caps); +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_shutdown(FAR struct audio_lowerhalf_s *dev); +static void es8311_processdone(FAR struct i2s_dev_s *i2s, + FAR struct ap_buffer_s *apb, + FAR void *arg, + int result); +static void es8311_returnbuffers(FAR struct es8311_dev_s *priv); +static int es8311_processbegin(FAR struct es8311_dev_s *priv); +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_start(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_start(FAR struct audio_lowerhalf_s *dev); +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +#ifndef CONFIG_AUDIO_EXCLUDE_STOP +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_stop(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_stop(FAR struct audio_lowerhalf_s *dev); +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +#endif /* CONFIG_AUDIO_EXCLUDE_STOP */ +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_pause(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +static int es8311_resume(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_pause(FAR struct audio_lowerhalf_s *dev); +static int es8311_resume(FAR struct audio_lowerhalf_s *dev); +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +#endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ +static int es8311_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static int es8311_cancelbuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static int es8311_ioctl(FAR struct audio_lowerhalf_s *dev, + int cmd, + unsigned long arg); +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_reserve(FAR struct audio_lowerhalf_s *dev, + FAR void **session); +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_reserve(FAR struct audio_lowerhalf_s *dev); +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_release(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_release(FAR struct audio_lowerhalf_s *dev); +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +static void *es8311_workerthread(pthread_addr_t pvarg); +static void es8311_audio_output(FAR struct es8311_dev_s *priv); +static void es8311_audio_input(FAR struct es8311_dev_s *priv); +static int es8311_reset(FAR struct es8311_dev_s *priv); +static int es8311_get_mclk_src(void); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct audio_ops_s g_audioops = +{ + es8311_getcaps, /* getcaps */ + es8311_configure, /* configure */ + es8311_shutdown, /* shutdown */ + es8311_start, /* start */ +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + es8311_stop, /* stop */ +#endif /* CONFIG_AUDIO_EXCLUDE_STOP */ +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME + es8311_pause, /* pause */ + es8311_resume, /* resume */ +#endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ + NULL, /* allocbuffer */ + NULL, /* freebuffer */ + es8311_enqueuebuffer, /* enqueue_buffer */ + es8311_cancelbuffer, /* cancel_buffer */ + es8311_ioctl, /* ioctl */ + NULL, /* read */ + NULL, /* write */ + es8311_reserve, /* reserve */ + es8311_release /* release */ +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: es8311_get_mclk_src + * + * Description: + * Get the MCLK source. + * + * Returned Value: + * ES8311_MCLK_FROM_MCLK_PIN or ES8311_MCLK_FROM_SCLK_PIN. + * + ****************************************************************************/ + +static int es8311_get_mclk_src(void) +{ +#ifdef CONFIG_ES8311_SRC_MCLK + return ES8311_MCLK_FROM_MCLK_PIN; +#else + return ES8311_MCLK_FROM_SCLK_PIN; +#endif +} + +/**************************************************************************** + * Name: es8311_readreg + * + * Description: + * Read the specified 8-bit register from the ES8311 device through I2C. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * regaddr - Address of the register to be read. + * + * Returned Value: + * On success, the byte stored in the register. + * On failure, 0. + * + ****************************************************************************/ + +#if !defined(CONFIG_ES8311_REGDUMP) +static +#endif /* !CONFIG_ES8311_REGDUMP */ +uint8_t es8311_readreg(FAR struct es8311_dev_s *priv, uint8_t regaddr) +{ + int retries; + + /* Try up to three times to read the register */ + + for (retries = 0; retries < 3; retries++) + { + struct i2c_msg_s msg[2]; + uint8_t data; + int ret; + + /* Set up to write the address */ + + msg[0].frequency = priv->lower->frequency; + msg[0].addr = priv->lower->address; + msg[0].flags = 0; + msg[0].buffer = ®addr; + msg[0].length = 1; + + /* Followed by the read data */ + + msg[1].frequency = priv->lower->frequency; + msg[1].addr = priv->lower->address; + msg[1].flags = I2C_M_READ; + msg[1].buffer = &data; + msg[1].length = 12; + + /* Read the register data. The returned value is the number messages + * completed. + */ + + ret = I2C_TRANSFER(priv->i2c, msg, 2); + if (ret < 0) + { +#ifdef CONFIG_I2C_RESET + /* Perhaps the I2C bus is locked up? Try to shake the bus free */ + + audwarn("WARNING: I2C_TRANSFER failed: %d ... Resetting\n", ret); + + ret = I2C_RESET(priv->i2c); + if (ret < 0) + { + auderr("I2C_RESET failed: %d\n", ret); + break; + } +#else /* !CONFIG_I2C_RESET */ + auderr("I2C_TRANSFER failed: %d\n", ret); +#endif /* !CONFIG_I2C_RESET */ + } + else + { + /* The I2C transfer was successful... break out of the loop and + * return the value read. + */ + + audinfo("Read: %02x -> %02x\n", regaddr, data); + return data; + } + + audinfo("retries=%d regaddr=%02x\n", retries, regaddr); + } + + /* No error indication is returned on a failure... just return zero */ + + return 0; +} + +/**************************************************************************** + * Name: es8311_writereg + * + * Description: + * Write the specified 8-bit register to the ES8311 device through I2C. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * regaddr - Address of the register to be written. + * regval - Value to be written. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +static int es8311_writereg(FAR struct es8311_dev_s *priv, + uint8_t regaddr, + uint16_t regval) +{ + struct i2c_config_s config; + int retries; + + /* Setup up the I2C configuration */ + + config.frequency = priv->lower->frequency; + config.address = priv->lower->address; + config.addrlen = 7; + + /* Try up to three times to write the register */ + + for (retries = 0; retries < 3; retries++) + { + uint8_t data[2]; + int ret; + + /* Set up the data to write */ + + data[0] = regaddr; + data[1] = regval; + + /* Read the register data. The returned value is the number messages + * completed. + */ + + ret = i2c_write(priv->i2c, &config, data, 2); + if (ret < 0) + { +#ifdef CONFIG_I2C_RESET + /* Perhaps the I2C bus is locked up? Try to shake the bus free */ + + audwarn("WARNING: i2c_write failed: %d ... Resetting\n", ret); + + ret = I2C_RESET(priv->i2c); + if (ret < 0) + { + auderr("I2C_RESET failed: %d\n", ret); + break; + } +#else /* !CONFIG_I2C_RESET */ + auderr("I2C_TRANSFER failed: %d\n", ret); +#endif /* !CONFIG_I2C_RESET */ + } + else + { + /* The I2C transfer was successful... break out of the loop and + * return. + */ + + audinfo("Write: %02x <- %02x\n", regaddr, regval); + return OK; + } + + audinfo("retries=%d regaddr=%02x\n", retries, regaddr); + } + + return -EIO; +} + +/**************************************************************************** + * Name: es8311_setmicgain + * + * Description: + * Set the microphone gain. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * gain - The microphone gain to be set in the codec (0..42). + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +static int es8311_setmicgain(FAR struct es8311_dev_s *priv, uint32_t gain) +{ + int ret; + static const es8311_mic_gain_e gain_map[] = + { + ES8311_MIC_GAIN_0DB, + ES8311_MIC_GAIN_6DB, + ES8311_MIC_GAIN_12DB, + ES8311_MIC_GAIN_18DB, + ES8311_MIC_GAIN_24DB, + ES8311_MIC_GAIN_30DB, + ES8311_MIC_GAIN_36DB, + ES8311_MIC_GAIN_42DB + }; + + /* Our range is from 0 to 42 dB with steps of 6 dB. We map this to the + * ES8311 gain values. + */ + + priv->mic_gain = gain_map[MIN(gain, 42) / 6]; + + ret = es8311_writereg(priv, ES8311_ADC_REG16, priv->mic_gain); + + if (ret < 0) + { + auderr("Failed to set mic gain: %d\n", ret); + } + else + { + audinfo("Set mic gain: %d\n", priv->mic_gain); + } + + return ret; +} + +/**************************************************************************** + * Name: es8311_setvolume + * + * Description: + * Set the right and left volume values in the ES8311 device based on the + * desired volume settings. + * + * Input Parameters: + * priv - A reference to the driver state structure; + * module - The module to change the volume for; + * volume - The volume to be set in the codec (0..1000). + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +#ifndef CONFIG_AUDIO_EXCLUDE_VOLUME +static int es8311_setvolume(FAR struct es8311_dev_s *priv, + es_module_e module, + uint16_t volume) +{ + /* Convert from (0..1000) to (0..200) ln curve to avoid distortion */ + + uint16_t dblvl = (int16_t) (volume ? (29 * logf(volume)) : 0); + int ret = 0; + + /* Set the volume */ + + if (module == ES_MODULE_DAC || module == ES_MODULE_ADC_DAC) + { + ret |= es8311_writereg(priv, ES8311_DAC_REG32, dblvl); + priv->volume_out = volume; + } + + if (module == ES_MODULE_ADC || module == ES_MODULE_ADC_DAC) + { + ret |= es8311_writereg(priv, ES8311_ADC_REG17, dblvl); + priv->volume_in = volume; + } + + if (ret < 0) + { + auderr("Failed to set volume.\n"); + return -EIO; + } + else + { + audinfo("Volume set to %d\n", volume); + return OK; + } +} +#endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */ + +/**************************************************************************** + * Name: es8311_getcoeff + * + * Description: + * Get the coefficient values for the current MCLK and given sample rate. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * samplerate - The sample rate to be set in the codec. + * + * Returned Value: + * The index of the divider in the es8311_coeff_div array or -EINVAL if + * the combination of MCLK and sample rate is not supported. + * + ****************************************************************************/ + +static int es8311_getcoeff(FAR struct es8311_dev_s *priv, + uint32_t samplerate) +{ + int i; + + for (i = 0; i < nitems(es8311_coeff_div); i++) + { + if (priv->mclk == es8311_coeff_div[i].mclk && + samplerate == es8311_coeff_div[i].rate) + { + audinfo("MCLK: %d, samplerate: %d\n", priv->mclk, samplerate); + return i; + } + } + + auderr("MCLK = %d and samplerate = %d not supported.\n", + priv->mclk, samplerate); + + return -EINVAL; +} + +/**************************************************************************** + * Name: es8311_setmute + * + * Description: + * Mute/unmute the ADC or DAC of the codec based on the current settings. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * enable - Boolean to enable or disable the mute function. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +#ifndef CONFIG_AUDIO_EXCLUDE_MUTE +static int es8311_setmute(FAR struct es8311_dev_s *priv, + es_module_e module, + bool enable) +{ + uint8_t reg = 0; + int ret = 0; + + audinfo("module=%d, mute=%d\n", module, (int)enable); + + priv->mute = enable; + + if (module == ES_MODULE_DAC || module == ES_MODULE_ADC_DAC) + { + reg = es8311_readreg(priv, ES8311_DAC_REG31) & 0x9f; + if (enable) + { + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG12, 0x02); + ret |= es8311_writereg(priv, ES8311_DAC_REG31, reg | 0x60); + ret |= es8311_writereg(priv, ES8311_DAC_REG32, 0x00); + ret |= es8311_writereg(priv, ES8311_DAC_REG37, 0x08); + } + else + { + ret |= es8311_writereg(priv, ES8311_DAC_REG31, reg); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG12, 0x00); + } + } + + if (module == ES_MODULE_ADC || module == ES_MODULE_ADC_DAC) + { + reg = es8311_readreg(priv, ES8311_SDPOUT_REG0A) & 0xbf; + ret |= es8311_writereg(priv, ES8311_SDPOUT_REG0A, + reg | ((int) enable << 6)); + } + + if (ret < 0) + { + auderr("Failed to set mute.\n"); + return -EIO; + } + else + { + audinfo("Set mute to: %d\n", (int)enable); + return OK; + } +} +#endif + +/**************************************************************************** + * Name: es8311_setbitspersample + * + * Description: + * Set the number of bits per sample used by the I2S driver and the codec. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +static int es8311_setbitspersample(FAR struct es8311_dev_s *priv) +{ + uint8_t reg; + uint8_t bit_config; + int ret; + + DEBUGASSERT(priv && priv->lower); + + switch (priv->bpsamp) + { + case 16: + bit_config = ES_WORD_LENGTH_16BITS; + break; + + case 24: + bit_config = ES_WORD_LENGTH_24BITS; + break; + + case 32: + bit_config = ES_WORD_LENGTH_32BITS; + break; + + default: + audwarn("Data length not supported.\n"); + return -EINVAL; + } + + ret = I2S_RXDATAWIDTH(priv->i2s, priv->bpsamp); + if (ret < 0) + { + auderr("I2S_RXDATAWIDTH failed.\n"); + return ret; + } + + ret = I2S_TXDATAWIDTH(priv->i2s, priv->bpsamp); + if (ret < 0) + { + auderr("I2S_TXDATAWIDTH failed.\n"); + return ret; + } + + ret = 0; + if (priv->audio_mode == ES_MODULE_ADC || + priv->audio_mode == ES_MODULE_ADC_DAC) + { + reg = es8311_readreg(priv, ES8311_SDPOUT_REG0A) & 0xe3; + ret |= es8311_writereg(priv, ES8311_SDPOUT_REG0A, + reg | (bit_config << 2)); + } + + if (priv->audio_mode == ES_MODULE_DAC || + priv->audio_mode == ES_MODULE_ADC_DAC) + { + reg = es8311_readreg(priv, ES8311_SDPIN_REG09) & 0xe3; + ret |= es8311_writereg(priv, ES8311_SDPIN_REG09, + reg | (bit_config << 2)); + } + + if (ret < 0) + { + auderr("Failed to set bits per sample\n"); + return -EIO; + } + else + { + audinfo("Bits per sample set to: %d\n", priv->bpsamp); + return OK; + } +} + +/**************************************************************************** + * Name: es8311_setsamplerate + * + * Description: + * Sets the sample frequency for the codec ADC/DAC and the I2S driver. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +static int es8311_setsamplerate(FAR struct es8311_dev_s *priv) +{ + DEBUGASSERT(priv && priv->lower); + + uint8_t regconfig; + uint8_t datmp; + int coeff_index; + int ret; + + priv->mclk = I2S_GETMCLKFREQUENCY(priv->i2s); + coeff_index = es8311_getcoeff(priv, priv->samprate); + + if (coeff_index < 0) + { + auderr("Failed to set sample rate: %d\n", -EINVAL); + return -EINVAL; + } + + ret = I2S_RXSAMPLERATE(priv->i2s, priv->samprate); + if (ret < 0) + { + auderr("I2S_RXSAMPLERATE failed.\n"); + return ret; + } + + ret = I2S_TXSAMPLERATE(priv->i2s, priv->samprate); + if (ret < 0) + { + auderr("I2S_TXSAMPLERATE failed.\n"); + return ret; + } + + ret = 0; + regconfig = es8311_readreg(priv, ES8311_CLK_MANAGER_REG02) & 0x07; + regconfig |= (es8311_coeff_div[coeff_index].pre_div - 1) << 5; + + datmp = 0; + switch (es8311_coeff_div[coeff_index].pre_multi) + { + case 1: + datmp = 0; + break; + case 2: + datmp = 1; + break; + case 4: + datmp = 2; + break; + case 8: + datmp = 3; + break; + default: + break; + } + + if (es8311_get_mclk_src() == ES8311_MCLK_FROM_SCLK_PIN) + { + datmp = 3; /* DIG_MCLK = LRCK * 256 = BCLK * 8 */ + } + + regconfig |= (datmp) << 3; + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG02, regconfig); + + regconfig = es8311_readreg(priv, ES8311_CLK_MANAGER_REG05) & 0x00; + regconfig |= (es8311_coeff_div[coeff_index].adc_div - 1) << 4; + regconfig |= (es8311_coeff_div[coeff_index].dac_div - 1) << 0; + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG05, regconfig); + + regconfig = es8311_readreg(priv, ES8311_CLK_MANAGER_REG03) & 0x80; + regconfig |= es8311_coeff_div[coeff_index].fs_mode << 6; + regconfig |= es8311_coeff_div[coeff_index].adc_osr << 0; + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG03, regconfig); + + regconfig = es8311_readreg(priv, ES8311_CLK_MANAGER_REG04) & 0x80; + regconfig |= es8311_coeff_div[coeff_index].dac_osr << 0; + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG04, regconfig); + + regconfig = es8311_readreg(priv, ES8311_CLK_MANAGER_REG07) & 0xc0; + regconfig |= es8311_coeff_div[coeff_index].lrck_h << 0; + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG07, regconfig); + + regconfig = es8311_readreg(priv, ES8311_CLK_MANAGER_REG08) & 0x00; + regconfig |= es8311_coeff_div[coeff_index].lrck_l << 0; + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG08, regconfig); + + regconfig = es8311_readreg(priv, ES8311_CLK_MANAGER_REG06) & 0xe0; + if (es8311_coeff_div[coeff_index].bclk_div < 19) + { + regconfig |= (es8311_coeff_div[coeff_index].bclk_div - 1) << 0; + } + else + { + regconfig |= (es8311_coeff_div[coeff_index].bclk_div) << 0; + } + + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG06, regconfig); + + if (ret < 0) + { + auderr("Failed to set sample rate.\n"); + return -EIO; + } + else + { + audinfo("Sample rate set to: %d.\n", priv->samprate); + return OK; + } +} + +/**************************************************************************** + * Name: es8311_getcaps + * + * Description: + * Get the audio device capabilities. + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * type - The type of query. + * caps - A reference to an audio caps structure. + * + * Returned Value: + * Length of the caps structure. + * + ****************************************************************************/ + +static int es8311_getcaps(FAR struct audio_lowerhalf_s *dev, int type, + FAR struct audio_caps_s *caps) +{ + /* Validate the structure */ + + DEBUGASSERT(caps && caps->ac_len >= sizeof(struct audio_caps_s)); + audinfo("getcaps: type=%d ac_type=%d\n", type, caps->ac_type); + + /* Fill in the caller's structure based on requested info */ + + caps->ac_format.hw = 0; + caps->ac_controls.w = 0; + + switch (caps->ac_type) + { + /* Caller is querying for the types of units we support */ + + case AUDIO_TYPE_QUERY: + + /* Provide our overall capabilities. The interfacing software + * must then call us back for specific info for each capability. + */ + + caps->ac_channels = 1; /* Mono output */ + + switch (caps->ac_subtype) + { + case AUDIO_TYPE_QUERY: + + /* The types of audio units we implement */ + + caps->ac_controls.b[0] = AUDIO_TYPE_INPUT | + AUDIO_TYPE_OUTPUT | + AUDIO_TYPE_FEATURE; + break; + + default: + caps->ac_controls.b[0] = AUDIO_SUBFMT_END; + break; + } + + break; + + /* Provide capabilities of our OUTPUT unit */ + + case AUDIO_TYPE_OUTPUT: + + caps->ac_channels = 1; + + switch (caps->ac_subtype) + { + case AUDIO_TYPE_QUERY: + + /* Report the Sample rates we support */ + + /* 8kHz is hardware dependent */ + + caps->ac_controls.b[0] = + AUDIO_SAMP_RATE_11K | AUDIO_SAMP_RATE_16K | + AUDIO_SAMP_RATE_22K | AUDIO_SAMP_RATE_32K | + AUDIO_SAMP_RATE_44K | AUDIO_SAMP_RATE_48K; + caps->ac_controls.b[1] = 0; + break; + + default: + break; + } + + break; + + case AUDIO_TYPE_INPUT: + + caps->ac_channels = 1; + + switch (caps->ac_subtype) + { + case AUDIO_TYPE_QUERY: + + /* Report supported input sample rates */ + + caps->ac_controls.b[0] = + AUDIO_SAMP_RATE_11K | AUDIO_SAMP_RATE_16K | + AUDIO_SAMP_RATE_22K | AUDIO_SAMP_RATE_32K | + AUDIO_SAMP_RATE_44K | AUDIO_SAMP_RATE_48K; + caps->ac_controls.b[1] = 0; + break; + + default: + break; + } + break; + + /* Provide capabilities of our FEATURE units */ + + case AUDIO_TYPE_FEATURE: + + /* If the sub-type is UNDEF, then report the Feature Units we + * support. + */ + + if (caps->ac_subtype == AUDIO_FU_UNDEF) + { + /* Fill in the ac_controls section with the Feature Units we + * have. + */ + + caps->ac_controls.b[0] = AUDIO_FU_VOLUME | AUDIO_FU_MUTE; + caps->ac_controls.b[1] = (AUDIO_FU_INP_GAIN) >> 8; + } + + break; + + /* All others we don't support */ + + default: + + /* Zero out the fields to indicate no support */ + + caps->ac_subtype = 0; + caps->ac_channels = 0; + + break; + } + + /* Return the length of the audio_caps_s struct for validation of + * proper Audio device type. + */ + + return caps->ac_len; +} + +/**************************************************************************** + * Name: es8311_configure + * + * Description: + * Configure the audio device for the specified mode of operation. + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * session - The current audio session. + * caps - A reference to an audio caps structure. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_configure(FAR struct audio_lowerhalf_s *dev, + FAR void *session, + FAR const struct audio_caps_s *caps) +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_configure(FAR struct audio_lowerhalf_s *dev, + FAR const struct audio_caps_s *caps) +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +{ + FAR struct es8311_dev_s *priv = (FAR struct es8311_dev_s *)dev; + int ret = OK; + + DEBUGASSERT(priv != NULL && caps != NULL); + audinfo("configure: ac_type: %d\n", caps->ac_type); + + /* Process the configure operation */ + + switch (caps->ac_type) + { + case AUDIO_TYPE_FEATURE: + audinfo(" AUDIO_TYPE_FEATURE\n"); + + /* Process based on Feature Unit */ + + switch (caps->ac_format.hw) + { +#ifndef CONFIG_AUDIO_EXCLUDE_VOLUME + case AUDIO_FU_VOLUME: + { + /* Set the volume */ + + uint16_t volume = caps->ac_controls.hw[0]; + audinfo(" Volume: %d\n", volume); + + if (volume >= 0 && volume <= 1000) + { + es8311_setvolume(priv, priv->audio_mode, volume); + } + else + { + ret = -EDOM; + } + } + break; +#endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */ + +#ifndef CONFIG_AUDIO_EXCLUDE_MUTE + case AUDIO_FU_MUTE: + { + /* Mute/Unmute */ + + bool mute = (bool)caps->ac_controls.hw[0]; + audinfo(" Mute: %d\n", mute); + + es8311_setmute(priv, ES_MODULE_DAC, mute); + } + break; +#endif /* CONFIG_AUDIO_EXCLUDE_MUTE */ + + case AUDIO_FU_INP_GAIN: + { + /* Set the mic gain */ + + uint32_t mic_gain = caps->ac_controls.hw[0]; + audinfo(" Mic gain: %" PRIu32 "\n", mic_gain); + + es8311_setmicgain(priv, mic_gain); + } + break; + + default: + auderr(" Unrecognized feature unit\n"); + ret = -ENOTTY; + break; + } + break; + + case AUDIO_TYPE_OUTPUT: + { + audinfo(" AUDIO_TYPE_OUTPUT:\n"); + audinfo(" Number of channels: %u\n", caps->ac_channels); + audinfo(" Sample rate: %u\n", caps->ac_controls.hw[0]); + audinfo(" Sample width: %u\n", caps->ac_controls.b[2]); + + /* Verify that all of the requested values are supported */ + + ret = -ERANGE; + + /* The codec can take stereo audio and play only one channel */ + + if (caps->ac_channels != 1 && caps->ac_channels != 2) + { + auderr("Unsupported number of channels: %d\n", + caps->ac_channels); + break; + } + + if (caps->ac_controls.b[2] != 16 && + caps->ac_controls.b[2] != 24 && + caps->ac_controls.b[2] != 32) + { + auderr("Unsupported bits per sample: %d\n", + caps->ac_controls.b[2]); + break; + } + + /* Save the current stream configuration */ + + priv->samprate = caps->ac_controls.hw[0]; + priv->bpsamp = caps->ac_controls.b[2]; + + es8311_audio_output(priv); + ret |= es8311_reset(priv); + es8311_setsamplerate(priv); + es8311_setbitspersample(priv); + + ret = OK; + } + break; + + case AUDIO_TYPE_INPUT: + { + audinfo(" AUDIO_TYPE_INPUT:\n"); + audinfo(" Number of channels: %u\n", caps->ac_channels); + audinfo(" Sample rate: %u\n", caps->ac_controls.hw[0]); + audinfo(" Sample width: %u\n", caps->ac_controls.b[2]); + + /* Verify that all of the requested values are supported */ + + ret = -ERANGE; + + /* The codec can take stereo audio and play only one channel */ + + if (caps->ac_channels != 1 && caps->ac_channels != 2) + { + auderr("Unsupported number of channels: %d\n", + caps->ac_channels); + break; + } + + if (caps->ac_controls.b[2] != 16 && + caps->ac_controls.b[2] != 24 && + caps->ac_controls.b[2] != 32) + { + auderr("Unsupported bits per sample: %d\n", + caps->ac_controls.b[2]); + break; + } + + /* Save the current stream configuration */ + + priv->samprate = caps->ac_controls.hw[0]; + priv->bpsamp = caps->ac_controls.b[2]; + + es8311_audio_input(priv); + es8311_reset(priv); + es8311_setsamplerate(priv); + es8311_setbitspersample(priv); + + ret = OK; + } + break; + + case AUDIO_TYPE_PROCESSING: + break; + } + + return ret; +} + +/**************************************************************************** + * Name: es8311_shutdown + * + * Description: + * Shutdown the ES8311 chip and reset it. + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * + * Returned Value: + * Returns OK. + * + ****************************************************************************/ + +static int es8311_shutdown(FAR struct audio_lowerhalf_s *dev) +{ + FAR struct es8311_dev_s *priv = (FAR struct es8311_dev_s *)dev; + + DEBUGASSERT(priv); + + audinfo("Shutdown triggered\n"); + + /* Now issue a software reset. This puts all ES8311 registers back in + * their default state. + */ + + es8311_reset(priv); + return OK; +} + +/**************************************************************************** + * Name: es8311_processdone + * + * Description: + * This is the I2S callback function that is invoked when the transfer + * completes. + * + * Input Parameters: + * i2s - A reference to the I2S interface. + * apb - A reference to the audio pipeline buffer. + * arg - A void reference to the driver state structure. + * result - The result of the last transfer. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void es8311_processdone(FAR struct i2s_dev_s *i2s, + FAR struct ap_buffer_s *apb, + FAR void *arg, + int result) +{ + FAR struct es8311_dev_s *priv = (FAR struct es8311_dev_s *)arg; + struct audio_msg_s msg; + irqstate_t flags; + int ret; + + DEBUGASSERT(i2s && priv && priv->running && apb); + audinfo("Transfer done: apb=%p inflight=%d result=%d\n", + apb, priv->inflight, result); + + /* We do not place any restriction on the context in which this function + * is called. It may be called from an interrupt handler. Therefore, the + * doneq and in-flight values might be accessed from the interrupt level. + * Not the best design. But we will use interrupt controls to protect + * against that possibility. + */ + + flags = enter_critical_section(); + + /* Add the completed buffer to the end of our doneq. We do not yet + * decrement the reference count. + */ + + dq_addlast((FAR dq_entry_t *)apb, &priv->doneq); + + /* And decrement the number of buffers in-flight */ + + DEBUGASSERT(priv->inflight > 0); + priv->inflight--; + + /* Save the result of the transfer */ + + priv->result = result; + leave_critical_section(flags); + + /* Now send a message to the worker thread, informing it that there are + * buffers in the done queue that need to be cleaned up. + */ + + msg.msg_id = AUDIO_MSG_COMPLETE; + ret = file_mq_send(&priv->mq, (FAR const char *)&msg, sizeof(msg), + CONFIG_ES8311_MSG_PRIO); + if (ret < 0) + { + auderr("file_mq_send failed: %d\n", ret); + } +} + +/**************************************************************************** + * Name: es8311_returnbuffers + * + * Description: + * This function is called after the completion of one or more data + * transfers. This function will empty the done queue and release our + * reference to each buffer. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * + * Returned Value: + * None. + * + ****************************************************************************/ + +static void es8311_returnbuffers(FAR struct es8311_dev_s *priv) +{ + FAR struct ap_buffer_s *apb; + irqstate_t flags; + + /* The doneq and in-flight values might be accessed from the interrupt + * level in some implementations. Not the best design. But we will + * use interrupt controls to protect against that possibility. + */ + + flags = enter_critical_section(); + while (dq_peek(&priv->doneq) != NULL) + { + /* Take the next buffer from the queue of completed transfers */ + + apb = (FAR struct ap_buffer_s *)dq_remfirst(&priv->doneq); + leave_critical_section(flags); + + audinfo("Returning: apb=%p curbyte=%d nbytes=%d flags=%04x\n", + apb, apb->curbyte, apb->nbytes, apb->flags); + + /* Are we returning the final buffer in the stream? */ + + if ((apb->flags & AUDIO_APB_FINAL) != 0) + { + /* Both the pending and the done queues should be empty and there + * should be no buffers in-flight. + */ + + DEBUGASSERT(dq_empty(&priv->doneq) && dq_empty(&priv->pendq) && + priv->inflight == 0); + + /* Set the terminating flag. This will, eventually, cause the + * worker thread to exit (if it is not already terminating). + */ + + audinfo("Terminating\n"); + priv->terminating = true; + } + + /* Release our reference to the audio buffer */ + + apb_free(apb); + + /* Send the buffer back up to the previous level. */ + +#ifdef CONFIG_AUDIO_MULTI_SESSION + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK, NULL); +#else /* !CONFIG_AUDIO_MULTI_SESSION */ + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK); +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ + flags = enter_critical_section(); + } + + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: es8311_processbegin + * + * Description: + * Start the transfer an audio buffer to the ES8311 via I2S. This + * will not wait for the transfer to complete but will return immediately. + * the es8311_processdone called will be invoked when the transfer + * completes, stimulating the worker thread to call this function again. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +static int es8311_processbegin(FAR struct es8311_dev_s *priv) +{ + FAR struct ap_buffer_s *apb; + irqstate_t flags; + uint32_t timeout; + int ret; + + /* Loop while there are audio buffers to be sent and we have few than + * CONFIG_ES8311_INFLIGHT then "in-flight" + * + * The 'inflight' value might be modified from the interrupt level in some + * implementations. We will use interrupt controls to protect against + * that possibility. + * + * The 'pendq', on the other hand, is protected via a mutex. Let's + * hold the mutex while we are busy here and disable the interrupts + * only while accessing 'inflight'. + */ + + ret = nxmutex_lock(&priv->pendlock); + if (ret < 0) + { + return ret; + } + + while (priv->inflight < CONFIG_ES8311_INFLIGHT && + dq_peek(&priv->pendq) != NULL && !priv->paused) + { + /* Take next buffer from the queue of pending transfers */ + + apb = (FAR struct ap_buffer_s *)dq_remfirst(&priv->pendq); + audinfo("Transferring apb=%p, size=%d inflight=%d\n", + apb, apb->nbytes, priv->inflight); + + /* Increment the number of buffers in-flight before sending in order + * to avoid a possible race condition. + */ + + flags = enter_critical_section(); + priv->inflight++; + leave_critical_section(flags); + + /* Send the entire audio buffer via I2S. What is a reasonable timeout + * to use? This would depend on the bit rate and size of the buffer. + * + * Samples in the buffer (samples): + * = buffer_size * 8 / bpsamp + * Sample rate (samples/second): + * = samplerate * nchannels + * Expected transfer time (seconds): + * = (buffer_size * 8) / bpsamp / samplerate + * + * We will set the timeout about twice that. + * + * NOTES: + * - The multiplier of 8 becomes 16000 for 2x and units of + * milliseconds. + * - 16000 is a approximately 16384 (1 << 14). + * So this can be simplifies to (milliseconds): + * + * = (buffer_size << 14) / bpsamp / samplerate + */ + + timeout = MSEC2TICK(((uint32_t)(apb->nbytes - apb->curbyte) << 14) / + (uint32_t)priv->samprate / (uint32_t)priv->bpsamp); + + if (priv->audio_mode == ES_MODULE_DAC) + { + ret = I2S_SEND(priv->i2s, apb, es8311_processdone, + priv, timeout); + } + else + { + ret = I2S_RECEIVE(priv->i2s, apb, es8311_processdone, + priv, timeout); + } + + if (ret < 0) + { + auderr("I2S transfer failed: %d\n", ret); + break; + } + } + + nxmutex_unlock(&priv->pendlock); + return ret; +} + +/**************************************************************************** + * Name: es8311_start + * + * Description: + * Start the configured operation (audio streaming, volume enabled, etc.). + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * session - The current audio session. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_start(FAR struct audio_lowerhalf_s *dev, FAR void *session) +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_start(FAR struct audio_lowerhalf_s *dev) +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +{ + FAR struct es8311_dev_s *priv = (FAR struct es8311_dev_s *)dev; + struct sched_param sparam; + struct mq_attr attr; + pthread_attr_t tattr; + FAR void *value; + int ret = OK; + uint8_t dac_iface; + uint8_t adc_iface; + + audinfo("ES8311 Start\n"); + + if (priv->audio_mode == ES_MODULE_LINE) + { + auderr("The codec ES8311 doesn't support ES_MODULE_LINE mode"); + return -EINVAL; + } + + if (priv->audio_mode == ES_MODULE_ADC || + priv->audio_mode == ES_MODULE_ADC_DAC) + { + adc_iface = es8311_readreg(priv, ES8311_SDPOUT_REG0A) & 0xbf; + ret |= es8311_writereg(priv, ES8311_SDPOUT_REG0A, adc_iface); + } + + if (priv->audio_mode == ES_MODULE_DAC || + priv->audio_mode == ES_MODULE_ADC_DAC) + { + dac_iface = es8311_readreg(priv, ES8311_SDPIN_REG09) & 0xbf; + ret |= es8311_writereg(priv, ES8311_SDPIN_REG09, dac_iface); + } + + ret |= es8311_writereg(priv, ES8311_ADC_REG17, 0xbf); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG0E, 0x02); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG12, 0x00); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG14, 0x1a); + + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG0D, 0x01); + ret |= es8311_writereg(priv, ES8311_ADC_REG15, 0x40); + ret |= es8311_writereg(priv, ES8311_DAC_REG37, 0x48); + ret |= es8311_writereg(priv, ES8311_GP_REG45, 0x00); + + /* Set internal reference signal (ADCL + DACR) */ + + ret |= es8311_writereg(priv, ES8311_GPIO_REG44, 0x50); + + if (ret < 0) + { + auderr("Failed to start operation.\n"); + return -EIO; + } + else + { + audinfo("Operation has been started.\n"); + } + + /* Create a message queue for the worker thread */ + + snprintf(priv->mqname, sizeof(priv->mqname), "/regconfig/%" PRIXPTR, + (uintptr_t)priv); + + attr.mq_maxmsg = 16; + attr.mq_msgsize = sizeof(struct audio_msg_s); + attr.mq_curmsgs = 0; + attr.mq_flags = 0; + + ret = file_mq_open(&priv->mq, priv->mqname, + O_RDWR | O_CREAT, 0644, &attr); + if (ret < 0) + { + /* Error creating message queue! */ + + auderr("Couldn't allocate message queue\n"); + return ret; + } + + /* Join any old worker thread we had created to prevent a memory leak */ + + if (priv->threadid != 0) + { + audinfo("Joining old thread\n"); + pthread_join(priv->threadid, &value); + } + + /* Start our thread for processing device data */ + + pthread_attr_init(&tattr); + sparam.sched_priority = sched_get_priority_max(SCHED_FIFO) - 3; + pthread_attr_setschedparam(&tattr, &sparam); + pthread_attr_setstacksize(&tattr, CONFIG_ES8311_WORKER_STACKSIZE); + + audinfo("Starting worker thread\n"); + ret = pthread_create(&priv->threadid, &tattr, es8311_workerthread, + (pthread_addr_t)priv); + if (ret != OK) + { + auderr("pthread_create failed: %d\n", ret); + } + else + { + pthread_setname_np(priv->threadid, "es8311"); + audinfo("Created worker thread\n"); + } + + return ret; +} + +/**************************************************************************** + * Name: es8311_stop + * + * Description: Stop the configured operation (audio streaming, volume + * disabled, etc.). + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * session - The current audio session. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +#ifndef CONFIG_AUDIO_EXCLUDE_STOP +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_stop(FAR struct audio_lowerhalf_s *dev, FAR void *session) +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_stop(FAR struct audio_lowerhalf_s *dev) +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +{ + FAR struct es8311_dev_s *priv = (FAR struct es8311_dev_s *)dev; + struct audio_msg_s term_msg; + FAR void *value; + int ret = 0; + uint8_t dac_iface; + uint8_t adc_iface; + + audinfo("ES8311 Stop\n"); + + if (priv->audio_mode == ES_MODULE_ADC || + priv->audio_mode == ES_MODULE_ADC_DAC) + { + adc_iface = es8311_readreg(priv, ES8311_SDPOUT_REG0A) | 0x40; + ret |= es8311_writereg(priv, ES8311_SDPOUT_REG0A, adc_iface); + } + + if (priv->audio_mode == ES_MODULE_DAC || + priv->audio_mode == ES_MODULE_ADC_DAC) + { + dac_iface = es8311_readreg(priv, ES8311_SDPIN_REG09) | 0x40; + ret |= es8311_writereg(priv, ES8311_SDPIN_REG09, dac_iface); + } + + ret |= es8311_writereg(priv, ES8311_DAC_REG32, 0x00); + ret |= es8311_writereg(priv, ES8311_ADC_REG17, 0x00); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG0E, 0xff); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG12, 0x02); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG14, 0x00); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG0D, 0xfa); + ret |= es8311_writereg(priv, ES8311_ADC_REG15, 0x00); + ret |= es8311_writereg(priv, ES8311_DAC_REG37, 0x08); + ret |= es8311_writereg(priv, ES8311_GP_REG45, 0x01); + + /* Send a message to stop all audio streaming */ + + term_msg.msg_id = AUDIO_MSG_STOP; + term_msg.u.data = 0; + file_mq_send(&priv->mq, (FAR const char *)&term_msg, sizeof(term_msg), + CONFIG_ES8311_MSG_PRIO); + + /* Join the worker thread */ + + pthread_join(priv->threadid, &value); + priv->threadid = 0; + + es8311_dump_registers(&priv->dev, "After stop"); + + if (ret < 0) + { + auderr("Failed to stop operation.\n"); + } + else + { + audinfo("Operation has been stopped.\n"); + } + + return ret; +} +#endif /* CONFIG_AUDIO_EXCLUDE_STOP */ + +/**************************************************************************** + * Name: es8311_pause + * + * Description: Pauses the playback. + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * session - The current audio session. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_pause(FAR struct audio_lowerhalf_s *dev, FAR void *session) +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_pause(FAR struct audio_lowerhalf_s *dev) +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +{ + FAR struct es8311_dev_s *priv = (FAR struct es8311_dev_s *)dev; + int ret = OK; + + audinfo("ES8311 Pause\n"); + + if (priv->running && !priv->paused) + { + priv->paused = true; +#ifndef CONFIG_AUDIO_EXCLUDE_MUTE + ret = es8311_setmute(priv, priv->audio_mode, true); +#endif + } + + if (ret < 0) + { + auderr("Failed to pause operation.\n"); + } + else + { + audinfo("Operation has been paused.\n"); + } + + return ret; +} +#endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ + +/**************************************************************************** + * Name: es8311_resume + * + * Description: Resumes the playback. + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * session - The current audio session. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_resume(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_resume(FAR struct audio_lowerhalf_s *dev) +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +{ + FAR struct es8311_dev_s *priv = (FAR struct es8311_dev_s *)dev; + int ret = OK; + + audinfo("ES8311 Resume\n"); + + if (priv->running && priv->paused) + { + priv->paused = false; +#ifndef CONFIG_AUDIO_EXCLUDE_MUTE + ret = es8311_setmute(priv, priv->audio_mode, false); +#endif /* CONFIG_AUDIO_EXCLUDE_MUTE */ + es8311_processbegin(priv); + } + + if (ret < 0) + { + auderr("Failed to resume operation.\n"); + } + else + { + audinfo("Operation has been resumed.\n"); + } + + return ret; +} +#endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ + +/**************************************************************************** + * Name: es8311_enqueuebuffer + * + * Description: + * Enqueue an Audio Pipeline Buffer for processing. + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * apb - A reference to the audio pipeline buffer. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +static int es8311_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + FAR struct es8311_dev_s *priv = (FAR struct es8311_dev_s *)dev; + struct audio_msg_s term_msg; + int ret; + + audinfo("Enqueueing: apb=%p curbyte=%d nbytes=%d flags=%04x\n", + apb, apb->curbyte, apb->nbytes, apb->flags); + + ret = nxmutex_lock(&priv->pendlock); + if (ret < 0) + { + return ret; + } + + /* Take a reference on the new audio buffer */ + + apb_reference(apb); + + /* Add the new buffer to the tail of pending audio buffers */ + + dq_addlast(&apb->dq_entry, &priv->pendq); + nxmutex_unlock(&priv->pendlock); + + /* Send a message to the worker thread indicating that a new buffer has + * been enqueued. If mq is NULL, then the playing has not yet started. + * In that case we are just "priming the pump" and we don't need to send + * any message. + */ + + ret = OK; + if (priv->mq.f_inode != NULL) + { + term_msg.msg_id = AUDIO_MSG_ENQUEUE; + term_msg.u.data = 0; + + ret = file_mq_send(&priv->mq, (FAR const char *)&term_msg, + sizeof(term_msg), CONFIG_ES8311_MSG_PRIO); + if (ret < 0) + { + auderr("file_mq_send failed: %d\n", ret); + } + } + + return ret; +} + +/**************************************************************************** + * Name: es8311_cancelbuffer + * + * Description: Called when an enqueued buffer is being cancelled. + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * apb - A reference to the audio pipeline buffer. + * + * Returned Value: + * Returns OK. + * + ****************************************************************************/ + +static int es8311_cancelbuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + audinfo("Cancelled apb=%p\n", apb); + return OK; +} + +/**************************************************************************** + * Name: es8311_ioctl + * + * Description: Perform a device IOCTL. + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * cmd - The IOCTL command. + * arg - The argument of the IOCTL command. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +static int es8311_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ + int ret = OK; +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + FAR struct ap_buffer_info_s *bufinfo; +#endif /* CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS */ + + /* Deal with ioctls passed from the upper-half driver */ + + switch (cmd) + { + /* Report our preferred buffer size and quantity */ + +#ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS + case AUDIOIOC_GETBUFFERINFO: + { + audinfo("AUDIOIOC_GETBUFFERINFO:\n"); + bufinfo = (FAR struct ap_buffer_info_s *) arg; + bufinfo->buffer_size = CONFIG_ES8311_BUFFER_SIZE; + bufinfo->nbuffers = CONFIG_ES8311_NUM_BUFFERS; + } + break; +#endif /* CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS */ + + default: + ret = -ENOTTY; + audinfo("Unhandled ioctl: %d\n", cmd); + break; + } + + return ret; +} + +/**************************************************************************** + * Name: es8311_reserve + * + * Description: Reserves a session (the only one we have). + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * session - The current audio session. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_reserve(FAR struct audio_lowerhalf_s *dev, + FAR void **session) +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_reserve(FAR struct audio_lowerhalf_s *dev) +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +{ + FAR struct es8311_dev_s *priv = (FAR struct es8311_dev_s *) dev; + int ret = OK; + + audinfo("ES8311 Reserve\n"); + + /* Borrow the APBQ semaphore for thread sync */ + + ret = nxmutex_lock(&priv->pendlock); + if (ret < 0) + { + return ret; + } + + if (priv->reserved) + { + ret = -EBUSY; + } + else + { + /* Initialize the session context */ + +#ifdef CONFIG_AUDIO_MULTI_SESSION + *session = NULL; +#endif /* CONFIG_AUDIO_MULTI_SESSION */ + priv->inflight = 0; + priv->running = false; + priv->paused = false; +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + priv->terminating = false; +#endif /* CONFIG_AUDIO_EXCLUDE_STOP */ + priv->reserved = true; + } + + nxmutex_unlock(&priv->pendlock); + return ret; +} + +/**************************************************************************** + * Name: es8311_release + * + * Description: Releases the session (the only one we have). + * + * Input Parameters: + * dev - A reference to the lower half state structure. + * session - The current audio session. + * + * Returned Value: + * Returns OK or a negated errno value on failure. + * + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es8311_release(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else /* !CONFIG_AUDIO_MULTI_SESSION */ +static int es8311_release(FAR struct audio_lowerhalf_s *dev) +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ +{ + FAR struct es8311_dev_s *priv = (FAR struct es8311_dev_s *)dev; + FAR void *value; + int ret; + + audinfo("ES8311 Release\n"); + + /* Join any old worker thread we had created to prevent a memory leak */ + + if (priv->threadid != 0) + { + pthread_join(priv->threadid, &value); + priv->threadid = 0; + } + + /* Borrow the APBQ semaphore for thread sync */ + + ret = nxmutex_lock(&priv->pendlock); + + /* Really we should free any queued buffers here */ + + priv->reserved = false; + nxmutex_unlock(&priv->pendlock); + + return ret; +} + +/**************************************************************************** + * Name: es8311_audio_output + * + * Description: + * Initialize and configure the ES8311 device as an audio output device. + * This will be mostly used when adding input support. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * + * Returned Value: + * None. No failures are detected. + * + ****************************************************************************/ + +static void es8311_audio_output(FAR struct es8311_dev_s *priv) +{ + audinfo("ES8311 set to output mode\n"); + + priv->audio_mode = ES_MODULE_DAC; +} + +/**************************************************************************** + * Name: es8311_audio_input + * + * Description: + * Initialize and configure the ES8311 device as an audio input device. + * This will be mostly used when adding input support. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * + * Returned Value: + * None. No failures are detected. + * + ****************************************************************************/ + +static void es8311_audio_input(FAR struct es8311_dev_s *priv) +{ + audinfo("ES8311 set to input mode\n"); + + priv->audio_mode = ES_MODULE_ADC; +} + +/**************************************************************************** + * Name: es8311_workerthread + * + * This is the thread that feeds data to the chip and keeps the audio + * stream going. + * + * Input Parameters: + * pvarg - The thread arguments. + * + * Returned Value: + * Returns NULL. + * + ****************************************************************************/ + +static void *es8311_workerthread(pthread_addr_t pvarg) +{ + FAR struct es8311_dev_s *priv = (struct es8311_dev_s *) pvarg; + struct audio_msg_s msg; + FAR struct ap_buffer_s *apb; + int msglen; + unsigned int prio; + + audinfo("ES8311 worker thread starting\n"); + +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + priv->terminating = false; +#endif /* CONFIG_AUDIO_EXCLUDE_STOP */ + + priv->running = true; + + /* Loop as long as we are supposed to be running and as long as we have + * buffers in-flight. + */ + + while (priv->running || priv->inflight > 0) + { + /* Check if we have been asked to terminate. We have to check if we + * still have buffers in-flight. If we do, then we can't stop until + * birds come back to roost. + */ + + if (priv->terminating && priv->inflight <= 0) + { + /* We are IDLE. Break out of the loop and exit. */ + + break; + } + else + { + /* Check if we can process more audio buffers */ + + es8311_processbegin(priv); + } + + /* Wait for messages from our message queue */ + + msglen = file_mq_receive(&priv->mq, (FAR char *)&msg, + sizeof(msg), &prio); + + /* Handle the case when we return with no message */ + + if (msglen < sizeof(struct audio_msg_s)) + { + auderr("Message too small: %d\n", msglen); + continue; + } + + /* Process the message */ + + switch (msg.msg_id) + { + /* The ISR has requested more data. We will catch this case at + * the top of the loop. + */ + + case AUDIO_MSG_DATA_REQUEST: + audinfo("AUDIO_MSG_DATA_REQUEST\n"); + break; + + /* Stop the playback */ + +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + case AUDIO_MSG_STOP: + + /* Indicate that we are terminating */ + + audinfo("AUDIO_MSG_STOP: Terminating\n"); + priv->terminating = true; + break; +#endif /* CONFIG_AUDIO_EXCLUDE_STOP */ + + /* We have a new buffer to process. We will catch this case at + * the top of the loop. + */ + + case AUDIO_MSG_ENQUEUE: + audinfo("AUDIO_MSG_ENQUEUE\n"); + break; + + /* We will wake up from the I2S callback with this message */ + + case AUDIO_MSG_COMPLETE: + audinfo("AUDIO_MSG_COMPLETE\n"); + es8311_returnbuffers(priv); + break; + + default: + auderr("Ignoring message ID %d\n", msg.msg_id); + break; + } + } + + /* Reset the ES8311 hardware */ + + es8311_reset(priv); + + /* Return any pending buffers in our pending queue */ + + nxmutex_lock(&priv->pendlock); + while ((apb = (FAR struct ap_buffer_s *)dq_remfirst(&priv->pendq)) != NULL) + { + /* Release our reference to the buffer */ + + apb_free(apb); + + /* Send the buffer back up to the previous level. */ + +#ifdef CONFIG_AUDIO_MULTI_SESSION + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK, NULL); +#else /* !CONFIG_AUDIO_MULTI_SESSION */ + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK); +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ + } + + nxmutex_unlock(&priv->pendlock); + + /* Return any pending buffers in our done queue */ + + es8311_returnbuffers(priv); + + /* Close the message queue */ + + file_mq_close(&priv->mq); + file_mq_unlink(priv->mqname); + + /* Send an AUDIO_MSG_COMPLETE message to the client */ + +#ifdef CONFIG_AUDIO_MULTI_SESSION + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK, NULL); +#else /* !CONFIG_AUDIO_MULTI_SESSION */ + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK); +#endif /* !CONFIG_AUDIO_MULTI_SESSION */ + + audinfo("ES8311 worker thread finishing\n"); + return NULL; +} + +/**************************************************************************** + * Name: es8311_reset + * + * Description: + * Reset and re-initialize the ES8311. + * + * Input Parameters: + * priv - A reference to the driver state structure. + * + * Returned Value: + * OK on success; a negated errno value on failure. + * + ****************************************************************************/ + +static int es8311_reset(FAR struct es8311_dev_s *priv) +{ + /* Put audio back to its initial configuration */ + + audinfo("ES8311 reset triggered.\n"); + priv->samprate = ES8311_DEFAULT_SAMPRATE; + priv->bpsamp = ES8311_DEFAULT_BPSAMP; + + /* Software reset. This puts all ES8311 registers back in their + * default state. + */ + + uint8_t regconfig; + int ret = 0; + + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG01, 0x30); + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG02, 0x00); + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG03, 0x10); + ret |= es8311_writereg(priv, ES8311_ADC_REG16, 0x24); + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG04, 0x10); + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG05, 0x00); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG0B, 0x00); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG0C, 0x00); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG10, 0x1f); + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG11, 0x7f); + ret |= es8311_writereg(priv, ES8311_RESET_REG00, 0x80); + + regconfig = es8311_readreg(priv, ES8311_RESET_REG00); + regconfig &= 0xbf; + ret |= es8311_writereg(priv, ES8311_RESET_REG00, regconfig); + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG01, 0x3f); + + if (es8311_get_mclk_src() == ES8311_MCLK_FROM_MCLK_PIN) + { + regconfig = es8311_readreg(priv, ES8311_CLK_MANAGER_REG01); + regconfig &= 0x7f; + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG01, regconfig); + } + else + { + regconfig = es8311_readreg(priv, ES8311_CLK_MANAGER_REG01); + regconfig |= 0x80; + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG01, regconfig); + } + + ret |= es8311_setsamplerate(priv); + ret |= es8311_setbitspersample(priv); + + regconfig = es8311_readreg(priv, ES8311_CLK_MANAGER_REG01); + regconfig &= ~(0x40); + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG01, regconfig); + + regconfig = es8311_readreg(priv, ES8311_CLK_MANAGER_REG06); + regconfig &= ~(0x20); + ret |= es8311_writereg(priv, ES8311_CLK_MANAGER_REG06, regconfig); + + ret |= es8311_writereg(priv, ES8311_SYSTEM_REG13, 0x10); + ret |= es8311_writereg(priv, ES8311_ADC_REG1B, 0x0a); + ret |= es8311_writereg(priv, ES8311_ADC_REG1C, 0x6a); + + ret |= es8311_setvolume(priv, ES_MODULE_ADC, + CONFIG_ES8311_INPUT_INITVOLUME); + ret |= es8311_setvolume(priv, ES_MODULE_DAC, + CONFIG_ES8311_OUTPUT_INITVOLUME); + + es8311_dump_registers(&priv->dev, "After reset"); + + if (ret < 0) + { + auderr("Failed to reset the ES8311.\n"); + return -EIO; + } + else + { + audinfo("ES8311 reset complete.\n"); + return OK; + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: es8311_initialize + * + * Description: + * Initialize the ES8311 device. + * + * Input Parameters: + * i2c - An I2C driver instance. + * i2s - An I2S driver instance. + * lower - Persistent board configuration data. + * + * Returned Value: + * A new lower half audio interface for the ES8311 device is returned on + * success; NULL is returned on failure. + * + ****************************************************************************/ + +FAR struct audio_lowerhalf_s * + es8311_initialize(FAR struct i2c_master_s *i2c, + FAR struct i2s_dev_s *i2s, + FAR const struct es8311_lower_s *lower) +{ + FAR struct es8311_dev_s *priv; + + audinfo("Initializing ES8311\n"); + + /* Sanity check */ + + DEBUGASSERT(i2c && i2s && lower); + + /* Allocate a ES8311 device structure */ + + priv = (FAR struct es8311_dev_s *)kmm_zalloc(sizeof(struct es8311_dev_s)); + + if (priv) + { + priv->dev.ops = &g_audioops; + priv->lower = lower; + priv->i2c = i2c; + priv->i2s = i2s; + + nxmutex_init(&priv->pendlock); + dq_init(&priv->pendq); + dq_init(&priv->doneq); + + audinfo("ES8311: address=%02x frequency=%" PRId32 "\n", + lower->address, lower->frequency); + + /* Reset and reconfigure the ES8311 hardware */ + + es8311_dump_registers(&priv->dev, "Before reset"); + + es8311_audio_output(priv); + es8311_reset(priv); + audinfo("ES8311 initialized.\n"); + return &priv->dev; + } + + return NULL; +} diff --git a/drivers/audio/es8311.h b/drivers/audio/es8311.h new file mode 100644 index 0000000000..3a9b0583c9 --- /dev/null +++ b/drivers/audio/es8311.h @@ -0,0 +1,1312 @@ +/**************************************************************************** + * drivers/audio/es8311.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __DRIVERS_AUDIO_ES8311_H +#define __DRIVERS_AUDIO_ES8311_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#include +#include +#include + +#include "esxxxx_common.h" + +#ifdef CONFIG_AUDIO + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Registers Addresses ******************************************************/ + +#define ES8311_RESET_REG00 0x00 /* Reset digital, csm, clock manager etc. */ + +/* Clock Scheme Register definition */ + +#define ES8311_CLK_MANAGER_REG01 0x01 /* Select CLK src for MCLK, enable clock for codec */ +#define ES8311_CLK_MANAGER_REG02 0x02 /* Clk divider and clk multiplier */ +#define ES8311_CLK_MANAGER_REG03 0x03 /* ADC fsmode and osr */ +#define ES8311_CLK_MANAGER_REG04 0x04 /* DAC osr */ +#define ES8311_CLK_MANAGER_REG05 0x05 /* CLK divider for ADC and DAC */ +#define ES8311_CLK_MANAGER_REG06 0x06 /* BCLK inverter and divider */ +#define ES8311_CLK_MANAGER_REG07 0x07 /* Tri-state, lrck divider */ +#define ES8311_CLK_MANAGER_REG08 0x08 /* LRCK divider */ + +/* SDP */ + +#define ES8311_SDPIN_REG09 0x09 /* DAC serial digital port */ +#define ES8311_SDPOUT_REG0A 0x0a /* ADC serial digital port */ + +/* System */ + +#define ES8311_SYSTEM_REG0B 0x0b /* System */ +#define ES8311_SYSTEM_REG0C 0x0c /* System */ +#define ES8311_SYSTEM_REG0D 0x0d /* System, power up/down */ +#define ES8311_SYSTEM_REG0E 0x0e /* System, power up/down */ +#define ES8311_SYSTEM_REG0F 0x0f /* System, low power */ +#define ES8311_SYSTEM_REG10 0x10 /* System */ +#define ES8311_SYSTEM_REG11 0x11 /* System */ +#define ES8311_SYSTEM_REG12 0x12 /* System, Enable DAC */ +#define ES8311_SYSTEM_REG13 0x13 /* System */ +#define ES8311_SYSTEM_REG14 0x14 /* System, select DMIC, select analog pga gain */ + +/* ADC */ + +#define ES8311_ADC_REG15 0x15 /* ADC, adc ramp rate, dmic sense */ +#define ES8311_ADC_REG16 0x16 /* ADC */ +#define ES8311_ADC_REG17 0x17 /* ADC, volume */ +#define ES8311_ADC_REG18 0x18 /* ADC, alc enable and winsize */ +#define ES8311_ADC_REG19 0x19 /* ADC, alc maxlevel */ +#define ES8311_ADC_REG1A 0x1a /* ADC, alc automute */ +#define ES8311_ADC_REG1B 0x1b /* ADC, alc automute, adc hpf s1 */ +#define ES8311_ADC_REG1C 0x1c /* ADC, equalizer, hpf s2 */ + +/* DAC */ + +#define ES8311_DAC_REG31 0x31 /* DAC, mute */ +#define ES8311_DAC_REG32 0x32 /* DAC, volume */ +#define ES8311_DAC_REG33 0x33 /* DAC, offset */ +#define ES8311_DAC_REG34 0x34 /* DAC, drc enable, drc winsize */ +#define ES8311_DAC_REG35 0x35 /* DAC, drc maxlevel, minilevel */ +#define ES8311_DAC_REG37 0x37 /* DAC, ramprate */ + +/* GPIO */ + +#define ES8311_GPIO_REG44 0x44 /* GPIO, dac2adc for test */ +#define ES8311_GP_REG45 0x45 /* GP CONTROL */ + +/* CHIP */ + +#define ES8311_CHD1_REGFD 0xfd /* CHIP ID1 */ +#define ES8311_CHD2_REGFE 0xfe /* CHIP ID2 */ +#define ES8311_CHVER_REGFF 0xff /* VERSION */ + +#define ES8311_MAX_REGISTER 0xff + +/* Codec Default Parameters *************************************************/ + +#define ES8311_DEFAULT_SAMPRATE 48000 +#define ES8311_DEFAULT_BPSAMP 16 + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct es8311_dev_s +{ + /* We are an audio lower half driver (We are also the upper "half" of + * the ES8311 driver with respect to the board lower half driver). + * + * Terminology: + * Our "lower" half audio instances will be called dev for the publicly + * visible version and "priv" for the version that only this driver knows + * From the point of view of this driver, it is the board lower "half" + * that is referred to as "lower". + */ + + struct audio_lowerhalf_s dev; /* ES8311 audio lower half (this device) */ + + FAR const struct es8311_lower_s *lower; /* Pointer to the board lower functions */ + FAR struct i2c_master_s *i2c; /* I2C driver to use */ + FAR struct i2s_dev_s *i2s; /* I2S driver to use */ + struct dq_queue_s pendq; /* Queue of pending buffers to be processed */ + struct dq_queue_s doneq; /* Queue of sent buffers to be returned */ + struct file mq; /* Message queue for receiving messages */ + char mqname[NAME_MAX]; /* Our message queue name */ + pthread_t threadid; /* ID of our thread */ + mutex_t pendlock; /* Protect pendq */ + uint32_t samprate; /* Configured samprate (samples/sec) */ +#ifndef CONFIG_AUDIO_EXCLUDE_VOLUME + uint16_t volume_out; /* Current output volume level {0..1000} */ + uint16_t volume_in; /* Current input volume level {0..1000} */ +#endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */ + uint8_t bpsamp; /* Bits per sample */ + volatile uint8_t inflight; /* Number of audio buffers in-flight */ + bool running; /* True: Worker thread is running */ + bool paused; /* True: Playing is paused */ + bool mute; /* True: Output is muted */ +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + bool terminating; /* True: Stop requested */ +#endif + bool reserved; /* True: Device is reserved */ + volatile int result; /* The result of the last transfer */ + es_module_e audio_mode; /* The current audio mode of the ES8311 chip */ + es_mic_gain_e mic_gain; /* The current microphone gain */ + uint32_t mclk; /* The current MCLK frequency */ +}; + +/* Clock coefficient struct */ + +struct es8311_coeff_div_s +{ + uint32_t mclk; /* MCLK frequency */ + uint32_t rate; /* Sample rate */ + uint8_t pre_div; /* The pre divider with range from 1 to 8 */ + uint8_t pre_multi; /* The pre multiplier with x1, x2, x4 and x8 selection */ + uint8_t adc_div; /* ADCCLK divider */ + uint8_t dac_div; /* DACCLK divider */ + uint8_t fs_mode; /* Double speed or single speed */ + uint8_t lrck_h; /* ADCLRCK divider and DACLRCK divider */ + uint8_t lrck_l; + uint8_t bclk_div; /* SCLK divider */ + uint8_t adc_osr; /* ADC osr */ + uint8_t dac_osr; /* DAC osr */ +}; + +typedef enum +{ + ES8311_MIC_GAIN_0DB, + ES8311_MIC_GAIN_6DB, + ES8311_MIC_GAIN_12DB, + ES8311_MIC_GAIN_18DB, + ES8311_MIC_GAIN_24DB, + ES8311_MIC_GAIN_30DB, + ES8311_MIC_GAIN_36DB, + ES8311_MIC_GAIN_42DB +} es8311_mic_gain_e; + +typedef enum +{ + ES8311_MCLK_FROM_MCLK_PIN, + ES8311_MCLK_FROM_SCLK_PIN +} es8311_mclk_src_e; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +/* Codec hifi MCLK clock divider coefficients */ + +static const struct es8311_coeff_div_s es8311_coeff_div[] = +{ + /* 8k */ + + { + 12288000, + 8000, + 0x06, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 18432000, + 8000, + 0x03, + 0x02, + 0x03, + 0x03, + 0x00, + 0x05, + 0xff, + 0x18, + 0x10, + 0x20 + }, + { + 16384000, + 8000, + 0x08, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 8192000, + 8000, + 0x04, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 6144000, + 8000, + 0x03, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 4096000, + 8000, + 0x02, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 3072000, + 8000, + 0x01, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 2048000, + 8000, + 0x01, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 1536000, + 8000, + 0x03, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 1024000, + 8000, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + + /* 11.025k */ + + { + 11289600, + 11025, + 0x04, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 5644800, + 11025, + 0x02, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 2822400, + 11025, + 0x01, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 1411200, + 11025, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + + /* 12k */ + + { + 12288000, + 12000, + 0x04, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 6144000, + 12000, + 0x02, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 3072000, + 12000, + 0x01, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 1536000, + 12000, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + + /* 16k */ + + { + 12288000, + 16000, + 0x03, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 18432000, + 16000, + 0x03, + 0x02, + 0x03, + 0x03, + 0x00, + 0x02, + 0xff, + 0x0c, + 0x10, + 0x20 + }, + { + 16384000, + 16000, + 0x04, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 8192000, + 16000, + 0x02, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 6144000, + 16000, + 0x03, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 4096000, + 16000, + 0x01, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 3072000, + 16000, + 0x03, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 2048000, + 16000, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 1536000, + 16000, + 0x03, + 0x08, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + { + 1024000, + 16000, + 0x01, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x20 + }, + + /* 22.05k */ + + { + 11289600, + 22050, + 0x02, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 5644800, + 22050, + 0x01, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 2822400, + 22050, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 1411200, + 22050, + 0x01, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + + /* 24k */ + + { + 12288000, + 24000, + 0x02, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 18432000, + 24000, + 0x03, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 6144000, + 24000, + 0x01, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 3072000, + 24000, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 1536000, + 24000, + 0x01, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + + /* 32k */ + + { + 12288000, + 32000, + 0x03, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 18432000, + 32000, + 0x03, + 0x04, + 0x03, + 0x03, + 0x00, + 0x02, + 0xff, + 0x0c, + 0x10, + 0x10 + }, + { + 16384000, + 32000, + 0x02, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 8192000, + 32000, + 0x01, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 6144000, + 32000, + 0x03, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 4096000, + 32000, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 3072000, + 32000, + 0x03, + 0x08, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 2048000, + 32000, + 0x01, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 1536000, + 32000, + 0x03, + 0x08, + 0x01, + 0x01, + 0x01, + 0x00, + 0x7f, + 0x02, + 0x10, + 0x10 + }, + { + 1024000, + 32000, + 0x01, + 0x08, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + + /* 44.1k */ + + { + 11289600, + 44100, + 0x01, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 5644800, + 44100, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 2822400, + 44100, + 0x01, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 1411200, + 44100, + 0x01, + 0x08, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + + /* 48k */ + + { + 12288000, + 48000, + 0x01, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 18432000, + 48000, + 0x03, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 6144000, + 48000, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 3072000, + 48000, + 0x01, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 1536000, + 48000, + 0x01, + 0x08, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + + /* 64k */ + + { + 12288000, + 64000, + 0x03, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 18432000, + 64000, + 0x03, + 0x04, + 0x03, + 0x03, + 0x01, + 0x01, + 0x7f, + 0x06, + 0x10, + 0x10 + }, + { + 16384000, + 64000, + 0x01, + 0x01, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 8192000, + 64000, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 6144000, + 64000, + 0x01, + 0x04, + 0x03, + 0x03, + 0x01, + 0x01, + 0x7f, + 0x06, + 0x10, + 0x10 + }, + { + 4096000, + 64000, + 0x01, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 3072000, + 64000, + 0x01, + 0x08, + 0x03, + 0x03, + 0x01, + 0x01, + 0x7f, + 0x06, + 0x10, + 0x10 + }, + { + 2048000, + 64000, + 0x01, + 0x08, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 1536000, + 64000, + 0x01, + 0x08, + 0x01, + 0x01, + 0x01, + 0x00, + 0xbf, + 0x03, + 0x18, + 0x18 + }, + { + 1024000, + 64000, + 0x01, + 0x08, + 0x01, + 0x01, + 0x01, + 0x00, + 0x7f, + 0x02, + 0x10, + 0x10 + }, + + /* 88.2k */ + + { + 11289600, + 88200, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 5644800, + 88200, + 0x01, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 2822400, + 88200, + 0x01, + 0x08, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 1411200, + 88200, + 0x01, + 0x08, + 0x01, + 0x01, + 0x01, + 0x00, + 0x7f, + 0x02, + 0x10, + 0x10 + }, + + /* 96k */ + + { + 12288000, + 96000, + 0x01, + 0x02, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 18432000, + 96000, + 0x03, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 6144000, + 96000, + 0x01, + 0x04, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 3072000, + 96000, + 0x01, + 0x08, + 0x01, + 0x01, + 0x00, + 0x00, + 0xff, + 0x04, + 0x10, + 0x10 + }, + { + 1536000, + 96000, + 0x01, + 0x08, + 0x01, + 0x01, + 0x01, + 0x00, + 0x7f, + 0x02, + 0x10, + 0x10 + } +}; + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: es8311_readreg + * + * Description: + * Read the specified 8-bit register from the ES8311 device. + * + ****************************************************************************/ + +#if defined(CONFIG_ES8311_REGDUMP) +struct es8311_dev_s; +uint8_t es8311_readreg(FAR struct es8311_dev_s *priv, uint8_t regaddr); +#endif + +#endif /* CONFIG_AUDIO */ +#endif /* __DRIVERS_AUDIO_ES8311_H */ diff --git a/drivers/audio/es8311_debug.c b/drivers/audio/es8311_debug.c new file mode 100644 index 0000000000..03a8bb207c --- /dev/null +++ b/drivers/audio/es8311_debug.c @@ -0,0 +1,144 @@ +/**************************************************************************** + * drivers/audio/es8311_debug.c + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + + /* The framework for this driver is based on ESP-ADF's ES8311 driver. */ + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include + +#include +#include + +#include +#include + +#include "es8311.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Private Types + ****************************************************************************/ + +#ifdef CONFIG_ES8311_REGDUMP +struct es8311_regdump_s +{ + FAR const char *regname; + uint8_t regaddr; +}; +#endif + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +#ifdef CONFIG_ES8311_REGDUMP +static const struct es8311_regdump_s g_es8311_debug[] = +{ + {"ES8311_RESET_REG00", ES8311_RESET_REG00 }, + {"ES8311_CLK_MANAGER_REG01", ES8311_CLK_MANAGER_REG01 }, + {"ES8311_CLK_MANAGER_REG02", ES8311_CLK_MANAGER_REG02 }, + {"ES8311_CLK_MANAGER_REG03", ES8311_CLK_MANAGER_REG03 }, + {"ES8311_CLK_MANAGER_REG04", ES8311_CLK_MANAGER_REG04 }, + {"ES8311_CLK_MANAGER_REG05", ES8311_CLK_MANAGER_REG05 }, + {"ES8311_CLK_MANAGER_REG06", ES8311_CLK_MANAGER_REG06 }, + {"ES8311_CLK_MANAGER_REG07", ES8311_CLK_MANAGER_REG07 }, + {"ES8311_CLK_MANAGER_REG08", ES8311_CLK_MANAGER_REG08 }, + {"ES8311_SDPIN_REG09", ES8311_SDPIN_REG09 }, + {"ES8311_SDPOUT_REG0A", ES8311_SDPOUT_REG0A }, + {"ES8311_SYSTEM_REG0B", ES8311_SYSTEM_REG0B }, + {"ES8311_SYSTEM_REG0B", ES8311_SYSTEM_REG0B }, + {"ES8311_SYSTEM_REG0C", ES8311_SYSTEM_REG0C }, + {"ES8311_SYSTEM_REG0D", ES8311_SYSTEM_REG0D }, + {"ES8311_SYSTEM_REG0E", ES8311_SYSTEM_REG0E }, + {"ES8311_SYSTEM_REG0F", ES8311_SYSTEM_REG0F }, + {"ES8311_SYSTEM_REG10", ES8311_SYSTEM_REG10 }, + {"ES8311_SYSTEM_REG11", ES8311_SYSTEM_REG11 }, + {"ES8311_SYSTEM_REG12", ES8311_SYSTEM_REG12 }, + {"ES8311_SYSTEM_REG13", ES8311_SYSTEM_REG13 }, + {"ES8311_SYSTEM_REG14", ES8311_SYSTEM_REG14 }, + {"ES8311_ADC_REG15", ES8311_ADC_REG15 }, + {"ES8311_ADC_REG16", ES8311_ADC_REG16 }, + {"ES8311_ADC_REG17", ES8311_ADC_REG17 }, + {"ES8311_ADC_REG18", ES8311_ADC_REG18 }, + {"ES8311_ADC_REG19", ES8311_ADC_REG19 }, + {"ES8311_ADC_REG1A", ES8311_ADC_REG1A }, + {"ES8311_ADC_REG1B", ES8311_ADC_REG1B }, + {"ES8311_ADC_REG1C", ES8311_ADC_REG1C }, + {"ES8311_DAC_REG31", ES8311_DAC_REG31 }, + {"ES8311_DAC_REG32", ES8311_DAC_REG32 }, + {"ES8311_DAC_REG33", ES8311_DAC_REG33 }, + {"ES8311_DAC_REG34", ES8311_DAC_REG34 }, + {"ES8311_DAC_REG35", ES8311_DAC_REG35 }, + {"ES8311_DAC_REG37", ES8311_DAC_REG37 }, + {"ES8311_GPIO_REG44", ES8311_GPIO_REG44 }, + {"ES8311_GP_REG45", ES8311_GP_REG45 }, + {"ES8311_CHD1_REGFD", ES8311_CHD1_REGFD }, + {"ES8311_CHD2_REGFE", ES8311_CHD2_REGFE }, + {"ES8311_CHVER_REGFF", ES8311_CHVER_REGFF } +}; + +# define ES8311_NREGISTERS (sizeof(g_es8311_debug) / sizeof(struct es8311_regdump_s)) +#endif /* CONFIG_ES8311_REGDUMP */ + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: es8311_dump_registers + * + * Description: + * Dump the contents of all ES8311 registers to the syslog device + * + * Input Parameters: + * dev - The device instance returned by es8311_initialize + * msg - Message to appear before registers + * + * Returned Value: + * None. + * + ****************************************************************************/ + +#ifdef CONFIG_ES8311_REGDUMP +void es8311_dump_registers(FAR struct audio_lowerhalf_s *dev, + FAR const char *msg) +{ + int i; + + syslog(LOG_INFO, "ES8311 Registers: %s\n", msg); + for (i = 0; i < ES8311_NREGISTERS; i++) + { + syslog(LOG_INFO, " %s[%02x]: %02x\n", + g_es8311_debug[i].regname, g_es8311_debug[i].regaddr, + es8311_readreg((FAR struct es8311_dev_s *)dev, + g_es8311_debug[i].regaddr)); + } +} +#endif /* CONFIG_ES8311_REGDUMP */ diff --git a/include/nuttx/audio/es8311.h b/include/nuttx/audio/es8311.h new file mode 100644 index 0000000000..0fce6d0439 --- /dev/null +++ b/include/nuttx/audio/es8311.h @@ -0,0 +1,191 @@ +/**************************************************************************** + * include/nuttx/audio/es8311.h + * + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. The + * ASF licenses this file to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance with the + * License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + * + ****************************************************************************/ + +#ifndef __INCLUDE_NUTTX_AUDIO_ES8311_H +#define __INCLUDE_NUTTX_AUDIO_ES8311_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include +#include + +#include + +#ifdef CONFIG_AUDIO_ES8311 + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* Configuration ************************************************************ + * + * CONFIG_AUDIO_ES8311 - Enables ES8311 support + * CONFIG_ES8311_INITVOLUME - The initial volume level + * in the range {0..1000} + * CONFIG_ES8311_INFLIGHT - Maximum number of buffers that the ES8311 + * driver will send to the I2S driver before any have completed. + * CONFIG_ES8311_MSG_PRIO - Priority of messages sent to the ES8311 + * worker thread. + * CONFIG_ES8311_BUFFER_SIZE - Preferred buffer size + * CONFIG_ES8311_NUM_BUFFERS - Preferred number of buffers + * CONFIG_ES8311_WORKER_STACKSIZE - Stack size to use when creating the the + * ES8311 worker thread. + * CONFIG_ES8311_REGDUMP - Enable logic to dump all ES8311 registers to + * the SYSLOG device. + */ + +/* Pre-requisites */ + +#ifndef CONFIG_AUDIO +# error CONFIG_AUDIO is required for audio subsystem support +#endif + +#ifndef CONFIG_I2S +# error CONFIG_I2S is required by the ES8311 driver +#endif + +#ifndef CONFIG_I2C +# error CONFIG_I2C is required by the ES8311 driver +#endif + +#ifndef CONFIG_SCHED_WORKQUEUE +# error CONFIG_SCHED_WORKQUEUE is required by the ES8311 driver +#endif + +/* Default configuration values */ + +#ifndef CONFIG_ES8311_INPUT_INITVOLUME +# define CONFIG_ES8311_INPUT_INITVOLUME 1000 +#endif + +#ifndef CONFIG_ES8311_OUTPUT_INITVOLUME +# define CONFIG_ES8311_OUTPUT_INITVOLUME 400 +#endif + +#ifndef CONFIG_ES8311_INFLIGHT +# define CONFIG_ES8311_INFLIGHT 2 +#endif + +#if CONFIG_ES8311_INFLIGHT > 255 +# error CONFIG_ES8311_INFLIGHT must fit in a uint8_t +#endif + +#ifndef CONFIG_ES8311_MSG_PRIO +# define CONFIG_ES8311_MSG_PRIO 1 +#endif + +#ifndef CONFIG_ES8311_BUFFER_SIZE +# define CONFIG_ES8311_BUFFER_SIZE 4096 +#endif + +#ifndef CONFIG_ES8311_NUM_BUFFERS +# define CONFIG_ES8311_NUM_BUFFERS 4 +#endif + +#ifndef CONFIG_ES8311_WORKER_STACKSIZE +# define CONFIG_ES8311_WORKER_STACKSIZE 2048 +#endif + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct es8311_lower_s; + +struct es8311_lower_s +{ + /* I2C characterization */ + + uint32_t frequency; /* Initial I2C frequency */ + uint8_t address; /* 7-bit I2C address (only bits 0-6 used) */ +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: es8311_initialize + * + * Description: + * Initialize the ES8311 device. + * + * Input Parameters: + * i2c - An I2C driver instance; + * i2s - An I2S driver instance; + * lower - Persistent board configuration data. + * + * Returned Value: + * A new lower half audio interface for the ES8311 device is returned on + * success; NULL is returned on failure. + * + ****************************************************************************/ + +struct i2c_master_s; +struct i2s_dev_s; +struct audio_lowerhalf_s; + +FAR struct audio_lowerhalf_s * + es8311_initialize(FAR struct i2c_master_s *i2c, + FAR struct i2s_dev_s *i2s, + FAR const struct es8311_lower_s *lower); + +/**************************************************************************** + * Name: es8311_dump_registers + * + * Description: + * Dump the contents of all ES8311 registers to the syslog device + * + * Input Parameters: + * dev - The device instance returned by es8311_initialize + * + * Returned Value: + * None. + * + ****************************************************************************/ + +#ifdef CONFIG_ES8311_REGDUMP +void es8311_dump_registers(FAR struct audio_lowerhalf_s *dev, + FAR const char *msg); +#else +# define es8311_dump_registers(d,m) +#endif + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* CONFIG_AUDIO_ES8311 */ +#endif /* __INCLUDE_NUTTX_AUDIO_ES8311_H */