/**************************************************************************** * drivers/audio/vs1053.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 #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "vs1053.h" /**************************************************************************** * Pre-processor Definitions ****************************************************************************/ #ifndef CONFIG_VS1053_SPIMODE # define CONFIG_VS1053_SPIMODE SPIDEV_MODE0 #endif #ifndef CONFIG_VS1053_XTALI # define CONFIG_VS1053_XTALI 12288000 #endif #ifndef CONFIG_VS1053_MP3_DECODE_FREQ # define CONFIG_VS1053_MP3_DECODE_FREQ 43000000 #endif #ifndef CONFIG_VS1053_MSG_PRIO # define CONFIG_VS1053_MSG_PRIO 1 #endif #ifndef CONFIG_VS1053_BUFFER_SIZE # define CONFIG_VS1053_BUFFER_SIZE 8192 #endif #ifndef CONFIG_VS1053_NUM_BUFFERS # define CONFIG_VS1053_NUM_BUFFERS 2 #endif #ifndef CONFIG_VS1053_WORKER_STACKSIZE # define CONFIG_VS1053_WORKER_STACKSIZE 768 #endif #define VS1053_DUMMY 0xFF #define VS1053_DEFAULT_XTALI 12288000 #define VS1053_DATA_FREQ 20000000 #define VS1053_RST_USECS 2000 /**************************************************************************** * Private Types ****************************************************************************/ struct vs1053_struct_s { /* We are an audio lower half driver */ struct audio_lowerhalf_s lower; /* Our specific driver data goes here */ const FAR struct vs1053_lower_s *hw_lower; /* Pointer to the hardware lower functions */ FAR struct spi_dev_s *spi; /* Pointer to the SPI bus */ FAR struct ap_buffer_s *apb; /* Pointer to the buffer we are processing */ struct dq_queue_s apbq; /* Our queue for enqueued buffers */ unsigned long spi_freq; /* Frequency to run the SPI bus at. */ unsigned long chip_freq; /* Current chip frequency */ struct file mq; /* Message queue for receiving messages */ char mqname[16]; /* Our message queue name */ pthread_t threadid; /* ID of our thread */ sem_t apbq_sem; /* Audio Pipeline Buffer Queue sem access */ #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME int16_t volume; /* Current volume level */ #ifndef CONFIG_AUDIO_EXCLUDE_BALANCE int16_t balance; /* Current balance level */ #endif /* CONFIG_AUDIO_EXCLUDE_BALANCE */ #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */ #ifndef CONFIG_AUDIO_EXCLUDE_TONE uint8_t bass; /* Bass level */ uint8_t treble; /* Bass level */ #endif uint16_t endfillbytes; uint8_t endfillchar; /* Fill char to send when no more data */ bool running; bool paused; bool endmode; #ifndef CONFIG_AUDIO_EXCLUDE_STOP bool cancelmode; #endif bool busy; /* Set true when device reserved */ }; /**************************************************************************** * Private Function Prototypes ****************************************************************************/ static int vs1053_getcaps(FAR struct audio_lowerhalf_s *lower, int type, FAR struct audio_caps_s *caps); static int vs1053_shutdown(FAR struct audio_lowerhalf_s *lower); #ifdef CONFIG_AUDIO_MULTI_SESSION static int vs1053_configure(FAR struct audio_lowerhalf_s *lower, FAR void *session, FAR const struct audio_caps_s *caps); static int vs1053_start(FAR struct audio_lowerhalf_s *lower, FAR void *session); #ifndef CONFIG_AUDIO_EXCLUDE_STOP static int vs1053_stop(FAR struct audio_lowerhalf_s *lower, FAR void *session); #endif #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME static int vs1053_pause(FAR struct audio_lowerhalf_s *lower, FAR void *session); static int vs1053_resume(FAR struct audio_lowerhalf_s *lower, FAR void *session); #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ static int vs1053_reserve(FAR struct audio_lowerhalf_s *lower, FAR void** session); static int vs1053_release(FAR struct audio_lowerhalf_s *lower, FAR void *session); #else static int vs1053_configure(FAR struct audio_lowerhalf_s *lower, FAR const struct audio_caps_s *caps); static int vs1053_start(FAR struct audio_lowerhalf_s *lower); #ifndef CONFIG_AUDIO_EXCLUDE_STOP static int vs1053_stop(FAR struct audio_lowerhalf_s *lower); #endif #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME static int vs1053_pause(FAR struct audio_lowerhalf_s *lower); static int vs1053_resume(FAR struct audio_lowerhalf_s *lower); #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ static int vs1053_reserve(FAR struct audio_lowerhalf_s *lower); static int vs1053_release(FAR struct audio_lowerhalf_s *lower); #endif /* CONFIG_AUDIO_MULTI_SESION */ static int vs1053_enqueuebuffer(FAR struct audio_lowerhalf_s *lower, FAR struct ap_buffer_s *apb); static int vs1053_cancelbuffer(FAR struct audio_lowerhalf_s *lower, FAR struct ap_buffer_s *apb); static int vs1053_ioctl(FAR struct audio_lowerhalf_s *lower, int cmd, unsigned long arg); /**************************************************************************** * Private Data ****************************************************************************/ static const struct audio_ops_s g_audioops = { vs1053_getcaps, /* getcaps */ vs1053_configure, /* configure */ vs1053_shutdown, /* shutdown */ vs1053_start, /* start */ #ifndef CONFIG_AUDIO_EXCLUDE_STOP vs1053_stop, /* stop */ #endif #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME vs1053_pause, /* pause */ vs1053_resume, /* resume */ #endif NULL, /* alloc_buffer */ NULL, /* free_buffer */ vs1053_enqueuebuffer, /* enqueue_buffer */ vs1053_cancelbuffer, /* cancel_buffer */ vs1053_ioctl, /* ioctl */ NULL, /* read */ NULL, /* write */ vs1053_reserve, /* reserve */ vs1053_release /* release */ }; /* Volume control log table. This table is in increments of 2% of * requested volume level and is the register value that should be * programmed to the VS1053 to achieve that volume percentage. */ #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME static const uint8_t g_logtable [] = { 254, 170, 140, 122, 110, /* 0 - 8 */ 100, 92, 85, 80, 74, /* 10 - 18 */ 70, 66, 62, 59, 55, /* 20 - 28 */ 52, 49, 47, 44, 42, /* 30 - 38 */ 40, 38, 36, 34, 32, /* 40 - 48 */ 30, 28, 27, 25, 24, /* 50 - 58 */ 22, 21, 19, 18, 17, /* 60 - 68 */ 15, 14, 13, 12, 11, /* 70 - 78 */ 10, 9, 8, 7, 6, /* 80 - 88 */ 5, 4, 3, 2, 1, /* 90 - 98 */ 0 /* 100 */ }; #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */ /**************************************************************************** * Private Functions ****************************************************************************/ /**************************************************************************** * Name: vs1053_spi_lock ****************************************************************************/ static void vs1053_spi_lock(FAR struct spi_dev_s *dev, unsigned long freq_mhz) { /* On SPI buses where there are multiple devices, it will be necessary to * lock SPI to have exclusive access to the buses for a sequence of * transfers. The bus should be locked before the chip is selected. * * This is a blocking call and will not return until we have exclusive * access to the SPI bus. * We will retain that exclusive access until the bus is unlocked. */ SPI_LOCK(dev, true); /* After locking the SPI bus, the we also need call the setfrequency, * setbits, and setmode methods to make sure that the SPI is properly * configured for the device. * If the SPI bus is being shared, then it may have been left in an * incompatible state. */ SPI_SETMODE(dev, CONFIG_VS1053_SPIMODE); SPI_SETBITS(dev, 8); SPI_HWFEATURES(dev, 0); SPI_SETFREQUENCY(dev, freq_mhz); } /**************************************************************************** * Name: vs1053_spi_unlock ****************************************************************************/ static inline void vs1053_spi_unlock(FAR struct spi_dev_s *dev) { SPI_LOCK(dev, false); } /**************************************************************************** * Name: vs1053_readreg - Read the specified 16-bit register from the * VS1053 device. Caller must hold the SPI lock. ****************************************************************************/ static uint16_t vs1053_readreg(FAR struct vs1053_struct_s *dev, uint8_t reg) { uint16_t ret; FAR struct spi_dev_s *spi = dev->spi; /* Select the AUDIO_CTRL device on the SPI bus */ SPI_SELECT(spi, SPIDEV_AUDIO_CTRL(0), true); /* Send the WRITE command followed by the address */ SPI_SEND(spi, VS1053_OPCODE_READ); SPI_SEND(spi, reg); /* Now read the 16-bit value */ ret = SPI_SEND(spi, VS1053_DUMMY) << 8; ret |= SPI_SEND(spi, VS1053_DUMMY); /* Deselect the CODEC */ SPI_SELECT(spi, SPIDEV_AUDIO_CTRL(0), false); return ret; } /**************************************************************************** * Name: vs1053_writereg - Write the specified 16-bit register to the * VS1053 device. Caller must hold the SPI lock. ****************************************************************************/ static void vs1053_writereg(FAR struct vs1053_struct_s *dev, uint8_t reg, uint16_t val) { FAR struct spi_dev_s *spi = dev->spi; /* Select the AUDIO_CTRL device on the SPI bus */ audinfo("Write Reg %d = 0x%0X\n", reg, val); SPI_SELECT(spi, SPIDEV_AUDIO_CTRL(0), true); /* Send the WRITE command followed by the address */ SPI_SEND(spi, VS1053_OPCODE_WRITE); SPI_SEND(spi, reg); /* Now read the 16-bit value */ SPI_SEND(spi, val >> 8); SPI_SEND(spi, val & 0xff); /* Deselect the CODEC */ SPI_SELECT(spi, SPIDEV_AUDIO_CTRL(0), false); /* Short delay after a write for VS1053 processing time */ nxsig_usleep(10); } /**************************************************************************** * Name: vs1053_setfrequency * * Description: Get the audio device capabilities * ****************************************************************************/ static int vs1053_setfrequency(FAR struct vs1053_struct_s *dev, uint32_t freq) { double factor; uint16_t reg; uint8_t timeout; audinfo("Entry\n"); /* Calculate the clock divisor based on the input frequency */ factor = (double) freq / (double) CONFIG_VS1053_XTALI * 10.0 + 0.5; /* Check the input frequency against bounds */ if (factor > 50.0) { audinfo("Frequency too high! Limiting to XTALI * 5\n"); factor = 50.0; return -EINVAL; } if (factor < 10.0) { factor = 10.0; } /* Calculate the clock mulit register based on the factor */ if ((int) factor == 10) { reg = 0; } else { reg = (((int) factor - 15) / 5) << VS1053_SC_MULT_SHIFT; } /* Set the MULT_ADD factor to the max to allow the chip to dynamically * increase the frequency the maximum amount as needed */ reg |= (VS1053_SC_ADD_XTALI_X20 << VS1053_SC_ADD_SHIFT); /* If we aren't running with a 12.228Mhz input crystal, then we * must tell the chip what the frequency is */ if (CONFIG_VS1053_XTALI != VS1053_DEFAULT_XTALI) { /* Calculate register value based on equation: (XTALI - 8000000) / 4000 * per the datasheet. */ reg |= (CONFIG_VS1053_XTALI - 8000000) / 4000; } /* Now set the new clock multiplier register */ vs1053_writereg(dev, VS1053_SCI_CLOCKF, reg); /* Wait for DREQ to go active */ timeout = 200; while (!dev->hw_lower->read_dreq(dev->hw_lower) && timeout) { nxsig_usleep(1000); timeout--; } /* Update our internal variables */ dev->chip_freq = freq; dev->spi_freq = freq / 7; return OK; } /**************************************************************************** * Name: vs1053_logapprox - * Approximate the register value in .5 dB increments * level based on the percentage using a log table since * math libraries aren't available. ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME uint8_t vs1053_logapprox(int percent) { /* Check percentage for bounds */ if (percent >= 100) { return 0; } return (g_logtable[percent >> 1] + g_logtable[(percent + 1) >> 1]) >> 1; } #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */ /**************************************************************************** * Name: vs1053_setvolume - * Set the right and left volume values in the VS1053 * device based on the current volume and balance settings. ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME static void vs1053_setvolume(FAR struct vs1053_struct_s *dev) { FAR struct spi_dev_s *spi = dev->spi; uint32_t leftlevel; uint32_t rightlevel; uint8_t leftreg; uint8_t rightreg; /* Constrain balance */ #ifndef CONFIG_AUDIO_EXCLUDE_BALANCE if (dev->balance > 1000) { dev->balance = 1000; } /* Calculate the left channel volume level */ if (dev->balance <= 500) { leftlevel = dev->volume; } else if (dev->balance == 1000) { leftlevel = 0; } else { leftlevel = dev->volume * (1000 - dev->balance) / 500; } /* Calculate the right channel volume level */ if (dev->balance >= 500) { rightlevel = dev->volume; } else if (dev->balance == 0) { rightlevel = 0; } else { rightlevel = dev->volume * dev->balance / 500; } #else leftlevel = rightlevel = dev->volume; #endif /* Calculate the left and right register values */ /* The register sets the volume in dB which is a logrithmic scale, * so we must use log() to calculate the register value. */ leftreg = vs1053_logapprox(leftlevel / 10); rightreg = vs1053_logapprox(rightlevel / 10); /* Lock the SPI bus to get exclusive access to the chip. */ vs1053_spi_lock(spi, dev->spi_freq); vs1053_writereg(dev, VS1053_SCI_VOL, (leftreg << 8) | rightreg); vs1053_spi_unlock(spi); } #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */ /**************************************************************************** * Name: vs1053_setbass - Set the bass and treble level as specified in the * context's bass and treble variables.. * * The level and range are in whole percentage levels (0-100). * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_TONE static void vs1053_setbass(FAR struct vs1053_struct_s *dev) { FAR struct spi_dev_s *spi = dev->spi; int bass_range; int bass_boost; int treble_range; int treble_boost; /* Calculate range and boost based on level */ bass_boost = 15 * dev->bass / 100; bass_range = 15; treble_boost = 15 * dev->treble / 100; treble_range = 15; /* Lock the SPI bus to get exclsive access to the chip. */ vs1053_spi_lock(spi, dev->spi_freq); vs1053_writereg(dev, VS1053_SCI_BASS, (treble_boost << 12) | (treble_range << 8) | (bass_boost << 4) | bass_range); vs1053_spi_unlock(spi); } #endif /* CONFIG_AUDIO_EXCLUDE_TONE */ /**************************************************************************** * Name: vs1053_getcaps * * Description: Get the audio device capabilities * ****************************************************************************/ static int vs1053_getcaps(FAR struct audio_lowerhalf_s *lower, int type, FAR struct audio_caps_s *caps) { audinfo("Entry\n"); /* Validate the structure */ DEBUGASSERT(caps->ac_len >= sizeof(struct audio_caps_s)); /* Fill in the caller's structure based on requested info */ 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. */ caps->ac_channels = 2; /* Stereo output */ switch (caps->ac_subtype) { case AUDIO_TYPE_QUERY: /* The input formats we can decode / accept */ caps->ac_format.hw = 0 #ifdef CONFIG_AUDIO_FORMAT_AC3 | (1 << (AUDIO_FMT_AC3 - 1)) #endif #ifdef CONFIG_AUDIO_FORMAT_MP3 | (1 << (AUDIO_FMT_MP3 - 1)) #endif #ifdef CONFIG_AUDIO_FORMAT_WMA | (1 << (AUDIO_FMT_WMA - 1)) #endif #ifdef CONFIG_AUDIO_FORMAT_MIDI | (1 << (AUDIO_FMT_MIDI - 1)) #endif #ifdef CONFIG_AUDIO_FORMAT_PCM | (1 << (AUDIO_FMT_PCM - 1)) #endif #ifdef CONFIG_AUDIO_FORMAT_OGG_VORBIS | (1 << (AUDIO_FMT_OGG_VORBIS - 1)) #endif ; /* The types of audio units we implement */ caps->ac_controls.b[0] = AUDIO_TYPE_OUTPUT | AUDIO_TYPE_FEATURE | AUDIO_TYPE_PROCESSING; break; /* Report sub-formats for MIDI if requested */ #ifdef CONFIG_AUDIO_FORMAT_MIDI case AUDIO_FMT_MIDI: /* We only support Format 0 */ caps->ac_controls.b[0] = AUDIO_SUBFMT_MIDI_0; caps->ac_controls.b[1] = AUDIO_SUBFMT_END; break; #endif default: caps->ac_controls.b[0] = AUDIO_SUBFMT_END; break; } break; /* Provide capabilities of our OUTPUT unit */ case AUDIO_TYPE_OUTPUT: caps->ac_channels = 2; switch (caps->ac_subtype) { case AUDIO_TYPE_QUERY: /* Report the Sample rates we support */ caps->ac_controls.b[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; break; case AUDIO_FMT_MP3: case AUDIO_FMT_WMA: case AUDIO_FMT_PCM: /* Report the Bit rates we support. * The bit rate support is actually a complex function of the * format and selected sample rate, and the datasheet has * multiple tables to indicate the supported bit rate vs sample * rate vsformat. * The selected sample rate should be provided in the ac_format * field of the query, and only a single sample rate should be * given. */ /* TODO: Create a table or set of tables to report this! */ break; default: break; } break; /* Provide capabilities of our FEATURE units */ case AUDIO_TYPE_FEATURE: /* If the sub-type is UNDEF, * then report the Feature Units we support */ if (caps->ac_subtype == AUDIO_FU_UNDEF) { /* Fill in the ac_controls section with the * Feature Units we have */ caps->ac_controls.b[0] = AUDIO_FU_VOLUME | AUDIO_FU_BASS | AUDIO_FU_TREBLE; caps->ac_controls.b[1] = AUDIO_FU_BALANCE >> 8; } else { /* TODO: * Do we need to provide specific info for the Feature Units, * such as volume setting ranges, etc.? */ } break; /* Provide capabilities of our PROCESSING unit */ case AUDIO_TYPE_PROCESSING: switch (caps->ac_subtype) { case AUDIO_PU_UNDEF: /* Provide the type of Processing Units we support */ caps->ac_controls.b[0] = AUDIO_PU_STEREO_EXTENDER; break; case AUDIO_PU_STEREO_EXTENDER: /* Proivde capabilities of our Stereo Extender */ caps->ac_controls.b[0] = AUDIO_STEXT_ENABLE | AUDIO_STEXT_WIDTH; break; default: /* Other types of processing uint we don't support */ break; } break; /* All others we don't support */ default: /* Zero out the fields to indicate no support */ caps->ac_subtype = 0; caps->ac_channels = 0; break; } /* Return the length of the audio_caps_s struct for validation of * proper Audio device type. */ return caps->ac_len; } /**************************************************************************** * Name: vs1053_configure * * Description: Configure the audio device for the specified mode of * operation. * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int vs1053_configure(FAR struct audio_lowerhalf_s *lower, FAR void *session, FAR const struct audio_caps_s *caps) #else static int vs1053_configure(FAR struct audio_lowerhalf_s *lower, FAR const struct audio_caps_s *caps) #endif { int ret = OK; #if !defined(CONFIG_AUDIO_EXCLUDE_VOLUME) || !defined(CONFIG_AUDIO_EXCLUDE_TONE) FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower; #endif audinfo("Entry\n"); /* Process the configure operation */ switch (caps->ac_type) { case AUDIO_TYPE_FEATURE: /* Process based on Feature Unit */ switch (caps->ac_format.hw) { #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME case AUDIO_FU_VOLUME: /* Set the volume */ dev->volume = caps->ac_controls.hw[0]; vs1053_setvolume(dev); break; #endif /* CONFIG_AUDIO_EXCLUDE_VOLUME */ #if !defined(CONFIG_AUDIO_EXCLUDE_TONE) && !defined(CONFIG_AUDIO_EXCLUDE_VOLUME) case AUDIO_FU_BALANCE: /* Set the volume */ dev->balance = caps->ac_controls.hw[0]; vs1053_setvolume(dev); break; #endif #ifndef CONFIG_AUDIO_EXCLUDE_TONE case AUDIO_FU_BASS: /* Set the bass. The percentage level (0-100) is in the * ac_controls[0] parameter. */ dev->bass = caps->ac_controls.b[0]; if (dev->bass > 100) dev->bass = 100; vs1053_setbass(dev); break; case AUDIO_FU_TREBLE: /* Set the treble. The percentage level (0-100) is in the * ac_controls.b[0] parameter. */ dev->treble = caps->ac_controls.b[0]; if (dev->treble > 100) dev->treble = 100; vs1053_setbass(dev); break; #endif /* CONFIG_AUDIO_EXCLUDE_TONE */ default: /* Others we don't support */ break; } break; case AUDIO_TYPE_OUTPUT: break; case AUDIO_TYPE_PROCESSING: /* We only support STEREO_EXTENDER */ if (caps->ac_format.hw == AUDIO_PU_STEREO_EXTENDER) { } break; } return ret; } /**************************************************************************** * Name: vs1053_softreset * * Description: Performs a soft reset on the VS1053 chip by setting the * RESET bit of the MODE register. * ****************************************************************************/ static int vs1053_softreset(FAR struct vs1053_struct_s *dev) { uint16_t reg; uint16_t timeout; /* First disable interrupts, lower the frequency and lock the SPI bus */ dev->hw_lower->disable(dev->hw_lower); /* Disable the DREQ interrupt */ vs1053_spi_lock(dev->spi, VS1053_DEFAULT_XTALI / 7); /* Now issue a reset command */ reg = vs1053_readreg(dev, VS1053_SCI_MODE); vs1053_writereg(dev, VS1053_SCI_MODE, reg | VS1053_SM_RESET); /* Now wait for the SM_RESET to go inactive */ timeout = 1000; while (vs1053_readreg(dev, VS1053_SCI_MODE) & VS1053_SM_RESET && timeout) { timeout--; } /* Switch to low frequency, Unlock the SPI bus and exit */ vs1053_setfrequency(dev, CONFIG_VS1053_XTALI); vs1053_spi_unlock(dev->spi); return OK; } /**************************************************************************** * Name: vs1053_hardreset * * Description: Performs a hardware reset on the VS1053 chip by toggling * the RST line, disabling IRQ, and setting the default * XTALI frequency. * ****************************************************************************/ static int vs1053_hardreset(FAR struct vs1053_struct_s *dev) { dev->hw_lower->disable(dev->hw_lower); /* Disable the DREQ interrupt */ dev->hw_lower->reset(dev->hw_lower, false); nxsig_usleep(10); dev->hw_lower->reset(dev->hw_lower, true); nxsig_usleep(VS1053_RST_USECS); vs1053_setfrequency(dev, CONFIG_VS1053_XTALI); /* Slow speed at first */ return OK; } /**************************************************************************** * Name: vs1053_shutdown * * Description: Shutdown the VS1053 chip and put it in the lowest power * state possible. * ****************************************************************************/ static int vs1053_shutdown(FAR struct audio_lowerhalf_s *lower) { FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower; FAR struct spi_dev_s *spi = dev->spi; audinfo("Entry\n"); vs1053_spi_lock(spi, dev->spi_freq); /* Lock the device */ vs1053_setfrequency(dev, CONFIG_VS1053_XTALI); /* Reduce speed to minimum */ vs1053_writereg(dev, VS1053_SCI_VOL, 0xfefe); /* Power down the DAC outputs */ vs1053_spi_unlock(spi); /* Unlock the device */ return OK; } /**************************************************************************** * Name: vs1053_feeddata * * Description: Feeds more data to the vs1053 chip from the enqueued * buffers. It will continue feeding data until the DREQ * line indicates it can't accept any more data. * ****************************************************************************/ static void vs1053_feeddata(FAR struct vs1053_struct_s *dev) { int bytecount; int ret; uint8_t *samp = NULL; uint16_t reg; struct ap_buffer_s *apb; FAR struct spi_dev_s *spi = dev->spi; /* Check for false interrupt caused by an SCI transaction */ if (!dev->hw_lower->read_dreq(dev->hw_lower) || dev->paused) { return; } /* Grab the SPI bus. We can run at 20Mhz because we increased the * chip frequency above 40Mhz for the decode operation. */ vs1053_spi_lock(spi, VS1053_DATA_FREQ); /* Lock the SPI bus */ SPI_SELECT(spi, SPIDEV_AUDIO_DATA(0), true); /* Select the VS1053 data bus */ /* Local stack copy of our active buffer */ apb = dev->apb; /* audinfo("Entry apb=%p, Bytes left=%d\n", * apb, apb->nbytes - apb->curbyte); */ /* Setup pointer to the next sample in the buffer */ if (apb) { samp = &apb->samp[apb->curbyte]; } else if (!dev->endmode) { SPI_SELECT(spi, SPIDEV_AUDIO_DATA(0), false); vs1053_spi_unlock(spi); return; } /* Loop until the FIFO is full */ while (dev->hw_lower->read_dreq(dev->hw_lower)) { /* If endmode, then send fill characters */ if (dev->endmode) { bytecount = 32; while (bytecount) { SPI_SEND(spi, dev->endfillchar); bytecount--; } /* For the VS1053, after the file has been played, we must * send 2052 bytes of endfillchar per the datasheet. */ dev->endfillbytes += 32; /* Process end mode logic. We send 2080 bytes of endfillchar as * directed by the datasheet, then set SM_CANCEL. Then we wait * until the chip clears SM_CANCEL while sending endfillchar * 32 bytes at a time. */ if (dev->endfillbytes == 32 * 65) { /* After at least 2052 bytes, we send an SM_CANCEL */ dev->hw_lower->disable(dev->hw_lower); /* Disable the DREQ interrupt */ SPI_SETFREQUENCY(dev->spi, dev->spi_freq); reg = vs1053_readreg(dev, VS1053_SCI_MODE); vs1053_writereg(dev, VS1053_SCI_MODE, reg | VS1053_SM_CANCEL); dev->hw_lower->enable(dev->hw_lower); /* Enable the DREQ interrupt */ } else if (dev->endfillbytes >= 32 * 130) { /* Do a hard reset and terminate */ vs1053_hardreset(dev); dev->running = false; dev->endmode = false; break; } else if (dev->endfillbytes > 32 * 65) { /* After each 32 byte of endfillchar, check the status * register to see if SM_CANCEL has been cleared. If * it has been cleared, then we're done. */ if (!(vs1053_readreg(dev, VS1053_SCI_STATUS) & VS1053_SM_CANCEL)) { SPI_SETFREQUENCY(dev->spi, dev->spi_freq); dev->hw_lower->disable(dev->hw_lower); /* Disable the DREQ interrupt */ audinfo("HDAT1: 0x%0X HDAT0: 0x%0X\n", vs1053_readreg(dev, VS1053_SCI_HDAT1), vs1053_readreg(dev, VS1053_SCI_HDAT0)); vs1053_writereg(dev, VS1053_SCI_WRAMADDR, VS1053_END_FILL_BYTE); dev->endfillchar = vs1053_readreg(dev, VS1053_SCI_WRAM) >> 8; audinfo("EndFillChar: 0x%0X\n", dev->endfillchar); reg = vs1053_readreg(dev, VS1053_SCI_MODE); vs1053_writereg(dev, VS1053_SCI_MODE, reg | VS1053_SM_RESET); dev->running = false; dev->endmode = false; break; } } } else { /* Send 32 more bytes. We only send 32 at a time because this is * the meaning of DREQ active from the chip ... that it can * accept at least 32 more bytes. After each 32 byte block, we * will recheck the DREQ line again. */ bytecount = apb->nbytes - apb->curbyte; if (bytecount > 32) { bytecount = 32; } #if 1 SPI_SNDBLOCK(spi, samp, bytecount); samp += bytecount; #else bytecount = bytecount; while (bytecount--) { /* Send next byte from the buffer */ SPI_SEND(spi, *samp); samp++; } #endif apb->curbyte += bytecount; /* Test if we are in cancel mode. If we are, then we need * to continue sending file data and check for the SM_CANCEL * bit going inactive. */ #ifndef CONFIG_AUDIO_EXCLUDE_STOP if (dev->cancelmode) { /* Read the VS1053 MODE register */ dev->hw_lower->disable(dev->hw_lower); /* Disable the DREQ interrupt */ SPI_SETFREQUENCY(dev->spi, dev->spi_freq); reg = vs1053_readreg(dev, VS1053_SCI_MODE); SPI_SETFREQUENCY(dev->spi, VS1053_DATA_FREQ); dev->hw_lower->enable(dev->hw_lower); /* Enable the DREQ interrupt */ /* Check the SM_CANCEL bit */ if (!(reg & VS1053_SM_CANCEL)) { /* Cancel has begun. Switch to endmode */ apb->nbytes = 0; apb->curbyte = 0; } } #endif /* CONFIG_AUDIO_EXCLUDE_STOP */ /* Test if we are at the end of the buffer */ if (apb->curbyte >= apb->nbytes) { /* Check if this was the final buffer in stream */ if ((apb->flags & AUDIO_APB_FINAL) != 0) { /* This is the final buffer. Get the VS1053 endfillchar */ dev->hw_lower->disable(dev->hw_lower); /* Disable the DREQ interrupt */ SPI_SETFREQUENCY(dev->spi, dev->spi_freq); vs1053_writereg(dev, VS1053_SCI_WRAMADDR, VS1053_END_FILL_BYTE); dev->endfillchar = vs1053_readreg(dev, VS1053_SCI_WRAM) >> 8; SPI_SETFREQUENCY(dev->spi, VS1053_DATA_FREQ); dev->hw_lower->enable(dev->hw_lower); /* Enable the DREQ interrupt */ /* Mark the device as endmode */ dev->endmode = true; #ifndef CONFIG_AUDIO_EXCLUDE_STOP if (dev->cancelmode) { /* If we are in cancel mode, then we don't dequeue the * buffer or need to send another SM_CANCEL, so jump * into the middle of the stop sequence. */ dev->endfillbytes = 32 * 65 + 1; continue; } else #endif /* CONFIG_AUDIO_EXCLUDE_STOP */ { dev->endfillbytes = 0; } } /* We referenced the buffer so we must free it */ apb_free(apb); #ifdef CONFIG_AUDIO_MULTI_SESSION dev->lower.upper(dev->lower.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK, NULL); #else dev->lower.upper(dev->lower.priv, AUDIO_CALLBACK_DEQUEUE, apb, OK); #endif /* Lock the buffer queue to pop the next buffer */ if ((ret = nxsem_wait(&dev->apbq_sem)) < 0) { #ifdef CONFIG_AUDIO_MULTI_SESSION dev->lower.upper(dev->lower.priv, AUDIO_CALLBACK_IOERR, NULL, ret, NULL); #else dev->lower.upper(dev->lower.priv, AUDIO_CALLBACK_IOERR, NULL, ret); #endif auderr("ERROR: I/O error!\n"); goto err_out; } /* Pop the next entry */ apb = (struct ap_buffer_s *) dq_remfirst(&dev->apbq); dev->apb = apb; /* audinfo("Next Buffer = %p, bytes = %d\n", * apb, apb ? apb->nbytes : 0); */ if (apb == NULL) { nxsem_post(&dev->apbq_sem); break; } samp = &apb->samp[apb->curbyte]; apb_reference(apb); /* Add our buffer reference */ nxsem_post(&dev->apbq_sem); } } } /* Deselect the SPI bus and unlock it */ err_out: SPI_SELECT(spi, SPIDEV_AUDIO_DATA(0), false); vs1053_spi_unlock(spi); } /**************************************************************************** * Name: vs1053_dreq_isr * * This is the ISR that services the DREQ pin from the VS1053, which * indicates the chip is ready to receive additional data. We use it to * send a message to our workertherad message queue so it knows to wake * up and send more data. * ****************************************************************************/ static int vs1053_dreq_isr(int irq, FAR void *context, FAR void *arg) { struct vs1053_struct_s *dev = (struct vs1053_struct_s *)arg; struct audio_msg_s msg; DEBUGASSERT(dev != NULL); /* Now create a message and send it to the workerthread */ if (dev->running) { msg.msg_id = AUDIO_MSG_DATA_REQUEST; file_mq_send(&dev->mq, (FAR const char *)&msg, sizeof(msg), CONFIG_VS1053_MSG_PRIO); } else { msg.msg_id = AUDIO_MSG_DATA_REQUEST; } return 0; } /**************************************************************************** * Name: vs1053_workerthread * * This is the thread that feeds data to the chip and keeps the audio * stream going. * ****************************************************************************/ static void *vs1053_workerthread(pthread_addr_t pvarg) { FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) pvarg; struct audio_msg_s msg; FAR struct ap_buffer_s *apb; int size; unsigned int prio; #ifndef CONFIG_AUDIO_EXCLUDE_STOP uint16_t reg; #endif uint8_t timeout; audinfo("Entry\n"); #ifndef CONFIG_AUDIO_EXCLUDE_STOP dev->cancelmode = false; #endif dev->endmode = false; dev->endfillbytes = 0; /* Fill the VS1053 FIFO with initial data. */ vs1053_feeddata(dev); /* Fill the VS1053 FIFO */ /* Wait for DREQ to go active so we can issue a READ command */ timeout = 200; while (!dev->hw_lower->read_dreq(dev->hw_lower) && timeout) { nxsig_usleep(100); timeout--; } /* Loop as long as we are supposed to be running */ dev->running = true; dev->hw_lower->enable(dev->hw_lower); /* Enable the DREQ interrupt */ while (dev->running || dev->endmode) { if (dev->hw_lower->read_dreq(dev->hw_lower)) { vs1053_feeddata(dev); /* Feed more data to the VS1053 FIFO */ } /* Wait for messages from our message queue */ size = file_mq_receive(&dev->mq, (FAR char *)&msg, sizeof(msg), &prio); /* Handle the case when we return with no message */ if (size == 0) { /* Should we just stop running? */ dev->running = false; break; } /* Process the message */ switch (msg.msg_id) { /* The ISR has requested more data */ case AUDIO_MSG_DATA_REQUEST: nxsig_usleep(500); vs1053_feeddata(dev); /* Feed more data to the VS1053 FIFO */ break; /* Stop the playback */ #ifndef CONFIG_AUDIO_EXCLUDE_STOP case AUDIO_MSG_STOP: if (!dev->hw_lower->read_dreq(dev->hw_lower)) { nxsig_usleep(300); } /* Send CANCEL message to VS1053 */ dev->hw_lower->disable(dev->hw_lower); vs1053_spi_lock(dev->spi, dev->spi_freq); reg = vs1053_readreg(dev, VS1053_SCI_MODE); vs1053_writereg(dev, VS1053_SCI_MODE, reg | VS1053_SM_CANCEL); vs1053_spi_unlock(dev->spi); dev->hw_lower->enable(dev->hw_lower); /* Set cancelmode */ dev->cancelmode = true; break; #endif /* We will wake up when a new buffer enqueued just in case */ case AUDIO_MSG_ENQUEUE: break; default: break; } } /* Disable the DREQ interrupt */ dev->hw_lower->disable(dev->hw_lower); /* Cancel any leftover buffer in our queue */ if (nxsem_wait(&dev->apbq_sem) == OK) { /* Get the next buffer from the queue */ while ((apb = (FAR struct ap_buffer_s *) dq_remfirst(&dev->apbq)) != NULL) ; } nxsem_post(&dev->apbq_sem); /* Free the active buffer */ if (dev->apb != NULL) { apb_free(dev->apb); dev->apb = NULL; } /* Close the message queue */ file_mq_close(&dev->mq); file_mq_unlink(dev->mqname); /* Send an AUDIO_MSG_COMPLETE message to the client */ #ifdef CONFIG_AUDIO_MULTI_SESSION dev->lower.upper(dev->lower.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK, NULL); #else dev->lower.upper(dev->lower.priv, AUDIO_CALLBACK_COMPLETE, NULL, OK); #endif audinfo("Exit\n"); return NULL; } /**************************************************************************** * Name: vs1053_start * * Description: Start the configured operation (audio streaming, volume * enabled, etc.). * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int vs1053_start(FAR struct audio_lowerhalf_s *lower, FAR void *session) #else static int vs1053_start(FAR struct audio_lowerhalf_s *lower) #endif { FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower; struct mq_attr attr; struct sched_param sparam; pthread_attr_t tattr; int ret; void *value; audinfo("Entry\n"); vs1053_spi_lock(dev->spi, dev->spi_freq); /* Lock the device */ audinfo("Entry HDAT1=0x%0X HDAT0=0x%0X\n", vs1053_readreg(dev, VS1053_SCI_HDAT1), vs1053_readreg(dev, VS1053_SCI_HDAT0)); vs1053_spi_unlock(dev->spi); /* Do a soft reset, just in case */ vs1053_softreset(dev); /* Increase the frequency of the part during processing */ vs1053_spi_lock(dev->spi, dev->spi_freq); /* Lock the device */ vs1053_setfrequency(dev, CONFIG_VS1053_MP3_DECODE_FREQ); audinfo("Reset HDAT1=0x%0X HDAT0=0x%0X\n", vs1053_readreg(dev, VS1053_SCI_HDAT1), vs1053_readreg(dev, VS1053_SCI_HDAT0)); vs1053_spi_unlock(dev->spi); /* Create a message queue for the worker thread */ snprintf(dev->mqname, sizeof(dev->mqname), "/tmp/%" PRIXPTR, (uintptr_t)dev); attr.mq_maxmsg = 16; attr.mq_msgsize = sizeof(struct audio_msg_s); attr.mq_curmsgs = 0; attr.mq_flags = 0; ret = file_mq_open(&dev->mq, dev->mqname, O_RDWR | O_CREAT, 0644, &attr); if (ret < 0) { /* Error creating message queue! */ auderr("ERROR: Couldn't allocate message queue\n"); return ret; } /* Pop the first enqueued buffer */ if ((ret = nxsem_wait(&dev->apbq_sem)) == OK) { dev->apb = (FAR struct ap_buffer_s *) dq_remfirst(&dev->apbq); apb_reference(dev->apb); /* Add our buffer reference */ nxsem_post(&dev->apbq_sem); } else { auderr("ERROR: Error getting APB Queue sem\n"); return ret; } /* Join any old worker thread we had created to prevent a memory leak */ if (dev->threadid != 0) { audinfo("Joining old thread\n"); pthread_join(dev->threadid, &value); } /* Start our thread for sending data to the device */ pthread_attr_init(&tattr); sparam.sched_priority = sched_get_priority_max(SCHED_FIFO) - 3; pthread_attr_setschedparam(&tattr, &sparam); pthread_attr_setstacksize(&tattr, CONFIG_VS1053_WORKER_STACKSIZE); audinfo("Starting workerthread\n"); ret = pthread_create(&dev->threadid, &tattr, vs1053_workerthread, (pthread_addr_t) dev); if (ret != OK) { auderr("ERROR: Can't create worker thread, ret=%d\n", ret); } else { pthread_setname_np(dev->threadid, "vs1053"); audinfo("Created worker thread\n"); } return ret; } /**************************************************************************** * Name: vs1053_stop * * Description: Stop the configured operation (audio streaming, volume * disabled, etc.). * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_STOP #ifdef CONFIG_AUDIO_MULTI_SESSION static int vs1053_stop(FAR struct audio_lowerhalf_s *lower, FAR void *session) #else static int vs1053_stop(FAR struct audio_lowerhalf_s *lower) #endif { FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower; struct audio_msg_s term_msg; FAR void *value; /* Send a message to stop all audio streaming */ term_msg.msg_id = AUDIO_MSG_STOP; term_msg.u.data = 0; file_mq_send(&dev->mq, (FAR const char *)&term_msg, sizeof(term_msg), CONFIG_VS1053_MSG_PRIO); /* Join the worker thread */ pthread_join(dev->threadid, &value); dev->threadid = 0; /* Reduce the decoder's operating frequency to save power */ vs1053_spi_lock(dev->spi, dev->spi_freq); /* Lock the device */ vs1053_setfrequency(dev, CONFIG_VS1053_XTALI); vs1053_spi_unlock(dev->spi); /* Wait for a bit */ up_mdelay(40); return OK; } #endif /**************************************************************************** * Name: vs1053_pause * * Description: Pauses the playback. * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME #ifdef CONFIG_AUDIO_MULTI_SESSION static int vs1053_pause(FAR struct audio_lowerhalf_s *lower, FAR void *session) #else static int vs1053_pause(FAR struct audio_lowerhalf_s *lower) #endif { FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower; if (!dev->running) { return OK; } /* Disable interrupts to prevent us from supplying any more data */ dev->paused = true; dev->hw_lower->disable(dev->hw_lower); /* Disable the DREQ interrupt */ return OK; } #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ /**************************************************************************** * Name: vs1053_resume * * Description: Resuems the playback. * ****************************************************************************/ #ifndef CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME #ifdef CONFIG_AUDIO_MULTI_SESSION static int vs1053_resume(FAR struct audio_lowerhalf_s *lower, FAR void *session) #else static int vs1053_resume(FAR struct audio_lowerhalf_s *lower) #endif { FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower; if (!dev->running) { return OK; } /* Enable interrupts to allow suppling data */ dev->paused = false; vs1053_feeddata(dev); dev->hw_lower->enable(dev->hw_lower); /* Enable the DREQ interrupt */ return OK; } #endif /* CONFIG_AUDIO_EXCLUDE_PAUSE_RESUME */ /**************************************************************************** * Name: vs1053_enqueuebuffer * * Description: Enqueue an Audio Pipeline Buffer for playback/ processing. * ****************************************************************************/ static int vs1053_enqueuebuffer(FAR struct audio_lowerhalf_s *lower, FAR struct ap_buffer_s *apb) { FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *)lower; struct audio_msg_s term_msg; int ret; audinfo("Entry\n"); /* Lock access to the apbq */ if ((ret = nxsem_wait(&dev->apbq_sem)) == OK) { /* We can now safely add the buffer to the queue */ apb->curbyte = 0; apb->flags |= AUDIO_APB_OUTPUT_ENQUEUED; dq_addlast(&apb->dq_entry, &dev->apbq); nxsem_post(&dev->apbq_sem); /* Send a message indicating a new buffer enqueued */ if (dev->mq.f_inode != NULL) { term_msg.msg_id = AUDIO_MSG_ENQUEUE; term_msg.u.data = 0; file_mq_send(&dev->mq, (FAR const char *)&term_msg, sizeof(term_msg), CONFIG_VS1053_MSG_PRIO); } } return ret; } /**************************************************************************** * Name: vs1053_cancelbuffer * * Description: Called when an enqueued buffer is being cancelled. * ****************************************************************************/ static int vs1053_cancelbuffer(FAR struct audio_lowerhalf_s *lower, FAR struct ap_buffer_s *apb) { return OK; } /**************************************************************************** * Name: vs1053_ioctl * * Description: Perform a device ioctl * ****************************************************************************/ static int vs1053_ioctl(FAR struct audio_lowerhalf_s *lower, int cmd, unsigned long arg) { int ret = OK; #ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS FAR struct ap_buffer_info_s *bufinfo; #endif /* Deal with ioctls passed from the upper-half driver */ switch (cmd) { /* Check for AUDIOIOC_HWRESET ioctl. This ioctl is passed straight * through from the upper-half audio driver. */ case AUDIOIOC_HWRESET: vs1053_hardreset((FAR struct vs1053_struct_s *) lower); break; /* Report our preferred buffer size and quantity */ #ifdef CONFIG_AUDIO_DRIVER_SPECIFIC_BUFFERS case AUDIOIOC_GETBUFFERINFO: bufinfo = (FAR struct ap_buffer_info_s *) arg; bufinfo->buffer_size = CONFIG_VS1053_BUFFER_SIZE; bufinfo->nbuffers = CONFIG_VS1053_NUM_BUFFERS; break; #endif default: ret = -ENOTTY; break; } return ret; } /**************************************************************************** * Name: vs1053_reserve * * Description: Reserves a session (the only one we have). * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int vs1053_reserve(FAR struct audio_lowerhalf_s *lower, FAR void **psession) #else static int vs1053_reserve(FAR struct audio_lowerhalf_s *lower) #endif { FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower; int ret; /* Borrow the APBQ semaphore for thread sync */ ret = nxsem_wait(&dev->apbq_sem); if (ret < 0) { return ret; } if (dev->busy) { ret = -EBUSY; } else { /* Initialize the session context. We don't really use it. */ #ifdef CONFIG_AUDIO_MULTI_SESSION *psession = NULL; #endif dev->busy = true; dev->running = false; dev->paused = false; } nxsem_post(&dev->apbq_sem); return ret; } /**************************************************************************** * Name: vs1053_release * * Description: Releases the session (the only one we have). * ****************************************************************************/ #ifdef CONFIG_AUDIO_MULTI_SESSION static int vs1053_release(FAR struct audio_lowerhalf_s *lower, FAR void *psession) #else static int vs1053_release(FAR struct audio_lowerhalf_s *lower) #endif { FAR struct vs1053_struct_s *dev = (struct vs1053_struct_s *) lower; void *value; int ret; /* Join any old worker thread we had created to prevent a memory leak */ if (dev->threadid != 0) { pthread_join(dev->threadid, &value); dev->threadid = 0; } /* Borrow the APBQ semaphore for thread sync */ ret = nxsem_wait(&dev->apbq_sem); if (ret < 0) { return ret; } /* Really we should free any queued buffers here */ dev->busy = false; nxsem_post(&dev->apbq_sem); return OK; } /**************************************************************************** * Public Functions ****************************************************************************/ /**************************************************************************** * Name: vs1053_initialize * * Description: * Initialize the VS1053 device * * Input Parameters: * spidevice - This is a placeholder argument until the Audio interface * has been flushed out a bit. * ****************************************************************************/ struct audio_lowerhalf_s *vs1053_initialize(FAR struct spi_dev_s *spi, FAR const struct vs1053_lower_s *lower, unsigned int devno) { FAR struct vs1053_struct_s *dev; uint16_t status; uint8_t id; uint8_t retry; /* Sanity check */ DEBUGASSERT(spi != NULL); DEBUGASSERT(lower != NULL); DEBUGASSERT(lower->reset != NULL); /* Allocate a VS1053 device structure */ dev = (struct vs1053_struct_s *)kmm_zalloc(sizeof(struct vs1053_struct_s)); if (dev) { /* Initialize the VS1053 device structure */ dev->lower.ops = &g_audioops; dev->hw_lower = lower; dev->spi_freq = CONFIG_VS1053_XTALI / 7; dev->spi = spi; #ifndef CONFIG_AUDIO_EXCLUDE_VOLUME dev->volume = 250; /* 25% volume as default */ #ifndef CONFIG_AUDIO_EXCLUDE_BALANCE dev->balance = 500; /* Center balance */ #endif #endif nxsem_init(&dev->apbq_sem, 0, 1); dq_init(&dev->apbq); /* Reset the VS1053 chip */ lower->reset(lower, false); up_udelay(10); lower->reset(lower, true); up_udelay(VS1053_RST_USECS); #if CONFIG_VS1053_XTALI == VS1053_DEFAULT_XTALI /* If we have a standard crystal, then wait extra time * for the DREQ to be active indicating the device is ready */ retry = 200; while (!lower->read_dreq(lower) && retry) { up_udelay(10); retry--; } #endif /* Do device detection to validate the chip is there. * We have to hold the SPI lock during reads / writes. */ vs1053_spi_lock(spi, dev->spi_freq); status = vs1053_readreg(dev, VS1053_SCI_STATUS); vs1053_spi_unlock(spi); /* Validate the device ID read from the chip */ id = (status & VS1053_SS_VER) >> VS1053_VER_SHIFT; if (id != VS1053_VER_VS1053) { auderr("ERROR: Unexpected VER bits: 0x%0X\n", id); kmm_free(dev); return NULL; } else { audinfo("VS1053 Detected!\n"); } /* Attach our ISR to this device */ dev->hw_lower->attach(dev->hw_lower, vs1053_dreq_isr, dev); /* Do some initialization of the codec */ vs1053_shutdown(&dev->lower); /* Go to shutdown state */ } return &dev->lower; }