WM8904: Correct calculation of bitrate (I am not sure why this is correct). LR clock divisor is now a constant 32-bits per frame. Conditioned out interrupt logic; it is not being used. Also added some FLL lock interrupt/poll logic (which was subsequently disabled).
This commit is contained in:
parent
e30d31367a
commit
dc8b040573
@ -61,6 +61,7 @@
|
||||
|
||||
#include <nuttx/kmalloc.h>
|
||||
#include <nuttx/clock.h>
|
||||
#include <nuttx/wqueue.h>
|
||||
#include <nuttx/i2c.h>
|
||||
#include <nuttx/fs/fs.h>
|
||||
#include <nuttx/fs/ioctl.h>
|
||||
@ -105,7 +106,6 @@ static void wm8904_settreble(FAR struct wm8904_dev_s *priv, uint8_t treble);
|
||||
|
||||
static void wm8904_setdatawidth(FAR struct wm8904_dev_s *priv);
|
||||
static void wm8904_setbitrate(FAR struct wm8904_dev_s *priv);
|
||||
static void wm8904_setlrclock(FAR struct wm8904_dev_s *priv);
|
||||
|
||||
/* Audio lower half methods (and close friends) */
|
||||
|
||||
@ -123,9 +123,7 @@ static void wm8904_senddone(FAR struct i2s_dev_s *i2s,
|
||||
FAR struct ap_buffer_s *apb, FAR void *arg, int result);
|
||||
static void wm8904_returnbuffers(FAR struct wm8904_dev_s *priv);
|
||||
static int wm8904_sendbuffer(FAR struct wm8904_dev_s *priv);
|
||||
static int wm8904_interrupt(FAR const struct wm8904_lower_s *lower,
|
||||
FAR void *arg);
|
||||
static void *wm8904_workerthread(pthread_addr_t pvarg);
|
||||
|
||||
#ifdef CONFIG_AUDIO_MULTI_SESSION
|
||||
static int wm8904_start(FAR struct audio_lowerhalf_s *dev,
|
||||
FAR void *session);
|
||||
@ -170,6 +168,16 @@ static int wm8904_release(FAR struct audio_lowerhalf_s *dev,
|
||||
static int wm8904_release(FAR struct audio_lowerhalf_s *dev);
|
||||
#endif
|
||||
|
||||
/* Interrupt handling an worker thread */
|
||||
|
||||
#ifdef WM8904_USE_FFLOCK_INT
|
||||
static void wm8904_interrupt_work(FAR void *arg);
|
||||
static int wm8904_interrupt(FAR const struct wm8904_lower_s *lower,
|
||||
FAR void *arg);
|
||||
#endif
|
||||
|
||||
static void *wm8904_workerthread(pthread_addr_t pvarg);
|
||||
|
||||
/* Initialization */
|
||||
|
||||
static void wm8904_audio_output(FAR struct wm8904_dev_s *priv);
|
||||
@ -619,25 +627,50 @@ static void wm8904_setbitrate(FAR struct wm8904_dev_s *priv)
|
||||
unsigned int fllndx;
|
||||
unsigned int divndx;
|
||||
unsigned int outdiv;
|
||||
#ifdef WM8904_USE_FFLOCK_INT
|
||||
bool enabled;
|
||||
int retries;
|
||||
#endif
|
||||
|
||||
DEBUGASSERT(priv && priv->lower);
|
||||
|
||||
/* First calculate the desired bitrate (fout) */
|
||||
|
||||
fout = (uint32_t)priv->samprate * priv->nchannels * (priv->bpsamp + WM8904_STARTBITS);
|
||||
#if 0
|
||||
/* This is the correct calculation. However, the resulting bitrate is two
|
||||
* times too fast???
|
||||
*/
|
||||
|
||||
fout = (uint32_t)priv->samprate * (uint32_t)priv->nchannels * (uint32_t)priv->bpsamp;
|
||||
#else
|
||||
/* Ahhh.. much better */
|
||||
|
||||
fout = (uint32_t)priv->samprate * (uint32_t)priv->bpsamp;
|
||||
#endif
|
||||
|
||||
audvdbg("sample rate=%u nchannels=%u bpsamp=%u fout=%lu\n",
|
||||
priv->samprate, priv->nchannels, priv->bpsamp, (unsigned long)fout);
|
||||
|
||||
/* Disable the SYSCLK.
|
||||
*
|
||||
* "The SYSCLK signal is enabled by register bit CLK_SYS_ENA. This bit should be
|
||||
* set to 0 when reconfiguring clock sources. ... "
|
||||
* "The SYSCLK signal is enabled by register bit CLK_SYS_ENA. This bit
|
||||
* should be set to 0 when reconfiguring clock sources. ... "
|
||||
*
|
||||
* REVISIT: This does not appear necessary if we are just reconfiguring
|
||||
* the FLL. Disabling the FLL will stop the SYSCLK input just fine.
|
||||
*/
|
||||
|
||||
regval = WM8904_SYSCLK_SRCFLL | WM8904_CLK_DSP_ENA;
|
||||
wm8904_writereg(priv, WM8904_CLKRATE2, regval);
|
||||
|
||||
#if 0 /* Unnecessary */
|
||||
/* Unlock forced oscillator control and switch it off */
|
||||
|
||||
wm8904_writereg(priv, WM8904_CTRLIF_TEST_1, WM8904_USER_KEY);
|
||||
wm8904_writereg(priv, WM8904_FLL_NCO_TEST1, 0);
|
||||
wm8904_writereg(priv, WM8904_CTRLIF_TEST_1, 0);
|
||||
#endif
|
||||
|
||||
/* "The FLL is enabled using the FLL_ENA register bit. Note that, when
|
||||
* changing FLL settings, it is recommended that the digital circuit be
|
||||
* disabled via FLL_ENA and then re-enabled after the other register
|
||||
@ -840,12 +873,62 @@ static void wm8904_setbitrate(FAR struct wm8904_dev_s *priv)
|
||||
regval = WM8904_FLL_FRACN_ENA | WM8904_FLL_ENA;
|
||||
wm8904_writereg(priv, WM8904_FLL_CTRL1, regval);
|
||||
|
||||
/* Allow time for FLL lock. Typical is 2 MSec. Lock status is available
|
||||
* in the WM8904 interrupt status register.
|
||||
#if defined(WM8904_USE_FFLOCK_INT)
|
||||
/* Make sure that interrupts are enabled */
|
||||
|
||||
enabled = WM8904_ENABLE(priv->lower);
|
||||
|
||||
/* Enable the FLL lock interrupt. Here we can be sloppy since the FLL
|
||||
* lock is the only interrupt every enabled.
|
||||
*/
|
||||
|
||||
priv->locked = false;
|
||||
regval = WM8904_ALL_INTS & ~WM8904_FLL_LOCK_INT;
|
||||
wm8904_writereg(priv, WM8904_INT_MASK, regval);
|
||||
|
||||
/* Allow time for FLL lock. Typical is 2 MSec. No exotic interlock
|
||||
* here; we just poll a flag set by the interrupt handler.
|
||||
* REVISIT: Probably not necessary.
|
||||
*/
|
||||
|
||||
usleep(5*5000);
|
||||
retries = 5;
|
||||
do
|
||||
{
|
||||
usleep(5*5000);
|
||||
}
|
||||
while (priv->locked == false && --retries > 0);
|
||||
|
||||
/* Make sure that the FLL lock interrupt is disabled and clear any pending
|
||||
* interrupt status (again cutting* some corners). NOTE: The interrupt
|
||||
* handler will do these things if there is no timeout.
|
||||
*/
|
||||
|
||||
WM8904_DISABLE(priv->lower);
|
||||
wm8904_writereg(priv, WM8904_INT_MASK, WM8904_ALL_INTS);
|
||||
wm8904_writereg(priv, WM8904_INT_STATUS, WM8904_ALL_INTS);
|
||||
|
||||
/* Restore the interrupt state. */
|
||||
|
||||
WM8904_RESTORE(priv->lower, enabled)
|
||||
|
||||
#elif defined(WM8904_USE_FFLOCK_POLL)
|
||||
/* Allow time for FLL lock. Typical is 2 MSec. */
|
||||
|
||||
retries = 5;
|
||||
do
|
||||
{
|
||||
usleep(5*5000);
|
||||
}
|
||||
while ((wm8904_readreg(priv, WM8904_INT_STATUS) & WM8904_FLL_LOCK_INT) != 0 ||
|
||||
--retries > 0);
|
||||
|
||||
/* Clear all pending status bits by writing 1's into the interrupt status
|
||||
* register.
|
||||
*/
|
||||
|
||||
wm8904_writereg(priv, WM8904_INT_STATUS, WM8904_ALL_INTS);
|
||||
|
||||
#endif /* !WM8904_USE_FFLOCK_INT && !WM8904_USE_FFLOCK_POLL */
|
||||
|
||||
/* Re-enable the SYSCLK. */
|
||||
|
||||
@ -853,36 +936,6 @@ static void wm8904_setbitrate(FAR struct wm8904_dev_s *priv)
|
||||
wm8904_writereg(priv, WM8904_CLKRATE2, regval);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: wm8904_setlrclock
|
||||
*
|
||||
* Description:
|
||||
* Program the LRLCK (left/right clock) to trigger each frame at the
|
||||
* correct rate.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void wm8904_setlrclock(FAR struct wm8904_dev_s *priv)
|
||||
{
|
||||
unsigned int lrperiod;
|
||||
uint16_t regval;
|
||||
|
||||
/* The number of bits in one sample depends on the number of bits in one
|
||||
* word plus any extra start bits.
|
||||
*
|
||||
* The number of channels is not important. However, I2C needs an edge
|
||||
* on each frame of the following gives the number of BCLKS to achieve
|
||||
* an LRCLK edge at each sample.
|
||||
*/
|
||||
|
||||
lrperiod = 2 * (unsigned int)(priv->bpsamp + WM8904_STARTBITS);
|
||||
|
||||
/* Set the new LRCLK clock frequency is the, divider */
|
||||
|
||||
regval = WM8904_LRCLK_DIR | WM8904_LRCLK_RATE(lrperiod);
|
||||
wm8904_writereg(priv, WM8904_AIF3, regval);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: wm8904_getcaps
|
||||
*
|
||||
@ -1185,7 +1238,6 @@ static int wm8904_configure(FAR struct audio_lowerhalf_s *dev,
|
||||
|
||||
wm8904_setdatawidth(priv);
|
||||
wm8904_setbitrate(priv);
|
||||
wm8904_setlrclock(priv);
|
||||
wm8904_writereg(priv, WM8904_DUMMY, 0x55aa);
|
||||
|
||||
wm8904_clock_analysis(&priv->dev, "AUDIO_TYPE_OUTPUT");
|
||||
@ -1434,203 +1486,6 @@ static int wm8904_sendbuffer(FAR struct wm8904_dev_s *priv)
|
||||
return ret;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: wm8904_interrupt
|
||||
*
|
||||
* This is the ISR that services the WM8904 pin from the WM8904, which
|
||||
* indicates the chip is ready to receive additional data. We use it to
|
||||
* send a message to our worker thread message queue so it knows to wake
|
||||
* up and send more data.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static int wm8904_interrupt(FAR const struct wm8904_lower_s *lower,
|
||||
FAR void *arg)
|
||||
{
|
||||
FAR struct wm8904_dev_s *priv = (FAR struct wm8904_dev_s *)arg;
|
||||
struct audio_msg_s msg;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(lower && priv);
|
||||
|
||||
/* Create a message and send it to the worker thread */
|
||||
/* REVISIT this */
|
||||
|
||||
if (priv->running)
|
||||
{
|
||||
msg.msgId = AUDIO_MSG_DATA_REQUEST;
|
||||
ret = mq_send(priv->mq, &msg, sizeof(msg), CONFIG_WM8904_MSG_PRIO);
|
||||
if (ret < 0)
|
||||
{
|
||||
audlldbg("ERROR: mq_send failed: %d\n", errno);
|
||||
}
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: wm8904_workerthread
|
||||
*
|
||||
* This is the thread that feeds data to the chip and keeps the audio
|
||||
* stream going.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void *wm8904_workerthread(pthread_addr_t pvarg)
|
||||
{
|
||||
FAR struct wm8904_dev_s *priv = (struct wm8904_dev_s *) pvarg;
|
||||
struct audio_msg_s msg;
|
||||
FAR struct ap_buffer_s *apb;
|
||||
int msglen;
|
||||
int prio;
|
||||
|
||||
audvdbg("Entry\n");
|
||||
|
||||
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
|
||||
priv->terminating = false;
|
||||
#endif
|
||||
|
||||
/* Mark ourself as running and make sure that WM8904 interrupts are
|
||||
* enabled.
|
||||
*/
|
||||
|
||||
priv->running = true;
|
||||
WM8904_ENABLE(priv->lower);
|
||||
wm8904_setvolume(priv, priv->volume, false);
|
||||
|
||||
/* 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 send more audio buffers to the WM8904 */
|
||||
|
||||
wm8904_sendbuffer(priv);
|
||||
}
|
||||
|
||||
/* Wait for messages from our message queue */
|
||||
|
||||
msglen = mq_receive(priv->mq, &msg, sizeof(msg), &prio);
|
||||
|
||||
/* Handle the case when we return with no message */
|
||||
|
||||
if (msglen < sizeof(struct audio_msg_s))
|
||||
{
|
||||
auddbg("ERROR: Message too small: %d\n", msglen);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Process the message */
|
||||
|
||||
switch (msg.msgId)
|
||||
{
|
||||
/* The ISR has requested more data. We will catch this case at
|
||||
* the top of the loop.
|
||||
*/
|
||||
|
||||
case AUDIO_MSG_DATA_REQUEST:
|
||||
audvdbg("AUDIO_MSG_DATA_REQUEST\n");
|
||||
break;
|
||||
|
||||
/* Stop the playback */
|
||||
|
||||
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
|
||||
case AUDIO_MSG_STOP:
|
||||
/* Indicate that we are terminating */
|
||||
|
||||
audvdbg("AUDIO_MSG_STOP: Terminating\n");
|
||||
priv->terminating = true;
|
||||
break;
|
||||
#endif
|
||||
|
||||
/* We have a new buffer to send. We will catch this case at
|
||||
* the top of the loop.
|
||||
*/
|
||||
|
||||
case AUDIO_MSG_ENQUEUE:
|
||||
audvdbg("AUDIO_MSG_ENQUEUE\n");
|
||||
break;
|
||||
|
||||
/* We will wake up from the I2S callback with this message */
|
||||
|
||||
case AUDIO_MSG_COMPLETE:
|
||||
audvdbg("AUDIO_MSG_COMPLETE\n");
|
||||
wm8904_returnbuffers(priv);
|
||||
break;
|
||||
|
||||
default:
|
||||
auddbg("ERROR: Ignoring message ID %d\n", msg.msgId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Disable the WM8904 interrupt */
|
||||
|
||||
WM8904_DISABLE(priv->lower);
|
||||
|
||||
/* Mute the volume and disable the FLL output */
|
||||
|
||||
wm8904_setvolume(priv, priv->volume, true);
|
||||
wm8904_writereg(priv, WM8904_FLL_CTRL1, 0);
|
||||
wm8904_writereg(priv, WM8904_DUMMY, 0x55aa);
|
||||
|
||||
/* Return any pending buffers in our pending queue */
|
||||
|
||||
wm8904_takesem(&priv->pendsem);
|
||||
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
|
||||
priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK);
|
||||
#endif
|
||||
}
|
||||
|
||||
wm8904_givesem(&priv->pendsem);
|
||||
|
||||
/* Return any pending buffers in our done queue */
|
||||
|
||||
wm8904_returnbuffers(priv);
|
||||
|
||||
/* Close the message queue */
|
||||
|
||||
mq_close(priv->mq);
|
||||
mq_unlink(priv->mqname);
|
||||
priv->mq = NULL;
|
||||
|
||||
/* 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
|
||||
priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK);
|
||||
#endif
|
||||
|
||||
audvdbg("Exit\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: wm8904_start
|
||||
*
|
||||
@ -1796,7 +1651,9 @@ static int wm8904_resume(FAR struct audio_lowerhalf_s *dev)
|
||||
/* Enable interrupts to allow sampling data */
|
||||
|
||||
wm8904_sendbuffer(priv);
|
||||
#ifdef WM8904_USE_FFLOCK_INT
|
||||
WM8904_ENABLE(priv->lower);
|
||||
#endif
|
||||
}
|
||||
|
||||
return OK;
|
||||
@ -2005,6 +1862,264 @@ static int wm8904_release(FAR struct audio_lowerhalf_s *dev)
|
||||
return OK;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: wm8904_interrupt_work
|
||||
*
|
||||
* Description:
|
||||
* WM8904 interrupt actions cannot be performed in the interrupt handler
|
||||
* because I2C access is not possible in that context. Instead, all I2C
|
||||
* operations are deferred to the work queue.
|
||||
*
|
||||
* Assumptions:
|
||||
* WM8904 interrupts were disabled in the interrupt handler.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef WM8904_USE_FFLOCK_INT
|
||||
static void wm8904_interrupt_work(FAR void *arg)
|
||||
{
|
||||
FAR struct wm8904_dev_s *priv = (FAR struct wm8904_dev_s *)arg;
|
||||
uint16_t regval;
|
||||
|
||||
DEBUGASSERT(priv && priv->lower);
|
||||
|
||||
/* Sample the interrupt status */
|
||||
|
||||
regval = wm8904_readreg(priv, WM8904_INT_STATUS);
|
||||
audvdbg("INT_STATUS: %04x\n", regval);
|
||||
|
||||
/* Check for the FLL lock interrupt. We are sloppy here since at
|
||||
* present, only the FLL lock interrupt is used.
|
||||
*/
|
||||
|
||||
DEBUGASSERT((regval & WM8904_FLL_LOCK_INT) != 0 && !priv->locked);
|
||||
UNUSED(regval);
|
||||
|
||||
priv->locked = true;
|
||||
|
||||
/* Clear all pending interrupts by write 1's to the interrupt status
|
||||
* register.
|
||||
*
|
||||
* REVISIT: Since I2C is slow and not atomic with respect to WM8904 event,
|
||||
* could this not cause the lost of interrupts?
|
||||
*/
|
||||
|
||||
wm8904_writereg(priv, WM8904_INT_STATUS, WM8904_ALL_INTS);
|
||||
|
||||
/* Disable further FLL lock interrupts. We are sloppy here since at
|
||||
* present, only the FLL lock interrupt is used.
|
||||
*/
|
||||
|
||||
wm8904_writereg(priv, WM8904_INT_MASK, WM8904_ALL_INTS);
|
||||
|
||||
#ifdef WM8904_USE_FFLOCK_INT
|
||||
/* Re-enable WM8904 interrupts */
|
||||
|
||||
WM8904_ENABLE(priv->lower);
|
||||
#endif
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Name: wm8904_interrupt
|
||||
*
|
||||
* Description:
|
||||
* This is the ISR that services the GPIO1/IRQ pin from the WM8904. It
|
||||
* signals WM8904 events such FLL lock.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
#ifdef WM8904_USE_FFLOCK_INT
|
||||
static int wm8904_interrupt(FAR const struct wm8904_lower_s *lower,
|
||||
FAR void *arg)
|
||||
{
|
||||
FAR struct wm8904_dev_s *priv = (FAR struct wm8904_dev_s *)arg;
|
||||
int ret;
|
||||
|
||||
DEBUGASSERT(lower && priv);
|
||||
|
||||
/* Disable further interrupts and perform all interrupt related activities
|
||||
* on the work thread. There is nothing that we can do from the interrupt
|
||||
* handler because we cannot perform I2C operations here.
|
||||
*/
|
||||
|
||||
WM8904_DISABLE(priv->lower);
|
||||
|
||||
DEBUGASSERT(work_available(&priv->work));
|
||||
ret = work_queue(LPWORK, &priv->work, wm8904_interrupt_work, priv, 0);
|
||||
if (ret < 0)
|
||||
{
|
||||
audlldbg("ERROR: Failed to schedule work\n");
|
||||
}
|
||||
|
||||
return OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
/****************************************************************************
|
||||
* Name: wm8904_workerthread
|
||||
*
|
||||
* This is the thread that feeds data to the chip and keeps the audio
|
||||
* stream going.
|
||||
*
|
||||
****************************************************************************/
|
||||
|
||||
static void *wm8904_workerthread(pthread_addr_t pvarg)
|
||||
{
|
||||
FAR struct wm8904_dev_s *priv = (struct wm8904_dev_s *) pvarg;
|
||||
struct audio_msg_s msg;
|
||||
FAR struct ap_buffer_s *apb;
|
||||
int msglen;
|
||||
int prio;
|
||||
|
||||
audvdbg("Entry\n");
|
||||
|
||||
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
|
||||
priv->terminating = false;
|
||||
#endif
|
||||
|
||||
/* Mark ourself as running and make sure that WM8904 interrupts are
|
||||
* enabled.
|
||||
*/
|
||||
|
||||
priv->running = true;
|
||||
#ifdef WM8904_USE_FFLOCK_INT
|
||||
WM8904_ENABLE(priv->lower);
|
||||
#endif
|
||||
wm8904_setvolume(priv, priv->volume, false);
|
||||
|
||||
/* 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 send more audio buffers to the WM8904 */
|
||||
|
||||
wm8904_sendbuffer(priv);
|
||||
}
|
||||
|
||||
/* Wait for messages from our message queue */
|
||||
|
||||
msglen = mq_receive(priv->mq, &msg, sizeof(msg), &prio);
|
||||
|
||||
/* Handle the case when we return with no message */
|
||||
|
||||
if (msglen < sizeof(struct audio_msg_s))
|
||||
{
|
||||
auddbg("ERROR: Message too small: %d\n", msglen);
|
||||
continue;
|
||||
}
|
||||
|
||||
/* Process the message */
|
||||
|
||||
switch (msg.msgId)
|
||||
{
|
||||
/* The ISR has requested more data. We will catch this case at
|
||||
* the top of the loop.
|
||||
*/
|
||||
|
||||
case AUDIO_MSG_DATA_REQUEST:
|
||||
audvdbg("AUDIO_MSG_DATA_REQUEST\n");
|
||||
break;
|
||||
|
||||
/* Stop the playback */
|
||||
|
||||
#ifndef CONFIG_AUDIO_EXCLUDE_STOP
|
||||
case AUDIO_MSG_STOP:
|
||||
/* Indicate that we are terminating */
|
||||
|
||||
audvdbg("AUDIO_MSG_STOP: Terminating\n");
|
||||
priv->terminating = true;
|
||||
break;
|
||||
#endif
|
||||
|
||||
/* We have a new buffer to send. We will catch this case at
|
||||
* the top of the loop.
|
||||
*/
|
||||
|
||||
case AUDIO_MSG_ENQUEUE:
|
||||
audvdbg("AUDIO_MSG_ENQUEUE\n");
|
||||
break;
|
||||
|
||||
/* We will wake up from the I2S callback with this message */
|
||||
|
||||
case AUDIO_MSG_COMPLETE:
|
||||
audvdbg("AUDIO_MSG_COMPLETE\n");
|
||||
wm8904_returnbuffers(priv);
|
||||
break;
|
||||
|
||||
default:
|
||||
auddbg("ERROR: Ignoring message ID %d\n", msg.msgId);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
/* Disable the WM8904 interrupt */
|
||||
|
||||
WM8904_DISABLE(priv->lower);
|
||||
|
||||
/* Mute the volume and disable the FLL output */
|
||||
|
||||
wm8904_setvolume(priv, priv->volume, true);
|
||||
wm8904_writereg(priv, WM8904_FLL_CTRL1, 0);
|
||||
wm8904_writereg(priv, WM8904_DUMMY, 0x55aa);
|
||||
|
||||
/* Return any pending buffers in our pending queue */
|
||||
|
||||
wm8904_takesem(&priv->pendsem);
|
||||
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
|
||||
priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK);
|
||||
#endif
|
||||
}
|
||||
|
||||
wm8904_givesem(&priv->pendsem);
|
||||
|
||||
/* Return any pending buffers in our done queue */
|
||||
|
||||
wm8904_returnbuffers(priv);
|
||||
|
||||
/* Close the message queue */
|
||||
|
||||
mq_close(priv->mq);
|
||||
mq_unlink(priv->mqname);
|
||||
priv->mq = NULL;
|
||||
|
||||
/* 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
|
||||
priv->dev.upper(priv->dev.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK);
|
||||
#endif
|
||||
|
||||
audvdbg("Exit\n");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
* Name: wm8904_audio_output
|
||||
*
|
||||
@ -2133,7 +2248,10 @@ static void wm8904_audio_output(FAR struct wm8904_dev_s *priv)
|
||||
|
||||
/* Audio Interface 3
|
||||
*
|
||||
* Set LRCLK as an output with rate = BCLK / 64
|
||||
* Set LRCLK as an output with rate = BCLK / 64. This is a constant value
|
||||
* used with audio. Since I2S will send a word on each edge of LRCLK (after
|
||||
* a delay), this essentially means that each audio frame is 32 bits in
|
||||
* length;
|
||||
*/
|
||||
|
||||
regval = WM8904_LRCLK_DIR | WM8904_LRCLK_RATE(64);
|
||||
@ -2179,12 +2297,6 @@ static void wm8904_audio_output(FAR struct wm8904_dev_s *priv)
|
||||
|
||||
regval = WM8904_CP_DYN_PWR | 0x0004;
|
||||
wm8904_writereg(priv, WM8904_CLASS_W0, regval);
|
||||
|
||||
/* Configure the FLL */
|
||||
|
||||
wm8904_setbitrate(priv);
|
||||
wm8904_setlrclock(priv);
|
||||
wm8904_writereg(priv, WM8904_DUMMY, 0x55aa);
|
||||
}
|
||||
|
||||
/****************************************************************************
|
||||
@ -2192,7 +2304,8 @@ static void wm8904_audio_output(FAR struct wm8904_dev_s *priv)
|
||||
*
|
||||
* Description:
|
||||
* Initialize and configure the WM8904 device as an audio output device
|
||||
* (Right input only).
|
||||
* (Right input only). wm8904_audio_output() must be called first, this
|
||||
* function then modifies the configuration to support audio input.
|
||||
*
|
||||
* Input Parameters:
|
||||
* prive - A reference to the driver state structure
|
||||
@ -2304,12 +2417,39 @@ FAR struct audio_lowerhalf_s *
|
||||
/* Configure the WM8904 hardware as an audio input device */
|
||||
|
||||
wm8904_audio_output(priv);
|
||||
wm8904_dump_registers(&priv->dev, "After configuration");
|
||||
wm8904_clock_analysis(&priv->dev, "After configuration");
|
||||
|
||||
/* Attach our ISR to this device */
|
||||
#ifdef WM8904_USE_FFLOCK_INT
|
||||
/* Configure GPIO1 as an IRQ
|
||||
*
|
||||
* WM8904_GPIO1_PU=0 : No pull-up
|
||||
* WM8904_GPIO1_PD=1 : Pulled-down
|
||||
* WM8904_GPIO1_SEL_IRQ : Configured as IRQ
|
||||
*/
|
||||
|
||||
regval = (WM8904_GPIO1_SEL_IRQ | WM8904_GPIO1_PD);
|
||||
wm8904_writereg(priv, WM8904_GPIO_CTRL1, regval);
|
||||
|
||||
/* Attach our handler to the GPIO1/IRQ interrupt */
|
||||
|
||||
WM8904_ATTACH(lower, wm8904_interrupt, priv);
|
||||
|
||||
/* Configure interrupts. wm8904_setbitrate() depends on FLL interrupts. */
|
||||
|
||||
wm8904_writereg(priv, WM8904_INT_STATUS, WM8904_ALL_INTS);
|
||||
wm8904_writereg(priv, WM8904_INT_MASK, WM8904_ALL_INTS);
|
||||
wm8904_writereg(priv, WM8904_INT_POL, 0);
|
||||
wm8904_writereg(priv, WM8904_INT_DEBOUNCE, WM8904_ALL_INTS);
|
||||
#endif
|
||||
|
||||
/* Configure the FLL and the LRCLK */
|
||||
|
||||
wm8904_setbitrate(priv);
|
||||
wm8904_writereg(priv, WM8904_DUMMY, 0x55aa);
|
||||
|
||||
/* Dump some information and return the device instance */
|
||||
|
||||
wm8904_dump_registers(&priv->dev, "After configuration");
|
||||
wm8904_clock_analysis(&priv->dev, "After configuration");
|
||||
return &priv->dev;
|
||||
}
|
||||
|
||||
|
@ -50,6 +50,7 @@
|
||||
#include <pthread.h>
|
||||
#include <mqueue.h>
|
||||
|
||||
#include <nuttx/wqueue.h>
|
||||
#include <nuttx/fs/ioctl.h>
|
||||
|
||||
#ifdef CONFIG_AUDIO
|
||||
@ -57,6 +58,15 @@
|
||||
/****************************************************************************
|
||||
* Pre-Processor Definitions
|
||||
****************************************************************************/
|
||||
/* So far, I have not been able to get FLL lock interrupts. Worse, I have
|
||||
* been able to get the FLL to claim that it is locked at all even when
|
||||
* polling. What am I doing wrong?
|
||||
*
|
||||
* Hmmm.. seems unnecessary anyway
|
||||
*/
|
||||
|
||||
#undef WM8904_USE_FFLOCK_INT
|
||||
#undef WM8904_USE_FFLOCK_POLL
|
||||
|
||||
/* Registers Addresses ******************************************************/
|
||||
|
||||
@ -156,7 +166,9 @@
|
||||
#define WM8904_EQ22 0x9b /* EQ22 */
|
||||
#define WM8904_EQ23 0x9c /* EQ23 */
|
||||
#define WM8904_EQ24 0x9d /* EQ24 */
|
||||
#define WM8904_CTRLIF_TEST_1 0xa1 /* Control Interface Test 1 */
|
||||
#define WM8904_ADC_TEST 0xc6 /* ADC Test */
|
||||
#define WM8904_ANA_OUT_BIAS_0 0xCC /* Analogue Output Bias 0 */
|
||||
#define WM8904_FLL_NCO_TEST0 0xf7 /* FLL NCO Test 0 */
|
||||
#define WM8904_FLL_NCO_TEST1 0xf8 /* FLL NCO Test 1 */
|
||||
|
||||
@ -261,8 +273,10 @@
|
||||
#define WM8904_EQ22_DEFAULT 0x0564
|
||||
#define WM8904_EQ23_DEFAULT 0x0559
|
||||
#define WM8904_EQ24_DEFAULT 0x4000
|
||||
#define WM8904_ADC_TEST_DEFAULT 0x0000
|
||||
#define WM8904_CTRLIF_TEST_1_DEFAULT 0x0000
|
||||
#define WM8904_FLL_NCO_TEST0_DEFAULT 0x0000
|
||||
#define WM8904_ANA_OUT_BIAS_0_DEFAULT 0x0000
|
||||
#define WM8904_ADC_TEST_DEFAULT 0x0000
|
||||
#define WM8904_FLL_NCO_TEST1_DEFAULT 0x0019
|
||||
|
||||
/* Register Bit Definitions *************************************************/
|
||||
@ -949,16 +963,18 @@
|
||||
/* 0x81 Interrupt Polarity */
|
||||
/* 0x82 Interrupt Debounce */
|
||||
|
||||
#define WM8904_GPIO_BCLK_EINT (1 << 9) /* Bit 9: GPIO4 interrupt */
|
||||
#define WM8904_WSEQ_EINT (1 << 8) /* Bit 8: Write Sequence interrupt */
|
||||
#define WM8904_GPIO3_EINT (1 << 7) /* Bit 7: GPIO3 interrupt */
|
||||
#define WM8904_GPIO2_EINT (1 << 6) /* Bit 6: GPIO2 interrupt */
|
||||
#define WM8904_GPIO1_EINT (1 << 5) /* Bit 5: GPIO1 interrupt */
|
||||
#define WM8904_GPI8_EINT (1 << 4) /* Bit 4: GPI8 interrupt */
|
||||
#define WM8904_GPI7_EINT (1 << 3) /* Bit 3: GPI7 interrupt */
|
||||
#define WM8904_FLL_LOCK_EINT (1 << 2) /* Bit 2: FLL Lock interrupt */
|
||||
#define WM8904_MIC_SHRT_EINT (1 << 1) /* Bit 1: MICBIAS short circuit interrupt */
|
||||
#define WM8904_MIC_DET_EINT (1 << 0) /* Bit 0: MICBIAS short circuit interrupt */
|
||||
#define WM8904_GPIO_BCLK_INT (1 << 9) /* Bit 9: GPIO4 interrupt */
|
||||
#define WM8904_WSEQ_INT (1 << 8) /* Bit 8: Write Sequence interrupt */
|
||||
#define WM8904_GPIO3_INT (1 << 7) /* Bit 7: GPIO3 interrupt */
|
||||
#define WM8904_GPIO2_INT (1 << 6) /* Bit 6: GPIO2 interrupt */
|
||||
#define WM8904_GPIO1_INT (1 << 5) /* Bit 5: GPIO1 interrupt */
|
||||
#define WM8904_GPI8_INT (1 << 4) /* Bit 4: GPI8 interrupt */
|
||||
#define WM8904_GPI7_INT (1 << 3) /* Bit 3: GPI7 interrupt */
|
||||
#define WM8904_FLL_LOCK_INT (1 << 2) /* Bit 2: FLL Lock interrupt */
|
||||
#define WM8904_MIC_SHRT_INT (1 << 1) /* Bit 1: MICBIAS short circuit interrupt */
|
||||
#define WM8904_MIC_DET_INT (1 << 0) /* Bit 0: MICBIAS short circuit interrupt */
|
||||
|
||||
#define WM8904_ALL_INTS (0x03ff)
|
||||
|
||||
/* 0x7f Interrupt Status (only) */
|
||||
|
||||
@ -971,16 +987,30 @@
|
||||
/* 0x87-0x8b EQ2-EQ6: 5 bit equalizer value */
|
||||
/* 0x8c-0x9d EQ7-EQ24: 16-bit equalizer value */
|
||||
|
||||
/* 0xa1 Control Interface Test 1 */
|
||||
|
||||
#define WM8904_USER_KEY 0x0002
|
||||
|
||||
/* 0xc6 ADC Test */
|
||||
|
||||
#define WM8904_ADC_128_OSR_TST_MODE (1 << 2) /* Bit 2: ADC bias control (1) */
|
||||
#define WM8904_ADC_BIASX1P5 (1 << 0) /* Bit 0: ADC bias control (2) */
|
||||
|
||||
/* 0xcc Analogue Output Bias 0 */
|
||||
|
||||
#define WM8904_ANA_OUT_BIAS_SHIFT (4) /* Bits 4-6: */
|
||||
#define WM8904_ANA_OUT_BIAS_MASK (7 << WM8904_ANA_OUT_BIAS_SHIFT)
|
||||
# define WM8904_ANA_OUT_BIAS(n) ((uint16_t)(n) << WM8904_ANA_OUT_BIAS_SHIFT)
|
||||
|
||||
/* 0xf7 FLL NCO Test 0 */
|
||||
|
||||
#define WM8904_FLL_FRC_NCO (1 << 0) /* Bit 0: FLL Forced control select */
|
||||
|
||||
/* 0xf8 FLL NCO Test 1 (16-bit FLL forced oscillator value */
|
||||
/* 0xf8 FLL NCO Test 1 */
|
||||
|
||||
#define WM8904FLL_FRC_NCO_SHIFT (0) /* Bits 0-5: FLL forced oscillator value */
|
||||
#define WM8904FLL_FRC_NCO_MASK (0x3f << WM8904FLL_FRC_NCO_SHIFT)
|
||||
# define WM8904FLL_FRC_NCO_VAL(n) ((uint16_t)(n) << WM8904FLL_FRC_NCO_SHIFT)
|
||||
|
||||
/* FLL Configuration *********************************************************/
|
||||
/* Default FLL configuration */
|
||||
@ -989,8 +1019,6 @@
|
||||
#define WM8904_DEFAULT_NCHANNELS 1
|
||||
#define WM8904_DEFAULT_BPSAMP 16
|
||||
|
||||
#define WM8904_STARTBITS 2
|
||||
|
||||
#define WM8904_NFLLRATIO_DIV1 0
|
||||
#define WM8904_NFLLRATIO_DIV2 1
|
||||
#define WM8904_NFLLRATIO_DIV4 2
|
||||
@ -1045,6 +1073,9 @@ struct wm8904_dev_s
|
||||
pthread_t threadid; /* ID of our thread */
|
||||
uint32_t bitrate; /* Actual programmed bit rate */
|
||||
sem_t pendsem; /* Protect pendq */
|
||||
#ifdef WM8904_USE_FFLOCK_INT
|
||||
struct work_s work; /* Interrupt work */
|
||||
#endif
|
||||
uint16_t samprate; /* Configured samprate (samples/sec) */
|
||||
#ifndef CONFIG_AUDIO_EXCLUDE_VOLUME
|
||||
#ifndef CONFIG_AUDIO_EXCLUDE_BALANCE
|
||||
@ -1055,6 +1086,9 @@ struct wm8904_dev_s
|
||||
uint8_t nchannels; /* Number of channels (1 or 2) */
|
||||
uint8_t bpsamp; /* Bits per sample (8 or 16) */
|
||||
volatile uint8_t inflight; /* Number of audio buffers in-flight */
|
||||
#ifdef WM8904_USE_FFLOCK_INT
|
||||
volatile bool locked; /* FLL is locked */
|
||||
#endif
|
||||
bool running; /* True: Worker thread is running */
|
||||
bool paused; /* True: Playing is paused */
|
||||
bool mute; /* True: Output is muted */
|
||||
|
Loading…
Reference in New Issue
Block a user