452 lines
14 KiB
C
452 lines
14 KiB
C
/****************************************************************************
|
|
* drivers/audio/audio_i2s.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 <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;
|
|
int nchannels;
|
|
int 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;
|
|
}
|