Library to control and transfer data from NXP SGTL5000. As used on the Teensy Audio Shield. It uses DMA to transfer I2S FIFO data.
The Library now supports dual codecs. Allowing all 4 channels of the Teensy I2S interface to RX and TX data to separate SGTL5000 devices.
The ISR routines that handles pointer swaps for double buffering has been fully coded in assembler to reduce overhead and now takes < 800nS per FIFO transfer when using all 4 channels.
Support added for all typical sample rates and system Clock speeds of 96Mhz or 120Mhz.
Pause and Resume functions added to allow quick and simple suppression of IRQs and stream halting and restart. This required software triggered IRQ, in order to ensure accurate word sync control.
sgtl5000.cpp
- Committer:
- aidan1971
- Date:
- 2017-09-27
- Revision:
- 14:9043626add45
- Parent:
- 10:49bb33f71d32
File content as of revision 14:9043626add45:
/*! @ author Aidan Walton, aidan.walton@gmail.com @section LICENSE * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice, development funding notice, and this permission * notice shall be included in all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. @section DESCRIPTION Library for NXP SGTL5000 Codec */ #include "sgtl5000.h" namespace SGTL5000 { // Static variables required within ISRs volatile uint32_t * volatile SGTL5000::BufRX_L_safe; // Private pointers assigned to users data pointers volatile uint32_t * volatile SGTL5000::BufRX_R_safe; // These are used to flip user pointers between safe 'active' regions of volatile uint32_t * volatile SGTL5000::BufTX_L_safe; // double buffered space. volatile uint32_t * volatile SGTL5000::BufTX_R_safe; volatile uint32_t * volatile SGTL5000::BufRX_L_safe2; // Private pointers assigned to users data pointers volatile uint32_t * volatile SGTL5000::BufRX_R_safe2; // These are used to flip user pointers between safe 'active' regions of volatile uint32_t * volatile SGTL5000::BufTX_L_safe2; // double buffered space. volatile uint32_t * volatile SGTL5000::BufTX_R_safe2; uint32_t SGTL5000::BufRX_L_safeA; // Precalculated double buffer addresses uint32_t SGTL5000::BufRX_R_safeA; uint32_t SGTL5000::BufTX_L_safeA; uint32_t SGTL5000::BufTX_R_safeA; uint32_t SGTL5000::BufRX_L_safeB; // Precalculated double buffer addresses uint32_t SGTL5000::BufRX_R_safeB; uint32_t SGTL5000::BufTX_L_safeB; uint32_t SGTL5000::BufTX_R_safeB; uint32_t SGTL5000::BufRX_L_safeA2; // Precalculated double buffer addresses 2nd codec uint32_t SGTL5000::BufRX_R_safeA2; uint32_t SGTL5000::BufTX_L_safeA2; uint32_t SGTL5000::BufTX_R_safeA2; uint32_t SGTL5000::BufRX_L_safeB2; // Precalculated double buffer addresses 2nd codec uint32_t SGTL5000::BufRX_R_safeB2; uint32_t SGTL5000::BufTX_L_safeB2; uint32_t SGTL5000::BufTX_R_safeB2; uint32_t SGTL5000::I2S_RX_Buffer[16] = {0}; // Data buffers uint32_t SGTL5000::I2S_TX_Buffer[16] = {0}; uint32_t SGTL5000::I2S_RX_Buffer2[16] = {0}; uint32_t SGTL5000::I2S_TX_Buffer2[16] = {0}; IRQn SGTL5000::SYNC_swIRQ; // IRQn assigned by user to the software IRQ triggered by the FIFO queues. IRQn SGTL5000::TX_swIRQ; IRQn SGTL5000::RX_swIRQ; IRQn SGTL5000::CODEC_CTRL_IRQ; // Stream ctrl IRQ uint32_t SGTL5000::RX_DMAch; // User defined RX DMA channel number uint32_t SGTL5000::TX_DMAch; // User defined TX DMA channel number uint32_t SGTL5000::active_RX_DMAch_bm; uint32_t SGTL5000::active_TX_DMAch_bm; uint32_t SGTL5000::sync_dma_irq_acks = 0; Callback<void()> SGTL5000::TX_user_func = NULL; Callback<void()> SGTL5000::RX_user_func = NULL; Callback<void()> SGTL5000::SYNC_user_func = NULL; // User defined callback function uint32_t SGTL5000::db_phase_sync; uint32_t SGTL5000::db_phase_tx; uint32_t SGTL5000::db_phase_rx; bool SGTL5000::SYNC_run; bool SGTL5000::TX_run; bool SGTL5000::RX_run; uint32_t SGTL5000::ctrl_command = 0; bool SGTL5000::i2s_configured = false; // Shared I2S state bool SGTL5000::codec2_active = false; // Which codecs are instantiated bool SGTL5000::codec1_active = false; uint32_t SGTL5000::SYNC_attach_type; uint32_t SGTL5000::TX_attach_type; uint32_t SGTL5000::RX_attach_type; uint32_t SGTL5000::debug[16] = {0}; uint32_t SGTL5000::t1; // Used to debug timing issues uint32_t SGTL5000::proc_time; uint32_t SGTL5000::SYST_CVAL = 0xE000E018; bool SGTL5000::debug_read = false; SGTL5000::SGTL5000(PinName i2c_sda, PinName i2c_scl, int _i2c_freq, bool i2c_ctrl_adr0_cs, IRQn _ctrl_IRQn) : mI2C(i2c_sda, i2c_scl), i2c_freq(_i2c_freq) { if(i2c_ctrl_adr0_cs) { i2c_addr = SGTL5000_I2C_ADDR_CS_HIGH << 1; ctrl_codec = false; } else { i2c_addr = SGTL5000_I2C_ADDR_CS_LOW << 1; SGTL5000::CODEC_CTRL_IRQ = _ctrl_IRQn; NVIC_SetVector(SGTL5000::CODEC_CTRL_IRQ, (uint32_t)&SGTL5000::stream_ctrl_ISR); // Set ctrl command IRQ vector NVIC_SetPriority(SGTL5000::CODEC_CTRL_IRQ, 0); // Set irq priority NVIC_EnableIRQ(SGTL5000::CODEC_CTRL_IRQ); ctrl_codec = true; } codec_configured = false; SGTL5000::SYNC_run = false; SGTL5000::TX_run = false; SGTL5000::RX_run = false; SYNC_attached = false; TX_attached = false; RX_attached = false; } int32_t SGTL5000::init(void) { if(SGTL5000::codec1_active && SGTL5000::codec2_active) return -1; // We can only have 2 actively configured codec objects, if both are already active and configured bomb out. if((i2c_addr == (SGTL5000_I2C_ADDR_CS_LOW << 1)) && SGTL5000::codec1_active) return -2; if((i2c_addr == (SGTL5000_I2C_ADDR_CS_HIGH << 1)) && SGTL5000::codec2_active) return -3; if(this->ctrl_codec && !SGTL5000::i2s_configured) init_i2s(); mI2C.frequency(i2c_freq); int32_t error = init_codec(); if(error) return error; else codec_configured = true; return 0; } int32_t SGTL5000::sample_rate(uint32_t rate) { if(!codec_configured || !this->ctrl_codec) return -1; uint32_t I2S_MCLK_M; uint32_t I2S_MCLK_D; uint32_t codec_SYS_FS; uint32_t codec_RATE_MODE; switch(rate) { case 8: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 16; I2S_MCLK_D = 750; codec_SYS_FS = 0x02; codec_RATE_MODE = 0x01; break; case 120000000: I2S_MCLK_M = 16; I2S_MCLK_D = 936; codec_SYS_FS = 0x02; codec_RATE_MODE = 0x01; break; } break; case 11: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 1; I2S_MCLK_D = 34; codec_SYS_FS = 0x01; codec_RATE_MODE = 0x01; break; case 120000000: I2S_MCLK_M = 1; I2S_MCLK_D = 42; codec_SYS_FS = 0x01; codec_RATE_MODE = 0x01; break; } break; case 12: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 16; I2S_MCLK_D = 500; codec_SYS_FS = 0x02; codec_RATE_MODE = 0x01; break; case 120000000: I2S_MCLK_M = 16; I2S_MCLK_D = 624; codec_SYS_FS = 0x02; codec_RATE_MODE = 0x01; break; } break; case 16: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 32; I2S_MCLK_D = 750; codec_SYS_FS = 0x00; codec_RATE_MODE = 0x01; break; case 120000000: I2S_MCLK_M = 32; I2S_MCLK_D = 936; codec_SYS_FS = 0x00; codec_RATE_MODE = 0x01; break; } break; case 22: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 1; I2S_MCLK_D = 17; codec_SYS_FS = 0x01; codec_RATE_MODE = 0x01; break; case 120000000: I2S_MCLK_M = 1; I2S_MCLK_D = 21; codec_SYS_FS = 0x01; codec_RATE_MODE = 0x01; break; } break; case 24: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 32; I2S_MCLK_D = 500; codec_SYS_FS = 0x02; codec_RATE_MODE = 0x01; break; case 120000000: I2S_MCLK_M = 32; I2S_MCLK_D = 624; codec_SYS_FS = 0x02; codec_RATE_MODE = 0x01; break; } break; case 32: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 64; I2S_MCLK_D = 750; codec_SYS_FS = 0x00; codec_RATE_MODE = 0x0; break; case 120000000: I2S_MCLK_M = 64; I2S_MCLK_D = 936; codec_SYS_FS = 0x00; codec_RATE_MODE = 0x0; break; } break; case 44: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 2; I2S_MCLK_D = 17; codec_SYS_FS = 0x01; codec_RATE_MODE = 0x0; break; case 120000000: I2S_MCLK_M = 2; I2S_MCLK_D = 21; codec_SYS_FS = 0x01; codec_RATE_MODE = 0x0; break; } break; case 48: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 32; I2S_MCLK_D = 250; codec_SYS_FS = 0x02; codec_RATE_MODE = 0x0; break; case 120000000: I2S_MCLK_M = 32; I2S_MCLK_D = 312; codec_SYS_FS = 0x02; codec_RATE_MODE = 0x0; break; } break; case 96: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 32; I2S_MCLK_D = 125; codec_SYS_FS = 0x03; codec_RATE_MODE = 0x0; break; case 120000000: I2S_MCLK_M = 32; I2S_MCLK_D = 156; codec_SYS_FS = 0x03; codec_RATE_MODE = 0x0; break; } break; case 72: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 24; I2S_MCLK_D = 125; codec_SYS_FS = 0x03; codec_RATE_MODE = 0x0; break; case 120000000: I2S_MCLK_M = 24; I2S_MCLK_D = 156; codec_SYS_FS = 0x03; codec_RATE_MODE = 0x0; break; } break; case 144: switch(SystemCoreClock) { case 96000000: I2S_MCLK_M = 48; I2S_MCLK_D = 125; codec_SYS_FS = 0x03; codec_RATE_MODE = 0x0; break; case 120000000: I2S_MCLK_M = 48; I2S_MCLK_D = 156; codec_SYS_FS = 0x03; codec_RATE_MODE = 0x0; break; } break; case 192: switch(SystemCoreClock) { case 96000000: // Not officially supported by the codec, but it seems to work. I2S_MCLK_M = 64; I2S_MCLK_D = 125; codec_SYS_FS = 0x03; codec_RATE_MODE = 0x0; break; case 120000000: I2S_MCLK_M = 64; I2S_MCLK_D = 156; codec_SYS_FS = 0x03; codec_RATE_MODE = 0x0; break; } default: return -1; } if(modify_i2c(SGTL5000_CLK_CTRL, codec_SYS_FS, SGTL5000_CLK_CTRL_SYS_FS_MASK)) return -1; // Set CODEC clocking if(modify_i2c(SGTL5000_CLK_CTRL, codec_RATE_MODE, SGTL5000_CLK_CTRL_RATE_MODE_MASK)) return -1; // Set CODEC clocking if(SGTL5000::codec2_active) { if(modify_i2c(SGTL5000_CLK_CTRL, codec_SYS_FS, SGTL5000_CLK_CTRL_SYS_FS_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1))) return -1; // Set 2nd CODEC clocking if(modify_i2c(SGTL5000_CLK_CTRL, codec_RATE_MODE, SGTL5000_CLK_CTRL_RATE_MODE_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1))) return -1; // Set 2nd CODEC clocking } while(I2S0->MCR & I2S_MCR_DUF_MASK); // Check for active changes then set I2S Clock Fractions I2S0->MDR = (I2S0->MDR & ~(I2S_MDR_FRACT_MASK | I2S_MDR_DIVIDE_MASK)) | (I2S_MDR_FRACT(I2S_MCLK_M - 1) | I2S_MDR_DIVIDE(I2S_MCLK_D - 1)); return 0; } int32_t SGTL5000::read_i2c(uint32_t reg_addr, uint32_t mask, int _i2c_addr) { if(!_i2c_addr) _i2c_addr = i2c_addr; if(mask == 0x0 || mask > 0xFFFF) return -1; uint32_t shift; for (shift = 0; !(mask & (1<<shift)); ++shift) {}; uint32_t wire_data; uint32_t wire_addr = __rev(reg_addr) >> 16; mI2C.lock(); if(mI2C.write(_i2c_addr, (char *)&wire_addr, 2, false)) { mI2C.unlock(); return -1; } if(mI2C.read(_i2c_addr, (char *)&wire_data, 2, false)) { mI2C.unlock(); return -1; } return ((__rev(wire_data) >> 16) & mask) >> shift; } int32_t SGTL5000::write_i2c(uint32_t reg_addr, uint32_t data, int _i2c_addr) { if(!_i2c_addr) _i2c_addr = i2c_addr; uint32_t wire_data = (__rev(reg_addr) >> 16) | (__rev(data)); mI2C.lock(); if(mI2C.write(_i2c_addr, (char *)&wire_data, 4, false)) { mI2C.unlock(); return -1; } return 0; } int32_t SGTL5000::modify_i2c(uint32_t reg_addr, uint32_t data, uint32_t mask, int _i2c_addr) { if(!_i2c_addr) _i2c_addr = i2c_addr; if(mask == 0x0 || mask > 0xFFFF) return -1; uint32_t shift; for (shift = 0; !(mask & (1<<shift)); ++shift) {}; uint32_t wire_data = (__rev(reg_addr) >> 16) | (__rev((read_i2c(reg_addr) & (~mask & 0xFFFF)) | ((data << shift) & mask))); mI2C.lock(); if(mI2C.write(_i2c_addr, (char *)&wire_data, 4, false)) { mI2C.unlock(); return -1; } return 0; } int32_t SGTL5000::init_i2s(void) { if(!this->ctrl_codec) return -1; // Set default rate at 48Khz uint32_t I2S_MCLK_MULT; uint32_t I2S_MCLK_DIV; switch(SystemCoreClock) { case 96000000: I2S_MCLK_MULT = 32; I2S_MCLK_DIV = 250; break; case 120000000: I2S_MCLK_MULT = 32; I2S_MCLK_DIV = 312; break; } // Configure System Clock Distribution for I2S SIM->SCGC5 |= SIM_SCGC5_PORTC_MASK; // Enable Clock to PORTC control module. SIM->SCGC6 |= SIM_SCGC6_I2S_MASK; // Enable Clocking to I2S Module SIM->SCGC7 |= SIM_SCGC7_DMA_MASK; // Enable DMA Clock Gate Control SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK; // Enable DMA Mux Clock Gate Control // Configure I2S Master Clocking I2S0->MCR |= I2S_MCR_MICS(3); // Configure I2S MCLK Divder Source as System PLL while(I2S0->MCR & I2S_MCR_DUF_MASK); // Check for active changes then set I2S Clock Fractions I2S0->MDR = (I2S0->MDR & ~(I2S_MDR_FRACT_MASK | I2S_MDR_DIVIDE_MASK)) | (I2S_MDR_FRACT(I2S_MCLK_MULT - 1) | I2S_MDR_DIVIDE(I2S_MCLK_DIV - 1)); I2S0->MCR |= I2S_MCR_MOE_MASK; // Enable MCLK Output // Configure PIN Muxing PORTC->PCR[6] = (PORTC->PCR[6] & ~PORT_PCR_MUX_MASK) | PORT_PCR_MUX(6); // Set PTC6(pin51)(Teensy pin11) <> I2S0_TX_MCLK PORTC->PCR[2] = (PORTC->PCR[2] & ~PORT_PCR_MUX_MASK) | PORT_PCR_MUX(6); // Set PTC2(pin45)(Teensy pin23) <> I2S0_TX_FS(LRCLK) PORTC->PCR[3] = (PORTC->PCR[3] & ~PORT_PCR_MUX_MASK) | PORT_PCR_MUX(6); // Set PTC3(pin46)(Teensy pin9) <> I2S0_TX_BCLK PORTC->PCR[1] = (PORTC->PCR[1] & ~PORT_PCR_MUX_MASK) | PORT_PCR_MUX(6); // Set PTC1(pin44)(Teensy pin22) <> I2S0_TXD0 PORTC->PCR[5] = (PORTC->PCR[5] & ~PORT_PCR_MUX_MASK) | PORT_PCR_MUX(4); // Set PTC5(pin50)(Teensy pin13) <> I2S0_RXD0 PORTC->PCR[0] = (PORTC->PCR[0] & ~PORT_PCR_MUX_MASK) | PORT_PCR_MUX(6); // Set PTC0(pin43)(Teensy pin15) <> I2S0_TXD1 PORTC->PCR[11] = (PORTC->PCR[11] & ~PORT_PCR_MUX_MASK) | PORT_PCR_MUX(4); // Set PTC11(pin56)(Teensy pin30) <> I2S0_RXD1 // Config. TX I2S0->TMR = 0; // I2S TX Mask cleared. I2S0->TCR2 = (I2S0->TCR2 & ~I2S_TCR2_SYNC_MASK) | I2S_TCR2_SYNC(0); // Set TX Mode to Async I2S0->TCR2 |= I2S_TCR2_BCP_MASK; // Set TX Bit Clock Polarity sample riseing edge I2S0->TCR2 = (I2S0->TCR2 & ~I2S_TCR2_MSEL_MASK) | I2S_TCR2_MSEL(1); // Set TX MCLK Audio Master Clock to Master Clock 1. I2S0->TCR2 |= I2S_TCR2_BCD_MASK; // Set TX Clock Dir. internally generated I2S0->TCR2 = (I2S0->TCR2 & ~I2S_TCR2_DIV_MASK) | I2S_TCR2_DIV(3); // Set TX Bit Clock Divide I2S0->TCR3 = (I2S0->TCR3 & ~I2S_TCR3_WDFL_MASK) | I2S_TCR3_WDFL(0); // Set Word Flag Config to be 1st word in frame. I2S0->TCR4 = (I2S0->TCR4 & ~I2S_TCR4_FRSZ_MASK) | I2S_TCR4_FRSZ(1); // Set TX Frame Size @ 2 words per frame I2S0->TCR4 = (I2S0->TCR4 & ~I2S_TCR4_SYWD_MASK) | I2S_TCR4_SYWD(15); // Set TX frame sync width to 16bits I2S0->TCR4 |= I2S_TCR4_MF_MASK; // Set TX MSBit first I2S0->TCR4 |= I2S_TCR4_FSE_MASK; // Set TX Frame sync asserts before first bit I2S0->TCR4 |= I2S_TCR4_FSP_MASK; // Set TX Frame sync polarity active low. I2S0->TCR4 |= I2S_TCR4_FSD_MASK; // Set TX Frame sync direction from master clock I2S0->TCR5 = (I2S0->TCR5 & ~I2S_TCR5_WNW_MASK) | I2S_TCR5_WNW(15); // Set TX to 16bits per word. I2S0->TCR5 = (I2S0->TCR5 & ~I2S_TCR5_W0W_MASK) | I2S_TCR5_W0W(15); // Set TX to 16bits per first word. I2S0->TCR5 = (I2S0->TCR5 & ~I2S_TCR5_FBT_MASK) | I2S_TCR5_FBT(15); // Set TX first bit transmitted index to 16th bit . //Config RX I2S0->RMR = 0; // I2S RX Mask cleared. I2S0->RCR2 = (I2S0->RCR2 & ~I2S_RCR2_SYNC_MASK) | I2S_RCR2_SYNC(1); // Set RX Mode to Sync with TX I2S0->RCR2 |= I2S_TCR2_BCP_MASK; // Set RX Bit Clock Polarity sample riseing edge I2S0->RCR2 = (I2S0->RCR2 & ~I2S_RCR2_MSEL_MASK) | I2S_RCR2_MSEL(1); // Set RX MCLK Audio Master Clock to Master Clock 1. I2S0->RCR2 |= I2S_RCR2_BCD_MASK; // Set RX Clock Dir. internally generated I2S0->RCR2 = (I2S0->RCR2 & ~I2S_RCR2_DIV_MASK) | I2S_RCR2_DIV(3); // Set RX Bit Clock Divide I2S0->RCR3 = (I2S0->RCR3 & ~I2S_RCR3_WDFL_MASK) | I2S_RCR3_WDFL(0); // Set Word Flag Config to be 1st word in frame. I2S0->RCR4 = (I2S0->RCR4 & ~I2S_RCR4_FRSZ_MASK) | I2S_RCR4_FRSZ(1); // Set RX Frame Size @ 2 words per frame I2S0->RCR4 = (I2S0->RCR4 & ~I2S_RCR4_SYWD_MASK) | I2S_RCR4_SYWD(15); // Set RX frame sync width to 16bits I2S0->RCR4 |= I2S_RCR4_MF_MASK; // Set RX MSBit first I2S0->RCR4 |= I2S_RCR4_FSE_MASK; // Set RX Frame sync asserts before first bit I2S0->RCR4 |= I2S_RCR4_FSP_MASK; // Set RX Frame sync polarity active low. I2S0->RCR4 |= I2S_RCR4_FSD_MASK; // Set RX Frame sync direction from master clock I2S0->RCR5 = (I2S0->RCR5 & ~I2S_RCR5_WNW_MASK) | I2S_RCR5_WNW(15); // Set RX to 16bits per word. I2S0->RCR5 = (I2S0->RCR5 & ~I2S_RCR5_W0W_MASK) | I2S_RCR5_W0W(15); // Set RX to 16bits per first word. I2S0->RCR5 = (I2S0->RCR5 & ~I2S_RCR5_FBT_MASK) | I2S_RCR5_FBT(15); // Set RX first bit transmitted index to 16 . // Configure I2S Peripheral I2S0->TCSR |= I2S_TCSR_BCE_MASK; // Enable I2S Tx bit clock I2S0->RCSR |= I2S_RCSR_BCE_MASK; // Enable I2S Rx Bit Clock SGTL5000::i2s_configured = true; return 0; } int32_t SGTL5000::init_codec(void) { // Default Configure Codec if( write_i2c(SGTL5000_ANA_POWER,( (SGTL5000_ANA_POWER_STARTUP_POWERUP_MASK & (0x1 << SGTL5000_ANA_POWER_STARTUP_POWERUP_SHIFT)) | // Startup power sequence and reset. (SGTL5000_ANA_POWER_LINREG_SIMPLE_POWERUP_MASK & (0x1 << SGTL5000_ANA_POWER_LINREG_SIMPLE_POWERUP_SHIFT)) // Enable simple linear REG )) ) return -1; wait_us(10000); if( // Wait 10mS for powerup and reset write_i2c(SGTL5000_ANA_POWER,( (SGTL5000_ANA_POWER_LINREG_SIMPLE_POWERUP_MASK & (0x0 << SGTL5000_ANA_POWER_LINREG_SIMPLE_POWERUP_SHIFT)) | // Disable simple digital lineregulator. Teensy shield has external reg but sequence requires simple linereg on for reset and power-up (SGTL5000_ANA_POWER_STARTUP_POWERUP_MASK & (0x0 << SGTL5000_ANA_POWER_STARTUP_POWERUP_SHIFT)) | // Disable reset and powerup circuits. This Can be cleared after powerup as VDDD is external on Teensy (SGTL5000_ANA_POWER_VDDC_CHRGPMP_POWERUP_MASK & (0x0 << SGTL5000_ANA_POWER_VDDC_CHRGPMP_POWERUP_SHIFT)) | // Disable charge pump as VDDA & VDDIO > 3.1V (SGTL5000_ANA_POWER_PLL_POWERUP_MASK & (0x0 << SGTL5000_ANA_POWER_PLL_POWERUP_SHIFT)) | // PLL disabled as codec clocking is synchronous to I2S clock. (SGTL5000_ANA_POWER_LINREG_D_POWERUP_MASK & (0x0 << SGTL5000_ANA_POWER_LINREG_D_POWERUP_SHIFT)) | // Disable the VDDD line regulator as Teensy externally regulated at 1.85V (SGTL5000_ANA_POWER_VCOAMP_POWERUP_MASK & (0x0 << SGTL5000_ANA_POWER_VCOAMP_POWERUP_SHIFT)) | // Disable the PLL VCO Amp, we don't use PLL (SGTL5000_ANA_POWER_VAG_POWERUP_MASK & (0x1 << SGTL5000_ANA_POWER_VAG_POWERUP_SHIFT)) | // Powerup VAG ref buffer. Should be enabled before powering up headphone and lineout and disabled before powering down HP and LINEOUT (SGTL5000_ANA_POWER_DAC_MONO_MASK & (0x1 << SGTL5000_ANA_POWER_DAC_MONO_SHIFT)) | // Select stereo mode for DAC (SGTL5000_ANA_POWER_ADC_MONO_MASK & (0x1 << SGTL5000_ANA_POWER_ADC_MONO_SHIFT)) | // ADC in stereo mode (SGTL5000_ANA_POWER_REFTOP_POWERUP_MASK & (0x1 << SGTL5000_ANA_POWER_REFTOP_POWERUP_SHIFT)) | // Enable bias currents (SGTL5000_ANA_POWER_HEADPHONE_POWERUP_MASK & (0x0 << SGTL5000_ANA_POWER_HEADPHONE_POWERUP_SHIFT)) | // Disable HP amp until we set the reference levels (SGTL5000_ANA_POWER_DAC_POWERUP_MASK & (0x0 << SGTL5000_ANA_POWER_DAC_POWERUP_SHIFT)) | // Disable DAC until we set refs (SGTL5000_ANA_POWER_CAPLESS_HEADPHONE_POWERUP_MASK & (0x0 << SGTL5000_ANA_POWER_CAPLESS_HEADPHONE_POWERUP_SHIFT)) | // Disable capless headphones (SGTL5000_ANA_POWER_ADC_POWERUP_MASK & (0x0 << SGTL5000_ANA_POWER_ADC_POWERUP_SHIFT)) | // Disable ADC power until we set refs (SGTL5000_ANA_POWER_LINEOUT_POWERUP_MASK & (0x0 << SGTL5000_ANA_POWER_LINEOUT_POWERUP_SHIFT)) // Disable LINEOUT power until we set refs )) ) return -1; if( write_i2c(SGTL5000_LINREG_CTRL,( (SGTL5000_LINREG_CTRL_VDDC_MAN_ASSN_MASK & (0x0 << SGTL5000_LINREG_CTRL_VDDC_MAN_ASSN_SHIFT)) | // Manually assign charge pump rail (VDDC) source as VDDA. (SGTL5000_LINREG_CTRL_VDDC_ASSN_OVRD_MASK & (0x1 << SGTL5000_LINREG_CTRL_VDDC_ASSN_OVRD_SHIFT)) | // chargepump source manually assigned (SGTL5000_LINREG_CTRL_D_PROGRAMMING_MASK & (0xC << SGTL5000_LINREG_CTRL_D_PROGRAMMING_SHIFT)) // VDDD Line Reg not used so leave default )) ) return -2; if( write_i2c(SGTL5000_REF_CTRL,( (SGTL5000_REF_CTRL_VAG_VAL_MASK & (0x1F << SGTL5000_REF_CTRL_VAG_VAL_SHIFT)) | // Set VAG ref to 1.575V (SGTL5000_REF_CTRL_BIAS_CTRL_MASK & (0x1 << SGTL5000_REF_CTRL_BIAS_CTRL_SHIFT)) | // Set analog bias currents to +12.5% (SGTL5000_REF_CTRL_SMALL_POP_MASK & (0x0 << SGTL5000_REF_CTRL_SMALL_POP_SHIFT)) // Set small pop ramp normal )) ) return -3; if( write_i2c(SGTL5000_LINE_OUT_CTRL,( (SGTL5000_LINE_OUT_CTRL_OUT_CURRENT_MASK & (0x3 << SGTL5000_LINE_OUT_CTRL_OUT_CURRENT_SHIFT)) | // Set Lineout bias curent 0.36mA (SGTL5000_LINE_OUT_CTRL_LO_VAGCNTRL_MASK & (0x22 << SGTL5000_LINE_OUT_CTRL_LO_VAGCNTRL_SHIFT)) // Lineout AGND = 1.65v (line out will drive between the rails, so set at VDDA/2) )) ) return -4; if( write_i2c(SGTL5000_SHORT_CTRL,( (SGTL5000_SHORT_CTRL_LVLADJR_MASK & (0x4 << SGTL5000_SHORT_CTRL_LVLADJR_SHIFT)) | // HP R short detect 125mA (SGTL5000_SHORT_CTRL_LVLADJL_MASK & (0x4 << SGTL5000_SHORT_CTRL_LVLADJL_SHIFT)) | // HP L short detect 125mA (SGTL5000_SHORT_CTRL_LVLADJC_MASK & (0x4 << SGTL5000_SHORT_CTRL_LVLADJC_SHIFT)) | // Headphone capless short detect 250mA (SGTL5000_SHORT_CTRL_MODE_LR_MASK & (0x1 << SGTL5000_SHORT_CTRL_MODE_LR_SHIFT)) | // en short det reset 50mS (SGTL5000_SHORT_CTRL_MODE_CM_MASK & (0x2 << SGTL5000_SHORT_CTRL_MODE_CM_SHIFT)) // En capless short det reset vol rise )) ) return -5; if( write_i2c(SGTL5000_ANA_CTRL,( (SGTL5000_ANA_CTRL_MUTE_LO_MASK & (0x1 << SGTL5000_ANA_CTRL_MUTE_LO_SHIFT)) | // Mute LINEOUT (SGTL5000_ANA_CTRL_SELECT_HP_MASK & (0x0 << SGTL5000_ANA_CTRL_SELECT_HP_SHIFT)) | // Select DAC as input to HP amp (SGTL5000_ANA_CTRL_EN_ZCD_HP_MASK & (0x1 << SGTL5000_ANA_CTRL_EN_ZCD_HP_SHIFT)) | // Enable ZCD on HP (SGTL5000_ANA_CTRL_MUTE_HP_MASK & (0x1 << SGTL5000_ANA_CTRL_MUTE_HP_SHIFT)) | // Mute the headphones (SGTL5000_ANA_CTRL_SELECT_ADC_MASK & (0x1 << SGTL5000_ANA_CTRL_SELECT_ADC_SHIFT)) | // Select LINEIN as input to ADC (SGTL5000_ANA_CTRL_EN_ZCD_ADC_MASK & (0x1 << SGTL5000_ANA_CTRL_EN_ZCD_ADC_SHIFT)) | // Enable ADC ZCD (SGTL5000_ANA_CTRL_MUTE_ADC_MASK & (0x1 << SGTL5000_ANA_CTRL_MUTE_ADC_SHIFT)) // Mute ADC pre-amp )) ) return -6; if(modify_i2c(SGTL5000_ANA_POWER, 0x1, SGTL5000_ANA_POWER_HEADPHONE_POWERUP_MASK)) return -7; // Power up Headphone amp if(modify_i2c(SGTL5000_ANA_POWER, 0x1, SGTL5000_ANA_POWER_DAC_POWERUP_MASK)) return -8; // Power up DAC if(modify_i2c(SGTL5000_ANA_POWER, 0x1, SGTL5000_ANA_POWER_CAPLESS_HEADPHONE_POWERUP_MASK)) return -9; // Power up capless HP block if(modify_i2c(SGTL5000_ANA_POWER, 0x1, SGTL5000_ANA_POWER_ADC_POWERUP_MASK)) return -10; // Power up ADC if(modify_i2c(SGTL5000_ANA_POWER, 0x1, SGTL5000_ANA_POWER_LINEOUT_POWERUP_MASK)) return -11; // Power up Lineout Amp if(modify_i2c(SGTL5000_ANA_POWER, 0x0, SGTL5000_ANA_POWER_LINREG_SIMPLE_POWERUP_MASK)) return -12; // Turn of simple regulator now the blocks are powered-up if( write_i2c(SGTL5000_DIG_POWER,( (SGTL5000_DIG_POWER_ADC_POWERUP_MASK & (0x1 << SGTL5000_DIG_POWER_ADC_POWERUP_SHIFT)) | (SGTL5000_DIG_POWER_DAC_POWERUP_MASK & (0x1 << SGTL5000_DIG_POWER_DAC_POWERUP_SHIFT)) | (SGTL5000_DIG_POWER_DAP_POWERUP_MASK & (0x1 << SGTL5000_DIG_POWER_DAP_POWERUP_SHIFT)) | (SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK & (0x1 << SGTL5000_DIG_POWER_I2S_OUT_POWERUP_SHIFT)) | // Keep I2S data lines disabled until config complete (SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK & (0x1 << SGTL5000_DIG_POWER_I2S_IN_POWERUP_SHIFT)) )) ) return -13; write_i2c(SGTL5000_LINE_OUT_VOL, ( (SGTL5000_LINE_OUT_VOL_LO_VOL_RIGHT_MASK & (0x11 << SGTL5000_LINE_OUT_VOL_LO_VOL_RIGHT_SHIFT)) | // Lineout Vol normalised (SGTL5000_LINE_OUT_VOL_LO_VOL_LEFT_MASK & (0x11 << SGTL5000_LINE_OUT_VOL_LO_VOL_LEFT_SHIFT)) // Verified with oscope at p2p = 2.82v driving ~30k load )); if( write_i2c(SGTL5000_PAD_STRENGTH,( (SGTL5000_PAD_STRENGTH_I2S_LRCLK_MASK & (0x0 << SGTL5000_PAD_STRENGTH_I2S_LRCLK_SHIFT)) | // I2S LR_CLK drive dissabled (codec is slave) (SGTL5000_PAD_STRENGTH_I2S_SCLK_MASK & (0x0 << SGTL5000_PAD_STRENGTH_I2S_SCLK_SHIFT)) | // I2S SCLK drive dissabled (codec is slave) (SGTL5000_PAD_STRENGTH_I2S_DOUT_MASK & (0x1 << SGTL5000_PAD_STRENGTH_I2S_DOUT_SHIFT)) | // I2S DOUT drive @ 4.02mA (SGTL5000_PAD_STRENGTH_CTRL_DATA_MASK & (0x3 << SGTL5000_PAD_STRENGTH_CTRL_DATA_SHIFT)) | // I2C DATA drive @ 12.05mA. (SGTL5000_PAD_STRENGTH_CTRL_CLK_MASK & (0x3 << SGTL5000_PAD_STRENGTH_CTRL_CLK_SHIFT)) // I2C CLOCK drive @ 12.05mA. )) )return -14; if( write_i2c(SGTL5000_CLK_CTRL,( (SGTL5000_CLK_CTRL_RATE_MODE_MASK & (0x0 << SGTL5000_CLK_CTRL_RATE_MODE_SHIFT)) | // Sample rate mode Fs = SYS_FS (SGTL5000_CLK_CTRL_SYS_FS_MASK & (0x02 << SGTL5000_CLK_CTRL_SYS_FS_SHIFT)) | // Set SYS_FS (sampling rate depending on mode) @ 48Khz (SGTL5000_CLK_CTRL_MCLK_FREQ_MASK & (0x0 << SGTL5000_CLK_CTRL_MCLK_FREQ_SHIFT)) // Set SYS MCLK @ 256*Fs )) ) return -15; if( write_i2c(SGTL5000_I2S_CTRL,( (SGTL5000_I2S_CTRL_SCLKFREQ_MASK & (0x1 << SGTL5000_I2S_CTRL_SCLKFREQ_SHIFT)) | // I2S SCLK 32*Fs (SGTL5000_I2S_CTRL_MS_MASK & (0x0 << SGTL5000_I2S_CTRL_MS_SHIFT)) | // Slave (SGTL5000_I2S_CTRL_SCLK_INV_MASK & (0x0 << SGTL5000_I2S_CTRL_SCLK_INV_SHIFT)) | // Data on riseing edge (SGTL5000_I2S_CTRL_DLEN_MASK & (0x3 << SGTL5000_I2S_CTRL_DLEN_SHIFT)) | // Data 16bits (SGTL5000_I2S_CTRL_MODE_MASK & (0x0 << SGTL5000_I2S_CTRL_MODE_SHIFT)) | // I2S mode (SGTL5000_I2S_CTRL_LRALIGN_MASK & (0x0 << SGTL5000_I2S_CTRL_LRALIGN_SHIFT)) | // I2S format (SGTL5000_I2S_CTRL_LRPOL_MASK & (0x0 << SGTL5000_I2S_CTRL_LRPOL_SHIFT)) // Left word on LRCLK low )) ) return -16; if( write_i2c(SGTL5000_SSS_CTRL,( (SGTL5000_SSS_CTRL_DAP_MIX_LRSWAP_MASK & (0x0 << SGTL5000_SSS_CTRL_DAP_MIX_LRSWAP_SHIFT)) | // Define all inputs un-swapped (SGTL5000_SSS_CTRL_DAP_LRSWAP_MASK & (0x0 << SGTL5000_SSS_CTRL_DAP_LRSWAP_SHIFT)) | (SGTL5000_SSS_CTRL_DAC_LRSWAP_MASK & (0x0 << SGTL5000_SSS_CTRL_DAC_LRSWAP_SHIFT)) | (SGTL5000_SSS_CTRL_I2S_LRSWAP_MASK & (0x0 << SGTL5000_SSS_CTRL_I2S_LRSWAP_SHIFT)) | (SGTL5000_SSS_CTRL_DAP_MIX_SELECT_MASK & (0x0 << SGTL5000_SSS_CTRL_DAP_MIX_SELECT_SHIFT)) | // DAP mixer source ADC (SGTL5000_SSS_CTRL_DAP_SELECT_MASK & (0x0 << SGTL5000_SSS_CTRL_DAP_SELECT_SHIFT)) | // DAP Input source ADC (SGTL5000_SSS_CTRL_DAC_SELECT_MASK & (0x1 << SGTL5000_SSS_CTRL_DAC_SELECT_SHIFT)) | // DAC Input source I2SIN (SGTL5000_SSS_CTRL_I2S_SELECT_MASK & (0x0 << SGTL5000_SSS_CTRL_I2S_SELECT_SHIFT)) // I2SOUT source ADC )) ) return -17; if( write_i2c(SGTL5000_ADCDAC_CTRL,( (SGTL5000_ADCDAC_CTRL_VOL_RAMP_EN_MASK & (0x0 << SGTL5000_ADCDAC_CTRL_VOL_RAMP_EN_SHIFT)) | // Disable Vol ramp (SGTL5000_ADCDAC_CTRL_VOL_EXPO_RAMP_MASK & (0x0 << SGTL5000_ADCDAC_CTRL_VOL_EXPO_RAMP_SHIFT)) | (SGTL5000_ADCDAC_CTRL_DAC_MUTE_RIGHT_MASK & (0x0 << SGTL5000_ADCDAC_CTRL_DAC_MUTE_RIGHT_SHIFT)) | (SGTL5000_ADCDAC_CTRL_DAC_MUTE_LEFT_MASK & (0x0 << SGTL5000_ADCDAC_CTRL_DAC_MUTE_LEFT_SHIFT)) | (SGTL5000_ADCDAC_CTRL_ADC_HPF_FREEZE_MASK & (0x0 << SGTL5000_ADCDAC_CTRL_ADC_HPF_FREEZE_SHIFT)) | // ADC HPF normal operation (SGTL5000_ADCDAC_CTRL_ADC_HPF_BYPASS_MASK & (0x0 << SGTL5000_ADCDAC_CTRL_ADC_HPF_BYPASS_SHIFT)) // ADC HPF enabled )) ) return -18; if( write_i2c(SGTL5000_DAC_VOL,( (SGTL5000_DAC_VOL_DAC_VOL_RIGHT_MASK & (0x3C << SGTL5000_DAC_VOL_DAC_VOL_RIGHT_SHIFT)) | // DAC R Vol 0dB (SGTL5000_DAC_VOL_DAC_VOL_LEFT_MASK & (0x3C << SGTL5000_DAC_VOL_DAC_VOL_LEFT_SHIFT)) // DAC L Vol 0dB )) ) return -19; if( write_i2c(SGTL5000_ANA_HP_CTRL,( (SGTL5000_ANA_HP_CTRL_HP_VOL_RIGHT_MASK & (0x7F << SGTL5000_ANA_HP_CTRL_HP_VOL_RIGHT_SHIFT)) | // HP Vol set minimum (SGTL5000_ANA_HP_CTRL_HP_VOL_LEFT_MASK & (0x7F << SGTL5000_ANA_HP_CTRL_HP_VOL_LEFT_SHIFT)) )) ) return -20; if(modify_i2c(SGTL5000_ANA_CTRL, 0x0, SGTL5000_ANA_CTRL_MUTE_LO_MASK)) return -21; // Un-mute Lineout if(modify_i2c(SGTL5000_ANA_CTRL, 0x0, SGTL5000_ANA_CTRL_MUTE_HP_MASK)) return -22; // Un-mute HP if(modify_i2c(SGTL5000_ANA_CTRL, 0x0, SGTL5000_ANA_CTRL_MUTE_ADC_MASK)) return -23; // Un-mute ADC pre-amp if(modify_i2c(SGTL5000_DIG_POWER, 0x1, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK)) return -24; // I2S DOUT enable if(modify_i2c(SGTL5000_DIG_POWER, 0x1, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK)) return -25; // I2S DIN enable if(this->i2c_addr == (SGTL5000_I2C_ADDR_CS_LOW << 1)) SGTL5000::codec1_active = true; if(this->i2c_addr == (SGTL5000_I2C_ADDR_CS_HIGH << 1)) SGTL5000::codec2_active = true; return 0; } int32_t SGTL5000::attach_SYNC(Callback<void()> func) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow for SYNC. if(SYNC_attached) return -1; // Assign Callback function SGTL5000::SYNC_user_func = func; SGTL5000::SYNC_attach_type = 0; SYNC_attached = true; return 0; } int32_t SGTL5000::attach_SYNC_NB(void* user_ISR, uint32_t irq_pri, IRQn sw_irq) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow. SGTL5000::SYNC_swIRQ = sw_irq; // Assign ISR address and enable users IRQ NVIC_SetVector(SGTL5000::SYNC_swIRQ, (uint32_t)user_ISR); NVIC_SetPriority(SGTL5000::SYNC_swIRQ, irq_pri); NVIC_EnableIRQ(SGTL5000::SYNC_swIRQ); SGTL5000::SYNC_attach_type = 1; SYNC_attached = true; return 0; } int32_t SGTL5000::detach_SYNC(void) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow. if(SYNC_attached) { if(SGTL5000::SYNC_attach_type) NVIC_DisableIRQ(SGTL5000::SYNC_swIRQ); // When using Interrupt driven NB calls, we disable the IRQ. The SYNC channel continues running, a different user function can be assigned with another call to the attach_NB function else SGTL5000::stop_SYNC(); // When using callback we must stop SYNC channel before we detach. SYNC_attached = false; } return 0; } void SGTL5000::stream_ctrl_ISR(void) { switch(SGTL5000::ctrl_command) { case STOP_SYNC: I2S0->TMR = 0xFFFFFFFF; // Mask TX channels to stop stream I2S0->RMR = 0xFFFFFFFF; // Mask RX channels to stop stream while(!(DMA0->TCD[SGTL5000::TX_DMAch].CSR | DMA_CSR_DONE_MASK)); // Ensure any active DMA transfers are complete while(!(DMA0->TCD[SGTL5000::RX_DMAch].CSR | DMA_CSR_DONE_MASK)); // Ensure any active DMA transfers are complete I2S0->TCSR &= ~I2S_TCSR_FRDE_MASK; // Disable TX FIFO DMA request based on watermark I2S0->RCSR &= ~I2S_RCSR_FRDE_MASK; // Disable RX FIFO DMA request based on watermark if(SGTL5000::codec2_active) { I2S0->TCR3 = (I2S0->TCR3 & ~I2S_TCR3_TCE_MASK) & ~I2S_TCR3_TCE(3); // Disable TX channels 0 & 1. I2S0->RCR3 = (I2S0->RCR3 & ~I2S_RCR3_RCE_MASK) & ~I2S_RCR3_RCE(3); // Disable RX channels 0 & 1. } else { I2S0->TCR3 = (I2S0->TCR3 & ~I2S_TCR3_TCE_MASK) & ~I2S_TCR3_TCE(1); // Disable TX channel 0. I2S0->RCR3 = (I2S0->RCR3 & ~I2S_RCR3_RCE_MASK) & ~I2S_RCR3_RCE(1); // Disable RX channel 0 } I2S0->TCSR &= ~I2S_TCSR_TE_MASK; // TX Disable I2S0->RCSR &= ~I2S_RCSR_RE_MASK; // RX Disable while((I2S0->RCSR & I2S_RCSR_RE_MASK) || (I2S0->TCSR & I2S_TCSR_TE_MASK)); // Ensure TX & RX have stopped. SGTL5000::SYNC_run = false; break; case PAUSE_SYNC: I2S0->RCSR |= I2S_RCSR_WSF_MASK; // Clear RX Word Start Flag while(!(I2S0->RCSR & I2S_RCSR_WSF_MASK)); // wait for Frame Start I2S0->RMR = 0xFFFFFFFF; // Mask RX channels to halt stream I2S0->TMR = 0xFFFFFFFF; // Mask TX channels to halt stream while(!(DMA0->TCD[SGTL5000::RX_DMAch].CSR | DMA_CSR_DONE_MASK) || !(DMA0->TCD[SGTL5000::TX_DMAch].CSR | DMA_CSR_DONE_MASK)); // Ensure any active DMA transfers are complete break; case RESUME_SYNC: I2S0->RCSR |= I2S_RCSR_WSF_MASK; // Clear RX Word Start Flag while(!(I2S0->RCSR & I2S_RCSR_WSF_MASK)); // wait for Frame Start I2S0->RMR = 0x0; // Unmask RX channels to resume stream I2S0->TMR = 0x0; // Unmask TX channels to resume stream break; case STOP_TX: I2S0->TMR = 0xFFFFFFFF; // Mask TX channels to halt stream while(!(DMA0->TCD[SGTL5000::TX_DMAch].CSR | DMA_CSR_DONE_MASK)); // Ensure any active DMA transfers are complete I2S0->TCSR &= ~I2S_TCSR_FRDE_MASK; // Disable TX FIFO DMA request based on watermark if(SGTL5000::codec2_active) I2S0->TCR3 = (I2S0->TCR3 & ~I2S_TCR3_TCE_MASK) & ~I2S_TCR3_TCE(3); // Disable TX channel 0 & 1. else (I2S0->TCR3 & ~I2S_TCR3_TCE_MASK) & ~I2S_TCR3_TCE(1); // Disable TX channel 0. I2S0->TCSR &= ~I2S_TCSR_TE_MASK; // TX Disable while(I2S0->TCSR & I2S_TCSR_TE_MASK); // Ensure TX has stopped. SGTL5000::TX_run = false; break; case PAUSE_TX: I2S0->TCSR |= I2S_TCSR_WSF_MASK; // Clear TX Word Start Flag while(!(I2S0->TCSR & I2S_TCSR_WSF_MASK)); // wait for Frame Start I2S0->TMR = 0xFFFFFFFF; // Mask TX channels to halt stream while(!(DMA0->TCD[SGTL5000::TX_DMAch].CSR | DMA_CSR_DONE_MASK)); // Ensure any active DMA transfers are complete break; case RESUME_TX: I2S0->TCSR |= I2S_TCSR_WSF_MASK; // Clear TX Word Start Flag while(!(I2S0->TCSR & I2S_TCSR_WSF_MASK)); // wait for Frame Start I2S0->TMR = 0x0; // Clear TX mask to start stream break; case STOP_RX: I2S0->RMR = 0xFFFFFFFF; // Mask RX channels to halt stream while(!(DMA0->TCD[SGTL5000::RX_DMAch].CSR | DMA_CSR_DONE_MASK)); // Ensure any active DMA transfers are complete I2S0->RCSR &= ~I2S_RCSR_FRDE_MASK; // Disable RX FIFO DMA request based on watermark if(SGTL5000::codec2_active) I2S0->RCR3 = (I2S0->RCR3 & ~I2S_RCR3_RCE_MASK) & ~I2S_RCR3_RCE(3); // Disable RX channel 0 & 1. else (I2S0->RCR3 & ~I2S_RCR3_RCE_MASK) & ~I2S_RCR3_RCE(1); // Disable RX channel 0. I2S0->RCSR &= ~I2S_RCSR_RE_MASK; // RX Disable while(I2S0->RCSR & I2S_RCSR_RE_MASK); // Ensure RX has stopped. SGTL5000::RX_run = false; break; case PAUSE_RX: I2S0->RCSR |= I2S_RCSR_WSF_MASK; // Clear RX Word Start Flag while(!(I2S0->RCSR & I2S_RCSR_WSF_MASK)); // wait for Frame Start I2S0->RMR = 0xFFFFFFFF; // Mask TX channels to halt stream while(!(DMA0->TCD[SGTL5000::RX_DMAch].CSR | DMA_CSR_DONE_MASK)); // Ensure any active DMA transfers are complete break; case RESUME_RX: I2S0->RCSR |= I2S_RCSR_WSF_MASK; // Clear RX Word Start Flag while(!(I2S0->RCSR & I2S_RCSR_WSF_MASK)); // wait for Frame Start I2S0->RMR = 0x0; // Unmask TX channels to resume stream break; } SGTL5000::ctrl_command = 0; } int32_t SGTL5000::start_SYNC(uint32_t _BufRX_L_safe, uint32_t _BufRX_R_safe, uint32_t _BufTX_L_safe, uint32_t _BufTX_R_safe, uint32_t _block_size, bool _packed_RX, bool _packed_TX, bool _RX_shift, bool _TX_shift, uint32_t _RX_DMAch, uint32_t _TX_DMAch, uint32_t _DMA_irq_pri, uint32_t _BufRX_L_safe2, uint32_t _BufRX_R_safe2, uint32_t _BufTX_L_safe2, uint32_t _BufTX_R_safe2) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow. if(SGTL5000::SYNC_run || SGTL5000::TX_run || SGTL5000::RX_run ) return -1; // Check if i2s is already started if(!SYNC_attached && !SGTL5000::SYNC_attach_type) return -1; // Check we have a handler if using callback if(_RX_DMAch > 15 || _TX_DMAch > 15) return -1; // Sanity check DMAMUX channels if (!(_block_size == 2 || _block_size == 4 || _block_size == 8)) return -1; // Only accept block size 2^n within range. // Enable I2S interfaces for active codecs if(read_i2c(SGTL5000_DIG_POWER, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_LOW << 1)) == 0) { if(modify_i2c(SGTL5000_DIG_POWER, 0x1, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_LOW << 1))) return -1; } if(read_i2c(SGTL5000_DIG_POWER, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_LOW << 1)) == 0) { if(modify_i2c(SGTL5000_DIG_POWER, 0x1, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_LOW << 1))) return -1; } if(SGTL5000::codec2_active) { if(read_i2c(SGTL5000_DIG_POWER, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1)) == 0) { if(modify_i2c(SGTL5000_DIG_POWER, 0x1, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1))) return -1; } if(read_i2c(SGTL5000_DIG_POWER, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1)) == 0) { if(modify_i2c(SGTL5000_DIG_POWER, 0x1, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1))) return -1; } } packed_RX = _packed_RX; packed_TX = _packed_TX; TX_block_size = _block_size; RX_block_size = TX_block_size; TX_bs_bytes = _block_size * 4; RX_bs_bytes = TX_bs_bytes; if(packed_RX) RX_shift = false; else RX_shift = _RX_shift; if(packed_TX) TX_shift = false; else TX_shift = _TX_shift; SGTL5000::RX_DMAch = _RX_DMAch; SGTL5000::TX_DMAch = _TX_DMAch; SGTL5000::active_RX_DMAch_bm = 0x1 << _RX_DMAch; SGTL5000::active_TX_DMAch_bm = 0x1 << _TX_DMAch; SGTL5000::BufRX_L_safe = (uint32_t*)_BufRX_L_safe; // Assign the users pointer addresses codec 1 SGTL5000::BufRX_R_safe = (uint32_t*)_BufRX_R_safe; SGTL5000::BufTX_L_safe = (uint32_t*)_BufTX_L_safe; SGTL5000::BufTX_R_safe = (uint32_t*)_BufTX_R_safe; SGTL5000::BufRX_L_safeA = (uint32_t)&SGTL5000::I2S_RX_Buffer[0]; // Precalculate double buffer addresses SGTL5000::BufRX_R_safeA = (uint32_t)&SGTL5000::I2S_RX_Buffer[0 + (RX_block_size / 2)]; SGTL5000::BufTX_L_safeA = (uint32_t)&SGTL5000::I2S_TX_Buffer[0]; SGTL5000::BufTX_R_safeA = (uint32_t)&SGTL5000::I2S_TX_Buffer[0 + (TX_block_size / 2)]; SGTL5000::BufRX_L_safeB = (uint32_t)&SGTL5000::I2S_RX_Buffer[8]; SGTL5000::BufRX_R_safeB = (uint32_t)&SGTL5000::I2S_RX_Buffer[8 + (RX_block_size / 2)]; SGTL5000::BufTX_L_safeB = (uint32_t)&SGTL5000::I2S_TX_Buffer[8]; SGTL5000::BufTX_R_safeB = (uint32_t)&SGTL5000::I2S_TX_Buffer[8 + (TX_block_size / 2)]; *SGTL5000::BufRX_L_safe = SGTL5000::BufRX_L_safeB; // Assign starting addresses to double buffer pointers *SGTL5000::BufRX_R_safe = SGTL5000::BufRX_R_safeB; *SGTL5000::BufTX_L_safe = SGTL5000::BufTX_L_safeB; *SGTL5000::BufTX_R_safe = SGTL5000::BufTX_L_safeB; if(SGTL5000::codec2_active) { SGTL5000::BufRX_L_safe2 = (uint32_t*)_BufRX_L_safe2; // Assign the users pointer addresses codec 2 SGTL5000::BufRX_R_safe2 = (uint32_t*)_BufRX_R_safe2; SGTL5000::BufTX_L_safe2 = (uint32_t*)_BufTX_L_safe2; SGTL5000::BufTX_R_safe2 = (uint32_t*)_BufTX_R_safe2; SGTL5000::BufRX_L_safeA2 = (uint32_t)&SGTL5000::I2S_RX_Buffer2[0]; // Precalculate double buffer addresses SGTL5000::BufRX_R_safeA2 = (uint32_t)&SGTL5000::I2S_RX_Buffer2[0 + (RX_block_size / 2)]; SGTL5000::BufTX_L_safeA2 = (uint32_t)&SGTL5000::I2S_TX_Buffer2[0]; SGTL5000::BufTX_R_safeA2 = (uint32_t)&SGTL5000::I2S_TX_Buffer2[0 + (TX_block_size / 2)]; SGTL5000::BufRX_L_safeB2 = (uint32_t)&SGTL5000::I2S_RX_Buffer2[8]; SGTL5000::BufRX_R_safeB2 = (uint32_t)&SGTL5000::I2S_RX_Buffer2[8 + (RX_block_size / 2)]; SGTL5000::BufTX_L_safeB2 = (uint32_t)&SGTL5000::I2S_TX_Buffer2[8]; SGTL5000::BufTX_R_safeB2 = (uint32_t)&SGTL5000::I2S_TX_Buffer2[8 + (TX_block_size / 2)]; *SGTL5000::BufRX_L_safe2 = SGTL5000::BufRX_L_safeB2; // Assign starting addresses to double buffer pointers *SGTL5000::BufRX_R_safe2 = SGTL5000::BufRX_R_safeB2; *SGTL5000::BufTX_L_safe2 = SGTL5000::BufTX_L_safeB2; *SGTL5000::BufTX_R_safe2 = SGTL5000::BufTX_L_safeB2; } I2S0->TCSR &= ~I2S_TCSR_TE_MASK; // TX Disable I2S0->RCSR &= ~I2S_RCSR_RE_MASK; // RX Disable I2S0->TMR = 0xFFFFFFFF; // Mask TX & RX traffic to prevent buffer under/overflow before we aquire sync. I2S0->RMR = 0xFFFFFFFF; I2S0->TCSR |= I2S_TCSR_SR_MASK; // Reset TX & RX logic I2S0->RCSR |= I2S_RCSR_SR_MASK; I2S0->TCSR &= ~I2S_TCSR_SR_MASK; I2S0->RCSR &= ~I2S_RCSR_SR_MASK; I2S0->TCR1 = (I2S0->TCR1 & ~I2S_TCR1_TFW_MASK) | I2S_TCR1_TFW(8 - TX_block_size); // Set TX FIFO watermark I2S0->RCR1 = (I2S0->RCR1 & ~I2S_RCR1_RFW_MASK) | I2S_RCR1_RFW(RX_block_size - 1); // Set RX FIFO watermark I2S0->TCSR |= I2S_TCSR_FRDE_MASK; // Enable DMA request based on TX FIFO watermark I2S0->RCSR |= I2S_RCSR_FRDE_MASK; // Enable DMA request based on RX FIFO watermark while((I2S0->RCSR & I2S_RCSR_RE_MASK) || (I2S0->TCSR & I2S_TCSR_TE_MASK)); // Make sure TX & RX are disabled before enabling corresponding channels NVIC_SetVector((IRQn)SGTL5000::TX_DMAch, (uint32_t)&SGTL5000::SYNC_TX_dma_ISR); // Set TX_DMAch transfer complete vector NVIC_SetVector((IRQn)SGTL5000::RX_DMAch, (uint32_t)&SGTL5000::SYNC_RX_dma_ISR); // Set RX_DMAch transfer complete vector NVIC_SetPriority((IRQn)SGTL5000::TX_DMAch, _DMA_irq_pri); // Set irq priorities the same NVIC_SetPriority((IRQn)SGTL5000::RX_DMAch, _DMA_irq_pri); NVIC_EnableIRQ((IRQn)SGTL5000::TX_DMAch); NVIC_EnableIRQ((IRQn)SGTL5000::RX_DMAch); if(SGTL5000::codec2_active) { I2S0->TCR3 = (I2S0->TCR3 & ~I2S_TCR3_TCE_MASK) | I2S_TCR3_TCE(3); // Enable TX channels 0 & 1. I2S0->RCR3 = (I2S0->RCR3 & ~I2S_RCR3_RCE_MASK) | I2S_RCR3_RCE(3); // Enable RX channels 0 & 1 } else { I2S0->TCR3 = (I2S0->TCR3 & ~I2S_TCR3_TCE_MASK) | I2S_TCR3_TCE(1); // Enable TX channel 0. I2S0->RCR3 = (I2S0->RCR3 & ~I2S_RCR3_RCE_MASK) | I2S_RCR3_RCE(1); // Enable RX channel 0 } NVIC_SetVector(I2S0_Rx_IRQn, (uint32_t)&SGTL5000::sync_I2S_WS_ISR); // Set vector for SYNC word start ISR NVIC_SetPriority(I2S0_Rx_IRQn, 0); // Set priority of SYNC word start ISR NVIC_EnableIRQ(I2S0_Rx_IRQn); // Enable SYNC word start ISR using RX direction I2S0->RCSR |= I2S_RCSR_WSF_MASK; // Clear RX Word Start Flag I2S0->RCSR |= I2S_RCSR_WSIE_MASK; // Enable I2S RX word start IRQ SGTL5000::db_phase_sync = 0; SGTL5000::SYNC_run = true; init_DMA(); I2S0->TCSR |= I2S_TCSR_TE_MASK; // Start TX I2S0->RCSR |= I2S_RCSR_RE_MASK; // Start RX return 0; } int32_t SGTL5000::pause_SYNC(void) { if(!codec_configured || !this->ctrl_codec || !SGTL5000::SYNC_run) return -1; // Must use codec1 to control data flow. SGTL5000::ctrl_command = PAUSE_SYNC; NVIC->STIR = CODEC_CTRL_IRQ; while(NVIC_GetActive(CODEC_CTRL_IRQ) || NVIC_GetPendingIRQ(CODEC_CTRL_IRQ)); return 0; } int32_t SGTL5000::resume_SYNC(void) { if(!codec_configured || !this->ctrl_codec || !SGTL5000::SYNC_run) return -1; // Must use codec1 to control data flow. SGTL5000::ctrl_command = RESUME_SYNC; NVIC->STIR = CODEC_CTRL_IRQ; while(NVIC_GetActive(CODEC_CTRL_IRQ) || NVIC_GetPendingIRQ(CODEC_CTRL_IRQ)); return 0; } int32_t SGTL5000::stop_SYNC(void) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow. modify_i2c(SGTL5000_DIG_POWER, 0x0, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK); // codec I2S data lines tristate modify_i2c(SGTL5000_DIG_POWER, 0x0, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK); if(SGTL5000::codec2_active) { // codec2 I2S data lines tristate modify_i2c(SGTL5000_DIG_POWER, 0x0, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1)); modify_i2c(SGTL5000_DIG_POWER, 0x0, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1)); } SGTL5000::ctrl_command = STOP_SYNC; NVIC->STIR = CODEC_CTRL_IRQ; while(NVIC_GetActive(CODEC_CTRL_IRQ) || NVIC_GetPendingIRQ(CODEC_CTRL_IRQ)); return 0; } void SGTL5000::sync_I2S_WS_ISR(void) { I2S0->RCSR &= ~I2S_RCSR_WSIE_MASK; // Disable RX word start IRQs I2S0->TCSR |= I2S_TCSR_FR_MASK; // Reset TX FIFO pointers I2S0->RCSR |= I2S_RCSR_FR_MASK; // Reset RX FIFO pointers for(uint32_t i = 0; i <= ((I2S0->RCR1 & I2S_RCR1_RFW_MASK) >> I2S_RCR1_RFW_SHIFT); ++i) { // Stuff the TX FIFO(s) with samples, to bring RX & TX FIFO demand requests to within 1 I2S frame length of each other. Hopefully giving max processing time for DSP work I2S0->TDR[0] = 0; } if(SGTL5000::codec2_active) { for(uint32_t i = 0; i <= ((I2S0->RCR1 & I2S_RCR1_RFW_MASK) >> I2S_RCR1_RFW_SHIFT); ++i) { I2S0->TDR[1] = 0; } } I2S0->TMR = 0x0; // Clear TX & RX Masks to start data flowing I2S0->RMR = 0x0; } void SGTL5000::SYNC_TX_dma_ISR(void) { SGTL5000::t_stamp_stop(); register uint32_t ACKS; register uint32_t TX_IRQ_BM; register uint32_t DMA_INT; __asm { LDR TX_IRQ_BM, [&SGTL5000::active_TX_DMAch_bm] LDR DMA_INT, [&DMA0->INT] LDR ACKS, [&sync_dma_irq_acks] ANDS TX_IRQ_BM, TX_IRQ_BM, DMA_INT ITT NE ORRNE ACKS, #1 STRNE TX_IRQ_BM, [&DMA0->INT] TEQ ACKS, #3 IT NE BNE finish_isr MOV ACKS, #0 BL SGTL5000::SYNC_pointer_swap finish_isr: STR ACKS, [&sync_dma_irq_acks] } } void SGTL5000::SYNC_RX_dma_ISR(void) { SGTL5000::t_stamp_start(); register uint32_t ACKS; register uint32_t RX_IRQ_BM; register uint32_t DMA_INT; __asm { LDR RX_IRQ_BM, [&SGTL5000::active_RX_DMAch_bm] LDR DMA_INT, [&DMA0->INT] LDR ACKS, [&sync_dma_irq_acks] ANDS RX_IRQ_BM, RX_IRQ_BM, DMA_INT ITT NE ORRNE ACKS, #2 STRNE RX_IRQ_BM, [&DMA0->INT] TEQ ACKS, #3 IT NE BNE finish_isr MOV ACKS, #0 BL SGTL5000::SYNC_pointer_swap finish_isr: STR ACKS, [&sync_dma_irq_acks] } } void SGTL5000::SYNC_pointer_swap(void) { register uint32_t CODEC2; register uint32_t IRQNR; register uint32_t DSP_BUSY; register uint32_t DB_PHASE; register uint32_t TYPE; register uint32_t BU_RX_L; register uint32_t BU_RX_R; register uint32_t BU_TX_L; register uint32_t BU_TX_R; register uint32_t BU_RX_L2; register uint32_t BU_RX_R2; register uint32_t BU_TX_L2; register uint32_t BU_TX_R2; __asm { LDR DB_PHASE, [&SGTL5000::db_phase_sync] LDR TYPE, [&SGTL5000::SYNC_attach_type] LDRB CODEC2, [&SGTL5000::codec2_active] TEQ CODEC2, #0x1 IT EQ BEQ dual_codecs TEQ DB_PHASE, #0x0 IT EQ BEQ buf_base LDR BU_RX_L, [&SGTL5000::BufRX_L_safeB] LDR BU_RX_R, [&SGTL5000::BufRX_R_safeB] LDR BU_TX_L, [&SGTL5000::BufTX_L_safeB] LDR BU_TX_R, [&SGTL5000::BufTX_R_safeB] SUB DB_PHASE, #0x1 B store buf_base: LDR BU_RX_L, [&SGTL5000::BufRX_L_safeA] LDR BU_RX_R, [&SGTL5000::BufRX_R_safeA] LDR BU_TX_L, [&SGTL5000::BufTX_L_safeA] LDR BU_TX_R, [&SGTL5000::BufTX_R_safeA] ADD DB_PHASE, #0x1 store: STR BU_RX_L, [SGTL5000::BufRX_L_safe] // Pipeline memory access STR BU_RX_R, [SGTL5000::BufRX_R_safe] STR BU_TX_L, [SGTL5000::BufTX_L_safe] STR BU_TX_R, [SGTL5000::BufTX_R_safe] STR DB_PHASE, [&SGTL5000::db_phase_sync] B finish_update dual_codecs: TEQ DB_PHASE, #0x0 IT EQ BEQ buf_base2 LDR BU_RX_L, [&SGTL5000::BufRX_L_safeB] LDR BU_RX_R, [&SGTL5000::BufRX_R_safeB] LDR BU_TX_L, [&SGTL5000::BufTX_L_safeB] LDR BU_TX_R, [&SGTL5000::BufTX_R_safeB] LDR BU_RX_L2, [&SGTL5000::BufRX_L_safeB2] LDR BU_RX_R2, [&SGTL5000::BufRX_R_safeB2] LDR BU_TX_L2, [&SGTL5000::BufTX_L_safeB2] LDR BU_TX_R2, [&SGTL5000::BufTX_R_safeB2] SUB DB_PHASE, #0x1 B store2 buf_base2: LDR BU_RX_L, [&SGTL5000::BufRX_L_safeA] LDR BU_RX_R, [&SGTL5000::BufRX_R_safeA] LDR BU_TX_L, [&SGTL5000::BufTX_L_safeA] LDR BU_TX_R, [&SGTL5000::BufTX_R_safeA] LDR BU_RX_L2, [&SGTL5000::BufRX_L_safeA2] LDR BU_RX_R2, [&SGTL5000::BufRX_R_safeA2] LDR BU_TX_L2, [&SGTL5000::BufTX_L_safeA2] LDR BU_TX_R2, [&SGTL5000::BufTX_R_safeA2] ADD DB_PHASE, #0x1 store2: STR BU_RX_L, [SGTL5000::BufRX_L_safe] // Pipeline memory access STR BU_RX_R, [SGTL5000::BufRX_R_safe] STR BU_TX_L, [SGTL5000::BufTX_L_safe] STR BU_TX_R, [SGTL5000::BufTX_R_safe] STR BU_RX_L2, [SGTL5000::BufRX_L_safe2] // Pipeline memory access STR BU_RX_R2, [SGTL5000::BufRX_R_safe2] STR BU_TX_L2, [SGTL5000::BufTX_L_safe2] STR BU_TX_R2, [SGTL5000::BufTX_R_safe2] STR DB_PHASE, [&SGTL5000::db_phase_sync] finish_update: TEQ TYPE, #1 IT EQ BEQ call_sync_nb } SGTL5000::SYNC_user_func.call(); __asm { B finish_isr call_sync_nb: LDRB IRQNR, [&SGTL5000::SYNC_swIRQ] BL NVIC_GetActive, {r0=IRQNR}, {DSP_BUSY=r0} TEQ DSP_BUSY, #1 IT EQ BEQ finish_isr STR IRQNR, [&NVIC->STIR] // Trigger swIRQ only if previous call completed. This will keep things flowing, but if the irq is still active, good luck with the pointers! finish_isr: } } int32_t SGTL5000::attach_TX(Callback<void()> func) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow for SYNC. if(TX_attached) return -1; // Assign Callback function SGTL5000::TX_user_func = func; SGTL5000::TX_attach_type = 0; TX_attached = true; return 0; } int32_t SGTL5000::attach_TX_NB(void* user_ISR, uint32_t irq_pri, IRQn sw_irq) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow. SGTL5000::TX_swIRQ = sw_irq; // Assign ISR address and enable users IRQ NVIC_SetVector(SGTL5000::TX_swIRQ, (uint32_t)user_ISR); NVIC_SetPriority(SGTL5000::TX_swIRQ, irq_pri); NVIC_EnableIRQ(SGTL5000::TX_swIRQ); SGTL5000::TX_attach_type = 1; TX_attached = true; return 0; } int32_t SGTL5000::detach_TX(void) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow. if(TX_attached) { if(SGTL5000::TX_attach_type) NVIC_DisableIRQ(SGTL5000::TX_swIRQ); // When using Interrupt driven NB calls, we disable the IRQ. The TX channel continues running, a different user function can be assigned with another call to the attach_NB function else SGTL5000::stop_TX(); // When using callback we must stop TX channel before we detach. TX_attached = false; } return 0; } int32_t SGTL5000::start_TX(uint32_t _BufTX_L_safe, uint32_t _BufTX_R_safe, uint32_t _block_size, bool _packed_TX, bool _TX_shift, uint32_t _TX_DMAch, uint32_t _DMA_irq_pri, uint32_t _BufTX_L_safe2, uint32_t _BufTX_R_safe2) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow. if(SGTL5000::SYNC_run || SGTL5000::TX_run ) return -1; // Check if i2s TX is already started if(!TX_attached && !SGTL5000::TX_attach_type) return -1; // Check we have a handler if using callback if(_TX_DMAch > 15) return -1; // Sanity check DMAMUX channels if (!(_block_size == 2 || _block_size == 4 || _block_size == 8)) return -1; // Only accept block size 2^n within range. // Enable I2S TX interfaces for active codecs if(read_i2c(SGTL5000_DIG_POWER, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_LOW << 1)) == 0) { if(modify_i2c(SGTL5000_DIG_POWER, 0x1, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_LOW << 1))) return -1; } if(SGTL5000::codec2_active) { if(read_i2c(SGTL5000_DIG_POWER, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1)) == 0) { if(modify_i2c(SGTL5000_DIG_POWER, 0x1, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1))) return -1; } } packed_TX = _packed_TX; TX_block_size = _block_size; TX_bs_bytes = _block_size * 4; if(packed_TX) TX_shift = false; else TX_shift = _TX_shift; SGTL5000::TX_DMAch = _TX_DMAch; SGTL5000::active_TX_DMAch_bm = 0x1 << _TX_DMAch; SGTL5000::BufTX_L_safe = (uint32_t*)_BufTX_L_safe; // Assign the users pointer addresses codec 1 SGTL5000::BufTX_R_safe = (uint32_t*)_BufTX_R_safe; SGTL5000::BufTX_L_safeA = (uint32_t)&SGTL5000::I2S_TX_Buffer[0]; // Precalculate double buffer addresses SGTL5000::BufTX_R_safeA = (uint32_t)&SGTL5000::I2S_TX_Buffer[0 + (TX_block_size / 2)]; SGTL5000::BufTX_L_safeB = (uint32_t)&SGTL5000::I2S_TX_Buffer[8]; SGTL5000::BufTX_R_safeB = (uint32_t)&SGTL5000::I2S_TX_Buffer[8 + (TX_block_size / 2)]; *SGTL5000::BufTX_L_safe = SGTL5000::BufTX_L_safeB; // Assign starting addresses to double buffer pointers *SGTL5000::BufTX_R_safe = SGTL5000::BufTX_L_safeB; if(SGTL5000::codec2_active) { SGTL5000::BufTX_L_safe2 = (uint32_t*)_BufTX_L_safe2; // Assign the users pointer addresses codec 2 SGTL5000::BufTX_R_safe2 = (uint32_t*)_BufTX_R_safe2; SGTL5000::BufTX_L_safeA2 = (uint32_t)&SGTL5000::I2S_TX_Buffer2[0]; // Precalculate double buffer addresses SGTL5000::BufTX_R_safeA2 = (uint32_t)&SGTL5000::I2S_TX_Buffer2[0 + (TX_block_size / 2)]; SGTL5000::BufTX_L_safeB2 = (uint32_t)&SGTL5000::I2S_TX_Buffer2[8]; SGTL5000::BufTX_R_safeB2 = (uint32_t)&SGTL5000::I2S_TX_Buffer2[8 + (TX_block_size / 2)]; *SGTL5000::BufTX_L_safe2 = SGTL5000::BufTX_L_safeB2; // Assign starting addresses to double buffer pointers *SGTL5000::BufTX_R_safe2 = SGTL5000::BufTX_L_safeB2; } I2S0->TCSR &= ~I2S_TCSR_TE_MASK; // TX Disable I2S0->TMR = 0xFFFFFFFF; // Mask TX traffic to prevent buffer underflow before we aquire sync. I2S0->TCSR |= I2S_TCSR_SR_MASK; // Reset TX logic I2S0->TCSR &= ~I2S_TCSR_SR_MASK; I2S0->TCR1 = (I2S0->TCR1 & ~I2S_TCR1_TFW_MASK) | I2S_TCR1_TFW(8 - TX_block_size); // Set TX FIFO watermark I2S0->TCSR |= I2S_TCSR_FRDE_MASK; // Enable DMA request based on TX FIFO watermark while(I2S0->TCSR & I2S_TCSR_TE_MASK); // Make sure TX is disabled before enabling corresponding channel NVIC_SetVector((IRQn)SGTL5000::TX_DMAch, (uint32_t)&SGTL5000::TX_dma_ISR); // Set TX_DMAch transfer complete vector NVIC_SetPriority((IRQn)SGTL5000::TX_DMAch, _DMA_irq_pri); // Set irq priority NVIC_EnableIRQ((IRQn)SGTL5000::TX_DMAch); if(SGTL5000::codec2_active) { I2S0->TCR3 = (I2S0->TCR3 & ~I2S_TCR3_TCE_MASK) | I2S_TCR3_TCE(3); // Enable TX channels 0 & 1. } else { I2S0->TCR3 = (I2S0->TCR3 & ~I2S_TCR3_TCE_MASK) | I2S_TCR3_TCE(1); // Enable TX channel 0. } NVIC_SetVector(I2S0_Tx_IRQn, (uint32_t)&SGTL5000::tx_I2S_WS_ISR); // Set vector for TX word start ISR NVIC_SetPriority(I2S0_Tx_IRQn, 0); // Set priority of TX word start ISR NVIC_EnableIRQ(I2S0_Tx_IRQn); // Enable TX word start ISR I2S0->TCSR |= I2S_TCSR_WSF_MASK; // Clear TX Word Start Flag I2S0->TCSR |= I2S_TCSR_WSIE_MASK; // Enable I2S TX word start IRQ SGTL5000::db_phase_tx = 0; SGTL5000::TX_run = true; init_DMA(); I2S0->TCSR |= I2S_TCSR_TE_MASK; // TX Enable return 0; } int32_t SGTL5000::pause_TX(void) { if(!codec_configured || !this->ctrl_codec || !SGTL5000::TX_run) return -1; // Must use codec1 to control data flow. SGTL5000::ctrl_command = PAUSE_TX; NVIC->STIR = CODEC_CTRL_IRQ; while(NVIC_GetActive(CODEC_CTRL_IRQ) || NVIC_GetPendingIRQ(CODEC_CTRL_IRQ)); return 0; } int32_t SGTL5000::resume_TX(void) { if(!codec_configured || !this->ctrl_codec || !SGTL5000::TX_run) return -1; // Must use codec1 to control data flow. SGTL5000::ctrl_command = RESUME_TX; NVIC->STIR = CODEC_CTRL_IRQ; while(NVIC_GetActive(CODEC_CTRL_IRQ) || NVIC_GetPendingIRQ(CODEC_CTRL_IRQ)); return 0; } int32_t SGTL5000::stop_TX(void) { if(!codec_configured || !this->ctrl_codec || !SGTL5000::TX_run) return -1; // Must use codec1 to control data flow. modify_i2c(SGTL5000_DIG_POWER, 0x0, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK); // codec I2S data lines tristate if(SGTL5000::codec2_active) modify_i2c(SGTL5000_DIG_POWER, 0x0, SGTL5000_DIG_POWER_I2S_IN_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1)); SGTL5000::ctrl_command = STOP_TX; NVIC->STIR = CODEC_CTRL_IRQ; while(NVIC_GetActive(CODEC_CTRL_IRQ) || NVIC_GetPendingIRQ(CODEC_CTRL_IRQ)); return 0; } void SGTL5000::tx_I2S_WS_ISR(void) { I2S0->TCSR &= ~I2S_TCSR_WSIE_MASK; // Disable TX word start IRQs I2S0->TCSR |= I2S_TCSR_FR_MASK; // Reset TX FIFO pointers for(uint32_t i = 1; i <= (8 - ((I2S0->TCR1 & I2S_TCR1_TFW_MASK) >> I2S_TCR1_TFW_SHIFT)); ++i) { // Stuff the TX FIFO(s) with samples. This is to avoid underrun before we DMA some data into the buffers. I2S0->TDR[0] = 0; // It allows the time between FIFO wm and an empty buffer at the current sample rate before a DMA request is made. } // This can be critical as at 192Ks/s we only have 5nS after starting TX fifo and an underrun, unless we stuff. if(SGTL5000::codec2_active) { for(uint32_t i = 1; i <= (8 - ((I2S0->TCR1 & I2S_TCR1_TFW_MASK) >> I2S_TCR1_TFW_SHIFT)); ++i) { I2S0->TDR[1] = 0; } } I2S0->TMR = 0x0; // Clear TX Mask to start data flowing } void SGTL5000::TX_dma_ISR(void) { register uint32_t TX_IRQ_BM; register uint32_t DMA_INT; register uint32_t CODEC2; register uint32_t IRQNR; register uint32_t DSP_BUSY; register uint32_t DB_PHASE; register uint32_t TYPE; register uint32_t BU_TX_L; register uint32_t BU_TX_R; register uint32_t BU_TX_L2; register uint32_t BU_TX_R2; __asm { LDR TX_IRQ_BM, [&SGTL5000::active_TX_DMAch_bm] LDR DMA_INT, [&DMA0->INT] AND TX_IRQ_BM, TX_IRQ_BM, DMA_INT // Clear DMA IRQ flag STR TX_IRQ_BM, [&DMA0->INT] LDR DB_PHASE, [&SGTL5000::db_phase_tx] LDR TYPE, [&SGTL5000::TX_attach_type] LDRB CODEC2, [&SGTL5000::codec2_active] TEQ CODEC2, #0x1 IT EQ BEQ dual_codecs TEQ DB_PHASE, #0x0 IT EQ BEQ buf_base LDR BU_TX_L, [&SGTL5000::BufTX_L_safeB] LDR BU_TX_R, [&SGTL5000::BufTX_R_safeB] SUB DB_PHASE, #0x1 B store buf_base: LDR BU_TX_L, [&SGTL5000::BufTX_L_safeA] LDR BU_TX_R, [&SGTL5000::BufTX_R_safeA] ADD DB_PHASE, #0x1 store: STR BU_TX_L, [SGTL5000::BufTX_L_safe] // Pipeline memory access STR BU_TX_R, [SGTL5000::BufTX_R_safe] STR DB_PHASE, [&SGTL5000::db_phase_tx] B finish_update dual_codecs: TEQ DB_PHASE, #0x0 IT EQ BEQ buf_base2 LDR BU_TX_L, [&SGTL5000::BufTX_L_safeB] LDR BU_TX_R, [&SGTL5000::BufTX_R_safeB] LDR BU_TX_L2, [&SGTL5000::BufTX_L_safeB2] LDR BU_TX_R2, [&SGTL5000::BufTX_R_safeB2] SUB DB_PHASE, #0x1 B store2 buf_base2: LDR BU_TX_L, [&SGTL5000::BufTX_L_safeA] LDR BU_TX_R, [&SGTL5000::BufTX_R_safeA] LDR BU_TX_L2, [&SGTL5000::BufTX_L_safeA2] LDR BU_TX_R2, [&SGTL5000::BufTX_R_safeA2] ADD DB_PHASE, #0x1 store2: STR BU_TX_L, [SGTL5000::BufTX_L_safe] // Pipeline memory access STR BU_TX_R, [SGTL5000::BufTX_R_safe] STR BU_TX_L2, [SGTL5000::BufTX_L_safe2] STR BU_TX_R2, [SGTL5000::BufTX_R_safe2] STR DB_PHASE, [&SGTL5000::db_phase_tx] finish_update: TEQ TYPE, #1 IT EQ BEQ call_tx_nb } SGTL5000::TX_user_func.call(); __asm { B finish_isr call_tx_nb: LDRB IRQNR, [&SGTL5000::TX_swIRQ] BL NVIC_GetActive, {r0=IRQNR}, {DSP_BUSY=r0} TEQ DSP_BUSY, #1 IT EQ BEQ finish_isr STR IRQNR, [&NVIC->STIR] // Trigger swIRQ only if previous call completed. This will keep things flowing, but if the irq is still active, good luck with the pointers! finish_isr: } } int32_t SGTL5000::attach_RX(Callback<void()> func) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow for SYNC. if(RX_attached) return -1; // Assign Callback function SGTL5000::RX_user_func = func; SGTL5000::RX_attach_type = 0; RX_attached = true; return 0; } int32_t SGTL5000::attach_RX_NB(void* user_ISR, uint32_t irq_pri, IRQn sw_irq) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow. SGTL5000::RX_swIRQ = sw_irq; // Assign ISR address and enable users IRQ NVIC_SetVector(SGTL5000::RX_swIRQ, (uint32_t)user_ISR); NVIC_SetPriority(SGTL5000::RX_swIRQ, irq_pri); NVIC_EnableIRQ(SGTL5000::RX_swIRQ); SGTL5000::RX_attach_type = 1; RX_attached = true; return 0; } int32_t SGTL5000::detach_RX(void) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow. if(RX_attached) { if(SGTL5000::RX_attach_type) NVIC_DisableIRQ(SGTL5000::RX_swIRQ); // When using Interrupt driven NB calls, we disable the IRQ. The RX channel continues running, a different user function can be assigned with another call to the attach_NB function else SGTL5000::stop_RX(); // When using callback we must stop RX channel before we detach. RX_attached = false; } return 0; } int32_t SGTL5000::start_RX(uint32_t _BufRX_L_safe, uint32_t _BufRX_R_safe, uint32_t _block_size, bool _packed_RX, bool _RX_shift, uint32_t _RX_DMAch, uint32_t _DMA_irq_pri, uint32_t _BufRX_L_safe2, uint32_t _BufRX_R_safe2) { if(!codec_configured || !this->ctrl_codec) return -1; // Must use codec1 to control data flow. if(SGTL5000::SYNC_run || SGTL5000::RX_run ) return -1; // Check if i2s RX is already started if(!RX_attached && !SGTL5000::RX_attach_type) return -1; // Check we have a handler if using callback if(_RX_DMAch > 15) return -1; // Sanity check DMAMUX channels if (!(_block_size == 2 || _block_size == 4 || _block_size == 8)) return -1; // Only accept block size 2^n within range. // Enable I2S RX interfaces for active codecs if(read_i2c(SGTL5000_DIG_POWER, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_LOW << 1)) == 0) { if(modify_i2c(SGTL5000_DIG_POWER, 0x1, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_LOW << 1))) return -1; } if(SGTL5000::codec2_active) { if(read_i2c(SGTL5000_DIG_POWER, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1)) == 0) { if(modify_i2c(SGTL5000_DIG_POWER, 0x1, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1))) return -1; } } packed_RX = _packed_RX; RX_block_size = _block_size; RX_bs_bytes = _block_size * 4; if(packed_RX) RX_shift = false; else RX_shift = _RX_shift; SGTL5000::RX_DMAch = _RX_DMAch; SGTL5000::active_RX_DMAch_bm = 0x1 << _RX_DMAch; SGTL5000::BufRX_L_safe = (uint32_t*)_BufRX_L_safe; // Assign the users pointer addresses codec 1 SGTL5000::BufRX_R_safe = (uint32_t*)_BufRX_R_safe; SGTL5000::BufRX_L_safeA = (uint32_t)&SGTL5000::I2S_RX_Buffer[0]; // Precalculate double buffer addresses SGTL5000::BufRX_R_safeA = (uint32_t)&SGTL5000::I2S_RX_Buffer[0 + (RX_block_size / 2)]; SGTL5000::BufRX_L_safeB = (uint32_t)&SGTL5000::I2S_RX_Buffer[8]; SGTL5000::BufRX_R_safeB = (uint32_t)&SGTL5000::I2S_RX_Buffer[8 + (RX_block_size / 2)]; *SGTL5000::BufRX_L_safe = SGTL5000::BufRX_L_safeB; // Assign starting addresses to double buffer pointers *SGTL5000::BufRX_R_safe = SGTL5000::BufRX_L_safeB; if(SGTL5000::codec2_active) { SGTL5000::BufRX_L_safe2 = (uint32_t*)_BufRX_L_safe2; // Assign the users pointer addresses codec 2 SGTL5000::BufRX_R_safe2 = (uint32_t*)_BufRX_R_safe2; SGTL5000::BufRX_L_safeA2 = (uint32_t)&SGTL5000::I2S_RX_Buffer2[0]; // Precalculate double buffer addresses SGTL5000::BufRX_R_safeA2 = (uint32_t)&SGTL5000::I2S_RX_Buffer2[0 + (RX_block_size / 2)]; SGTL5000::BufRX_L_safeB2 = (uint32_t)&SGTL5000::I2S_RX_Buffer2[8]; SGTL5000::BufRX_R_safeB2 = (uint32_t)&SGTL5000::I2S_RX_Buffer2[8 + (RX_block_size / 2)]; *SGTL5000::BufRX_L_safe2 = SGTL5000::BufRX_L_safeB2; // Assign starting addresses to double buffer pointers *SGTL5000::BufRX_R_safe2 = SGTL5000::BufRX_L_safeB2; } I2S0->RCSR &= ~I2S_RCSR_RE_MASK; // TX Disable I2S0->RMR = 0xFFFFFFFF; // Mask RX traffic to prevent buffer underflow before we aquire sync. I2S0->RCSR |= I2S_RCSR_SR_MASK; // Reset RX logic I2S0->RCSR &= ~I2S_RCSR_SR_MASK; I2S0->RCR1 = (I2S0->RCR1 & ~I2S_RCR1_RFW_MASK) | I2S_RCR1_RFW(RX_block_size - 1); // Set RX FIFO watermark I2S0->RCSR |= I2S_RCSR_FRDE_MASK; // Enable DMA request based on RX FIFO watermark while(I2S0->RCSR & I2S_RCSR_RE_MASK); // Make sure RX is disabled before enabling corresponding channel NVIC_SetVector((IRQn)SGTL5000::RX_DMAch, (uint32_t)&SGTL5000::RX_dma_ISR); // Set RX_DMAch transfer complete vector NVIC_SetPriority((IRQn)SGTL5000::RX_DMAch, _DMA_irq_pri); // Set irq priority NVIC_EnableIRQ((IRQn)SGTL5000::RX_DMAch); // Enable RX DMA complete IRQs if(SGTL5000::codec2_active) { I2S0->RCR3 = (I2S0->RCR3 & ~I2S_RCR3_RCE_MASK) | I2S_RCR3_RCE(3); // Enable RX channels 0 & 1. } else { I2S0->RCR3 = (I2S0->RCR3 & ~I2S_RCR3_RCE_MASK) | I2S_RCR3_RCE(1); // Enable RX channel 0. } NVIC_SetVector(I2S0_Rx_IRQn, (uint32_t)&SGTL5000::rx_I2S_WS_ISR); // Set vector for RX word start ISR NVIC_SetPriority(I2S0_Rx_IRQn, 0); // Set priority of RX word start ISR NVIC_EnableIRQ(I2S0_Rx_IRQn); // Enable RX word start ISR I2S0->RCSR |= I2S_RCSR_WSF_MASK; // Clear RX Word Start Flag I2S0->RCSR |= I2S_RCSR_WSIE_MASK; // Enable I2S RX word start IRQ SGTL5000::db_phase_rx = 0; SGTL5000::RX_run = true; init_DMA(); I2S0->RCSR |= I2S_RCSR_RE_MASK; // RX Enable return 0; } int32_t SGTL5000::pause_RX(void) { if(!codec_configured || !this->ctrl_codec || !SGTL5000::RX_run) return -1; // Must use codec1 to control data flow. SGTL5000::ctrl_command = PAUSE_RX; NVIC->STIR = CODEC_CTRL_IRQ; while(NVIC_GetActive(CODEC_CTRL_IRQ) || NVIC_GetPendingIRQ(CODEC_CTRL_IRQ)); return 0; } int32_t SGTL5000::resume_RX(void) { if(!codec_configured || !this->ctrl_codec || !SGTL5000::RX_run) return -1; // Must use codec1 to control data flow. SGTL5000::ctrl_command = RESUME_RX; NVIC->STIR = CODEC_CTRL_IRQ; while(NVIC_GetActive(CODEC_CTRL_IRQ) || NVIC_GetPendingIRQ(CODEC_CTRL_IRQ)); return 0; } int32_t SGTL5000::stop_RX(void) { if(!codec_configured || !this->ctrl_codec || !SGTL5000::RX_run) return -1; // Must use codec1 to control data flow. if(modify_i2c(SGTL5000_DIG_POWER, 0x0, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK)) return -1; // codec I2S data lines tristate if(SGTL5000::codec2_active) if(modify_i2c(SGTL5000_DIG_POWER, 0x0, SGTL5000_DIG_POWER_I2S_OUT_POWERUP_MASK, (SGTL5000_I2C_ADDR_CS_HIGH << 1))) return -1; SGTL5000::ctrl_command = STOP_RX; NVIC->STIR = CODEC_CTRL_IRQ; while(NVIC_GetActive(CODEC_CTRL_IRQ) || NVIC_GetPendingIRQ(CODEC_CTRL_IRQ)); return 0; } void SGTL5000::rx_I2S_WS_ISR(void) { I2S0->RCSR &= ~I2S_RCSR_WSIE_MASK; // Disable RX word start IRQs I2S0->RCSR |= I2S_RCSR_FR_MASK; // Reset RX FIFO pointers I2S0->RMR = 0x0; // Clear RX Mask to start data flowing } void SGTL5000::RX_dma_ISR(void) { register uint32_t RX_IRQ_BM; register uint32_t DMA_INT; register uint32_t CODEC2; register uint32_t IRQNR; register uint32_t DSP_BUSY; register uint32_t DB_PHASE; register uint32_t TYPE; register uint32_t BU_RX_L; register uint32_t BU_RX_R; register uint32_t BU_RX_L2; register uint32_t BU_RX_R2; __asm { LDR RX_IRQ_BM, [&SGTL5000::active_RX_DMAch_bm] LDR DMA_INT, [&DMA0->INT] AND RX_IRQ_BM, RX_IRQ_BM, DMA_INT // Clear DMA IRQ flag STR RX_IRQ_BM, [&DMA0->INT] LDR DB_PHASE, [&SGTL5000::db_phase_rx] LDR TYPE, [&SGTL5000::RX_attach_type] LDRB CODEC2, [&SGTL5000::codec2_active] TEQ CODEC2, #0x1 IT EQ BEQ dual_codecs TEQ DB_PHASE, #0x0 IT EQ BEQ buf_base LDR BU_RX_L, [&SGTL5000::BufRX_L_safeB] LDR BU_RX_R, [&SGTL5000::BufRX_R_safeB] SUB DB_PHASE, #0x1 B store buf_base: LDR BU_RX_L, [&SGTL5000::BufRX_L_safeA] LDR BU_RX_R, [&SGTL5000::BufRX_R_safeA] ADD DB_PHASE, #0x1 store: STR BU_RX_L, [SGTL5000::BufRX_L_safe] // Pipeline memory access STR BU_RX_R, [SGTL5000::BufRX_R_safe] STR DB_PHASE, [&SGTL5000::db_phase_rx] B finish_update dual_codecs: TEQ DB_PHASE, #0x0 IT EQ BEQ buf_base2 LDR BU_RX_L, [&SGTL5000::BufRX_L_safeB] LDR BU_RX_R, [&SGTL5000::BufRX_R_safeB] LDR BU_RX_L2, [&SGTL5000::BufRX_L_safeB2] LDR BU_RX_R2, [&SGTL5000::BufRX_R_safeB2] SUB DB_PHASE, #0x1 B store2 buf_base2: LDR BU_RX_L, [&SGTL5000::BufRX_L_safeA] LDR BU_RX_R, [&SGTL5000::BufRX_R_safeA] LDR BU_RX_L2, [&SGTL5000::BufRX_L_safeA2] LDR BU_RX_R2, [&SGTL5000::BufRX_R_safeA2] ADD DB_PHASE, #0x1 store2: STR BU_RX_L, [SGTL5000::BufRX_L_safe] // Pipeline memory access STR BU_RX_R, [SGTL5000::BufRX_R_safe] STR BU_RX_L2, [SGTL5000::BufRX_L_safe2] STR BU_RX_R2, [SGTL5000::BufRX_R_safe2] STR DB_PHASE, [&SGTL5000::db_phase_rx] finish_update: TEQ TYPE, #1 IT EQ BEQ call_rx_nb } SGTL5000::RX_user_func.call(); __asm { B finish_isr call_rx_nb: LDRB IRQNR, [&SGTL5000::RX_swIRQ] BL NVIC_GetActive, {r0=IRQNR}, {DSP_BUSY=r0} TEQ DSP_BUSY, #1 IT EQ BEQ finish_isr STR IRQNR, [&NVIC->STIR] // Trigger swIRQ only if previous call completed. This will keep things flowing, but if the irq is still active, good luck with the pointers! finish_isr: } } void SGTL5000::init_DMA(void) { /*! The DMA transfer uses 2 channels. 1 for RX data 1 for TX data. Each DMA splits the Left and Right channels across the respective buffer. The chain is configured as follows: Each minor loop moves 4 bytes of data (2 * 16bit words, 1 left 1 right channel). Bus reads/writes are 16bit and DOFF is used to jump forward BLOCK_SIZE/2 32bit words on each bus read or write respectively. At the end of a minor loop MLOFF jumps back (BLOCK_SIZE - 1) 32bit words or packed (BLOCK_SIZE - 0.5) words. At Major loop termination the source or dest address is reloaded with a new TCD which points to the source or dest respectively at the uppper half of the double buffer. eg. Unpacked buffer layout: Block Size = 2 Block Size = 4 Block Size = 8 Double Buffer A Double Buffer B Double Buffer A Double Buffer B Double Buffer A Double Buffer B |L0:x|R0:x|x:x|x:x|x:x|x:x|x:x|x:x||L0:x|R0:x|x:x|x:x|x:x|x:x|x:x|x:x| |L0:x|L1:x|R0:x|R1:x|x:x|x:x|x:x|x:x||L0:x|L1:x|R0:x|R1:x|x:x|x:x|x:x|x:x| |L0:x|L1:x|L2:x|L3:x|R0:x|R1:x|R2:x|R3:x||L0:x|L1:x|L2:x|L3:x|R0:x|R1:x|R2:x|R3:x| Packed buffer layout: Block Size = 2 Block Size = 4 Block Size = 8 Double Buffer A Double Buffer B Double Buffer A Double Buffer B Double Buffer A Double Buffer B |L0:x|R0:x|x:x|x:x|x:x|x:x|x:x|x:x||L0:x|R0:x|x:x|x:x|x:x|x:x|x:x|x:x| |L0:L1|x:x|R0:R1|x:x|x:x|x:x|x:x|x:x||L0:L1|x:x|R0:R1|x:x|x:x|x:x|x:x|x:x| |L0:L1|L2:L3|x:x|x:x|R0:R1|R2:R3|x:x|x:x||L0:L1|L2:L3|x:x|x:x|R0:R1|R2:R3|x:x|x:x| The users pointers are always updated to point to L0 & R0 of the current safe double buffer area. If unpacked and TX or RX shift option is specified the 16 LSBits of each word are shifted to the 16MSB bits of each word */ static uint32_t SG_rx_TCD_A[8] __attribute ((aligned (0x20)));// __attribute__((at(0x14000500))); // Allocate memory for scatter gather TCD definitions static uint32_t SG_rx_TCD_B[8] __attribute ((aligned (0x20)));// __attribute__((at(0x14000520))); static uint32_t SG_tx_TCD_A[8] __attribute ((aligned (0x20)));// __attribute__((at(0x14000540))); static uint32_t SG_tx_TCD_B[8] __attribute ((aligned (0x20)));// __attribute__((at(0x14000560))); static uint32_t SG_rx_TCD_A2[8] __attribute ((aligned (0x20)));// __attribute__((at(0x14000580))); // Allocate memory for scatter gather TCD2 definitions static uint32_t SG_rx_TCD_B2[8] __attribute ((aligned (0x20)));// __attribute__((at(0x140005A0))); static uint32_t SG_tx_TCD_A2[8] __attribute ((aligned (0x20)));// __attribute__((at(0x140005C0))); static uint32_t SG_tx_TCD_B2[8] __attribute ((aligned (0x20)));// __attribute__((at(0x140005E0))); // Clock Control config to DMA Controller and DMAMux and common DMA Setup SIM->SCGC6 |= SIM_SCGC6_DMAMUX_MASK; // Enable Clocking to DMAMUX Module SIM->SCGC7 |= SIM_SCGC7_DMA_MASK; // Enable Clocking to DMA Module DMA0->CR |= DMA_CR_EMLM_MASK; // Enable minor loop mapping if(SGTL5000::SYNC_run || SGTL5000::RX_run) { // DMAMUX Config DMAMUX->CHCFG[SGTL5000::RX_DMAch] &= ~DMAMUX_CHCFG_ENBL_MASK; // MUX channels disabled while we configure DMAMUX->CHCFG[SGTL5000::RX_DMAch] &= ~DMAMUX_CHCFG_TRIG_MASK; // Trigger Modes Normal DMAMUX->CHCFG[SGTL5000::RX_DMAch] = (DMAMUX->CHCFG[SGTL5000::RX_DMAch] & ~DMAMUX_CHCFG_SOURCE_MASK) | DMAMUX_CHCFG_SOURCE(14); // Route I2S FIFO RX request to DMA. i2s0 RX =14 TX = 15 DMA0->CERQ = DMA_CERQ_CERQ(SGTL5000::RX_DMAch); // Disable requests for chosen DMA channel first while its configured //if(SGTL5000::codec2_active) DMA0->CERQ = DMA_CERQ_CERQ(RX_DMAch2); //Configure RX DMA Transfer Control Descriptor for channel 0 DMA0->TCD[SGTL5000::RX_DMAch].NBYTES_MLOFFYES &= ~DMA_NBYTES_MLOFFYES_SMLOE_MASK; // Disable source offset on minor loop DMA0->TCD[SGTL5000::RX_DMAch].NBYTES_MLOFFYES |= DMA_NBYTES_MLOFFYES_DMLOE_MASK; // Enable dest offset on minor loop if(packed_RX) DMA0->TCD[SGTL5000::RX_DMAch].NBYTES_MLOFFYES = (DMA0->TCD[SGTL5000::RX_DMAch].NBYTES_MLOFFYES & ~DMA_NBYTES_MLOFFYES_MLOFF_MASK) | DMA_NBYTES_MLOFFYES_MLOFF((uint32_t)-(int32_t)(RX_bs_bytes - 2)); // After each minor loop step back to lower half of buffer and increment else DMA0->TCD[SGTL5000::RX_DMAch].NBYTES_MLOFFYES = (DMA0->TCD[SGTL5000::RX_DMAch].NBYTES_MLOFFYES & ~DMA_NBYTES_MLOFFYES_MLOFF_MASK) | DMA_NBYTES_MLOFFYES_MLOFF((uint32_t)-(int32_t)(RX_bs_bytes - 4)); // After each minor loop step back to lower half of buffer and increment DMA0->TCD[SGTL5000::RX_DMAch].NBYTES_MLOFFYES = (DMA0->TCD[SGTL5000::RX_DMAch].NBYTES_MLOFFYES & ~DMA_NBYTES_MLOFFYES_NBYTES_MASK) | DMA_NBYTES_MLOFFYES_NBYTES(4); // 4 Bytes (2 * 16bits words one left one right channel) transferred in each minor cycle. DMA0->TCD[SGTL5000::RX_DMAch].CSR &= ~DMA_CSR_DONE_MASK; // Clear channels CSR DONE bit to allow configuration of linkng fields in CSR register DMA0->TCD[SGTL5000::RX_DMAch].CSR = (DMA0->TCD[SGTL5000::RX_DMAch].CSR & ~DMA_CSR_BWC_MASK) | DMA_CSR_BWC(0); // Disable Bandwidth control DMA0->TCD[SGTL5000::RX_DMAch].CSR |= DMA_CSR_ESG_MASK; // Enable scatter gather DMA0->TCD[SGTL5000::RX_DMAch].CSR &= ~DMA_CSR_DREQ_MASK; // Disable auto reset of request enable on major DMA0->TCD[SGTL5000::RX_DMAch].CSR &= ~DMA_CSR_INTHALF_MASK; // Disable IRQ at Half Completion of Major cycle DMA0->TCD[SGTL5000::RX_DMAch].ATTR = DMA_ATTR_SMOD(0) | DMA_ATTR_SSIZE(1) | DMA_ATTR_DMOD(0) | DMA_ATTR_DSIZE(1); // Set data transfer size @ 16bits per memory access across the memory bus DMA0->TCD[SGTL5000::RX_DMAch].SOFF = 0; // Signed Source offset set to zero (always read from RDR[0]). DMA0->TCD[SGTL5000::RX_DMAch].DOFF = (RX_bs_bytes / 2); // After each write step into upper half of the buffer DMA0->TCD[SGTL5000::RX_DMAch].CITER_ELINKNO &= ~DMA_CITER_ELINKNO_ELINK_MASK; // Disable channel linking minor loop DMA0->TCD[SGTL5000::RX_DMAch].CITER_ELINKNO = (DMA0->TCD[SGTL5000::RX_DMAch].CITER_ELINKNO & ~DMA_CITER_ELINKNO_CITER_MASK) | DMA_CITER_ELINKNO_CITER(RX_block_size / 2); // Major loop current iter count starting value DMA0->TCD[SGTL5000::RX_DMAch].BITER_ELINKNO &= ~DMA_BITER_ELINKNO_ELINK_MASK; // Disable channel linking minor loop DMA0->TCD[SGTL5000::RX_DMAch].BITER_ELINKNO = (DMA0->TCD[SGTL5000::RX_DMAch].BITER_ELINKNO & ~DMA_BITER_ELINKNO_BITER_MASK) | DMA_BITER_ELINKNO_BITER(RX_block_size / 2); // Major loop iter count to load again at after major completes DMA0->TCD[SGTL5000::RX_DMAch].CSR = (DMA0->TCD[SGTL5000::RX_DMAch].CSR & ~DMA_CSR_MAJORLINKCH_MASK) | DMA_CSR_MAJORLINKCH(SGTL5000::RX_DMAch); // Major link to second channel DMA0->TCD[SGTL5000::RX_DMAch].SLAST = 0; if(SGTL5000::codec2_active) { DMA0->TCD[SGTL5000::RX_DMAch].CSR &= ~DMA_CSR_INTMAJOR_MASK; // Disable IRQ at Completion of Major cycle irq generated by major complete on second channel DMA0->TCD[SGTL5000::RX_DMAch].CSR |= DMA_CSR_MAJORELINK_MASK; // Enable major loop linking DMA0->TCD[SGTL5000::RX_DMAch].DLAST_SGA = (uint32_t)&SG_rx_TCD_A2[0]; // Set scatter gather TCD definition location to transfer channel 2 } else { DMA0->TCD[SGTL5000::RX_DMAch].CSR |= DMA_CSR_INTMAJOR_MASK; // Enable IRQ at Completion of Major cycle for first channel DMA0->TCD[SGTL5000::RX_DMAch].CSR &= ~DMA_CSR_MAJORELINK_MASK; // Disable major loop linking DMA0->TCD[SGTL5000::RX_DMAch].DLAST_SGA = (uint32_t)&SG_rx_TCD_B[0]; // Set scatter gather TCD definition location } if(!packed_RX && RX_shift) DMA0->TCD[SGTL5000::RX_DMAch].DADDR = (uint32_t)&SGTL5000::I2S_RX_Buffer[0] + 2; else DMA0->TCD[SGTL5000::RX_DMAch].DADDR = (uint32_t)&SGTL5000::I2S_RX_Buffer[0]; DMA0->TCD[SGTL5000::RX_DMAch].SADDR = (uint32_t)&I2S0->RDR[0]; // Set rxDMA Source addr pointer to 1st bit of I2S RX Data Reg memcpy(&SG_rx_TCD_A[0], (void*)&DMA0->TCD[SGTL5000::RX_DMAch], 32); // Copy TCD A to memory DMA0->TCD[SGTL5000::RX_DMAch].DLAST_SGA = (uint32_t)&SG_rx_TCD_B[0]; // Set scatter gather TCD definition location if(!packed_RX && RX_shift) DMA0->TCD[SGTL5000::RX_DMAch].DADDR = (uint32_t)&SGTL5000::I2S_RX_Buffer2[0] + 2; else DMA0->TCD[SGTL5000::RX_DMAch].DADDR = (uint32_t)&SGTL5000::I2S_RX_Buffer2[0]; DMA0->TCD[SGTL5000::RX_DMAch].SADDR = (uint32_t)&I2S0->RDR[1]; DMA0->TCD[SGTL5000::RX_DMAch].CSR |= DMA_CSR_INTMAJOR_MASK; // Enable IRQ at Completion of Major cycle for first channel DMA0->TCD[SGTL5000::RX_DMAch].CSR &= ~DMA_CSR_MAJORELINK_MASK; memcpy(&SG_rx_TCD_A2[0], (void*)&DMA0->TCD[SGTL5000::RX_DMAch], 32); // Copy TCD B to memory // Set TCD elements in the DMA controller back to initial TCD A if(SGTL5000::codec2_active) { DMA0->TCD[SGTL5000::RX_DMAch].CSR &= ~DMA_CSR_INTMAJOR_MASK; // Disable IRQ at Completion of Major cycle irq generated by major complete on second channel DMA0->TCD[SGTL5000::RX_DMAch].CSR |= DMA_CSR_MAJORELINK_MASK; // Enable major loop linking DMA0->TCD[SGTL5000::RX_DMAch].DLAST_SGA = (uint32_t)&SG_rx_TCD_B2[0]; // Set scatter gather TCD definition location to transfer channel 2 } else { DMA0->TCD[SGTL5000::RX_DMAch].CSR |= DMA_CSR_INTMAJOR_MASK; // Enable IRQ at Completion of Major cycle for first channel DMA0->TCD[SGTL5000::RX_DMAch].CSR &= ~DMA_CSR_MAJORELINK_MASK; // Disable major loop linking DMA0->TCD[SGTL5000::RX_DMAch].DLAST_SGA = (uint32_t)&SG_rx_TCD_A[0]; // Set scatter gather TCD definition location } // Set scatter gather TCD definition location if(!packed_RX && RX_shift) DMA0->TCD[SGTL5000::RX_DMAch].DADDR = (uint32_t)&SGTL5000::I2S_RX_Buffer[8] + 2; else DMA0->TCD[SGTL5000::RX_DMAch].DADDR = (uint32_t)&SGTL5000::I2S_RX_Buffer[8]; DMA0->TCD[SGTL5000::RX_DMAch].SADDR = (uint32_t)&I2S0->RDR[0]; memcpy(&SG_rx_TCD_B[0], (void*)&DMA0->TCD[SGTL5000::RX_DMAch], 32); DMA0->TCD[SGTL5000::RX_DMAch].CSR |= DMA_CSR_INTMAJOR_MASK; // Enable IRQ at Completion of Major cycle for first channel DMA0->TCD[SGTL5000::RX_DMAch].CSR &= ~DMA_CSR_MAJORELINK_MASK; if(!packed_RX && RX_shift) DMA0->TCD[SGTL5000::RX_DMAch].DADDR = (uint32_t)&SGTL5000::I2S_RX_Buffer2[8] + 2; else DMA0->TCD[SGTL5000::RX_DMAch].DADDR = (uint32_t)&SGTL5000::I2S_RX_Buffer2[8]; DMA0->TCD[SGTL5000::RX_DMAch].SADDR = (uint32_t)&I2S0->RDR[1]; DMA0->TCD[SGTL5000::RX_DMAch].DLAST_SGA = (uint32_t)&SG_rx_TCD_A[0]; memcpy(&SG_rx_TCD_B2[0], (void*)&DMA0->TCD[SGTL5000::RX_DMAch], 32); memcpy((void*)&DMA0->TCD[SGTL5000::RX_DMAch], &SG_rx_TCD_A[0], 32); // Initialise RX TCD to first SG definition } if(SGTL5000::SYNC_run || SGTL5000::TX_run) { DMAMUX->CHCFG[SGTL5000::TX_DMAch] &= ~DMAMUX_CHCFG_ENBL_MASK; DMAMUX->CHCFG[SGTL5000::TX_DMAch] &= ~DMAMUX_CHCFG_TRIG_MASK; DMAMUX->CHCFG[SGTL5000::TX_DMAch] = (DMAMUX->CHCFG[SGTL5000::TX_DMAch] & ~DMAMUX_CHCFG_SOURCE_MASK) | DMAMUX_CHCFG_SOURCE(15); // Route I2S FIFO TX request to DMA. DMA0->CERQ = DMA_CERQ_CERQ(SGTL5000::TX_DMAch); // Disable requests for first DMA channel while its configured. //Configure TX DMA Transfer Control Descriptor channel 1 DMA0->TCD[SGTL5000::TX_DMAch].NBYTES_MLOFFYES |= DMA_NBYTES_MLOFFYES_SMLOE_MASK; // Enable source offset on minor loop DMA0->TCD[SGTL5000::TX_DMAch].NBYTES_MLOFFYES &= ~DMA_NBYTES_MLOFFYES_DMLOE_MASK; // Disable dest offset on minor loop if(packed_TX) DMA0->TCD[SGTL5000::TX_DMAch].NBYTES_MLOFFYES = (DMA0->TCD[SGTL5000::TX_DMAch].NBYTES_MLOFFYES & ~DMA_NBYTES_MLOFFYES_MLOFF_MASK) | DMA_NBYTES_MLOFFYES_MLOFF((uint32_t)-(int32_t)(TX_bs_bytes - 2)); else DMA0->TCD[SGTL5000::TX_DMAch].NBYTES_MLOFFYES = (DMA0->TCD[SGTL5000::TX_DMAch].NBYTES_MLOFFYES & ~DMA_NBYTES_MLOFFYES_MLOFF_MASK) | DMA_NBYTES_MLOFFYES_MLOFF((uint32_t)-(int32_t)(TX_bs_bytes - 4)); // After each minor loop step back to lower half of buffer and increment DMA0->TCD[SGTL5000::TX_DMAch].NBYTES_MLOFFYES = (DMA0->TCD[SGTL5000::TX_DMAch].NBYTES_MLOFFYES & ~DMA_NBYTES_MLOFFYES_NBYTES_MASK) | DMA_NBYTES_MLOFFYES_NBYTES(4); // 4 Bytes (2 * 16bits words one left one right channel) transferred in each minor cycle. DMA0->TCD[SGTL5000::TX_DMAch].CSR &= ~DMA_CSR_DONE_MASK; // Clear channels CSR DONE bit to allow configuration of linkng fields in CSR register DMA0->TCD[SGTL5000::TX_DMAch].CSR = (DMA0->TCD[SGTL5000::TX_DMAch].CSR & ~DMA_CSR_BWC_MASK) | DMA_CSR_BWC(0); // Disable Bandwidth control DMA0->TCD[SGTL5000::TX_DMAch].CSR |= DMA_CSR_ESG_MASK; // Enable scatter gather DMA0->TCD[SGTL5000::TX_DMAch].CSR &= ~DMA_CSR_DREQ_MASK; // Disable auto reset of request enable on major DMA0->TCD[SGTL5000::TX_DMAch].CSR &= ~DMA_CSR_INTHALF_MASK; DMA0->TCD[SGTL5000::TX_DMAch].CSR = (DMA0->TCD[SGTL5000::TX_DMAch].CSR & ~DMA_CSR_MAJORLINKCH_MASK) | DMA_CSR_MAJORLINKCH(SGTL5000::TX_DMAch); DMA0->TCD[SGTL5000::TX_DMAch].ATTR = DMA_ATTR_SMOD(0) | DMA_ATTR_SSIZE(1) | DMA_ATTR_DMOD(0) | DMA_ATTR_DSIZE(1); // Set data transfer size @ 16bits per memory access across the memory bus DMA0->TCD[SGTL5000::TX_DMAch].DOFF = 0; // Signed Dest offset set to zero (always write TDR[0]). DMA0->TCD[SGTL5000::TX_DMAch].SOFF = (TX_bs_bytes / 2); // After each write step into upper half of the buffer DMA0->TCD[SGTL5000::TX_DMAch].CITER_ELINKNO &= ~DMA_CITER_ELINKNO_ELINK_MASK; // Disable channel linking minor loop DMA0->TCD[SGTL5000::TX_DMAch].CITER_ELINKNO = (DMA0->TCD[SGTL5000::TX_DMAch].CITER_ELINKNO & ~DMA_CITER_ELINKNO_CITER_MASK) | DMA_CITER_ELINKNO_CITER(TX_block_size / 2); // Major loop current iter count starting value DMA0->TCD[SGTL5000::TX_DMAch].BITER_ELINKNO &= ~DMA_BITER_ELINKNO_ELINK_MASK; // Disable channel linking minor loop DMA0->TCD[SGTL5000::TX_DMAch].BITER_ELINKNO = (DMA0->TCD[SGTL5000::TX_DMAch].BITER_ELINKNO & ~DMA_BITER_ELINKNO_BITER_MASK) | DMA_BITER_ELINKNO_BITER(TX_block_size / 2); // Major loop iter count to load again at after major completes // Reset dest addr to start address. if(SGTL5000::codec2_active) { // Disable IRQ at Half Completion of Major cycle DMA0->TCD[SGTL5000::TX_DMAch].CSR &= ~DMA_CSR_INTMAJOR_MASK; DMA0->TCD[SGTL5000::TX_DMAch].CSR |= DMA_CSR_MAJORELINK_MASK; DMA0->TCD[SGTL5000::TX_DMAch].DLAST_SGA = (uint32_t)&SG_tx_TCD_A2[0]; // Enable IRQ at Completion of Major cycle for first channel } else { DMA0->TCD[SGTL5000::TX_DMAch].CSR |= DMA_CSR_INTMAJOR_MASK; DMA0->TCD[SGTL5000::TX_DMAch].CSR &= ~DMA_CSR_MAJORELINK_MASK; DMA0->TCD[SGTL5000::TX_DMAch].DLAST_SGA = (uint32_t)&SG_tx_TCD_B[0]; } if(!packed_TX && TX_shift) DMA0->TCD[SGTL5000::TX_DMAch].SADDR = (uint32_t)&SGTL5000::I2S_TX_Buffer[0] + 2; else DMA0->TCD[SGTL5000::TX_DMAch].SADDR = (uint32_t)&SGTL5000::I2S_TX_Buffer[0]; DMA0->TCD[SGTL5000::TX_DMAch].DADDR = (uint32_t)&I2S0->TDR[0]; memcpy(&SG_tx_TCD_A[0], (void*)&DMA0->TCD[SGTL5000::TX_DMAch], 32); // Copy TCD A to memory DMA0->TCD[SGTL5000::TX_DMAch].DLAST_SGA = (uint32_t)&SG_tx_TCD_B[0]; // Set scatter gather TCD definition location if(!packed_TX && TX_shift) DMA0->TCD[SGTL5000::TX_DMAch].SADDR = (uint32_t)&SGTL5000::I2S_TX_Buffer2[0] + 2; else DMA0->TCD[SGTL5000::TX_DMAch].SADDR = (uint32_t)&SGTL5000::I2S_TX_Buffer2[0]; DMA0->TCD[SGTL5000::TX_DMAch].DADDR = (uint32_t)&I2S0->TDR[1]; DMA0->TCD[SGTL5000::TX_DMAch].CSR |= DMA_CSR_INTMAJOR_MASK; DMA0->TCD[SGTL5000::TX_DMAch].CSR &= ~DMA_CSR_MAJORELINK_MASK; memcpy(&SG_tx_TCD_A2[0], (void*)&DMA0->TCD[SGTL5000::TX_DMAch], 32); // Copy TCD B to memory if(!packed_TX && TX_shift) DMA0->TCD[SGTL5000::TX_DMAch].SADDR = (uint32_t)&SGTL5000::I2S_TX_Buffer[8] + 2; else DMA0->TCD[SGTL5000::TX_DMAch].SADDR = (uint32_t)&SGTL5000::I2S_TX_Buffer[8]; DMA0->TCD[SGTL5000::TX_DMAch].DADDR = (uint32_t)&I2S0->TDR[0]; if(SGTL5000::codec2_active) { // Disable IRQ at Half Completion of Major cycle DMA0->TCD[SGTL5000::TX_DMAch].CSR &= ~DMA_CSR_INTMAJOR_MASK; DMA0->TCD[SGTL5000::TX_DMAch].CSR |= DMA_CSR_MAJORELINK_MASK; DMA0->TCD[SGTL5000::TX_DMAch].DLAST_SGA = (uint32_t)&SG_tx_TCD_B2[0]; // Enable IRQ at Completion of Major cycle for first channel } else { DMA0->TCD[SGTL5000::TX_DMAch].CSR |= DMA_CSR_INTMAJOR_MASK; DMA0->TCD[SGTL5000::TX_DMAch].CSR &= ~DMA_CSR_MAJORELINK_MASK; DMA0->TCD[SGTL5000::TX_DMAch].DLAST_SGA = (uint32_t)&SG_tx_TCD_A[0]; } memcpy(&SG_tx_TCD_B[0], (void*)&DMA0->TCD[SGTL5000::TX_DMAch], 32); DMA0->TCD[SGTL5000::TX_DMAch].DLAST_SGA = (uint32_t)&SG_tx_TCD_A[0]; if(!packed_TX && TX_shift) DMA0->TCD[SGTL5000::TX_DMAch].SADDR = (uint32_t)&SGTL5000::I2S_TX_Buffer2[8] + 2; else DMA0->TCD[SGTL5000::TX_DMAch].SADDR = (uint32_t)&SGTL5000::I2S_TX_Buffer2[8]; DMA0->TCD[SGTL5000::TX_DMAch].DADDR = (uint32_t)&I2S0->TDR[1]; DMA0->TCD[SGTL5000::TX_DMAch].CSR |= DMA_CSR_INTMAJOR_MASK; DMA0->TCD[SGTL5000::TX_DMAch].CSR &= ~DMA_CSR_MAJORELINK_MASK; memcpy(&SG_tx_TCD_B2[0], (void*)&DMA0->TCD[SGTL5000::TX_DMAch], 32); memcpy((void*)&DMA0->TCD[SGTL5000::TX_DMAch], &SG_tx_TCD_A[0], 32); // Initialise TX TCD to first SG definition } if(SGTL5000::SYNC_run || SGTL5000::RX_run) { DMA0->SERQ = DMA_SERQ_SERQ(SGTL5000::RX_DMAch); // Enable requests for RX DMA channel 0 DMAMUX->CHCFG[SGTL5000::RX_DMAch] |= DMAMUX_CHCFG_ENBL_MASK; // DMAMUX channel enabled. Pass DMA requests from I2S to the DMA controller. } if(SGTL5000::SYNC_run || SGTL5000::TX_run) { DMA0->SERQ = DMA_SERQ_SERQ(SGTL5000::TX_DMAch); // Enable requests for TX DMA channel DMAMUX->CHCFG[SGTL5000::TX_DMAch] |= DMAMUX_CHCFG_ENBL_MASK; // DMAMUX channel enabled. Pass DMA requests from I2S to the DMA controller. } } int32_t SGTL5000::read_debug(uint32_t index, bool finished) { if(finished) SGTL5000::debug_read = true; return SGTL5000::debug[index]; }; // Time stamp functions void SGTL5000::t_stamp_start(void) { SGTL5000::t1 = *(uint32_t*)SGTL5000::SYST_CVAL; } void SGTL5000::t_stamp_stop(void) { SGTL5000::proc_time = SGTL5000::t1 - *(uint32_t*)SGTL5000::SYST_CVAL; } }