68951e8d72
* Remove multiple newlines at the end of files * Remove the whitespace from the end of lines
465 lines
15 KiB
C
465 lines
15 KiB
C
/****************************************************************************
|
|
* drivers/audio/audio_i2s.c
|
|
*
|
|
* Copyright (C) 2018 Pinecone Inc. All rights reserved.
|
|
* Author: Zhong An <zhongan@pinecone.net>
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
*
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in
|
|
* the documentation and/or other materials provided with the
|
|
* distribution.
|
|
* 3. Neither the name NuttX nor the names of its contributors may be
|
|
* used to endorse or promote products derived from this software
|
|
* without specific prior written permission.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
|
|
* "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
|
|
* LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
|
|
* FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
|
|
* COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
|
* INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
|
|
* BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
|
|
* OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
|
|
* AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
|
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
|
|
* ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*
|
|
****************************************************************************/
|
|
|
|
/****************************************************************************
|
|
* Included Files
|
|
****************************************************************************/
|
|
|
|
#include <nuttx/config.h>
|
|
#include <nuttx/audio/audio.h>
|
|
#include <nuttx/audio/i2s.h>
|
|
#include <nuttx/kmalloc.h>
|
|
|
|
/****************************************************************************
|
|
* Private Types
|
|
****************************************************************************/
|
|
|
|
struct audio_i2s_s
|
|
{
|
|
struct audio_lowerhalf_s dev;
|
|
struct i2s_dev_s *i2s;
|
|
bool playback;
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Function Prototypes
|
|
****************************************************************************/
|
|
|
|
static int audio_i2s_getcaps(FAR struct audio_lowerhalf_s *dev, int type,
|
|
FAR struct audio_caps_s *caps);
|
|
static int audio_i2s_shutdown(FAR struct audio_lowerhalf_s *dev);
|
|
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
|
static int audio_i2s_configure(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session,
|
|
FAR const struct audio_caps_s *caps);
|
|
static int audio_i2s_start(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session);
|
|
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
|
|
static int audio_i2s_stop(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session);
|
|
#endif
|
|
#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
|
|
static int audio_i2s_pause(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session);
|
|
static int audio_i2s_resume(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session);
|
|
#endif
|
|
static int audio_i2s_reserve(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void **session);
|
|
static int audio_i2s_release(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session);
|
|
#else
|
|
static int audio_i2s_configure(FAR struct audio_lowerhalf_s *dev,
|
|
FAR const struct audio_caps_s *caps);
|
|
static int audio_i2s_start(FAR struct audio_lowerhalf_s *dev);
|
|
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
|
|
static int audio_i2s_stop(FAR struct audio_lowerhalf_s *dev);
|
|
#endif
|
|
#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
|
|
static int audio_i2s_pause(FAR struct audio_lowerhalf_s *dev);
|
|
static int audio_i2s_resume(FAR struct audio_lowerhalf_s *dev);
|
|
#endif
|
|
static int audio_i2s_reserve(FAR struct audio_lowerhalf_s *dev);
|
|
static int audio_i2s_release(FAR struct audio_lowerhalf_s *dev);
|
|
#endif
|
|
static int audio_i2s_allocbuffer(FAR struct audio_lowerhalf_s *dev,
|
|
FAR struct audio_buf_desc_s *bufdesc);
|
|
static int audio_i2s_freebuffer(FAR struct audio_lowerhalf_s *dev,
|
|
FAR struct audio_buf_desc_s *bufdesc);
|
|
static int audio_i2s_enqueuebuffer(FAR struct audio_lowerhalf_s *dev,
|
|
FAR struct ap_buffer_s *apb);
|
|
static int audio_i2s_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd,
|
|
unsigned long arg);
|
|
static void audio_i2s_callback(struct i2s_dev_s *dev,
|
|
FAR struct ap_buffer_s *apb, FAR void *arg,
|
|
int result);
|
|
|
|
/****************************************************************************
|
|
* Private Data
|
|
****************************************************************************/
|
|
|
|
static const struct audio_ops_s g_audio_i2s_ops =
|
|
{
|
|
.getcaps = audio_i2s_getcaps,
|
|
.configure = audio_i2s_configure,
|
|
.shutdown = audio_i2s_shutdown,
|
|
.start = audio_i2s_start,
|
|
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
|
|
.stop = audio_i2s_stop,
|
|
#endif
|
|
#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
|
|
.pause = audio_i2s_pause,
|
|
.resume = audio_i2s_resume,
|
|
#endif
|
|
.allocbuffer = audio_i2s_allocbuffer,
|
|
.freebuffer = audio_i2s_freebuffer,
|
|
.enqueuebuffer = audio_i2s_enqueuebuffer,
|
|
.ioctl = audio_i2s_ioctl,
|
|
.reserve = audio_i2s_reserve,
|
|
.release = audio_i2s_release,
|
|
};
|
|
|
|
/****************************************************************************
|
|
* Private Functions
|
|
****************************************************************************/
|
|
|
|
static int audio_i2s_getcaps(FAR struct audio_lowerhalf_s *dev, int type,
|
|
FAR struct audio_caps_s *caps)
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = (struct audio_i2s_s *)dev;
|
|
FAR struct i2s_dev_s *i2s = audio_i2s->i2s;
|
|
|
|
/* Validate the structure */
|
|
|
|
DEBUGASSERT(caps && caps->ac_len >= sizeof(struct audio_caps_s));
|
|
audinfo("type=%d ac_type=%d\n", type, caps->ac_type);
|
|
|
|
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.
|
|
*/
|
|
|
|
switch (caps->ac_subtype)
|
|
{
|
|
case AUDIO_TYPE_QUERY:
|
|
|
|
/* We don't decode any formats! Only something above us in
|
|
* the audio stream can perform decoding on our behalf.
|
|
*/
|
|
|
|
/* The types of audio units we implement */
|
|
|
|
if (audio_i2s->playback)
|
|
{
|
|
caps->ac_controls.b[0] = AUDIO_TYPE_OUTPUT;
|
|
}
|
|
else
|
|
{
|
|
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;
|
|
|
|
/* Provide capabilities of our OUTPUT unit */
|
|
|
|
case AUDIO_TYPE_OUTPUT:
|
|
case AUDIO_TYPE_INPUT:
|
|
|
|
switch (caps->ac_subtype)
|
|
{
|
|
case AUDIO_TYPE_QUERY:
|
|
|
|
/* Report the Sample rates we support */
|
|
|
|
caps->ac_controls.hw[0] =
|
|
AUDIO_SAMP_RATE_8K | AUDIO_SAMP_RATE_11K |
|
|
AUDIO_SAMP_RATE_16K | AUDIO_SAMP_RATE_22K |
|
|
AUDIO_SAMP_RATE_32K | AUDIO_SAMP_RATE_44K |
|
|
AUDIO_SAMP_RATE_48K | AUDIO_SAMP_RATE_96K |
|
|
AUDIO_SAMP_RATE_128K | AUDIO_SAMP_RATE_160K |
|
|
AUDIO_SAMP_RATE_172K | AUDIO_SAMP_RATE_192K;
|
|
break;
|
|
|
|
default:
|
|
I2S_IOCTL(i2s, AUDIOIOC_GETCAPS, (unsigned long)caps);
|
|
break;
|
|
}
|
|
break;
|
|
|
|
default:
|
|
I2S_IOCTL(i2s, AUDIOIOC_GETCAPS, (unsigned long)caps);
|
|
break;
|
|
}
|
|
|
|
return caps->ac_len;
|
|
}
|
|
|
|
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
|
static int audio_i2s_configure(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session,
|
|
FAR const struct audio_caps_s *caps)
|
|
#else
|
|
static int audio_i2s_configure(FAR struct audio_lowerhalf_s *dev,
|
|
FAR const struct audio_caps_s *caps)
|
|
#endif
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = (struct audio_i2s_s *)dev;
|
|
FAR struct i2s_dev_s *i2s;
|
|
int samprate, nchannels, bpsamp;
|
|
int ret = OK;
|
|
|
|
DEBUGASSERT(audio_i2s != NULL && caps != NULL);
|
|
i2s = audio_i2s->i2s;
|
|
audinfo("ac_type: %d\n", caps->ac_type);
|
|
|
|
/* Process the configure operation */
|
|
|
|
switch (caps->ac_type)
|
|
{
|
|
case AUDIO_TYPE_OUTPUT:
|
|
case AUDIO_TYPE_INPUT:
|
|
|
|
/* Save the current stream configuration */
|
|
|
|
samprate = caps->ac_controls.hw[0] |
|
|
(caps->ac_controls.b[3] << 16);
|
|
nchannels = caps->ac_channels;
|
|
bpsamp = caps->ac_controls.b[2];
|
|
|
|
if (audio_i2s->playback)
|
|
{
|
|
I2S_TXCHANNELS(i2s, nchannels);
|
|
I2S_TXDATAWIDTH(i2s, bpsamp);
|
|
I2S_TXSAMPLERATE(i2s, samprate);
|
|
}
|
|
else
|
|
{
|
|
I2S_RXCHANNELS(i2s, nchannels);
|
|
I2S_RXDATAWIDTH(i2s, bpsamp);
|
|
I2S_RXSAMPLERATE(i2s, samprate);
|
|
}
|
|
break;
|
|
|
|
default:
|
|
ret = I2S_IOCTL(i2s, AUDIOIOC_CONFIGURE, (unsigned long)caps);
|
|
break;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
static int audio_i2s_shutdown(FAR struct audio_lowerhalf_s *dev)
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = (struct audio_i2s_s *)dev;
|
|
FAR struct i2s_dev_s *i2s = audio_i2s->i2s;
|
|
|
|
return I2S_IOCTL(i2s, AUDIOIOC_SHUTDOWN, audio_i2s->playback);
|
|
}
|
|
|
|
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
|
static int audio_i2s_start(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session)
|
|
#else
|
|
static int audio_i2s_start(FAR struct audio_lowerhalf_s *dev)
|
|
#endif
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = (struct audio_i2s_s *)dev;
|
|
FAR struct i2s_dev_s *i2s = audio_i2s->i2s;
|
|
|
|
return I2S_IOCTL(i2s, AUDIOIOC_START, audio_i2s->playback);
|
|
}
|
|
|
|
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
|
|
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
|
static int audio_i2s_stop(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session)
|
|
#else
|
|
static int audio_i2s_stop(FAR struct audio_lowerhalf_s *dev)
|
|
#endif
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = (struct audio_i2s_s *)dev;
|
|
FAR struct i2s_dev_s *i2s = audio_i2s->i2s;
|
|
|
|
return I2S_IOCTL(i2s, AUDIOIOC_STOP, audio_i2s->playback);
|
|
}
|
|
#endif
|
|
|
|
#ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME
|
|
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
|
static int audio_i2s_pause(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session)
|
|
#else
|
|
static int audio_i2s_pause(FAR struct audio_lowerhalf_s *dev)
|
|
#endif
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = (struct audio_i2s_s *)dev;
|
|
FAR struct i2s_dev_s *i2s = audio_i2s->i2s;
|
|
|
|
return I2S_IOCTL(i2s, AUDIOIOC_PAUSE, audio_i2s->playback);
|
|
}
|
|
|
|
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
|
static int audio_i2s_resume(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session)
|
|
#else
|
|
static int audio_i2s_resume(FAR struct audio_lowerhalf_s *dev)
|
|
#endif
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = (struct audio_i2s_s *)dev;
|
|
FAR struct i2s_dev_s *i2s = audio_i2s->i2s;
|
|
|
|
return I2S_IOCTL(i2s, AUDIOIOC_RESUME, audio_i2s->playback);
|
|
}
|
|
#endif
|
|
|
|
static int audio_i2s_allocbuffer(FAR struct audio_lowerhalf_s *dev,
|
|
FAR struct audio_buf_desc_s *bufdesc)
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = (struct audio_i2s_s *)dev;
|
|
FAR struct i2s_dev_s *i2s = audio_i2s->i2s;
|
|
|
|
return I2S_IOCTL(i2s, AUDIOIOC_ALLOCBUFFER, (unsigned long)bufdesc);
|
|
}
|
|
|
|
static int audio_i2s_freebuffer(FAR struct audio_lowerhalf_s *dev,
|
|
FAR struct audio_buf_desc_s *bufdesc)
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = (struct audio_i2s_s *)dev;
|
|
FAR struct i2s_dev_s *i2s = audio_i2s->i2s;
|
|
|
|
return I2S_IOCTL(i2s, AUDIOIOC_FREEBUFFER, (unsigned long)bufdesc);
|
|
}
|
|
|
|
static int audio_i2s_enqueuebuffer(FAR struct audio_lowerhalf_s *dev,
|
|
FAR struct ap_buffer_s *apb)
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = (struct audio_i2s_s *)dev;
|
|
FAR struct i2s_dev_s *i2s = audio_i2s->i2s;
|
|
|
|
if (audio_i2s->playback)
|
|
{
|
|
return I2S_SEND(i2s, apb, audio_i2s_callback, audio_i2s, 0);
|
|
}
|
|
else
|
|
{
|
|
return I2S_RECEIVE(i2s, apb, audio_i2s_callback, audio_i2s, 0);
|
|
}
|
|
}
|
|
|
|
static int audio_i2s_ioctl(FAR struct audio_lowerhalf_s *dev, int cmd,
|
|
unsigned long arg)
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = (struct audio_i2s_s *)dev;
|
|
FAR struct i2s_dev_s *i2s = audio_i2s->i2s;
|
|
|
|
return I2S_IOCTL(i2s, cmd, arg);
|
|
}
|
|
|
|
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
|
static int audio_i2s_reserve(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void **session)
|
|
#else
|
|
static int audio_i2s_reserve(FAR struct audio_lowerhalf_s *dev)
|
|
#endif
|
|
{
|
|
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
|
*session = (void *)audio_i2s->playback;
|
|
#endif
|
|
return OK;
|
|
}
|
|
|
|
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
|
static int audio_i2s_release(FAR struct audio_lowerhalf_s *dev,
|
|
FAR void *session)
|
|
#else
|
|
static int audio_i2s_release(FAR struct audio_lowerhalf_s *dev)
|
|
#endif
|
|
{
|
|
return OK;
|
|
}
|
|
|
|
static void audio_i2s_callback(struct i2s_dev_s *dev,
|
|
FAR struct ap_buffer_s *apb,
|
|
FAR void *arg, int result)
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s = arg;
|
|
bool final = false;
|
|
|
|
if ((apb->flags & AUDIO_APB_FINAL) != 0)
|
|
{
|
|
final = true;
|
|
}
|
|
|
|
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
|
audio_i2s->dev.upper(audio_i2s->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb,
|
|
OK, NULL);
|
|
#else
|
|
audio_i2s->dev.upper(audio_i2s->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb,
|
|
OK);
|
|
#endif
|
|
if (final)
|
|
{
|
|
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
|
audio_i2s->dev.upper(audio_i2s->dev.priv, AUDIO_CALLBACK_COMPLETE,
|
|
NULL, OK, NULL);
|
|
#else
|
|
audio_i2s->dev.upper(audio_i2s->dev.priv, AUDIO_CALLBACK_COMPLETE,
|
|
NULL, OK);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
/****************************************************************************
|
|
* Public Functions
|
|
****************************************************************************/
|
|
|
|
FAR struct audio_lowerhalf_s *audio_i2s_initialize(FAR struct i2s_dev_s *i2s,
|
|
bool playback)
|
|
{
|
|
FAR struct audio_i2s_s *audio_i2s;
|
|
|
|
if (i2s == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
audio_i2s = kmm_zalloc(sizeof(struct audio_i2s_s));
|
|
if (audio_i2s == NULL)
|
|
{
|
|
return NULL;
|
|
}
|
|
|
|
audio_i2s->playback = playback;
|
|
audio_i2s->i2s = i2s;
|
|
audio_i2s->dev.ops = &g_audio_i2s_ops;
|
|
|
|
return &audio_i2s->dev;
|
|
}
|