/* * Copyright (C) 2008-2009 by egnite GmbH * Copyright (C) 2001-2007 by egnite Software GmbH * * All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * 2. Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * 3. Neither the name of the copyright holders nor the names of * contributors may be used to endorse or promote products derived * from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS * ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS * FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE * COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, * BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS * OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED * AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF * THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * * For additional information see http://www.ethernut.de/ */ /* * $Id: spi_vscodec.c 5472 2013-12-06 00:16:28Z olereinhardt $ */ #include #include #include #include #include #include #include #include #include /*! * \addtogroup xgVsCodec */ /*@{*/ /*! \name Internal Command Flags */ /*@{*/ /*! \brief Force immediate player start. */ #define VSREQ_PLAY 0x00000001 /*! \brief Force immediate player stop. */ #define VSREQ_CANCEL 0x00000002 /*! \brief Volume update. */ #define VSREQ_VOLUPD 0x00000004 /*! \brief Audio enhancement update. */ #define VSREQ_AUDIOE 0x00000008 /*! \brief Sine wave test. */ #define VSREQ_BEEP 0x00000010 /*! \brief Start recording. */ #define VSREQ_RECORD 0x00000020 /*@}*/ /* Used to send zeros to the decoder. */ uint8_t zero_chunk[VSCODEC_DATA_CHUNK_SIZE]; /*! * \brief Wait until codec is ready. * * \param dev Sepcifies the codec device. * \param tmo Maximum number of milliseconds to wait. * * \return 0 on success, -1 on time out. */ int VsCodecWaitReady(NUTDEVICE *dev, uint32_t tmo) { VSDCB *dcb = (VSDCB *) dev->dev_dcb; while (!(* dcb->dcb_isready)()) { if (NutEventWait(&dcb->dcb_feedme, tmo)) { return -1; } } return 0; } /* * \brief Read from or write to a VLSI audio codec register. * * \param dev Specifies the codec device. * \param op Operation, either \ref VS_OPCODE_READ or * \ref VS_OPCODE_WRITE. * \param reg The register index. * \param val This value will be stored in the register on a write * operation. It is ignored on read operations. * * \return Register contents. On write operations, the contents before * the modification is returned. */ uint16_t VsCodecReg(NUTDEVICE *dev, uint_fast8_t op, uint_fast8_t reg, uint_fast16_t val) { uint8_t cmd[4]; VSDCB *dcb = (VSDCB *) dev->dev_dcb; /* Assemble command buffer. */ cmd[0] = (uint8_t) op; cmd[1] = (uint8_t) reg; cmd[2] = (uint8_t) (val >> 8); cmd[3] = (uint8_t) val; VsCodecWaitReady(dev, VSCODEC_CMD_TIMEOUT); (*dcb->dcb_sendcmd)(cmd, 4); val = cmd[2]; val <<= 8; val |= cmd[3]; return (uint16_t) val; } /*! * \brief Read and modify VLSI audio codec mode flags. * * Set both, flags and mask to zero to read the flags without change. * * \param dev Specifies the audio codec device. * \param flags Mode flags to set. All other flags are disabled, if they * appear in the mask. * \param mask Mode flags to update. All other flags are left unchanged. * * \return Previous mode flags. */ uint16_t VsCodecMode(NUTDEVICE *dev, uint_fast16_t flags, uint_fast16_t mask) { uint16_t rc; /* Read the mode register. */ rc = VsCodecReg(dev, VS_OPCODE_READ, VS_MODE_REG, 0); if (mask | flags) { VsCodecReg(dev, VS_OPCODE_WRITE, VS_MODE_REG, (rc & ~mask) | flags); /* Add delay after software reset, if configured. */ #if defined(VSCODEC_SWRST_RECOVER) if (flags & VS_SM_RESET) { NutSleep(VSCODEC_SWRST_RECOVER); } #endif } return rc; } /*! * \brief Set volume. * * \param dev Specifies the audio codec device. * \param left Left channel gain in db. * \param right Right channel gain in db. * * \return Always 0. */ int VsDecoderSetVolume(NUTDEVICE *dev, int left, int right) { VSDCB *dcb = (VSDCB *)dev->dev_dcb; uint16_t l; uint16_t r; /* Honor limits. */ left = left > AUDIO_DAC_MAX_GAIN ? AUDIO_DAC_MAX_GAIN : left; left = left < AUDIO_DAC_MIN_GAIN ? AUDIO_DAC_MIN_GAIN : left; right = right > AUDIO_DAC_MAX_GAIN ? AUDIO_DAC_MAX_GAIN : right; right = right < AUDIO_DAC_MIN_GAIN ? AUDIO_DAC_MIN_GAIN : right; /* Convert to register values. */ l = (uint16_t)(-2 * left); r = (uint16_t)(-2 * right); VsCodecReg(dev, VS_OPCODE_WRITE, VS_VOL_REG, (l << VS_VOL_LEFT_LSB) | (r << VS_VOL_RIGHT_LSB)); dcb->dcb_lvol = left; dcb->dcb_rvol = right; return 0; } /*! * \brief Set Bass. * * \param dev Specifies the audio codec device. * \param treb Treble amplitude x 1.5dB. * \param tset Treble frequency limit x 1000Hz * \param bass Bass amplitude x 1dB. * \param bset Bass frequency limit x 10Hz * * \return Always 0. */ int VsDecoderSetBass(NUTDEVICE *dev, int treb, int tfin, int bass, int bfin) { #ifdef VS_HAS_BASS_REG VSDCB *dcb = (VSDCB *)dev->dev_dcb; // uint16_t t; // uint16_t b; //printf("bass: t:%ddB tf:%dHz b:%ddB bf:%dHz\n", treb, tfin*1000, bass, bfin); /* Honor limits. */ treb = treb > AUDIO_DAC_MAX_TREB ? AUDIO_DAC_MAX_TREB : treb; treb = treb < 0 ? 0 : treb; tfin = tfin > AUDIO_DAC_MAX_TFIN ? AUDIO_DAC_MAX_TFIN : tfin; tfin = tfin < 0 ? 0 : tfin; bass = bass > AUDIO_DAC_MAX_BASS ? AUDIO_DAC_MAX_BASS : bass; bass = bass < 0 ? 0 : bass; bfin = bfin > AUDIO_DAC_MAX_BFIN ? AUDIO_DAC_MAX_BFIN : bfin; bfin = bfin < 0 ? 0 : bfin; /* Convert to register values. */ /* l = (uint16_t)(-2 * left); r = (uint16_t)(-2 * right); */ VsCodecReg(dev, VS_OPCODE_WRITE, VS_BASS_REG, (treb << VS_ST_AMPLITUDE_LSB) | (tfin << VS_ST_FREQLIMIT_LSB) | (treb << VS_SB_AMPLITUDE_LSB) | (tfin << VS_SB_FREQLIMIT_LSB) ); dcb->dcb_treb = treb; dcb->dcb_tfin = tfin; dcb->dcb_bass = bass; dcb->dcb_bfin = bfin; #endif return 0; } /*! * \brief Start or stop sine wave beeper. * * Old, VS1001 compatible routine, which supports a limit number of * frequencies only. It will try to find the best value. * * \param dev Specifies the audio codec device. * \param fsin Frequency, or 0 to switch beeper off. * * \return Actual frequency set. */ uint16_t VsCodecBeep(NUTDEVICE *dev, uint16_t fsin) { uint16_t rc = 0; static uint8_t on[] = { 0x53, 0xEF, 0x6E, 0x3F, 0x00, 0x00, 0x00, 0x00 }; static const uint8_t off[] = { 0x45, 0x78, 0x69, 0x74, 0x00, 0x00, 0x00, 0x00 }; static const uint16_t ftab[] = { 44100, 48000, 32000, 22050, 24000, 16000, 11025, 12000 }; static uint16_t mode; VSDCB *dcb = (VSDCB *)dev->dev_dcb; if (fsin) { uint_fast16_t s; uint32_t f; uint_fast16_t d; int_fast8_t i = sizeof(ftab) / sizeof(ftab[0]); int_fast8_t ie = 0; int_fast16_t dmin = fsin; uint32_t fs = (long)fsin * 128; while (--i >= ie) { /* Calculate skip speed. */ f = ftab[i]; s = (fs + f / 2) / f; /* Check that skip is within 5-bit range. */ if (s && s < 32) { /* Calculate the absolute deviation. */ f *= s; f += 64; f >>= 7; d = (uint_fast16_t)(fsin > f ? fsin - f : f - fsin); /* Check if this is a new minimum. */ if (d < dmin) { dmin = d; rc = (uint16_t) f; /* Set new skip speed and frequency index. */ on[3] = (i << 5) | s; /* We can stop earlier with this fit. */ if (i > 1) { ie = i - 2; } } } } mode = VsCodecMode(dev, VS_SM_RESET, VS_SM_RESET); #ifdef VS_SM_TESTS VsCodecMode(dev, mode | VS_SM_TESTS, 0xffff); #endif (*dcb->dcb_senddata)(on, sizeof(on)); } else { (*dcb->dcb_senddata)(off, sizeof(off)); #ifdef VS_SM_TESTS VsCodecMode(dev, VS_SM_RESET, VS_SM_RESET | VS_SM_TESTS); #else VsCodecMode(dev, VS_SM_RESET, VS_SM_RESET); #endif VsCodecMode(dev, mode, 0xffff); } return rc; } /* * Initialize the stream output buffer with a given size. * * \param dev Specifies the audio codec device. */ int VsDecoderBufferInit(NUTDEVICE *dev, uint32_t size) { VSDCB *dcb = (VSDCB *)dev->dev_dcb; /* Make sure that the decoder is idle. */ if (dcb->dcb_pbstat != CODEC_STATUS_IDLE) { return -1; } /* Set new buffer size. */ if (NutSegBufInit((size_t)size) == NULL) { return -1; } /* Set watermark defaults. */ dcb->dcb_pbwlo = NutSegBufAvailable() / 3; dcb->dcb_pbwhi = dcb->dcb_pbwlo * 2; return 0; } /* * VLSI decoder feeding thread. */ THREAD(FeederThread, arg) { uint8_t *bp; size_t avail; int filled; uint_fast8_t intest = 0; uint_fast16_t idlefill = 0; NUTDEVICE *dev = (NUTDEVICE *)arg; VSDCB *dcb = (VSDCB *)dev->dev_dcb; /* We are a high priority thread. */ NutThreadSetPriority(7); for (;;) { /* ** Idle mode processing. */ if (dcb->dcb_pbstat == CODEC_STATUS_IDLE) { /* Loop while in test mode. */ do { /* Wait for a request or a buffer update. */ NutEventWait(&dcb->dcb_feedme, NUT_WAIT_INFINITE); /* Process beep commands. */ if (dcb->dcb_scmd & VSREQ_BEEP) { if ((*dcb->dcb_isready)()) { dcb->dcb_scmd &= ~VSREQ_BEEP; intest = VsCodecBeep(dev, dcb->dcb_sinefreq) != 0; } } } while (intest); /* Check if we should change to record mode. */ if (dcb->dcb_scmd & VSREQ_RECORD) { dcb->dcb_scmd &= ~(VSREQ_RECORD | VSREQ_CANCEL); dcb->dcb_pbstat = CODEC_STATUS_RECORDING; /* Activate the encoder. */ VsCodecReg(dev, VS_OPCODE_WRITE, VS_AIADDR_REG, 0x0034); } /* Check if we should change to play mode. This will happen ** if we receive a play request or if the buffer contents ** reaches the high watermark. */ else if ((dcb->dcb_scmd & VSREQ_PLAY) != 0 || NutSegBufUsed() >= dcb->dcb_pbwhi) { dcb->dcb_scmd &= ~(VSREQ_PLAY | VSREQ_CANCEL); dcb->dcb_pbstat = CODEC_STATUS_PLAYING; VsDecoderSetVolume(dev, dcb->dcb_lvol, dcb->dcb_rvol); } } /* ** Play mode processing. */ if (dcb->dcb_pbstat == CODEC_STATUS_PLAYING) { /* Wait while decoder is busy. */ VsCodecWaitReady(dev, NUT_WAIT_INFINITE); /* Process cancel requests. */ if (dcb->dcb_scmd & VSREQ_CANCEL) { dcb->dcb_scmd &= ~VSREQ_CANCEL; NutSegBufReset(); #if VS_HAS_SM_CANCEL VsCodecMode(dev, VS_SM_CANCEL, VS_SM_CANCEL); #endif } /* Retrieve new data from the input buffer. */ bp = (uint8_t *) NutSegBufReadRequest(&avail); if (avail) { /* More data available, send as much as possible to the ** decoder. If no data was sent, then the bus may be ** blocked by another device. */ filled = (*dcb->dcb_senddata)(bp, avail); if (filled) { /* Update the buffer's read position. */ NutSegBufReadLast(filled); NutEventPost(&dcb->dcb_bufque); idlefill = 2048; } if (dcb->dcb_scmd & VSREQ_VOLUPD) { VsDecoderSetVolume(dev, dcb->dcb_lvol, dcb->dcb_rvol); dcb->dcb_scmd &= ~VSREQ_VOLUPD; } if (dcb->dcb_scmd & VSREQ_AUDIOE) { VsDecoderSetBass( dev, dcb->dcb_treb, dcb->dcb_tfin, dcb->dcb_bass, dcb->dcb_bfin); dcb->dcb_scmd &= ~VSREQ_AUDIOE; } } else if (NutSegBufUsed() == 0) { /* Running out of data. */ if (idlefill) { /* For some reason the HDAT0/HDAT1 registers in the ** VS1053 do not contain zero when the codec runs ** empty. I expected this when reading the datasheet. ** Instead we fill the buffer with zeros, to make ** sure that the whole buffer is decoded before we ** enter idle mode. */ idlefill -= (*dcb->dcb_senddata)(NULL, idlefill); } if (idlefill == 0) { /* Finally we reached idle mode. */ dcb->dcb_pbstat = CODEC_STATUS_IDLE; NutEventPost(&dcb->dcb_bufque); } } } /* ** Record mode processing. */ else if (dcb->dcb_pbstat == CODEC_STATUS_RECORDING) { for (;;) { /* Process cancel requests. */ if (dcb->dcb_scmd & VSREQ_CANCEL) { dcb->dcb_scmd &= ~VSREQ_CANCEL; dcb->dcb_pbstat = CODEC_STATUS_IDLE; break; } bp = (uint8_t *)NutSegBufWriteRequest(&avail); avail >>= 1; if (avail == 0) { /* Buffer overflow. */ dcb->dcb_pbstat = CODEC_STATUS_IDLE; break; } #if defined(VS_HDAT0_REG) && defined(VS_HDAT1_REG) /* TODO: Missing register definitions give compiler error. */ filled = VsCodecReg(dev, VS_OPCODE_READ, VS_HDAT1_REG, 0); if (filled > 255) { if (avail > filled) { avail = filled; } else { filled = avail; } (*dcb->dcb_recvdata)(bp, filled << 1); NutSegBufWriteLast(avail << 1); NutEventPost(&dcb->dcb_bufque); } else { NutSleep(200); } #endif } NutEventPost(&dcb->dcb_bufque); } } } /* TODO: This is a preview and may not work as expected. */ static int VsCodecLoadPlugIn(NUTDEVICE *dev, VS_PLUGIN_INFO *plg) { uint_fast8_t reg; uint_fast16_t cnt; size_t i = 0; #ifdef VS_SM_ADPCM VsCodecMode(dev, 0, VS_SM_ADPCM); #endif VsCodecReg(dev, VS_OPCODE_WRITE, VS_CLOCKF_REG, 0xC000); NutSleep(100); #if VS_HAS_BASS_REG VsCodecReg(dev, VS_OPCODE_WRITE, VS_BASS_REG, 0); #endif VsCodecReg(dev, VS_OPCODE_WRITE, VS_AIADDR_REG, 0); VsCodecReg(dev, VS_OPCODE_WRITE, VS_WRAMADDR_REG, 0xC01A); VsCodecReg(dev, VS_OPCODE_WRITE, VS_WRAM_REG, 0x0002); while (i + 3 <= plg->vsplg_size) { reg = (uint_fast8_t)plg->vsplg_data[i++]; cnt = plg->vsplg_data[i++]; if (cnt & 0x8000) { cnt &= 0x7FFF; while (cnt--) { VsCodecReg(dev, VS_OPCODE_WRITE, reg, plg->vsplg_data[i]); } i++; } else if (i + cnt <= plg->vsplg_size) { while (cnt--) { VsCodecReg(dev, VS_OPCODE_WRITE, reg, plg->vsplg_data[i]); i++; } } else { break; } } if (i != plg->vsplg_size) { return -1; } #ifdef VS_SM_ADPCM // set bits 12 and 13 of register SCI_MODE // 12 is the ADPCM bit, 13 is the MIC/LINE bit. VsCodecMode(dev, VS_SM_LINE_IN | VS_SM_ADPCM, VS_SM_LINE_IN | VS_SM_ADPCM); #endif #if VS_HAS_AICTRL0_REG // write 0 to SCI_AICTRL0 (maximum signal level, set by encoder to read later on) VsCodecReg(dev, VS_OPCODE_WRITE, VS_AICTRL0_REG, 0); #endif // recording gain 0: automatic gain control on //CodecReg(&codec_node, VS_OPCODE_WRITE, VS_AICTRL1_REG, 0); #if VS_HAS_AICTRL1_REG // recording gain 1 VsCodecReg(dev, VS_OPCODE_WRITE, VS_AICTRL1_REG, 1024); #endif #if VS_HAS_AICTRL2_REG // maximum autogain amplification, 4096 (=4x) is recommended VsCodecReg(dev, VS_OPCODE_WRITE, VS_AICTRL2_REG, 4096); #endif #if VS_HAS_AICTRL3_REG // setting SCI_AICTRL3 should be fine, too... VsCodecReg(dev, VS_OPCODE_WRITE, VS_AICTRL3_REG, 0); #endif return 0; } static int VsCodecWriteWRam(NUTDEVICE *dev, VS_WRAM_DATA *vswd) { size_t i; if (vswd == NULL) { /* * Initialize data transfer. */ #ifdef VS_SM_ADPCM VsCodecMode(dev, 0, VS_SM_ADPCM); #endif /* Set clock to 4.5x = 55.3 MHz. */ VsCodecReg(dev, VS_OPCODE_WRITE, VS_CLOCKF_REG, 0xC000); NutSleep(100); #if VS_HAS_BASS_REG /* Clear SCI bass register. */ VsCodecReg(dev, VS_OPCODE_WRITE, VS_BASS_REG, 0); #endif /* Disable all interrupts except SCI. */ VsCodecReg(dev, VS_OPCODE_WRITE, VS_AIADDR_REG, 0); VsCodecReg(dev, VS_OPCODE_WRITE, VS_WRAMADDR_REG, 0xC01A); VsCodecReg(dev, VS_OPCODE_WRITE, VS_WRAM_REG, 0x0002); } else if (vswd->vswd_data) { /* * Transfer data, if available. */ VsCodecReg(dev, VS_OPCODE_WRITE, VS_WRAMADDR_REG, vswd->vswd_addr); for (i = 0; i < vswd->vswd_size; i++) { VsCodecReg(dev, VS_OPCODE_WRITE, VS_WRAM_REG, vswd->vswd_data[i]); } } else { /* * All data transfered. */ #ifdef VS_SM_ADPCM VsCodecMode(dev, VS_SM_LINE_IN | VS_SM_ADPCM, VS_SM_LINE_IN | VS_SM_ADPCM); #endif #if VS_HAS_AICTRL0_REG VsCodecReg(dev, VS_OPCODE_WRITE, VS_AICTRL0_REG, 0); #endif #if VS_HAS_AICTRL1_REG VsCodecReg(dev, VS_OPCODE_WRITE, VS_AICTRL1_REG, 1024); #endif #if VS_HAS_AICTRL2_REG VsCodecReg(dev, VS_OPCODE_WRITE, VS_AICTRL2_REG, 4096); #endif #if VS_HAS_AICTRL3_REG VsCodecReg(dev, VS_OPCODE_WRITE, VS_AICTRL3_REG, 0); #endif } return 0; } /*! * \brief Handle I/O controls for audio codec. * * \param dev Specifies the audio codec device. * * - AUDIO_PLAY Force playback start, even if the buffer level is too lower. * - AUDIO_CANCEL Cancel playback and discard all buffered data. * - AUDIO_GET_STATUS Sets an int to 1 if the player is running, 0 if idle. * - AUDIO_GET_PLAYGAIN Sets an int to the current playback gain, 0..-127. * - AUDIO_SET_PLAYGAIN Reads the requested playback gain from an int, 0..-127. * - AUDIO_GET_PBSIZE Sets an unsigned long with the size of the playback buffer. * - AUDIO_SET_PBSIZE Sets the size the playback buffer using an unsigned long. * - AUDIO_GET_PBLEVEL Sets an unsigned long with the number of bytes in the playback buffer. * - AUDIO_GET_PBWLOW Sets an unsigned long with the low watermark of the playback buffer. * - AUDIO_SET_PBWLOW Sets the low watermark (unsigned long) of the playback buffer. * - AUDIO_GET_PBWHIGH Sets an unsigned long with the high watermark of the playback buffer. * - AUDIO_SET_PBWHIGH Sets the high watermark (unsigned long) of the playback buffer. * - AUDIO_BEEP Plays a short sine wave beep. * * \return 0 on success, -1 otherwise. */ int VsCodecIOCtl(NUTDEVICE * dev, int req, void *conf) { int rc = 0; uint32_t *lvp = (uint32_t *) conf; int *ivp = (int *) conf; VSDCB *dcb = (VSDCB *)dev->dev_dcb; switch (req) { case AUDIO_PLAY: if (dcb->dcb_pbstat == CODEC_STATUS_IDLE) { /* Immediately start playing. */ dcb->dcb_scmd |= VSREQ_PLAY; } NutEventPost(&dcb->dcb_feedme); break; case AUDIO_CANCEL: if (dcb->dcb_pbstat == CODEC_STATUS_PLAYING) { /* Immediately stop playing and discard buffer. */ dcb->dcb_scmd |= VSREQ_CANCEL; } NutEventPost(&dcb->dcb_feedme); break; case AUDIO_GET_STATUS: *ivp = dcb->dcb_pbstat; break; case AUDIO_GET_PLAYGAIN: *ivp = dcb->dcb_rvol > dcb->dcb_lvol ? dcb->dcb_rvol : dcb->dcb_lvol; break; case AUDIO_SET_PLAYGAIN: dcb->dcb_lvol = *ivp; dcb->dcb_rvol = *ivp; dcb->dcb_scmd |= VSREQ_VOLUPD; break; case AUDIO_SET_TREB: dcb->dcb_treb = *ivp; dcb->dcb_scmd |= VSREQ_AUDIOE; break; case AUDIO_SET_TFIN: dcb->dcb_tfin = *ivp; dcb->dcb_scmd |= VSREQ_AUDIOE; break; case AUDIO_SET_BASS: dcb->dcb_bass = *ivp; dcb->dcb_scmd |= VSREQ_AUDIOE; break; case AUDIO_SET_BFIN: dcb->dcb_bfin = *ivp; dcb->dcb_scmd |= VSREQ_AUDIOE; break; case AUDIO_GET_PBSIZE: *lvp = NutSegBufAvailable() + NutSegBufUsed(); break; case AUDIO_SET_PBSIZE: rc = VsDecoderBufferInit(dev, *lvp); break; case AUDIO_GET_PBLEVEL: *lvp = NutSegBufUsed(); break; case AUDIO_GET_PBWLOW: *lvp = dcb->dcb_pbwlo; break; case AUDIO_SET_PBWLOW: dcb->dcb_pbwlo = *lvp; break; case AUDIO_GET_PBWHIGH: *lvp = dcb->dcb_pbwhi; break; case AUDIO_SET_PBWHIGH: dcb->dcb_pbwhi = *lvp; break; case AUDIO_SETWRITETIMEOUT: dcb->dcb_wtmo = *lvp; break; case AUDIO_GETWRITETIMEOUT: *lvp = dcb->dcb_wtmo; break; case AUDIO_BEEP: if (dcb->dcb_pbstat == CODEC_STATUS_IDLE) { dcb->dcb_sinefreq = *((uint16_t *) conf); dcb->dcb_scmd |= VSREQ_BEEP; NutEventPost(&dcb->dcb_feedme); } else { rc = -1; } break; case AUDIO_GET_DECCAPS: /* Retrieve decoder capabilities. */ *lvp = dcb->dcb_dec_caps; break; case AUDIO_GET_CODCAPS: /* Retrieve encoder capabilities. */ *lvp = dcb->dcb_cod_caps; break; case AUDIO_GET_MIDCAPS: /* Retrieve midi capabilities. */ *lvp = dcb->dcb_midi_caps; break; #if 0 case AUDIO_GET_DECINFO: /* Retrieve decoder information. */ break; case AUDIO_GET_CODINFO: /* Retrieve encoder information. */ break; case AUDIO_GET_MIDINFO: /* Retrieve midi information. */ break; #endif case AUDIO_PLUGIN_UPLOAD: rc = VsCodecLoadPlugIn(dev, (VS_PLUGIN_INFO *) conf); break; case AUDIO_WRITE_CMEM: VsCodecWriteWRam(dev, (VS_WRAM_DATA *) conf); break; default: rc = (*dcb->dcb_control)(req, conf); break; } return rc; } /*! * \brief Flush VLSI audio decoder buffer. * * Waits until all currently buffered data had been processed by the * decoder. This makes sure that the end of the stream will not be * cut off. * * \param dev Specifies the audio codec device. * \param tmo Timeout in milliseconds. * * \return 0 on success, -1 on timeout. */ static int VsDecoderBufferFlush(NUTDEVICE *dev, uint32_t tmo) { int rc = 0; VSDCB *dcb = dev->dev_dcb; for (;;) { /* Keep the player running if the buffer contains data. */ if (NutSegBufUsed()) { VsCodecIOCtl(dev, AUDIO_PLAY, NULL); } /* No data in buffer. If idle, then we are done. */ else if (dcb->dcb_pbstat == CODEC_STATUS_IDLE) { /* No data and player is idle. */ break; } /* Wait for a new buffer update. */ rc = NutEventWait(&dcb->dcb_bufque, tmo); if (rc) { break; } } return rc; } /*! * \brief Read from the encoder. * * \param nfp Pointer to a \ref NUTFILE structure, obtained by a previous * call to VsCodecOpen(). * \param data Pointer to the data buffer. If NULL, the buffered data * will be flushed. * \param len Number of bytes to read. If 0, all buffered data will be * flushed. * * \return Number of characters sent. If a read timeout had been set, * then this may be less than the specified length. */ int VsCodecRead(NUTFILE * nfp, void *data, int len) { int rc = 0; uint8_t *bp; uint8_t *dp; size_t rbytes; NUTDEVICE *dev; VSDCB *dcb; dev = nfp->nf_dev; dcb = dev->dev_dcb; /* Flush buffer if data pointer is a NULL pointer. */ if (data == NULL || len == 0) { return 0; } if (dcb->dcb_pbstat == CODEC_STATUS_PLAYING) { /* Cannot read decoder data. */ return -1; } else if (dcb->dcb_pbstat == CODEC_STATUS_IDLE) { /* Start recording if idle. */ if (dcb->dcb_cod_mode & AUDIO_FMT_VORBIS) { } dcb->dcb_scmd |= VSREQ_RECORD; NutEventPost(&dcb->dcb_feedme); } dp = data; while (len) { /* Retrieve new data from the input buffer. */ bp = (uint8_t *) NutSegBufReadRequest(&rbytes); if (rbytes) { if (rbytes > len) { rbytes = len; } memcpy(dp, bp, rbytes); NutSegBufReadLast(rbytes); NutEventPost(&dcb->dcb_feedme); len -= rbytes; rc += rbytes; dp += rbytes; } /* Wait for data. */ else if (NutEventWait(&dcb->dcb_bufque, dcb->dcb_rtmo)) { /* Read timeout. */ break; } } return rc; } /*! * \brief Write to decoder. * * \param nfp Pointer to a \ref NUTFILE structure, obtained by a previous * call to VsCodecOpen(). * \param data Pointer to the data buffer. If NULL, the buffered data * will be flushed. * \param len Number of bytes that are available in the buffer. If 0, * all buffered data will be flushed. * * \return Number of characters sent. If a write timeout had been set, * then this may be less than the specified length. */ int VsCodecWrite(NUTFILE * nfp, const void *data, int len) { int rc = 0; uint8_t *bp; const uint8_t *dp; size_t rbytes; VSDCB *dcb = nfp->nf_dev->dev_dcb; /* Flush buffer if data pointer is a NULL pointer. */ if (data == NULL || len == 0) { return VsDecoderBufferFlush(nfp->nf_dev, dcb->dcb_wtmo); } dp = data; while (len) { bp = (uint8_t *)NutSegBufWriteRequest(&rbytes); if (rbytes == 0) { /* No buffer space, wait for change. */ if (NutEventWait(&dcb->dcb_bufque, dcb->dcb_wtmo)) { /* Write timeout. */ break; } } else { if (rbytes > len) { rbytes = len; } memcpy(bp, dp, rbytes); NutSegBufWriteLast(rbytes); NutEventPost(&dcb->dcb_feedme); len -= rbytes; rc += rbytes; dp += rbytes; } } return rc; } #ifdef __HARVARD_ARCH__ /*! * \brief Write program data to decoder. * * Similar to VsCodecWrite() except that the data is expected in program memory. * * This function is implemented for CPUs with Harvard Architecture only. * * This function is called by the low level output routines of the * \ref xrCrtLowio "C runtime library", using the * \ref _NUTDEVICE::dev_write_P entry. * * \param nfp Pointer to a \ref NUTFILE structure, obtained by a previous * call to VsCodecOpen(). * \param buffer Pointer to the data in program space. If zero, then the * output buffer will be flushed. * \param len Number of bytes to write. * * \return The number of bytes written. A return value of -1 indicates an * error. Currently this function is not implemented and always * returns -1. * */ int VsCodecWrite_P(NUTFILE * nfp, PGM_P buffer, int len) { return -1; } #endif /* * Open codec stream. */ NUTFILE *VsCodecOpen(NUTDEVICE * dev, const char *name, int mode, int acc) { NUTFILE *nfp; VSDCB *dcb; #if defined(VS_SW_RESET_ON_OPEN) VsCodecMode(dev, VS_SM_RESET, VS_SM_RESET); #endif dcb = dev->dev_dcb; if (mode & _O_WRONLY) { uint32_t on = 1; /* Decoder. */ if (VsCodecIOCtl(dev, AUDIO_IRQ_ENABLE, &on)) { return NULL; } } else { /* Encoder. */ if (strcmp(name, "vorbis") == 0) { dcb->dcb_cod_mode = AUDIO_FMT_VORBIS; } else { dcb->dcb_cod_mode = AUDIO_FMT_WAV_IMA_ADPCM; } } nfp = malloc(sizeof(NUTFILE)); if (nfp) { nfp->nf_dev = dev; nfp->nf_fcb = NULL; } NutSegBufReset(); return nfp; } /* * Close codec stream. */ int VsCodecClose(NUTFILE * nfp) { VSDCB *dcb = nfp->nf_dev->dev_dcb; int rc = VsDecoderBufferFlush(nfp->nf_dev, dcb->dcb_wtmo); if (nfp) { free(nfp); } return rc; } /*@}*/