From dc8b040573a480c3e03eff49d9d8a151ebb4b809 Mon Sep 17 00:00:00 2001 From: Gregory Nutt Date: Mon, 4 Aug 2014 15:08:20 -0600 Subject: [PATCH] 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). --- drivers/audio/wm8904.c | 638 +++++++++++++++++++++++++---------------- drivers/audio/wm8904.h | 62 +++- 2 files changed, 437 insertions(+), 263 deletions(-) diff --git a/drivers/audio/wm8904.c b/drivers/audio/wm8904.c index 323ec05194..3b7c21da5c 100644 --- a/drivers/audio/wm8904.c +++ b/drivers/audio/wm8904.c @@ -61,6 +61,7 @@ #include #include +#include #include #include #include @@ -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; } diff --git a/drivers/audio/wm8904.h b/drivers/audio/wm8904.h index 9b2059c6c5..47544d306a 100644 --- a/drivers/audio/wm8904.h +++ b/drivers/audio/wm8904.h @@ -50,6 +50,7 @@ #include #include +#include #include #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 */