; Copyright 2010 Castle Technology Ltd ; ; Licensed under the Apache License, Version 2.0 (the "License"); ; you may not use this file except in compliance with the License. ; You may obtain a copy of the License at ; ; http://www.apache.org/licenses/LICENSE-2.0 ; ; Unless required by applicable law or agreed to in writing, software ; distributed under the License is distributed on an "AS IS" BASIS, ; WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. ; See the License for the specific language governing permissions and ; limitations under the License. ; GET Hdr:ListOpts GET Hdr:Macros GET Hdr:System GET Hdr:Machine.<Machine> GET Hdr:ImageSize.<ImageSize> $GetIO GET Hdr:OSEntries GET Hdr:HALEntries GET Hdr:HALDevice GET Hdr:AudioDevice GET Hdr:MixerDevice GET Hdr:Proc GET hdr.omap3530 GET hdr.StaticWS GET hdr.PRCM AREA |Asm$$Code|, CODE, READONLY, PIC EXPORT Audio_Init IMPORT TPSRead IMPORT TPSWrite IMPORT memcpy IMPORT DebugHALPrint IMPORT DebugHALPrintReg IMPORT HAL_IRQClear IMPORT HAL_CounterDelay ; A brief rundown of OMAP HAL audio support: ; Audio in/out is typically provided via the TWL/TPS companion chip, using "port 2" of the TWL/TPS audio subsystem. "port 2" provides a I2S/TDM-compatible interface, capable of operating at a variety of sample rates. ; Two interfaces are used to link the audio subsystem to the OMAP: I2C is used to program the audio subsystem, and I2S is used to transmit & receive data (using the McBSP module on the OMAP side). ; Both the TWL/TPS and the OMAP are capable of acting as either the master or slave across the I2S link, so to keep things simple this implementation has the TWL/TPS acting as master and the OMAP acting as slave. ; Finally, to keep the McBSP buffer full, the SDMA module of the OMAP is used ; This means that, on the software side, audio is provided by the following network of components: ; ; HAL I2C driver ; | ; RISC OS I2C driver ; | | ; HAL mixer device <-> HAL audio device ; | | ; SoundControl module <-> SoundDMA module ; | ; DMAManager module ; | ; HAL DMA device ; ; (Note - there's also a small amount of communication between the HAL DMA device and the HAL audio device, to allow for the correct setting of the FIFO threshold/DMA packet size) ; With regards to the actual sound I/O features the TPS makes available: ; "Digital" data comes from ICs, "Analog" data are pins that may/may not be connected to speakers/microphones/etc. The "RX" path sends data out through the DACs, and the "TX" path accepts data from the ADCs. ; ; INPUTS - DIGITAL OUTPUTS - DIGITAL ;Stereo I2S data from CPU (tdm/i2s.din) Stereo I2S data to CPU (tdm/i2s.dout) ;PCM voice data from modem (pcm.vdr) PCM voice data to modem (pcm.vdx) ;PCM voice data from SUBMIC (pcm.vdr) ;PCM voice data from BT (bt.pcm.din) PCM voice data to BT (bt.pcm.dout) ; ; INPUTS - ANALOGUE OUTPUTS - ANALOGUE ;Mono headset microphone (hsmic) Stereo headset (hsor/hsol) ; Stereo hands-free speaker (ihf_l/r_p/m) ;Mono handset main microphone (mic_main) Mono handset earphone (earp/earm) ;Mono handset submicrophone (mic_sub) ;Digital microphone 0 ;Digital microphone 1 ;Stereo FM radio/auxiliary input (fml/fmr, auxl/auxr) ; Stereo predriver output (predriv_l/r) ;Mono carkit microphone (usb_din) Stereo carkit output (usb_d+/-) ; ; (The above ignores the 4-channel TDM capability, since we're using I2S) ; We'll be operating the mixer in the "option 1" setting, which gives "four simultaneous multimedia analog channels (four multimedia DACs and no modem voice path) and two simultaneous ADCs or four channels coming from digital microphones ; This basically means that the mixer looks like the satisfyingly confusing figure 14-6 in the TPS manual (swcu050d.pdf). In summary.... ; Output: Mode: PGA Amplifier: VDL_GAIN ARXL1 ARXR1 ARXL2 ARXR2 ; Headset left MIX * * * ; Headset right MIX * * * ; Predriver left MIX * * * * (1,2) ; Predriver right MIX * * * * (2) ; Hands-free left MUX * * * * (2,3) ; Hands-free right MUX * * * * (2,3) ; Carkit left MIX * * * ; Carkit right MIX * * * ; Earphone MIX * * * * ; ; (1) Also allows "Vmid" to be used as a mix source, whatever that is. ; (2) Stereo crossover is supported on ARXL2/ARXR2 ; (3) Lacks an output amplifier. All other outputs have output amplifiers (albeit ones with only a handful of gain settings). ; The "analog mixing" allows each PGA amplifier to have two inputs: ; PGA Amplifier: Inputs: ; VDL_GAIN Ampli_L, ????? (seriously - the diagram has two lines that don't go anywhere!) ; ARXL1 DACL1, Ampli_L ; ARXR1 DACR1, Ampli_R ; ARXL2 DACL2, Ampli_L ; ARXR2 DACR2, Ampli_R ; Digital muxes allow the DACs to have multiple sources. ; These muxes are controlled by the RX_PATH_SEL register. ; DAC: Inputs: SDRL1 SDRR1 SDRM1 SDRL2 SDRR2 SDRM2 ; DACL1 * * * * ; DACR1 * * * * ; DACL2 * * ; DACR2 * * ; SDRL1/SDRR1 is data received over the I2S link ; SDRL2/SDRR2 is data received over the I2S link, added to the output of the ATXARX(L/R)_PGA amplifiers (which source their data from the audio TX L1/R1 channels) ; The RX & TX I2S channels are enabled/disabled via the OPTION register ; And, speaking of the "TX" path, there are two input amplifiers (Ampli_L, Ampli_R), which feed two ADCs (ADCL, ARDC) (as well as potentially feeding back to the output PGA amplifiers). The Ampli_L & Ampli_R amplifiers take ther input from the following: ; Ampli_L Carkit mic, headset mic, handset main mic, FM radio/aux left ; Ampli_R Handset sub mic, FM radio/aux right ; The ADCL & ADCR output is combined with another source of data - the digital microphones - to produce four outputs: Audio TX L1, R1, L2, R2. These signals are fed into corresponding amplifiers (ATXL1PGA, ATXR1PGA, ATXL2PGA, ATXR2PGA) & filters, before being fed into the four mic channels of the I2S/TDM interface (and, in the case of the R1/L1 channels, into the loopback PGAs). ; The channels are mapped to the I2S/TDM mic channels as follows: ; Audio TX channel TDM mic channel ; L1 1 ; R1 3 ; L2 2 ; R2 4 ; Out of all the amplifiers, only the following have reasonably flexible gain controls: (ignoring BT, DTMF) ; ATXL1PGA, ATXR1PGA ; AVTXL2PGA, AVTXR2PGA ; ARXL1PGA, ARXR1PGA ; ARXL2PGA, ARXR2PGA ; VRX(L?)PGA (voice downlink) ; VSTPGA (voice sidetone) ; VRX2ARXPGA (voice RX to audio RX) ; ARX2VTXPGA (audio RX to voice TX) ; ARXL1_APGA, ARXR1_APGA ; ARXL2_APGA, ARXR2_APGA ; ATX2ARXL, ATX2ARXR (audio TX to audio RX) ; VDL_APGA (voice downlink again?) ; MICAMPL/R (stereo microphone amp - but which mic?) ; The following have rather restrictive gain settings: ; EAR (earpiece) ; HSR (headset left/right) ; L/R Predrivers ; Carkit L/R ; So, in summary..... ; * Since RISC OS only produces one stream of stereo data, we can assign one DAC/amplifier pair (DACL2/DACR2, to keep the voice path free for the future?) to playing the data generated by the OS ; * VDL_GAIN, DACL1, DACR1 amplifiers can be ignored (although VDL_GAIN would be useful for mono data received by Ampli_L?) ; * There are only two ADCs, so you can only really sample from one stereo source at a time (or two mono) ; * The plethora of outputs can select their mix from RISC OS sound data & the ADC data at will ; * In any case, RISC OS doesn't really have an API for sound recording (or setting the recording source), so we could just ignore all the inputs entirely until that gets rectified (although for most OMAP devices which RISC OS is targeting, it's likely that the stereo "auxiliary" input will be the one providing all the input data, so an API to select the recording source isn't entirely necessary) ; As for mapping inputs & outputs to those currently supported by the HAL mixer API: ; TPS HAL mixer category ; Headset left/right HEADPHONES ; Predriver left/right SPEAKER ; Hands-free left/right LINE_OUT ; Carkit left/right AUX_OUT (carkit is more likely to be useful than earphone, if a carkit-compatible USB driver is written) ; Earphone none? ; Headset mic MIC ; Handset main mic none? ; Handset sub mic none? ; Digital mic 0 none? ; Digital mic 1 none? ; Stereo FM/aux input LINE_IN ; Mono carkit mic AUX_IN (to match AUX_OUT) ; 'SYSTEM' should obviously control the volume of the sound RO generates - i.e. by using the ARXL2PGA/ARXR2PGA amplifiers ; For the beagleboard, the TWL/TPS is connected to the OMAP via I2C1 & McBSP2 ; The audio out header is connected to the headset L/R channels on the TPS ; The audio in header is connected to the aux L/R channels on the TPS ; Flag to enable gobs of debug output GBLL AudioDebug AudioDebug SETL {FALSE} Audio_Init Push "v1-v4,lr" [ AudioDebug DebugTX "Audio_Init" ] ADRL v1, AudioWS MOV a1, v1 ADR a2, AudioTemplate MOV a3, #Audio_DeviceSize BL memcpy STR sb, [v1, #:INDEX:AudioWorkspace] ; Get McBSP2 logical address LDR v2, L4_Per_Log ADD v2, v2, #L4_McBSP2-L4_Per STR v2, [v1, #:INDEX:AudioRegs] [ AudioDebug DebugReg v2, "McBSP2 @ " ] ; Before we go any further, turn on the power to McBSP2 & make sure it's in a reset state ; TODO - This should probably go in AudioActivate! LDR a1, L4_ClockMan_Log ADD a1, a1, #CM_FCLKEN_PER LDR a2, [a1] ORR a2, a2, #1 ; McBSP2 clock enable STR a2, [a1] LDR a2, [a1, #CM_ICLKEN_PER-CM_FCLKEN_PER]! ORR a2, a2, #1 STR a2, [a1] ; Clear CONTROL_DEVCONF.MCBSP2_CLKS? LDR a1, L4_Core_Log ADD a1, a1, #&2200 LDR a2, [a1, #&74] BIC a2, a2, #1<<6 STR a2, [a1, #&74] ; Set McBSP2 to reset state MOV a1, #0 STR a1, [v2, #MCBSPLP_SPCR1] MOV a1, #&200 STR a1, [v2, #MCBSPLP_SPCR2] ; Perform full reset, in fact MOV a1, #2 STR a1, [v2, #MCBSPLP_SYSCONFIG] 10 LDR a1, [v2, #MCBSPLP_SYSCONFIG] TST a1, #2 BNE %BT10 ; Set smart idle mode. No auto-idle available! :( MOV a1, #2<<3 STR a1, [v2, #MCBSPLP_SYSCONFIG] ; One-time setup of transmitter registers MOV a1, #&F ; Active-low RX/TX frame sync, RX on rising, TX on falling STR a1, [v2, #MCBSPLP_PCR] MOV a1, #&40 ; 16bit words, 1 word per frame STR a1, [v2, #MCBSPLP_XCR1] LDR a1, =&8041 ; dual-phase frame, (16 bit, 1 word), MSB first, 1-bit delay STR a1, [v2, #MCBSPLP_XCR2] ; Program sample rate generator registers - not sure if this is required, since we run in slave mode LDR a1, =&f01 ; 16 bit frame width-1, CLKGDV=1 STR a1, [v2, #MCBSPLP_SRGR1] MOV a1, #&1f ; FPER=2*16 bit-1 STR a1, [v2, #MCBSPLP_SRGR2] ; Now resume HAL device setup... ADD v2, v1, #Audio_DeviceSize MOV a1, v2 ADR a2, MixerTemplate MOV a3, #Mixer_DeviceSize BL memcpy ; Fill in pointers to each other STR v1, [v2, #HALDevice_MixerCtrlr] STR v2, [v1, #HALDevice_AudioMixer] LDRB a1, [sb, #BoardConfig_MixerChans] STRB a1, [v2, #:INDEX:MixerDisableFlags] ; Unavailable channels, and input channels, start muted ORR a1, a1, #(1<<MixerChannel_HeadsetMic)+(1<<MixerChannel_AuxInput)+(1<<MixerChannel_CarkitMic) ADD a2, v2, #:INDEX:MixerSettings 10 AND a3, a1, #1 MOVS a1, a1, LSR #1 STR a3, [a2], #8 BNE %BT10 ; Register devices MOV a2, v1 MOV a1, #0 CallOS OS_AddDevice MOV a2, v2 MOV a1, #0 CallOS OS_AddDevice ; Set TPS audio registers to default values MOV a1, #TPSAUDIO_IIC*2 ADR a2, TPSDefaults MOV a3, #1 ; Program CODEC_MODE first to make sure the power is off, then program everything else in one go MOV a4, #CODEC_MODE LDR v1, OSentries+4*OS_IICOpV BL TPSWrite [ AudioDebug CMP a1, #0 BEQ %FT10 DebugTX "Audio_Init: TPS CODEC_MODE reset failed!" 10 ] MOV a1, #TPSAUDIO_IIC*2 ADD a2, a2, #1 MOV a3, #(MISC_SET_2+1)-OPTION ADD a4, a4, #1 BL TPSWrite [ AudioDebug CMP a1, #0 BEQ %FT10 DebugTX "Audio_Init: TPS audio reset failed!" 10 ] ; Write APLL_CTL with the right value MOV a1, #TPSAUDIO_IIC*2 ADD a2, sb, #BoardConfig_APLL_CTL MOV a3, #1 MOV a4, #APLL_CTL BL TPSWrite [ AudioDebug CMP a1, #0 BEQ %FT10 DebugTX "Audio_Init: APLL_CTL write failed!" 10 ] [ AudioDebug DebugTX "Audio_Init done" ] Pull "v1-v4,pc" TPSDefaults ; List of default settings for TPS registers ; These are the settings taken from TRM (swcu050d.pdf), and then overlaid with the default settings that are required for sound output (i.e. steps 3-17, 19-27, 31 of the example given in section 14.5.3), and then tidied up a bit further DCB &00 ; CODEC_MODE DCB &C0 ; OPTION - RX path 2 enabled DCB &00 ; (unused) DCB &00 ; MCBIAS_CTL DCB &20 ; ANAMICL DCB &00 ; ANAMICR DCB &00 ; AVADC_CTL DCB &00 ; ADCMICSEL DCB &00 ; DIGMIXING DCB &00 ; ATXL1PGA DCB &00 ; ATXR1PGA DCB &00 ; ATXL2PGA DCB &00 ; AVTXR2PGA DCB &01 ; AUDIO_IF DCB &00 ; VOICE_IF DCB &00 ; ARXR1PGA mute DCB &00 ; ARXL1PGA mute DCB &3F ; ARXR2PGA 0dB DCB &3F ; ARXL2PGA 0dB DCB &00 ; VRXPGA DCB &00 ; VSTPGA DCB &00 ; VRX2ARXPGA DCB &0C ; AVDAC_CTL DCB &00 ; ARX2VTXPGA DCB &30 ; ARXL1_APGA_CTL mute DCB &30 ; ARXR1_APGA_CTL mute DCB &33 ; ARXL2_APGA_CTL 0dB DCB &33 ; ARXR2_APGA_CTL 0dB DCB &00 ; ATX2ARXPGA DCB &00 ; BT_IF DCB &00 ; BTPGA DCB &00 ; BTSTPGA DCB &00 ; EAR_CTL DCB &24 ; HS_SEL - TODO - Also HS_OUTLOW_EN? DCB &00 ; HS_GAIN_SET DCB &34 ; HS_POPN_SET DCB &00 ; PREDL_CTL - TODO - Also PREDL_OUTLOW_EN? DCB &00 ; PREDR_CTL DCB &00 ; PRECKL_CTL DCB &00 ; PRECKR_CTL DCB &00 ; HFL_CTL DCB &00 ; HFR_CTL DCB &05 ; ALC_CTL DCB &00 ; ALC_SET1 DCB &00 ; ALC_SET2 DCB &00 ; BOOST_CTL DCB &01 ; SOFTVOL_CTL - TODO - tweak delay setting DCB &13 ; DTMF_FREQSEL DCB &00 ; DTMF_TONEXT1H DCB &00 ; DTMF_TONEXT1L DCB &00 ; DTMF_TONEXT2H DCB &00 ; DTMF_TONEXT2L DCB &79 ; DTMF_TONOFF DCB &11 ; DTMF_WANONOFF DCB &00 ; CODEC_RX_SCRAMBLE_H DCB &00 ; CODEC_RX_SCRAMBLE_M DCB &00 ; CODEC_RX_SCRAMBLE_L DCB &00 ; APLL_CTL - gets filled in properly afterwards DCB &00 ; DTMF_CTL DCB &00 ; DTMF_PGA_CTL2 DCB &00 ; DTMF_PGA_CTL1 DCB &02 ; MISC_SET_1 DCB &00 ; PCMBTMUX DCB &00 ; (unused) DCB &00 ; (unused) DCB &00 ; (unused) DCB &0A ; RX_PATH_SEL - Map I2S RX path 2 to digital paths 1 and 2 (somewhat unecessary to map to both since we only need one) DCB &00 ; VDL_APGA_CTL DCB &00 ; VIBRA_CTL DCB &00 ; VIBRA_SET DCB &00 ; (unused) DCB &00 ; ANAMIC_GAIN DCB &00 ; MISC_SET_2 ASSERT (. - TPSDefaults) = (MISC_SET_2+1)-CODEC_MODE ALIGN ; Sample rate table ; The first 'reserved' byte is used to store the value that needs programming into the TPS CODEC_MODE register GBLA numrate numrate SETA 0 MACRO $lab cdf $freq, $per, $mode ; CD-derived rate ($freq in Hz, since all integral) $lab DCD $freq*1024 ; frequency value as reported by Sound_SampleRate DCB $per ; period as reported via Sound_Configure DCB $mode ; CODEC_MODE setting DCW 0 ; padding to 8 bytes numrate SETA numrate+1 MEND ASSERT HALDevice_AudioRateTableSize = 8 ratetab cdf 8000, 125, &01 ; 8kHz (125usec) AC97/6 cdf 11025, 91, &11 ; 11.025kHz (~91 usec) CD/4 cdf 12000, 83, &21 ; 12kHz (~83 usec) AC97/4 cdf 16000, 63, &41 ; 16kHz (~63 usec) AC97/3 cdf 22050, 45, &51 ; 22.05kHz (~45 usec) CD/2 cdf 24000, 42, &61 ; 24kHz (~42 usec) AC97/2 cdf 32000, 31, &81 ; 32kHz (~31 usec) AC97*2/3 cdf 44100, 23, &91 ; 44.1kHz (~23 usec) CD/1 cdf 48000, 21, &a1 ; 48kHz (~21 usec) AC97/1 cdf 96000, 10, &e1 ; 96kHz (~10 usec) AC97*2 ; Audio controller HAL device AudioTemplate DCW HALDeviceType_Audio + HALDeviceAudio_AudC DCW HALDeviceID_AudC_TPS65950 DCD HALDeviceBus_Ser + HALDeviceSerBus_IIC DCD 1:SHL:16 ; API version DCD AudioDesc DCD 0 ; Address - N/A % 12 ; Reserved DCD AudioActivate DCD AudioDeactivate DCD AudioReset DCD AudioSleep DCD McBSP2_IRQ ; Device DCD 0 ; TestIRQ cannot be called % 8 DCD 0 ; Filled in during init DCD 1 ; Output channels (supported so far) DCD 0 ; Input channels (supported so far) ASSERT (.-AudioTemplate) = HALDevice_Audio_Size ; DMA channel parameters DCD 0 ; flags DCD McBSP2_DMA_TX + 1 ; logical channel DCD 0 ; 'cycle speed' DCD 2 ; transfer unit size DCD L4_McBSP2+MCBSPLP_DXR ; *physical* address to send data to ; Enable/disable/IRQ routines DCD PreEnable DCD PostEnable DCD PreDisable DCD PostDisable DCD IRQHandle DCD numrate ; Number of sample rates DCD ratetab ; Sample rate table DCD AudioSetRate ; SetRate function ASSERT (.-AudioTemplate) = HALDevice_Audio_Size_1 DCD 0 ; Filled in during init DCD 0 ; Filled in during init DCD 0 ALIGN ASSERT (.-AudioTemplate) = Audio_DeviceSize ; Mixer HAL device MixerTemplate DCW HALDeviceType_Audio + HALDeviceAudio_Mixer DCW HALDeviceID_Mixer_TPS65950 DCD HALDeviceBus_Ser + HALDeviceSerBus_IIC DCD 1 ; API version DCD MixerDesc DCD 0 ; Address - N/A % 12 ; Reserved DCD MixerActivate DCD MixerDeactivate DCD MixerReset DCD MixerSleep DCD -1 ; Device DCD 0 ; TestIRQ cannot be called % 8 DCD 0 ; Filled in during init DCD MixerChannels DCD MixerGetFeatures DCD MixerSetMix DCD MixerGetMix DCD MixerGetMixLimits ASSERT (.-MixerTemplate) = HALDevice_Mixer_Size + 4 ; Default settings will be filled in during init DCD 0, 0 DCD 0, 0 DCD 0, 0 DCD 0, 0 DCD 0, 0 DCD 0, 0 DCD 0, 0 DCD 0, 0 DCW 0 ; MixerHeadsetGain DCW 0 ; MixerPredriverGain DCW 0 ; MixerHandsFreeGain DCW 0 ; MixerCarkitOutGain DCW &3F3F ; MixerSystemGain DCW &3333 ; MixerSystemGain2 % 4 ASSERT (.-MixerTemplate) = Mixer_DeviceSize AudioDesc = "TPS65950-compatible audio controller", 0 MixerDesc = "TPS65950-compatible audio mixer", 0 ALIGN AudioActivate ; TODO - Fix this! ; SoundDMA is higher up the module chain than SoundControl ; Which means we need the audio controller to do the mixer init, otherwise stuff might not work properly Entry "v1,sb", 4 ; Turn off codec, program mix params LDR v1, [a1, #HALDevice_AudioMixer] ADD v1, v1, #(:INDEX:MixerSettings)+8*MixerChannels MOV a2, #MixerChannels-1 LDR sb, AudioWorkspace [ AudioDebug DebugTX "AudioActivate: Setting defaults" ] ADR a3, ratetab LDRB a3, [a3, #5] ; Just use first rate for now STR a3, AudioMode BL SetCodecMode 10 LDMDB v1!,{a3-a4} BL ReallySetMix SUBS a2, a2, #1 BGE %BT10 ; Finished! MOV a1, #1 EXIT AudioDeactivate MOV pc, lr AudioReset MOV pc, lr AudioSleep MOV a1, #0 MOV pc, lr SetCodecMode ; Reprogram CODEC_MODE from softcopy ; Assumes a1=audio device ptr, sb=HAL workspace Entry "a1-a4,v1" ADR a2, AudioMode MOV a1, #TPSAUDIO_IIC*2 MOV a3, #1 MOV a4, #CODEC_MODE LDR v1, OSentries+4*OS_IICOpV BL TPSWrite [ AudioDebug CMP a1, #0 EXIT EQ DebugTX "SetCodecMode: TPS write failed!" ] EXIT PreEnable ; a2 = DMA buffer length ; Use the buffer length to calculate a DMA packet size that fits in the FIFO. LDR a3, AudioWorkspace MOV a4, #1 10 MOV a2, a2, LSR #1 ; Number of DMA elements/FIFO entries CMP a2, #&200 ; FIFO size is &500. To try and avoid underflow, try to make sure there's no more than &200 free entries. STRLE a2, [a3, #:INDEX:DMAPktSz_Audio] MOVLE pc, lr ; Divide size by two until we reach our goal TST a2, #1 MOVEQ a4, a4, LSL #1 BEQ %BT10 ; If we're here, it means we can't divide any more, and we're still stuck with a large transfer size. ; For RISC OS, the largest non-fittable size is 4K-4, which will result in a2=&3FF and a4=2 ; Since &3FF is smaller than the FIFO size we'll make an exception to our above rule and allow it to be used ; But to future-proof ourselves against buffers larger than 4K we'll fall back to using a4 if needed. Transferring only two elements at a time will hurt the memory bus a bit, but at least it'll work! CMP a2, #&400 STRLE a2, [a3, #:INDEX:DMAPktSz_Audio] STRGT a4, [a3, #:INDEX:DMAPktSz_Audio] MOV pc, lr PostEnable ; a2 = DMA buffer length ; This function is a mix of two things - the TPS programming steps from section 14.5.3 of the manual (swcu050d.pdf, page 715/716), and the McBSP programming steps ; The steps for both have been stripped down to the bare minimum that are required for (re)initialisation. ; The McBSP instance is initialised first, to allow the DMA to prefill the TX FIFO. The TWL/TPS is then initialised afterwards. Entry "v1-v3,sb" LDR sb, AudioWorkspace [ AudioDebug DebugTX "PostEnable" ] Push "a1" LDR v1, AudioRegs ; Set McBSP instance to reset state (although it should be reset already) MOV a1, #0 STR a1, [v1, #MCBSPLP_SPCR1] MOV a1, #&200 STR a1, [v1, #MCBSPLP_SPCR2] ; Reset IRQ state, and enable underflow/overflow IRQ MVN a1, #0 STR a1, [v1, #MCBSPLP_IRQSTATUS] MOV a1, #&1800 STR a1, [v1, #MCBSPLP_IRQENABLE] ; Program FIFO threshold value, using our precomputed packet size LDR a2, DMAPktSz_Audio [ AudioDebug DebugReg a2, "Packet size=" ] SUB a2, a2, #1 STR a2, [v1, #MCBSPLP_THRSH2] ; Threshold = number of 16bit samples that SDMA sends, minus one. Any other value is wrong and will cause audio corruption. ; Wait 2 SRG clock cycles ; For now, just wait for a msec or so MOV a1, #1024 BL HAL_CounterDelay ; Bring sample rate generator out of reset (required even with external clocks) MOV v2, #&240 STR v2, [v1, #MCBSPLP_SPCR2] ; Bring transmitter out of reset ORR v2, v2, #1 STR v2, [v1, #MCBSPLP_SPCR2] ; Wait some more, according to linux MOV a1, #1024 BL HAL_CounterDelay Pull "a1" ; Now start frame sync ORR v2, v2, #&80 STR v2, [v1, #MCBSPLP_SPCR2] ; Wait for FIFO to fill by DMA LDR v3, DMAPktSz_Audio ; Use the 32K timer to provide a clock reference for a timeout (in ; case DMA isn't running for some reason, e.g. DMAManager being ; naughty and sneakily resetting it during Service_PreReset) LDR v2, L4_32KTIMER_Log LDR a3, [v2, #REG_32KSYNCNT_CR] 10 LDR a2, [v1, #MCBSPLP_XBUFFSTAT] CMP a2, v3 BLE %FT20 LDR ip, [v2, #REG_32KSYNCNT_CR] SUB ip, ip, a3 CMP ip, #16384 ; Half a second should be more than enough BLO %BT10 [ AudioDebug DebugTX "FIFO fill timeout!" ] ; Since timeouts shouldn't happen, I don't think anyone will mind if ; we just bug out and don't start the audio codec EXIT 20 [ AudioDebug DebugReg a2, "Post-prime XBUFFSTAT=" ] ; TPS Step 18 - turn codec power on LDR v1, AudioMode ORR v1, v1, #2 STR v1, AudioMode BL SetCodecMode ; TPS Steps 28-30 - Set and wait for antipop BL SetAndWaitForAntiPop ; TPS Step 32 - Disable codec, for some unmentioned reason BIC v1, v1, #2 STR v1, AudioMode BL SetCodecMode ; TPS Step 33 - Enable codec again ORR v1, v1, #2 STR v1, AudioMode BL SetCodecMode ; TPS Steps 34-37 - Enable headset (if needed!) LDR a1, [a1, #HALDevice_AudioMixer] LDR a2, MixerSettings+MixerChannel_HeadsetOut*8 TST a2, #1 BLEQ UpdateHeadset EXIT PreDisable ; Disable the TWL/TPS, and then McBSP ; This should avoid unwanted underflow IRQs Entry "v1,sb" LDR sb, AudioWorkspace [ AudioDebug DebugTX "PreDisable" ] ; Disable IRQs LDR a2, AudioRegs MOV a3, #0 STR a3, [a2, #MCBSPLP_IRQENABLE] ; Disable headset output if required LDR v1, [a1, #HALDevice_AudioMixer] LDR a2, [v1, #:INDEX:MixerSettings+MixerChannel_HeadsetOut*8] ANDS a2, a2, #1 BNE %FT30 STRH a2, [v1, #:INDEX:MixerHeadsetGain] Push "a1" ADRL a2, HeadsetDisableSequence MOV a3, #1 LDR v1, OSentries+4*OS_IICOpV 10 LDRB a4, [a2],#1 CMP a4, #255 LDREQB a4, [a2],#1 BLEQ DoHeadsetRampDelay MOV a1, #TPSAUDIO_IIC*2 CMP a4, #0 BEQ %FT20 BL TPSWrite [ AudioDebug CMP a1, #0 BEQ %FT15 DebugReg a4,"PreDisable: Write failed, reg=" 15 ] ADD a2, a2, #1 B %BT10 20 Pull "a1" 30 ; Turn codec power off LDR a2, AudioMode BIC a2, a2, #2 STR a2, AudioMode BL SetCodecMode ; Disable McBSP LDR v1, AudioRegs MOV a1, #0 STR a1, [v1, #MCBSPLP_SPCR1] MOV a1, #&200 STR a1, [v1, #MCBSPLP_SPCR2] EXIT PostDisable MOV pc, lr IRQHandle [ AudioDebug Push "sb,lr" LDR sb, AudioWorkspace LDR a2, AudioRegs LDR a2, [a2, #MCBSPLP_IRQSTATUS] DebugReg a2,"IRQHandle: IRQSTATUS=" Pull "sb,lr" ] ; Just clear the IRQ and ask for an audio reset LDR a3, AudioRegs MOV a2, #0 STR a2, [a3, #MCBSPLP_IRQENABLE] ; Make sure we don't get bothered about this again - we can't guarantee when the OS will get round to resetting the audio, and could potentially get stuck in a loop if we allow the IRQ to keep firing MVN a2, #0 STR a2, [a3, #MCBSPLP_IRQSTATUS] MOV a1, #1 MOV pc, lr AudioSetRate ; a2 = sample rate index (0-based) Entry "sb" LDR sb, AudioWorkspace [ AudioDebug DebugReg a2, "AudioSetRate: " ] ; Reload CODEC_MODE with the required value ; The manual states APLL_RATE can only be changed if the codec is off! ADRL a3, ratetab ASSERT HALDevice_AudioRateTableSize = 8 ADD a3, a3, a2, LSL #3 LDRB a3, [a3, #5] LDR a2, AudioMode CMP a2, a3 EXIT EQ ; Redundant update, ignore TST a2, #2 [ AudioDebug BEQ %FT10 DebugTX "AudioSetRate: Failed, codec is active!" EXIT 10 | EXIT NE ] STR a3, AudioMode BL SetCodecMode EXIT MixerActivate MOV a1, #1 MixerDeactivate MOV pc, lr MixerReset Entry "v1,sb", 4 ; Turn off codec, program default mix params ADR v1, MixerSettings+8*MixerChannels LDR a1, [a1, #HALDevice_MixerCtrlr] MOV a2, #MixerChannels-1 LDR sb, AudioWorkspace [ AudioDebug DebugTX "MixerReset: Setting defaults" ] LDR a3, AudioMode BIC a3, a3, #2 STR a3, AudioMode BL SetCodecMode 10 LDMDB v1!,{a3-a4} BL ReallySetMix SUBS a2, a2, #1 BGE %BT10 EXIT MixerSleep ; TODO? MOV a1, #0 MOV pc, lr MixerGetFeatures ; Check if this channel is disabled and needs the 'fixed' flag MOV a4, #1 LDRB a3, MixerDisableFlags TST a3, a4, LSL a2 ADR a1, MixerFeaturesTab LDR a1, [a1, a2, LSL #2] ORRNE a1, a1, #MixerFeaturesFlag_Fixed MOV pc, lr MixerFeaturesTab ; Stereo headset output DCW 0 DCW MixerCategory_Headphones ; Stereo speaker predriver output DCW MixerFeaturesFlag_DefaultMute DCW MixerCategory_Speaker ; Stereo hands-free output DCW MixerFeaturesFlag_DefaultMute DCW MixerCategory_LineOut ; Stereo carkit output DCW MixerFeaturesFlag_DefaultMute DCW MixerCategory_AuxOut ; System audio DCW 0 DCW MixerCategory_System ; Mono headset mic DCW MixerFeaturesFlag_Mono :OR: MixerFeaturesFlag_DefaultMute DCW MixerCategory_Mic ; Stereo FM/aux input DCW MixerFeaturesFlag_DefaultMute DCW MixerCategory_LineIn ; Mono carkit mic DCW MixerFeaturesFlag_Mono :OR: MixerFeaturesFlag_DefaultMute DCW MixerCategory_AuxIn MixerGetMixLimits ADR a1, MixerLimitsTab ADD a1, a1, a2, LSL #3 ADD a1, a1, a2, LSL #2 LDMIA a1, {a1-a2,a4} STMIA a3, {a1-a2,a4} MOV pc, lr MixerLimitsTab ; Stereo headset output DCD -6*16 DCD 6*16 DCD 6*16 ; Stereo speaker predriver output DCD -6*16 DCD 6*16 DCD 6*16 ; Stereo hands-free output DCD 0 DCD 0 DCD 16 ; Stereo carkit output DCD -6*16 DCD 6*16 DCD 6*16 ; System audio DCD -62*16 + -18*16 DCD 12*16 + 12*16 DCD 16 ; Mono headset mic DCD 0 DCD 30*16 DCD 16 ; Stereo FM/aux input. May be wrong? (relies on ALC, which fig 14-6 says isn't available) DCD 0 DCD 30*16 DCD 16 ; Mono carkit mic DCD 0 DCD 30*16 DCD 16 MixerSetMix ; a1 = mixer device ; a2 = channel ; a3 = mute flag ; a4 = gain, in dB*16 Entry "v1" ; Double-check that we aren't being asked to program a disabled channel ; Although ReallySetMix will do another check, we must check here to make sure we don't update the stored settings (particularly the headset settings, which UpdateHeadset treats as gospel) MOV ip, #1 LDRB v1, MixerDisableFlags TST v1, ip, LSL a2 EXIT NE ADR v1, MixerSettings ADD v1, v1, a2, LSL #3 LDMIA v1, {ip, lr} TEQ a3, ip TEQEQ a4, lr STMNEIA v1, {a3, a4} LDRNE a1, [a1, #HALDevice_MixerCtrlr] BLNE ReallySetMix EXIT ReallySetMix ; a1 = *audio* device ; a2-a4 as above ; Preserves all regs ; Channel gain & mute control can't easily be abstracted, need to do it on a case-by-case basis :( ; Common code to get ready for IIC transfers Entry "a1-a4,v1-v5,sb,v7" LDR sb, AudioWorkspace [ AudioDebug DebugTX "ReallySetMix:" DebugReg a2,"Channel=" DebugReg a3,"Mute=" DebugReg a4,"Gain=" ] ; Triple-check that we aren't being asked to program a disabled channel ; (ReallySetMix gets called unconditionally in a few places; easier to just check for invalid channels here rather than in every place it gets called from) LDRB v5, [sb, #BoardConfig_MixerChans] MOV v2, #1 TST v5, v2, LSL a2 EXIT NE LDR v5, [a1, #HALDevice_AudioMixer] MOV v2, a3 LDR v4, AudioMode ADD v7, v5, #:INDEX: MixerSettings+4 LDR v1, OSentries+4*OS_IICOpV MOV a1, #TPSAUDIO_IIC*2 ADD v7, v7, a2, LSL #3 ADR a3, SetMixTab MOV v3, a4 LDR pc, [a3, a2, LSL #2] ; Following code entered with: ; a1, v1 ready for TPSRead/TPSWrite ; v2 = mute flag ; v3 = gain ; v4 = current CODEC_MODE setting ; v5 = mixer device ; sb = HAL WS ; v7 = gain writeback ptr SetMixTab DCD SetMixHeadsetOut DCD SetMixPredriver DCD SetMixHandsFree DCD SetMixCarkitOut DCD SetMixSystem DCD SetMixHeadsetMic DCD SetMixAuxInput DCD SetMixCarkitMic SetMixHeadsetOut ; HS_* regs ; If the codec is on, go through to UpdateHeadset ; Else do nothing TST v4, #2 MOVNE a1, v5 BLNE UpdateHeadset EXIT SetMixPredriver ; PRED* regs MOV v4, #&24 ; 0dB gain, AL2 MOV lr, #0 CMP v3, #6*16 MOVGE v4, #&14 ; 6dB gain, AL2 MOVGE lr, #6*16 CMP v3, #-6*16 MOVLE v4, #&34 ; -6dB gain, AL2 MOVLE lr, #-6*16 TST v2, #1 ORREQ v4, v4, v4, LSL #8 ; R channel settings MOVNE v4, #0 ; Else disable everything ADD a2, v5, #:INDEX:MixerPredriverGain LDRH ip, [a2] CMP ip, v4 EXIT EQ STRH v4, [a2] MOV a3, #2 STR lr, [v7] MOV a4, #PREDL_CTL BL TPSWrite [ AudioDebug CMP a1, #0 EXIT EQ DebugTX "SetMixPredriver: TPS write failed!" ] EXIT SetMixHandsFree ; HF* regs TST v2, #1 MOVEQ v4, #&3E ; AL2, enabled ORREQ v4, v4, #&3E00 ; AR2, enabled MOVNE v4, #0 ADD a2, v5, #:INDEX:MixerHandsFreeGain LDRH ip, [a2] CMP ip, v4 EXIT EQ MOV lr, #0 STRH v4, [a2] MOV a3, #2 STR lr, [v7] MOV a4, #HFL_CTL BL TPSWrite [ AudioDebug CMP a1, #0 EXIT EQ DebugTX "SetMixHandsFree: TPS write failed!" ] EXIT SetMixCarkitOut ; PRECK* regs MOV v4, #&64 ; 0dB gain, AL2 MOV lr, #0 CMP v3, #6*16 MOVGE v4, #&54 ; 6dB gain, AL2 MOVGE lr, #6*16 CMP v3, #-6*16 MOVLE v4, #&74 ; -6dB gain, AL2 MOVLE lr, #-6*16 TST v2, #1 ORREQ v4, v4, v4, LSL #8 ; R channel settings MOVNE v4, #0 ; Else disable everything ADD a2, v5, #:INDEX:MixerCarkitOutGain LDRH ip, [a2] CMP ip, v4 EXIT EQ STRH v4, [a2] MOV a3, #2 STR lr, [v7] MOV a4, #PRECKL_CTL BL TPSWrite [ AudioDebug CMP a1, #0 EXIT EQ DebugTX "SetMixCarkitOut: TPS write failed!" ] EXIT SetMixSystem ; ARX(L/R)2PGA (digital) and ARX(L/R)2_APGA_CTL (analogue) ; The ARX(L/R)2PGA regs have both a coarse grain stage (0dB, 6dB, 12dB) and fine-grain stage (-62dB -> 0dB) ; ARX(L/R)2_APGA_CTL are (relatively) fine-grain, from -18dB to 12dB ; Under the assumption the analogue amplifier will be more liable to noise/distortion, we'll try and do as much as possible using the digital amplifier ; TODO - Once other channels start depending on the analogue amplifiers this technique of using the two amplifiers in unison probably won't be workable. It may be worth splitting the analogue amplifiers off into a seperate mixer so the user is given full control? ; First, apply digital coarse grain MOV v4, #0 MOVS v3, v3, ASR #4 MOV ip, v3 ADDGT v4, v4, #&40 ; +6dB SUBGTS v3, v3, #6 ADDGT v4, v4, #&40 ; +12dB SUBGTS v3, v3, #6 ; Now apply digital fine-grain MOVGT v3, #0 ; Don't go any higher than 12dB ADD lr, v4, v4, LSR #1 ADDS v3, v3, #&3F SUB lr, lr, #63*16 MOVLE v3, #1 ; Clamp to -62dB ADD lr, lr, v3, LSL #4 ORR v4, v4, v3 TST v2, #1 ORREQ v4, v4, v4, LSL #8 ; L channel settings MOVNE v4, #0 ; Else disable everything ; We arrive here with: ; v4 = ARXR2PGA, ARXL2PGA settings ; ip = requested gain (dB) ; lr = digital gain achieved (dB*16) ; Apply analogue gain RSB v3, ip, lr, ASR #4 BIC v3, v3, #1 ; Ensure multiple of 2dB CMP v3, #-12 ; Actually +12dB MOVLT v3, #-12 CMP v3, #18 ; Actually -18dB MOVGT v3, #18 SUB lr, lr, v3, LSL #4 ; lr = final gain achieved ; Mung v3 into ARX(L/R)2_APGA_CTL values MOV v3, v3, LSL #2 ADD v3, v3, #(6<<3)+3 ORR v3, v3, v3, LSL #8 TST v2, #1 ; Check mute MOVNE v3, #0 ; Now program everything STR lr, [v7] ADD a2, v5, #:INDEX:MixerSystemGain LDRH ip, [a2] MOV a3, #2 CMP ip, v4 BEQ %FT10 STRH v4, [a2] MOV a4, #ARXR2PGA BL TPSWrite [ AudioDebug CMP a1, #0 BEQ %FT10 DebugTX "SetMixSystem: TPS write failed!" ] 10 ADD a2, v5, #:INDEX:MixerSystemGain2 LDRH ip, [a2] CMP ip, v3 EXIT EQ STRH v3, [a2] MOV a1, #TPSAUDIO_IIC*2 MOV a4, #ARXL2_APGA_CTL BL TPSWrite [ AudioDebug CMP a1, #0 EXIT EQ DebugTX "SetMixSystem: TPS write failed!" ] EXIT SetMixHeadsetMic ; TODO - Implement! SetMixAuxInput ; TODO - Implement! SetMixCarkitMic ; TODO - Implement! EXIT MixerGetMix ADR a1, MixerSettings ADD a1, a1, a2, LSL #3 LDMIA a1, {a1, a2} MOV pc, lr SetAndWaitForAntiPop ; Sets the CNCL_OFFSET_START bit in the ANAMICL register, then waits for it to clear ; Note: Assumes original register contents is &00! ; Also assumes that the codec power is on, otherwise (presumably) nothing will happen ; Input: sb = HAL workspace Entry "a1-a4,v1", 4 MOV a1, #TPSAUDIO_IIC*2 MOV a2, sp MOV a3, #1 MOV a4, #ANAMICL LDR v1, OSentries+4*OS_IICOpV MOV ip, #&a0 STR ip, [a2] BL TPSWrite [ AudioDebug CMP a1, #0 BEQ %FT10 DebugTX "SetAndWaitForAntiPop: TPS write failed!" ] 10 ; Linux uses a delay here to avoid spamming IIC, so we might as well use one too MOV a1, #1024 BL HAL_CounterDelay MOV a1, #TPSAUDIO_IIC*2 MOV a2, sp MOV a3, #1 MOV a4, #ANAMICL BL TPSRead [ AudioDebug CMP a1, #0 BEQ %FT15 DebugTX "SetAndWaitForAntiPop TPS read failed!" 15 ] CMP a1, #0 LDREQB a1, [a2] TSTEQ a1, #&80 ; Check if CNCL_OFFSET_START is still set BNE %BT10 EXIT UpdateHeadset ; This performs the steps necessary to enable/disable the headset output ; Input: ; a1 = mixer device ; sb = HAL workspace Entry "a1-a4,v1", 12 ; Get mixer settings for headset ADR lr, MixerSettings+MixerChannel_HeadsetOut*8 LDMIA lr, {a4,v1} ; a4 = mute flag, v1 = gain ; Calculate HS_GAIN_SET MOV ip, #&a ; 0dB gain MOV a3, #0 CMP v1, #6*16 MOVGE ip, #&5 ; 6dB gain MOVGE a3, #6*16 CMP v1, #-6*16 MOVLE ip, #&f ; -6dB gain MOVLE a3, #-6*16 STR a3, [lr, #4] ; Write back actual gain TST a4, #1 ADRNE a2, HeadsetDisableSequence ; Muted, so disable it MOVNE ip, #0 BNE %FT05 ; Copy enable sequence to stack so we can modify it with the correct gain ADR a3, HeadsetEnableSequence MOV a2, sp LDRH v1, MixerHeadsetGain CMP v1, #0 ; Is the headset currently turned off? LDMIA a3, {a3,a4,v1} ORR a4, a4, ip, LSL #8 ; Set correct gain STMEQIA a2, {a3,a4,v1} ; Full programming sequence if headset off BICNE a4, a4, #&FF0000 ; If headset on, only change HS_GAIN_SET STRNE a4, [a2] 05 ; a2 = sequence to program ; ip = HS_GAIN_SET value LDRH v1, MixerHeadsetGain [ AudioDebug DebugReg ip,"UpdateHeadset: New=" DebugReg v1,"Old=" ] CMP v1, ip EXIT EQ ; Avoid reprogramming identical values STRH ip, MixerHeadsetGain MOV a3, #1 LDR v1, OSentries+4*OS_IICOpV 10 LDRB a4, [a2],#1 CMP a4, #255 LDREQB a4, [a2],#1 BLEQ DoHeadsetRampDelay MOV a1, #TPSAUDIO_IIC*2 CMP a4, #0 EXIT EQ BL TPSWrite [ AudioDebug CMP a1, #0 BEQ %FT15 DebugReg a4,"UpdateHeadset: Write failed, reg=" 15 ] ADD a2, a2, #1 B %BT10 DoHeadsetRampDelay ; Wait for the headset anti-pop to take effect Entry "a1-a4,ip" LDR a1, HeadsetRampDelay BL HAL_CounterDelay EXIT RAMP_DELAY_VAL * 3 HeadsetRampDelay ; Required delay is 2^(19+RAMP_DELAY_VAL)/26 microseconds, assuming 26MHz system clock DCD (1:SHL:(19+RAMP_DELAY_VAL)) / 26 HeadsetEnableSequence DCB HS_SEL, &24 ; 19. Select the headset outputs DCB HS_POPN_SET,&40 + (RAMP_DELAY_VAL :SHL: 2) ; 34, 35. Program antipop bias ramp delay and enable vmid DCB HS_GAIN_SET,&00 ; 36. Enable headset output stage & gain setting. NOTE - This gets filled in above. DCB HS_POPN_SET,&42 + (RAMP_DELAY_VAL :SHL: 2) ; 37. Enable ramp DCB &FF ; Wait for ramp time DCB 0 ; Terminator ALIGN HeadsetDisableSequence DCB HS_POPN_SET,&40 + (RAMP_DELAY_VAL :SHL: 2) ; Disable ramp DCB &FF ; Wait for ramp time DCB HS_GAIN_SET,&00 ; Disable gain DCB HS_POPN_SET,&20 + (RAMP_DELAY_VAL :SHL: 2) ; Disable Vmid, enable external mute DCB HS_SEL,&00 ; Disable headset outputs (for when external mute isn't available) DCB 0 ; Terminator ALIGN END