This is an automated email from the ASF dual-hosted git repository. xiaoxiang pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/nuttx.git
commit 5ba0d062b11510639d865e6f58039f8cd32fdc53 Author: wangjianyu3 <[email protected]> AuthorDate: Mon Mar 23 10:21:33 2026 +0800 audio: add ES7210 4-ch ADC codec driver Add ES7210 audio ADC driver for NuttX implementing the audio lower-half interface. Supports 4-channel recording via I2S with configurable sample rate, bit depth, and mic gain. - drivers/audio/es7210.c: Codec driver using LPWORK for async buffer management, I2C register access, and NuttX audio buffer management - drivers/audio/es7210.h: Internal register definitions and macros - include/nuttx/audio/es7210.h: Public header with platform config structure and es7210_initialize() API - drivers/audio/Kconfig, Make.defs, CMakeLists.txt: Build system integration for CONFIG_AUDIO_ES7210 Key implementation details: - ES7210_SDP_NORMAL (normal I2S) instead of TDM, matching NuttX I2S standard Philips mode - ES7210_ADC_PGA_POWER_ON bit in gain registers REG43-46, required for analog front-end amplifier power-on - 50ms startup delay in es7210_start for codec clock stabilization - I2S_IOCTL(AUDIOIOC_STOP) in es7210_stop to notify I2S layer, preventing DMA from running without buffers after stop Signed-off-by: wangjianyu3 <[email protected]> --- drivers/audio/CMakeLists.txt | 4 + drivers/audio/Kconfig | 27 ++ drivers/audio/Make.defs | 4 + drivers/audio/es7210.c | 987 +++++++++++++++++++++++++++++++++++++++++++ drivers/audio/es7210.h | 216 ++++++++++ include/nuttx/audio/es7210.h | 95 +++++ 6 files changed, 1333 insertions(+) diff --git a/drivers/audio/CMakeLists.txt b/drivers/audio/CMakeLists.txt index fe720bfbf60..79371fead7c 100644 --- a/drivers/audio/CMakeLists.txt +++ b/drivers/audio/CMakeLists.txt @@ -43,6 +43,10 @@ if(CONFIG_DRIVERS_AUDIO) list(APPEND SRCS cs4344.c) endif() + if(CONFIG_AUDIO_ES7210) + list(APPEND SRCS es7210.c) + endif() + if(CONFIG_AUDIO_ES8311) list(APPEND SRCS es8311.c) if(CONFIG_ES8311_REGDUMP) diff --git a/drivers/audio/Kconfig b/drivers/audio/Kconfig index 10474cee5f9..84713a295bf 100644 --- a/drivers/audio/Kconfig +++ b/drivers/audio/Kconfig @@ -169,6 +169,33 @@ config CS4344_WORKER_STACKSIZE endif # AUDIO_CS4344 +config AUDIO_ES7210 + bool "ES7210 ADC codec chip" + default n + depends on AUDIO + ---help--- + Select to enable support for the ES7210 4-channel ADC codec by + Everest Semiconductor. + + NOTE: This driver also depends on both I2C and I2S support although + that dependency is not explicit here. + +if AUDIO_ES7210 + +config ES7210_INFLIGHT + int "ES7210 maximum in-flight audio buffers" + default 2 + +config ES7210_BUFFER_SIZE + int "ES7210 preferred buffer size" + default 8192 + +config ES7210_NUM_BUFFERS + int "ES7210 preferred number of buffers" + default 4 + +endif # AUDIO_ES7210 + config AUDIO_ES8311 bool "ES8311 codec chip" default n diff --git a/drivers/audio/Make.defs b/drivers/audio/Make.defs index c7a23928dde..5594b1d7262 100644 --- a/drivers/audio/Make.defs +++ b/drivers/audio/Make.defs @@ -43,6 +43,10 @@ ifeq ($(CONFIG_AUDIO_CS4344),y) CSRCS += cs4344.c endif +ifeq ($(CONFIG_AUDIO_ES7210),y) +CSRCS += es7210.c +endif + ifeq ($(CONFIG_AUDIO_ES8311),y) CSRCS += es8311.c ifeq ($(CONFIG_ES8311_REGDUMP),y) diff --git a/drivers/audio/es7210.c b/drivers/audio/es7210.c new file mode 100644 index 00000000000..10591f85038 --- /dev/null +++ b/drivers/audio/es7210.c @@ -0,0 +1,987 @@ +/**************************************************************************** + * drivers/audio/es7210.c + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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 <nuttx/config.h> + +#include <sys/types.h> +#include <sys/ioctl.h> + +#include <stdio.h> +#include <fcntl.h> +#include <stdint.h> +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> +#include <math.h> +#include <time.h> +#include <assert.h> +#include <errno.h> +#include <debug.h> +#include <unistd.h> + +#include <nuttx/kmalloc.h> +#include <nuttx/mutex.h> +#include <nuttx/nuttx.h> +#include <nuttx/signal.h> +#include <nuttx/wqueue.h> +#include <nuttx/audio/audio.h> +#include <nuttx/audio/i2s.h> +#include <nuttx/arch.h> +#include <nuttx/i2c/i2c_master.h> + +#include "es7210.h" + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* MCLK/LRCK ratio. ES7210 datasheet Table "Erta Coefficient Table" + * lists supported ratios: 256, 384, 512, 768, 1024, 1536. + * 256xFS is the most common setting (e.g. 48kHz → MCLK=12.288MHz). + * Also used by ESP-IDF es7210 component (es7210.c MCLK_DIV_FRE). + */ + +#define ES7210_MCLK_MULTIPLE 256 + +/**************************************************************************** + * Private Function Prototypes + ****************************************************************************/ + +static int es7210_getcaps(FAR struct audio_lowerhalf_s *dev, int type, + FAR struct audio_caps_s *caps); +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_configure(FAR struct audio_lowerhalf_s *dev, + FAR void *session, + FAR const struct audio_caps_s *caps); +#else +static int es7210_configure(FAR struct audio_lowerhalf_s *dev, + FAR const struct audio_caps_s *caps); +#endif +static int es7210_shutdown(FAR struct audio_lowerhalf_s *dev); + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_start(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +#else +static int es7210_start(FAR struct audio_lowerhalf_s *dev); +#endif + +#ifndef CONFIG_AUDIO_EXCLUDE_STOP +# ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_stop(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +# else +static int es7210_stop(FAR struct audio_lowerhalf_s *dev); +# endif +#endif + +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME +# ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_pause(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +static int es7210_resume(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +# else +static int es7210_pause(FAR struct audio_lowerhalf_s *dev); +static int es7210_resume(FAR struct audio_lowerhalf_s *dev); +# endif +#endif + +static int es7210_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static int es7210_cancelbuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb); +static int es7210_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, + unsigned long arg); + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_reserve(FAR struct audio_lowerhalf_s *dev, + FAR void **session); +static int es7210_release(FAR struct audio_lowerhalf_s *dev, + FAR void *session); +#else +static int es7210_reserve(FAR struct audio_lowerhalf_s *dev); +static int es7210_release(FAR struct audio_lowerhalf_s *dev); +#endif + +static void es7210_processbegin(FAR struct es7210_dev_s *priv); +static void es7210_processdone(FAR struct i2s_dev_s *i2s, + FAR struct ap_buffer_s *apb, + FAR void *arg, int result); +static void es7210_worker(FAR void *arg); +static void es7210_returnbuffers(FAR struct es7210_dev_s *priv); + +/**************************************************************************** + * Private Data + ****************************************************************************/ + +static const struct audio_ops_s g_audioops = +{ + .getcaps = es7210_getcaps, + .configure = es7210_configure, + .shutdown = es7210_shutdown, + .start = es7210_start, +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + .stop = es7210_stop, +#endif +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME + .pause = es7210_pause, + .resume = es7210_resume, +#endif + .enqueuebuffer = es7210_enqueuebuffer, + .cancelbuffer = es7210_cancelbuffer, + .ioctl = es7210_ioctl, + .reserve = es7210_reserve, + .release = es7210_release, +}; + +/**************************************************************************** + * Private Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: es7210_writereg + ****************************************************************************/ + +static int es7210_writereg(FAR struct es7210_dev_s *priv, uint8_t regaddr, + uint8_t regval) +{ + struct i2c_msg_s msg; + uint8_t txbuffer[2]; + int ret; + + txbuffer[0] = regaddr; + txbuffer[1] = regval; + + msg.frequency = priv->lower.frequency; + msg.addr = priv->lower.address; + msg.flags = 0; + msg.buffer = txbuffer; + msg.length = 2; + + ret = I2C_TRANSFER(priv->i2c, &msg, 1); + if (ret < 0) + { + auderr("ERROR: I2C_TRANSFER reg=0x%02x failed: %d\n", regaddr, ret); + } + + return ret; +} + +/**************************************************************************** + * Name: es7210_setmicgain + ****************************************************************************/ + +static int es7210_setmicgain(FAR struct es7210_dev_s *priv, uint8_t gain) +{ + /* ES7210_ADC_PGA_POWER_ON (bit 4) must be set or the analog + * front-end amplifier stays off and the ADC reads near-zero. + * Reference: ESP-IDF driver writes (gain | 0x10) for all channels. + */ + + es7210_writereg(priv, ES7210_ADC1_GAIN_REG43, + gain | ES7210_ADC_PGA_POWER_ON); + es7210_writereg(priv, ES7210_ADC2_GAIN_REG44, + gain | ES7210_ADC_PGA_POWER_ON); + es7210_writereg(priv, ES7210_ADC3_GAIN_REG45, + gain | ES7210_ADC_PGA_POWER_ON); + es7210_writereg(priv, ES7210_ADC4_GAIN_REG46, + gain | ES7210_ADC_PGA_POWER_ON); + return OK; +} + +/**************************************************************************** + * Name: es7210_reset + ****************************************************************************/ + +static int es7210_reset(FAR struct es7210_dev_s *priv) +{ + uint16_t lrck_div = ES7210_MCLK_MULTIPLE; + + audinfo("ES7210 reset\n"); + + /* Software reset */ + + es7210_writereg(priv, ES7210_RESET_REG00, ES7210_RESET_CMD); + up_mdelay(10); /* 10ms for ES7210 to complete reset */ + es7210_writereg(priv, ES7210_RESET_REG00, ES7210_RESET_NORMAL); + + /* Set the initialization time when device powers up */ + + es7210_writereg(priv, ES7210_TCT0_CHPINI_REG09, 0x30); + es7210_writereg(priv, ES7210_TCT1_CHPINI_REG0A, 0x30); + + /* Configure HPF for ADC1-4 */ + + es7210_writereg(priv, ES7210_ADC4_HPF_REG23, 0x2a); + es7210_writereg(priv, ES7210_ADC3_HPF_REG22, 0x0a); + es7210_writereg(priv, ES7210_ADC2_HPF_REG21, 0x2a); + es7210_writereg(priv, ES7210_ADC1_HPF_REG20, 0x0a); + + /* Set SDP: I2S format, 16-bit, normal I2S (no TDM). + * REG11=0x60: I2S format, 16-bit word length + * REG12=0x00: Normal I2S mode (NOT TDM). + * NuttX I2S driver uses standard Philips mode, so ES7210 must + * also be in normal I2S mode. The ESP-IDF reference example uses + * TDM mode with i2s_channel_init_tdm_mode(), but NuttX doesn't + * support TDM — so we must use normal I2S here. + */ + + es7210_writereg(priv, ES7210_SDP_CFG1_REG11, ES7210_SDP_I2S_16BIT); + es7210_writereg(priv, ES7210_SDP_CFG2_REG12, ES7210_SDP_NORMAL); + + /* Configure analog power: VMID voltage selection */ + + es7210_writereg(priv, ES7210_ANALOG_SYS_REG40, ES7210_VMID_SELECT); + + /* Set MIC bias to 2.87V */ + + es7210_writereg(priv, ES7210_MICBIAS12_REG41, ES7210_MICBIAS_2V87); + es7210_writereg(priv, ES7210_MICBIAS34_REG42, ES7210_MICBIAS_2V87); + + /* Set MIC gain (30dB) */ + + es7210_setmicgain(priv, ES7210_MIC_GAIN_30DB); + + /* Power on individual MIC1-4 */ + + es7210_writereg(priv, ES7210_ADC12_MUTE_REG47, ES7210_MIC_POWER_ON); + es7210_writereg(priv, ES7210_ADC34_MUTE_REG48, ES7210_MIC_POWER_ON); + es7210_writereg(priv, ES7210_MIC3_CTL_REG49, ES7210_MIC_POWER_ON); + es7210_writereg(priv, ES7210_MIC4_CTL_REG4A, ES7210_MIC_POWER_ON); + + /* Set sample rate: LRCK divider = MCLK / FS = MCLK_MULTIPLE. + * With MCLK = ES7210_MCLK_MULTIPLE * FS, the divider is constant + * regardless of the actual sample rate, so compute it from the + * defined multiple rather than hard-coding 48 kHz values. + */ + + es7210_writereg(priv, ES7210_ADC_OSR_REG07, 0x20); + es7210_writereg(priv, ES7210_MCLK_CTL_REG02, ES7210_MCLK_ADC_DIV1_DLL); + es7210_writereg(priv, ES7210_MST_LRCDIVH_REG04, + (uint8_t)((lrck_div >> 8) & 0xff)); + es7210_writereg(priv, ES7210_MST_LRCDIVL_REG05, + (uint8_t)(lrck_div & 0xff)); + + /* Power down DLL */ + + es7210_writereg(priv, ES7210_DIGITAL_PDN_REG06, ES7210_DLL_POWER_DOWN); + + /* Power on MIC1-4 bias & ADC1-4 & PGA1-4 */ + + es7210_writereg(priv, ES7210_MIC12_POWER_REG4B, ES7210_MIC_ADC_PGA_ON); + es7210_writereg(priv, ES7210_MIC34_POWER_REG4C, ES7210_MIC_ADC_PGA_ON); + + /* Enable device */ + + es7210_writereg(priv, ES7210_RESET_REG00, ES7210_CLK_OFF); + es7210_writereg(priv, ES7210_RESET_REG00, ES7210_DEVICE_ON); + + audinfo("ES7210 reset done\n"); + return OK; +} + +/**************************************************************************** + * Name: es7210_getcaps + ****************************************************************************/ + +static int es7210_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("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) + { + case AUDIO_TYPE_QUERY: + caps->ac_channels = 4; + + switch (caps->ac_subtype) + { + case AUDIO_TYPE_QUERY: + caps->ac_controls.b[0] = AUDIO_TYPE_INPUT; + caps->ac_format.hw = (1 << (AUDIO_FMT_PCM - 1)); + break; + + default: + caps->ac_controls.b[0] = AUDIO_SUBFMT_END; + break; + } + break; + + case AUDIO_TYPE_INPUT: + caps->ac_channels = 4; + + switch (caps->ac_subtype) + { + case AUDIO_TYPE_QUERY: + + /* Report supported sample rates */ + + caps->ac_controls.hw[0] = AUDIO_SAMP_RATE_8K | + AUDIO_SAMP_RATE_16K | + AUDIO_SAMP_RATE_32K | + AUDIO_SAMP_RATE_48K; + break; + + default: + break; + } + break; + + case AUDIO_TYPE_PROCESSING: + break; + + default: + break; + } + + return caps->ac_len; +} + +/**************************************************************************** + * Name: es7210_configure + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_configure(FAR struct audio_lowerhalf_s *dev, + FAR void *session, + FAR const struct audio_caps_s *caps) +#else +static int es7210_configure(FAR struct audio_lowerhalf_s *dev, + FAR const struct audio_caps_s *caps) +#endif +{ + FAR struct es7210_dev_s *priv = (FAR struct es7210_dev_s *)dev; + int ret = OK; + + DEBUGASSERT(priv != NULL && caps != NULL); + audinfo("ac_type: %d\n", caps->ac_type); + + switch (caps->ac_type) + { + 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]); + + /* Save current stream configuration */ + + if (caps->ac_channels > 0 && caps->ac_channels <= 4) + { + priv->nchannels = caps->ac_channels; + } + + if (caps->ac_controls.hw[0] > 0) + { + priv->samprate = caps->ac_controls.hw[0]; + } + + /* Save current stream configuration - actual I2S setup is + * deferred to es7210_start() to avoid starting the I2S RX + * channel without DMA buffers (which causes interrupt storm). + */ + + if (caps->ac_controls.b[2] == 16 || caps->ac_controls.b[2] == 32) + { + priv->bpsamp = caps->ac_controls.b[2]; + } + } + break; + + case AUDIO_TYPE_PROCESSING: + break; + + default: + break; + } + + return ret; +} + +/**************************************************************************** + * Name: es7210_shutdown + ****************************************************************************/ + +static int es7210_shutdown(FAR struct audio_lowerhalf_s *dev) +{ + audinfo("ES7210 shutdown\n"); + + /* Do nothing here. The nxrecorder "device" command triggers + * open→shutdown→close just to probe capabilities. Any I2C + * writes here could leave the chip in a state that causes + * problems during the subsequent es7210_reset() in start(). + * The full reset in es7210_start() handles all initialization. + */ + + return OK; +} + +/**************************************************************************** + * Name: es7210_processbegin + ****************************************************************************/ + +static void es7210_processbegin(FAR struct es7210_dev_s *priv) +{ + FAR struct ap_buffer_s *apb; + + /* Do not start a new transfer if we are no longer running. + * es7210_start may call us after es7210_stop has already run + * (race with the 50 ms codec stabilization sleep). + */ + + if (!priv->running) + { + priv->inflight = false; + return; + } + + /* Submit ALL pending buffers to the I2S layer at once. The I2S + * driver queues them internally: the first buffer starts DMA, + * subsequent buffers are placed on the pend queue and picked up + * automatically when the previous transfer completes (in + * i2s_rx_worker). This avoids the costly I2S stop/start cycle + * between every single buffer that was causing device freezes. + */ + + apb = (FAR struct ap_buffer_s *)dq_remfirst(&priv->pendq); + if (apb == NULL) + { + priv->inflight = false; + return; + } + + priv->inflight = true; + + while (apb != NULL) + { + I2S_RECEIVE(priv->i2s, apb, es7210_processdone, priv, 0); + apb = (FAR struct ap_buffer_s *)dq_remfirst(&priv->pendq); + } +} + +/**************************************************************************** + * Name: es7210_processdone + ****************************************************************************/ + +static void es7210_processdone(FAR struct i2s_dev_s *i2s, + FAR struct ap_buffer_s *apb, + FAR void *arg, int result) +{ + FAR struct es7210_dev_s *priv = (FAR struct es7210_dev_s *)arg; + irqstate_t flags; + + DEBUGASSERT(priv && apb); + + flags = enter_critical_section(); + + /* If we are no longer running, return buffer directly to upper half. + * We cannot just put it on pendq because es7210_stop/returnbuffers + * may have already run — the buffer would be leaked and nxrecorder + * would hang waiting for it. + */ + + if (!priv->running) + { + leave_critical_section(flags); + +#ifdef CONFIG_AUDIO_MULTI_SESSION + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, -ENODATA, NULL); +#else + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, -ENODATA); +#endif + return; + } + + /* Add the completed buffer to the done queue */ + + dq_addlast((FAR dq_entry_t *)apb, &priv->doneq); + + leave_critical_section(flags); + + /* Schedule LPWORK to return buffer and start next receive. + * Use 1-tick delay to prevent tight-looping when DMA completes + * faster than LPWORK can yield. + */ + + work_queue(LPWORK, &priv->work, es7210_worker, priv, 1); +} + +/**************************************************************************** + * Name: es7210_start + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_start(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else +static int es7210_start(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct es7210_dev_s *priv = (FAR struct es7210_dev_s *)dev; + + audinfo("ES7210 start\n"); + + priv->running = true; + priv->result = OK; + + /* Reset the ES7210 hardware */ + + es7210_reset(priv); + + /* Configure the I2S lower-half with the stream parameters. + * Note: I2S_RXSAMPLERATE starts the RX channel, so we must + * call processbegin immediately after to submit DMA buffers. + * If done in configure(), the channel runs without buffers + * causing an interrupt storm. + */ + + audinfo("ES7210 start: ch=%d bps=%d rate=%lu pendq_empty=%d\n", + priv->nchannels, priv->bpsamp, (unsigned long)priv->samprate, + dq_empty(&priv->pendq)); + + I2S_IOCTL(priv->i2s, AUDIOIOC_START, 0); + I2S_RXCHANNELS(priv->i2s, priv->nchannels); + I2S_RXDATAWIDTH(priv->i2s, priv->bpsamp); + I2S_RXSAMPLERATE(priv->i2s, priv->samprate); + + /* Wait for the ES7210 codec to start outputting valid I2S data. + * After reset + I2S channel start, the codec needs time to stabilize + * its clock and begin transmitting. Without this delay, the first + * DMA transfer may never complete because there is no data on DIN. + * This must come BEFORE processbegin so DMA is not started before + * the codec is ready. + * + * Note: use up_mdelay instead of nxsig_usleep because the audio + * framework calls start() with a semaphore held, which prevents + * signal-based sleep from completing. + */ + + up_mdelay(50); + + /* es7210_stop may have been called while we were sleeping. + * processbegin guards on running, but check here too so we + * avoid the I2S_RECEIVE call entirely. + */ + + if (!priv->running) + { + return OK; + } + + /* Start initial receive operations */ + + es7210_processbegin(priv); + + return OK; +} + +#ifndef CONFIG_AUDIO_EXCLUDE_STOP + +/**************************************************************************** + * Name: es7210_stop + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_stop(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else +static int es7210_stop(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct es7210_dev_s *priv = (FAR struct es7210_dev_s *)dev; + + audinfo("ES7210 stop\n"); + + priv->running = false; + priv->inflight = false; /* prevent enqueuebuffer from skipping processbegin */ + work_cancel(LPWORK, &priv->work); + + /* Tell I2S layer to stop streaming so DMA won't keep running */ + + I2S_IOCTL(priv->i2s, AUDIOIOC_STOP, 0); + + /* Mute and power down */ + + es7210_writereg(priv, ES7210_ADC12_MUTE_REG47, ES7210_ADC_MUTE); + es7210_writereg(priv, ES7210_ADC34_MUTE_REG48, ES7210_ADC_MUTE); + + /* Return any pending/done buffers */ + + es7210_returnbuffers(priv); + + /* Notify upper half that we are done */ + +#ifdef CONFIG_AUDIO_MULTI_SESSION + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK, NULL); +#else + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK); +#endif + + return OK; +} + +#endif /* CONFIG_AUDIO_EXCLUDE_STOP */ + +#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME + +/**************************************************************************** + * Name: es7210_pause + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_pause(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else +static int es7210_pause(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct es7210_dev_s *priv = (FAR struct es7210_dev_s *)dev; + + audinfo("ES7210 pause\n"); + priv->paused = true; + + /* Mute ADCs */ + + es7210_writereg(priv, ES7210_ADC12_MUTE_REG47, ES7210_ADC_MUTE); + es7210_writereg(priv, ES7210_ADC34_MUTE_REG48, ES7210_ADC_MUTE); + + return OK; +} + +/**************************************************************************** + * Name: es7210_resume + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_resume(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else +static int es7210_resume(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct es7210_dev_s *priv = (FAR struct es7210_dev_s *)dev; + + audinfo("ES7210 resume\n"); + priv->paused = false; + + /* Unmute ADCs */ + + es7210_writereg(priv, ES7210_ADC12_MUTE_REG47, ES7210_ADC_UNMUTE); + es7210_writereg(priv, ES7210_ADC34_MUTE_REG48, ES7210_ADC_UNMUTE); + + return OK; +} + +#endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ + +/**************************************************************************** + * Name: es7210_enqueuebuffer + ****************************************************************************/ + +static int es7210_enqueuebuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + FAR struct es7210_dev_s *priv = (FAR struct es7210_dev_s *)dev; + irqstate_t flags; + + DEBUGASSERT(priv && apb); + + flags = enter_critical_section(); + dq_addlast((FAR dq_entry_t *)apb, &priv->pendq); + leave_critical_section(flags); + + /* If we are running, submit pending buffers to the I2S layer. + * + * When inflight is true, the I2S driver already has active DMA and + * will queue additional buffers internally (on its pend queue). + * This keeps the I2S pipeline full and avoids the costly + * stop → restart cycle between every buffer. + * + * When inflight is false, processbegin starts the first DMA transfer. + */ + + if (priv->running && !priv->paused) + { + es7210_processbegin(priv); + } + + return OK; +} + +/**************************************************************************** + * Name: es7210_cancelbuffer + ****************************************************************************/ + +static int es7210_cancelbuffer(FAR struct audio_lowerhalf_s *dev, + FAR struct ap_buffer_s *apb) +{ + audinfo("apb=%p\n", apb); + return OK; +} + +/**************************************************************************** + * Name: es7210_ioctl + ****************************************************************************/ + +static int es7210_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd, + unsigned long arg) +{ + FAR struct es7210_dev_s *priv = (FAR struct es7210_dev_s *)dev; + int ret = OK; + + switch (cmd) + { + case AUDIOIOC_HWRESET: + ret = es7210_reset(priv); + break; + + case AUDIOIOC_GETBUFFERINFO: + { + FAR struct ap_buffer_info_s *bufinfo = + (FAR struct ap_buffer_info_s *)arg; + bufinfo->buffer_size = CONFIG_ES7210_BUFFER_SIZE; + bufinfo->nbuffers = CONFIG_ES7210_NUM_BUFFERS; + } + break; + + default: + ret = -ENOTTY; + break; + } + + return ret; +} + +/**************************************************************************** + * Name: es7210_reserve + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_reserve(FAR struct audio_lowerhalf_s *dev, + FAR void **session) +#else +static int es7210_reserve(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct es7210_dev_s *priv = (FAR struct es7210_dev_s *)dev; + int ret = OK; + + ret = nxmutex_lock(&priv->devlock); + if (ret < 0) + { + return ret; + } + + if (priv->reserved) + { + ret = -EBUSY; + } + else + { + priv->reserved = true; + } + + nxmutex_unlock(&priv->devlock); + return ret; +} + +/**************************************************************************** + * Name: es7210_release + ****************************************************************************/ + +#ifdef CONFIG_AUDIO_MULTI_SESSION +static int es7210_release(FAR struct audio_lowerhalf_s *dev, + FAR void *session) +#else +static int es7210_release(FAR struct audio_lowerhalf_s *dev) +#endif +{ + FAR struct es7210_dev_s *priv = (FAR struct es7210_dev_s *)dev; + + nxmutex_lock(&priv->devlock); + priv->reserved = false; + nxmutex_unlock(&priv->devlock); + + return OK; +} + +/**************************************************************************** + * Name: es7210_returnbuffers + * + * Description: + * Return all pending and done buffers to the upper half. + * + ****************************************************************************/ + +static void es7210_returnbuffers(FAR struct es7210_dev_s *priv) +{ + FAR struct ap_buffer_s *apb; + irqstate_t flags; + + flags = enter_critical_section(); + + while ((apb = (FAR struct ap_buffer_s *)dq_remfirst(&priv->pendq)) + != NULL) + { +#ifdef CONFIG_AUDIO_MULTI_SESSION + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, -ENODATA, NULL); +#else + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, -ENODATA); +#endif + } + + while ((apb = (FAR struct ap_buffer_s *)dq_remfirst(&priv->doneq)) + != NULL) + { +#ifdef CONFIG_AUDIO_MULTI_SESSION + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, -ENODATA, NULL); +#else + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, -ENODATA); +#endif + } + + leave_critical_section(flags); +} + +/**************************************************************************** + * Name: es7210_worker + * + * Description: + * LPWORK handler — return completed buffers and start next receive. + * + ****************************************************************************/ + +static void es7210_worker(FAR void *arg) +{ + FAR struct es7210_dev_s *priv = (FAR struct es7210_dev_s *)arg; + FAR struct ap_buffer_s *apb; + + /* Return all completed buffers to upper half */ + + for (; ; ) + { + irqstate_t flags = enter_critical_section(); + apb = (FAR struct ap_buffer_s *)dq_remfirst(&priv->doneq); + leave_critical_section(flags); + + if (apb == NULL) + { + break; + } + +#ifdef CONFIG_AUDIO_MULTI_SESSION + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, OK, NULL); +#else + priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, + apb, OK); +#endif + } + + /* Start next receive if still running */ + + if (priv->running && !priv->paused) + { + es7210_processbegin(priv); + } +} + +/**************************************************************************** + * Public Functions + ****************************************************************************/ + +/**************************************************************************** + * Name: es7210_initialize + ****************************************************************************/ + +FAR struct audio_lowerhalf_s * +es7210_initialize(FAR struct i2c_master_s *i2c, + FAR struct i2s_dev_s *i2s, + FAR const struct es7210_lower_s *lower) +{ + FAR struct es7210_dev_s *priv; + + audinfo("ES7210 initialize: addr=0x%02x freq=%lu\n", + lower->address, (unsigned long)lower->frequency); + + /* Sanity check */ + + DEBUGASSERT(i2c != NULL && i2s != NULL && lower != NULL); + + /* Allocate a ES7210 device structure */ + + priv = kmm_zalloc(sizeof(struct es7210_dev_s)); + if (priv == NULL) + { + auderr("ERROR: Failed to allocate ES7210 device structure\n"); + return NULL; + } + + /* Initialize the ES7210 device structure */ + + priv->dev.ops = &g_audioops; + priv->i2c = i2c; + priv->i2s = i2s; + priv->lower = *lower; + priv->samprate = ES7210_DEFAULT_SAMPRATE; + priv->nchannels = ES7210_DEFAULT_NCHANNELS; + priv->bpsamp = ES7210_DEFAULT_BPSAMP; + priv->mute = false; + priv->running = false; + priv->paused = false; + +#ifndef CONFIG_AUDIO_EXCLUDE_VOLUME + priv->volume = 1000; +#endif + + nxmutex_init(&priv->devlock); + dq_init(&priv->pendq); + dq_init(&priv->doneq); + + /* NOTE: Do NOT access I2C here. The ES7210 chip may not be powered + * yet at board bringup time, which would cause I2C to hang and block + * the entire boot process. Hardware init is deferred to the worker + * thread when recording actually starts. + */ + + return &priv->dev; +} diff --git a/drivers/audio/es7210.h b/drivers/audio/es7210.h new file mode 100644 index 00000000000..2470a579666 --- /dev/null +++ b/drivers/audio/es7210.h @@ -0,0 +1,216 @@ +/**************************************************************************** + * drivers/audio/es7210.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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_ES7210_H +#define __DRIVERS_AUDIO_ES7210_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#include <nuttx/audio/audio.h> +#include <nuttx/audio/i2s.h> +#include <nuttx/audio/es7210.h> +#include <nuttx/i2c/i2c_master.h> +#include <nuttx/mutex.h> +#include <nuttx/wqueue.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/* ES7210 Register Addresses */ + +#define ES7210_RESET_REG00 0x00 /* Reset control */ +#define ES7210_CLK_ON_REG01 0x01 /* Clock manager 1 */ +#define ES7210_MCLK_CTL_REG02 0x02 /* Master clock control */ +#define ES7210_MST_CLK_CTL_REG03 0x03 /* Master clock divider */ +#define ES7210_MST_LRCDIVH_REG04 0x04 /* LRCK divider high */ +#define ES7210_MST_LRCDIVL_REG05 0x05 /* LRCK divider low */ +#define ES7210_DIGITAL_PDN_REG06 0x06 /* Digital power down */ +#define ES7210_ADC_OSR_REG07 0x07 /* ADC over-sampling ratio */ +#define ES7210_MODE_CFG_REG08 0x08 /* Mode config */ +#define ES7210_TCT0_CHPINI_REG09 0x09 /* Time control 0 */ +#define ES7210_TCT1_CHPINI_REG0A 0x0A /* Time control 1 */ +#define ES7210_CHIP_STA_REG0B 0x0B /* Chip status */ +#define ES7210_IRQ_CTL_REG0C 0x0C /* IRQ control */ +#define ES7210_MISC_CTL_REG0D 0x0D /* Misc control */ +#define ES7210_DMIC_CTL_REG10 0x10 /* DMIC control */ +#define ES7210_SDP_CFG1_REG11 0x11 /* SDP config 1 */ +#define ES7210_SDP_CFG2_REG12 0x12 /* SDP config 2 */ +#define ES7210_ADC_AUTOMUTE_REG13 0x13 /* ADC automute */ +#define ES7210_ADC34_MUTEFLAG_REG14 0x14 /* ADC34 mute flag */ +#define ES7210_ADC12_MUTEFLAG_REG15 0x15 /* ADC12 mute flag */ +#define ES7210_ALC_SEL_REG16 0x16 /* ALC select */ +#define ES7210_ALC_COM_CFG1_REG17 0x17 /* ALC common config 1 */ +#define ES7210_ALC_COM_CFG2_REG18 0x18 /* ALC common config 2 */ +#define ES7210_ALC1_MAX_GAIN_REG1A 0x1A /* ALC1 max gain */ +#define ES7210_ALC1_MIN_GAIN_REG1B 0x1B /* ALC1 min gain */ +#define ES7210_ALC1_LVL_REG1C 0x1C /* ALC1 level */ +#define ES7210_ALC2_MAX_GAIN_REG1D 0x1D /* ALC2 max gain */ +#define ES7210_ALC2_MIN_GAIN_REG1E 0x1E /* ALC2 min gain */ +#define ES7210_ALC2_LVL_REG1F 0x1F /* ALC2 level */ +#define ES7210_ADC1_HPF_REG20 0x20 /* ADC1 HPF coefficient */ +#define ES7210_ADC2_HPF_REG21 0x21 /* ADC2 HPF coefficient */ +#define ES7210_ADC3_HPF_REG22 0x22 /* ADC3 HPF coefficient */ +#define ES7210_ADC4_HPF_REG23 0x23 /* ADC4 HPF coefficient */ +#define ES7210_ALC4_MIN_GAIN_REG24 0x24 /* ALC4 min gain */ +#define ES7210_ALC4_LVL_REG25 0x25 /* ALC4 level */ +#define ES7210_ADC12_HPF2_REG2A 0x2A /* ADC12 HPF coeff 2 */ +#define ES7210_ADC12_HPF1_REG2B 0x2B /* ADC12 HPF coeff 1 */ +#define ES7210_ADC34_HPF2_REG2C 0x2C /* ADC34 HPF coeff 2 */ +#define ES7210_ADC34_HPF1_REG2D 0x2D /* ADC34 HPF coeff 1 */ +#define ES7210_ADC1_GAIN_REG43 0x43 /* ADC1 PGA gain */ +#define ES7210_ADC2_GAIN_REG44 0x44 /* ADC2 PGA gain */ +#define ES7210_ADC3_GAIN_REG45 0x45 /* ADC3 PGA gain */ +#define ES7210_ADC4_GAIN_REG46 0x46 /* ADC4 PGA gain */ +#define ES7210_ADC12_MUTE_REG47 0x47 /* ADC12 mute */ +#define ES7210_ADC34_MUTE_REG48 0x48 /* ADC34 mute */ +#define ES7210_ANALOG_SYS_REG40 0x40 /* Analog system */ +#define ES7210_MICBIAS12_REG41 0x41 /* MIC bias 12 */ +#define ES7210_MICBIAS34_REG42 0x42 /* MIC bias 34 */ +#define ES7210_MIC3_CTL_REG49 0x49 /* MIC3 control */ +#define ES7210_MIC4_CTL_REG4A 0x4A /* MIC4 control */ +#define ES7210_MIC12_POWER_REG4B 0x4B /* MIC12 power */ +#define ES7210_MIC34_POWER_REG4C 0x4C /* MIC34 power */ + +/* SDP Format */ + +#define ES7210_SDP_FMT_I2S 0x00 +#define ES7210_SDP_FMT_LJ 0x01 +#define ES7210_SDP_FMT_DSP_A 0x03 +#define ES7210_SDP_FMT_DSP_B 0x13 + +/* SDP Word Length */ + +#define ES7210_SDP_WL_16BIT 0x03 +#define ES7210_SDP_WL_18BIT 0x02 +#define ES7210_SDP_WL_20BIT 0x01 +#define ES7210_SDP_WL_24BIT 0x00 +#define ES7210_SDP_WL_32BIT 0x04 + +/* MIC Gain (dB) */ + +#define ES7210_MIC_GAIN_0DB 0x00 +#define ES7210_MIC_GAIN_3DB 0x01 +#define ES7210_MIC_GAIN_6DB 0x02 +#define ES7210_MIC_GAIN_9DB 0x03 +#define ES7210_MIC_GAIN_12DB 0x04 +#define ES7210_MIC_GAIN_15DB 0x05 +#define ES7210_MIC_GAIN_18DB 0x06 +#define ES7210_MIC_GAIN_21DB 0x07 +#define ES7210_MIC_GAIN_24DB 0x08 +#define ES7210_MIC_GAIN_27DB 0x09 +#define ES7210_MIC_GAIN_30DB 0x0A +#define ES7210_MIC_GAIN_33DB 0x0B +#define ES7210_MIC_GAIN_34_5DB 0x0C +#define ES7210_MIC_GAIN_36DB 0x0D +#define ES7210_MIC_GAIN_37_5DB 0x0E + +/* PGA gain register bit 4: analog front-end power enable */ + +#define ES7210_ADC_PGA_POWER_ON 0x10 + +/* Reset register values */ + +#define ES7210_RESET_CMD 0xFF /* Software reset command */ +#define ES7210_RESET_NORMAL 0x32 /* Normal operation after reset */ +#define ES7210_CLK_OFF 0x71 /* Clock off before enable */ +#define ES7210_DEVICE_ON 0x41 /* Device enable */ + +/* SDP configuration */ + +#define ES7210_SDP_I2S_16BIT 0x60 /* I2S format, 16-bit */ +#define ES7210_SDP_NORMAL 0x00 /* Normal I2S (not TDM) */ + +/* Analog system */ + +#define ES7210_VMID_SELECT 0xC3 /* VMID voltage selection */ +#define ES7210_MICBIAS_2V87 0x70 /* MIC bias 2.87V */ + +/* MIC / ADC power */ + +#define ES7210_MIC_POWER_ON 0x08 /* Individual MIC power on */ +#define ES7210_MIC_ADC_PGA_ON 0x0F /* MIC bias + ADC + PGA on */ + +/* MCLK / clock */ + +#define ES7210_MCLK_ADC_DIV1_DLL 0x81 /* ADC_DIV=1, DLL enable */ +#define ES7210_DLL_POWER_DOWN 0x04 /* DLL power down */ + +/* Mute control */ + +#define ES7210_ADC_MUTE 0x03 /* Mute both ADC channels */ +#define ES7210_ADC_UNMUTE 0x00 /* Unmute both ADC channels */ + +/* Default configuration */ + +#define ES7210_DEFAULT_SAMPRATE 16000 +#define ES7210_DEFAULT_NCHANNELS 2 +#define ES7210_DEFAULT_BPSAMP 16 + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +struct es7210_dev_s +{ + /* We are an audio lower half driver (We are also the curved half of + * the curved half interface) + */ + + struct audio_lowerhalf_s dev; + + /* Our specific driver data goes here */ + + FAR struct i2c_master_s *i2c; /* I2C driver to use */ + FAR struct i2s_dev_s *i2s; /* I2S driver to use */ + struct es7210_lower_s lower; /* Platform-specific config */ + struct work_s work; /* Worker thread for LPWORK */ + mutex_t devlock; /* Assures mutually exclusive + * access to driver */ + + /* Driver state */ + + uint32_t samprate; /* Configured samprate (samples/sec) */ +#ifndef CONFIG_AUDIO_EXCLUDE_VOLUME + uint16_t volume; /* Current volume (0-1000) */ +#endif + uint8_t nchannels; /* Number of channels (1-4) */ + uint8_t bpsamp; /* Bits per sample (16 or 32) */ + volatile bool running; /* True: Worker thread is running */ + volatile bool paused; /* True: Playing is paused */ + volatile bool inflight; /* True: DMA transfer in progress */ + bool mute; /* True: Output is muted */ + bool reserved; /* True: Device is reserved */ + volatile int result; /* The result of the last transfer */ + + /* Buffer management */ + + struct dq_queue_s pendq; /* Queue of pending buffers to be + * received */ + struct dq_queue_s doneq; /* Queue of sent buffers to be + * returned */ +}; + +#endif /* __DRIVERS_AUDIO_ES7210_H */ diff --git a/include/nuttx/audio/es7210.h b/include/nuttx/audio/es7210.h new file mode 100644 index 00000000000..e2dd36b5785 --- /dev/null +++ b/include/nuttx/audio/es7210.h @@ -0,0 +1,95 @@ +/**************************************************************************** + * include/nuttx/audio/es7210.h + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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_ES7210_H +#define __INCLUDE_NUTTX_AUDIO_ES7210_H + +/**************************************************************************** + * Included Files + ****************************************************************************/ + +#include <nuttx/config.h> +#include <nuttx/audio/audio.h> +#include <nuttx/i2c/i2c_master.h> +#include <nuttx/audio/i2s.h> + +/**************************************************************************** + * Pre-processor Definitions + ****************************************************************************/ + +/**************************************************************************** + * Public Types + ****************************************************************************/ + +/* This structure is used to configure the ES7210 lower half driver */ + +struct es7210_lower_s +{ + uint32_t frequency; /* I2C frequency */ + uint8_t address; /* I2C address (7-bit, default 0x41) */ +}; + +/**************************************************************************** + * Public Data + ****************************************************************************/ + +#ifdef __cplusplus +#define EXTERN extern "C" +extern "C" +{ +#else +#define EXTERN extern +#endif + +/**************************************************************************** + * Public Function Prototypes + ****************************************************************************/ + +/**************************************************************************** + * Name: es7210_initialize + * + * Description: + * Initialize the ES7210 ADC codec chip. + * + * Input Parameters: + * i2c - An instance of the I2C interface to use to communicate with + * the ES7210 + * i2s - An instance of the I2S interface to use for audio data + * lower - Platform-specific lower half configuration + * + * Returned Value: + * A new lower half audio interface for the ES7210 on success; NULL on + * failure. + * + ****************************************************************************/ + +FAR struct audio_lowerhalf_s * +es7210_initialize(FAR struct i2c_master_s *i2c, + FAR struct i2s_dev_s *i2s, + FAR const struct es7210_lower_s *lower); + +#undef EXTERN +#ifdef __cplusplus +} +#endif + +#endif /* __INCLUDE_NUTTX_AUDIO_ES7210_H */
