/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2006 by Michael Sevakis * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include #include "system.h" #include "kernel.h" #include "logf.h" #include "audio.h" #if defined(HAVE_UDA1380) #include "uda1380.h" #elif defined(HAVE_TLV320) #include "tlv320.h" #endif #if defined(HAVE_SPDIF_IN) || defined(HAVE_SPDIF_OUT) #include "spdif.h" #endif /* Avoid further #ifdef's for some codec functions */ #if defined(HAVE_UDA1380) #define ac_init uda1380_init #define ac_mute uda1380_mute #define ac_set_frequency uda1380_set_frequency #elif defined(HAVE_TLV320) #define ac_init tlv320_init #define ac_mute tlv320_mute #define ac_set_frequency tlv320_set_frequency #endif /** Semi-private shared symbols **/ /* the registered callback function to ask for more pcm data */ extern volatile pcm_more_callback_type pcm_callback_for_more; extern volatile bool pcm_playing; extern volatile bool pcm_paused; /* the registered callback function for when more data is available */ extern volatile pcm_more_callback_type pcm_callback_more_ready; extern volatile bool pcm_recording; /* peaks */ static int play_peak_left, play_peak_right; static unsigned long *rec_peak_addr; static int rec_peak_left, rec_peak_right; #define IIS_DEFPARM ( (freq_ent[FPARM_CLOCKSEL] << 12) | \ (pcm_txsrc_select[pcm_monitor+1] << 8) | \ (4 << 2) ) /* 64 bit clocks / word clock */ #define IIS_RESET 0x800 #ifdef IAUDIO_X5 #define SET_IIS_CONFIG(x) IIS1CONFIG = (x); #define IIS_CONFIG IIS1CONFIG #define PLLCR_SET_AUDIO_BITS_DEFPARM \ ((freq_ent[FPARM_CLSEL] << 28) | (1 << 22)) #else #define SET_IIS_CONFIG(x) IIS2CONFIG = (x); #define IIS_CONFIG IIS2CONFIG #define PLLCR_SET_AUDIO_BITS_DEFPARM \ ((freq_ent[FPARM_CLSEL] << 28) | (3 << 22)) #endif /** Sample rates **/ #define FPARM_CLOCKSEL 0 #define FPARM_CLSEL 1 #define FPARM_FSEL 2 #if CONFIG_CPU == MCF5249 && defined(HAVE_UDA1380) static const unsigned char pcm_freq_parms[HW_NUM_FREQ][3] = { [HW_FREQ_88] = { 0x0c, 0x01, 0x03 }, [HW_FREQ_44] = { 0x06, 0x01, 0x02 }, [HW_FREQ_22] = { 0x04, 0x02, 0x01 }, [HW_FREQ_11] = { 0x02, 0x02, 0x00 }, }; #endif #if CONFIG_CPU == MCF5250 && defined(HAVE_TLV320) static const unsigned char pcm_freq_parms[HW_NUM_FREQ][3] = { [HW_FREQ_88] = { 0x0c, 0x01, 0x02 }, [HW_FREQ_44] = { 0x06, 0x01, 0x01 }, [HW_FREQ_22] = { 0x04, 0x01, 0x00 }, [HW_FREQ_11] = { 0x02, 0x02, 0x00 }, }; #endif static int pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ static const unsigned char *freq_ent = pcm_freq_parms[HW_FREQ_DEFAULT]; /* set frequency used by the audio hardware */ void pcm_set_frequency(unsigned int frequency) { int index; switch(frequency) { case SAMPR_11: index = HW_FREQ_11; break; case SAMPR_22: index = HW_FREQ_22; break; default: case SAMPR_44: index = HW_FREQ_44; break; case SAMPR_88: index = HW_FREQ_88; break; } /* remember table entry and rate */ freq_ent = pcm_freq_parms[index]; pcm_freq = hw_freq_sampr[index]; } /* pcm_set_frequency */ /** monitoring/source selection **/ static int pcm_monitor = AUDIO_SRC_PLAYBACK; static const unsigned char pcm_txsrc_select[AUDIO_NUM_SOURCES+1] = { [AUDIO_SRC_PLAYBACK+1] = 3, /* PDOR3 */ [AUDIO_SRC_MIC+1] = 4, /* IIS1 RcvData */ [AUDIO_SRC_LINEIN+1] = 4, /* IIS1 RcvData */ #ifdef HAVE_FMRADIO_IN [AUDIO_SRC_FMRADIO+1] = 4, /* IIS1 RcvData */ #endif #ifdef HAVE_SPDIF_IN [AUDIO_SRC_SPDIF+1] = 7, /* EBU1 RcvData */ #endif }; static const unsigned short pcm_dataincontrol[AUDIO_NUM_SOURCES+1] = { [AUDIO_SRC_PLAYBACK+1] = 0x0200, /* Reset PDIR2 data flow */ [AUDIO_SRC_MIC+1] = 0xc020, /* Int. when 6 samples in FIFO, PDIR2 src = ebu1RcvData */ [AUDIO_SRC_LINEIN+1] = 0xc020, /* Int. when 6 samples in FIFO, PDIR2 src = ebu1RcvData */ #ifdef HAVE_FMRADIO_IN [AUDIO_SRC_FMRADIO+1] = 0xc020, /* Int. when 6 samples in FIFO, PDIR2 src = ebu1RcvData */ #endif #ifdef HAVE_SPDIF_IN [AUDIO_SRC_SPDIF+1] = 0xc038, /* Int. when 6 samples in FIFO, PDIR2 src = ebu1RcvData */ #endif }; static int pcm_rec_src = AUDIO_SRC_PLAYBACK; void pcm_set_monitor(int monitor) { if ((unsigned)monitor >= AUDIO_NUM_SOURCES) monitor = AUDIO_SRC_PLAYBACK; pcm_monitor = monitor; } /* pcm_set_monitor */ void pcm_set_rec_source(int source) { if ((unsigned)source >= AUDIO_NUM_SOURCES) source = AUDIO_SRC_PLAYBACK; pcm_rec_src = source; } /* pcm_set_rec_source */ /* apply audio settings */ void pcm_apply_settings(bool reset) { static int last_pcm_freq = HW_SAMPR_DEFAULT; #if 0 static int last_pcm_monitor = AUDIO_SRC_PLAYBACK; #endif static int last_pcm_rec_src = AUDIO_SRC_PLAYBACK; /* Playback must prevent pops and record monitoring won't work at all adding IIS_RESET when setting IIS_CONFIG. Use a different method for each. */ if (reset && (pcm_monitor != AUDIO_SRC_PLAYBACK)) { /* Not playback - reset first */ SET_IIS_CONFIG(IIS_RESET); reset = false; } if (pcm_rec_src != last_pcm_rec_src) { last_pcm_rec_src = pcm_rec_src; DATAINCONTROL = pcm_dataincontrol[pcm_rec_src+1]; } if (pcm_freq != last_pcm_freq) { last_pcm_freq = pcm_freq; ac_set_frequency(freq_ent[FPARM_FSEL]); coldfire_set_pllcr_audio_bits(PLLCR_SET_AUDIO_BITS_DEFPARM); } SET_IIS_CONFIG(IIS_DEFPARM | (reset ? IIS_RESET : 0)); } /* pcm_apply_settings */ /** DMA **/ /**************************************************************************** ** Playback DMA transfer **/ /* Set up the DMA transfer that kicks in when the audio FIFO gets empty */ void pcm_play_dma_start(const void *addr, size_t size) { logf("pcm_play_dma_start"); addr = (void *)((unsigned long)addr & ~3); /* Align data */ size &= ~3; /* Size must be multiple of 4 */ pcm_playing = true; /* Set up DMA transfer */ SAR0 = (unsigned long)addr; /* Source address */ DAR0 = (unsigned long)&PDOR3; /* Destination address */ BCR0 = size; /* Bytes to transfer */ /* Enable the FIFO and force one write to it */ pcm_apply_settings(false); DCR0 = DMA_INT | DMA_EEXT | DMA_CS | DMA_AA | DMA_SINC | DMA_SSIZE(3) | DMA_START; } /* pcm_play_dma_start */ /* Stops the DMA transfer and interrupt */ void pcm_play_dma_stop(void) { logf("pcm_play_dma_stop"); pcm_playing = false; DCR0 = 0; DSR0 = 1; /* Reset the FIFO */ pcm_apply_settings(false); } /* pcm_play_dma_stop */ void pcm_init(void) { logf("pcm_init"); pcm_playing = false; pcm_paused = false; pcm_callback_for_more = NULL; MPARK = 0x81; /* PARK[1,0]=10 + BCR24BIT */ DIVR0 = 54; /* DMA0 is mapped into vector 54 in system.c */ DMAROUTE = (DMAROUTE & 0xffffff00) | DMA0_REQ_AUDIO_1; DMACONFIG = 1; /* DMA0Req = PDOR3, DMA1Req = PDIR2 */ /* Reset the audio FIFO */ SET_IIS_CONFIG(IIS_RESET); pcm_set_frequency(-1); pcm_set_monitor(-1); /* Prevent pops (resets DAC to zero point) */ SET_IIS_CONFIG(IIS_DEFPARM | IIS_RESET); #if defined(HAVE_SPDIF_IN) || defined(HAVE_SPDIF_OUT) spdif_init(); #endif /* Initialize default register values. */ ac_init(); #if defined(HAVE_UDA1380) /* Sleep a while so the power can stabilize (especially a long delay is needed for the line out connector). */ sleep(HZ); /* Power on FSDAC and HP amp. */ uda1380_enable_output(true); #elif defined(HAVE_TLV320) sleep(HZ/4); #endif /* UDA1380: Unmute the master channel (DAC should be at zero point now). */ ac_mute(false); /* Call pcm_play_dma_stop to initialize everything. */ pcm_play_dma_stop(); /* Enable interrupt at level 7, priority 0 */ ICR6 = (7 << 2); IMR &= ~(1 << 14); /* bit 14 is DMA0 */ } /* pcm_init */ size_t pcm_get_bytes_waiting(void) { return BCR0 & 0xffffff; } /* pcm_get_bytes_waiting */ /* DMA0 Interrupt is called when the DMA has finished transfering a chunk from the caller's buffer */ void DMA0(void) __attribute__ ((interrupt_handler, section(".icode"))); void DMA0(void) { int res = DSR0; DSR0 = 1; /* Clear interrupt */ DCR0 &= ~DMA_EEXT; /* Stop on error */ if ((res & 0x70) == 0) { pcm_more_callback_type get_more = pcm_callback_for_more; unsigned char *next_start; size_t next_size = 0; if (get_more) get_more(&next_start, &next_size); if (next_size > 0) { SAR0 = (unsigned long)next_start; /* Source address */ BCR0 = next_size; /* Bytes to transfer */ DCR0 |= DMA_EEXT; return; } else { /* Finished playing */ #if 0 /* int. logfs can trash the display */ logf("DMA0 No Data:0x%04x", res); #endif } } else { logf("DMA Error:0x%04x", res); } pcm_play_dma_stop(); } /* DMA0 */ /**************************************************************************** ** Recording DMA transfer **/ void pcm_rec_dma_start(const void *addr, size_t size) { logf("pcm_rec_dma_start"); addr = (void *)((unsigned long)addr & ~3); /* Align data */ size &= ~3; /* Size must be multiple of 4 */ pcm_recording = true; DAR1 = (unsigned long)addr; /* Destination address */ SAR1 = (unsigned long)&PDIR2; /* Source address */ BCR1 = size; /* Bytes to transfer */ rec_peak_addr = (unsigned long *)addr; pcm_apply_settings(false); /* Start the DMA transfer.. */ #ifdef HAVE_SPDIF_IN INTERRUPTCLEAR = 0x03c00000; #endif DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_AA | DMA_DINC | DMA_DSIZE(3) | DMA_START; } /* pcm_dma_start */ void pcm_rec_dma_stop(void) { logf("pcm_rec_dma_stop"); pcm_recording = false; DCR1 = 0; DSR1 = 1; /* Clear interrupt */ } /* pcm_dma_stop */ void pcm_init_recording(void) { logf("pcm_init_recording"); pcm_recording = false; pcm_callback_more_ready = NULL; AUDIOGLOB |= 0x180; /* IIS1 fifo auto sync = on, PDIR2 auto sync = on */ DIVR1 = 55; /* DMA1 is mapped into vector 55 in system.c */ DMACONFIG = 1; /* DMA0Req = PDOR3, DMA1Req = PDIR2 */ DMAROUTE = (DMAROUTE & 0xffff00ff) | DMA1_REQ_AUDIO_2; pcm_rec_dma_stop(); ICR7 = (7 << 2); /* Enable interrupt at level 7, priority 0 */ IMR &= ~(1 << 15); /* bit 15 is DMA1 */ } /* pcm_init_recording */ void pcm_close_recording(void) { logf("pcm_close_recording"); pcm_rec_dma_stop(); DMAROUTE &= 0xffff00ff; ICR7 = 0x00; /* Disable interrupt */ IMR |= (1 << 15); /* bit 15 is DMA1 */ } /* pcm_close_recording */ /* DMA1 Interrupt is called when the DMA has finished transfering a chunk into the caller's buffer */ void DMA1(void) __attribute__ ((interrupt_handler, section(".icode"))); void DMA1(void) { int res = DSR1; pcm_more_callback_type more_ready; unsigned char *next_start; ssize_t next_size = 0; /* passing <> 0 is indicates an error condition */ DSR1 = 1; /* Clear interrupt */ DCR1 &= ~DMA_EEXT; if (res & 0x70) { next_size = DMA_REC_ERROR_DMA; logf("DMA1 err: 0x%x", res); } #ifdef HAVE_SPDIF_IN else if (pcm_rec_src == AUDIO_SRC_SPDIF && (INTERRUPTSTAT & 0x01c00000)) /* valnogood, symbolerr, parityerr */ { INTERRUPTCLEAR = 0x03c00000; next_size = DMA_REC_ERROR_SPDIF; logf("spdif err"); } #endif more_ready = pcm_callback_more_ready; if (more_ready) more_ready(&next_start, &next_size); if (next_size > 0) { /* Start peaking at dest */ rec_peak_addr = (unsigned long *)next_start; DAR1 = (unsigned long)next_start; /* Destination address */ BCR1 = (unsigned long)next_size; /* Bytes to transfer */ DCR1 |= DMA_EEXT; return; } else { #if 0 /* int. logfs can trash the display */ logf("DMA1 No Data:0x%04x", res); #endif } /* Finished recording */ pcm_rec_dma_stop(); } /* DMA1 */ void pcm_mute(bool mute) { ac_mute(mute); if (mute) sleep(HZ/16); } /* pcm_mute */ void pcm_play_pause_pause(void) { /* Disable DMA peripheral request. */ DCR0 &= ~DMA_EEXT; pcm_apply_settings(true); } /* pcm_play_pause_pause */ void pcm_play_pause_unpause(void) { /* Enable the FIFO and force one write to it */ pcm_apply_settings(false); DCR0 |= DMA_EEXT | DMA_START; } /* pcm_play_pause_unpause */ /** * Return playback peaks - Peaks ahead in the DMA buffer based upon the * calling period to attempt to compensate for * delay. */ void pcm_calculate_peaks(int *left, int *right) { unsigned long samples; unsigned long *addr, *end; long peak_p, peak_n; int level; static unsigned long last_peak_tick = 0; static unsigned long frame_period = 0; /* Throttled peak ahead based on calling period */ unsigned long period = current_tick - last_peak_tick; /* Keep reasonable limits on period */ if (period < 1) period = 1; else if (period > HZ/5) period = HZ/5; frame_period = (3*frame_period + period) >> 2; last_peak_tick = current_tick; if (!pcm_playing || pcm_paused) { play_peak_left = play_peak_right = 0; goto peak_done; } /* prevent interrupt from setting up next transfer and be sure SAR0 and BCR0 refer to current transfer */ level = set_irq_level(HIGHEST_IRQ_LEVEL); addr = (long *)(SAR0 & ~3); samples = (BCR0 & 0xffffff) >> 2; set_irq_level(level); samples = MIN(frame_period*pcm_freq/HZ, samples); end = addr + samples; peak_p = peak_n = 0; if (left && right) { if (samples > 0) { long peak_rp = 0, peak_rn = 0; do { long value = *addr; long ch; ch = value >> 16; if (ch > peak_p) peak_p = ch; else if (ch < peak_n) peak_n = ch; ch = (short)value; if (ch > peak_rp) peak_rp = ch; else if (ch < peak_rn) peak_rn = ch; addr += 4; } while (addr < end); play_peak_left = MAX(peak_p, -peak_n); play_peak_right = MAX(peak_rp, -peak_rn); } } else if (left || right) { if (samples > 0) { if (left) { /* Put left channel in low word */ addr = (long *)((short *)addr - 1); end = (long *)((short *)end - 1); } do { long value = *(short *)addr; if (value > peak_p) peak_p = value; else if (value < peak_n) peak_n = value; addr += 4; } while (addr < end); if (left) play_peak_left = MAX(peak_p, -peak_n); else play_peak_right = MAX(peak_p, -peak_n); } } peak_done: if (left) *left = play_peak_left; if (right) *right = play_peak_right; } /* pcm_calculate_peaks */ /** * Return recording peaks - Looks at every 4th sample from last peak up to * current write position. */ void pcm_calculate_rec_peaks(int *left, int *right) { unsigned long *pkaddr, *addr, *end; long peak_lp, peak_ln; /* L +,- */ long peak_rp, peak_rn; /* R +,- */ int level; if (!pcm_recording) { rec_peak_left = rec_peak_right = 0; goto peak_done; } /* read these atomically or each value may not refer to the same data transfer */ level = set_irq_level(HIGHEST_IRQ_LEVEL); pkaddr = rec_peak_addr; addr = pkaddr; end = (unsigned long *)(DAR1 & ~3); set_irq_level(level); if (addr < end) { peak_lp = peak_ln = peak_rp = peak_rn = 0; /* peak one sample per line */ do { long value = *addr; long ch; ch = value >> 16; if (ch < peak_ln) peak_ln = ch; else if (ch > peak_lp) peak_lp = ch; ch = (short)value; if (ch > peak_rp) peak_rp = ch; else if (ch < peak_rn) peak_rn = ch; addr += 4; } while (addr < end); /* only update rec_peak_addr if a DMA interrupt hasn't already done so */ level = set_irq_level(HIGHEST_IRQ_LEVEL); if (pkaddr == rec_peak_addr) rec_peak_addr = end; set_irq_level(level); /* save peaks */ rec_peak_left = MAX(peak_lp, -peak_ln); rec_peak_right = MAX(peak_rp, -peak_rn); } peak_done: if (left) *left = rec_peak_left; if (right) *right = rec_peak_right; } /* pcm_calculate_rec_peaks */ /** * Select VINL & VINR source: 0=Line-in, 1=FM Radio */ /* All use GPIO */ #if defined(IAUDIO_X5) #define REC_MUX_BIT (1 << 29) #define REC_MUX_SET_LINE() or_l(REC_MUX_BIT, &GPIO_OUT) #define REC_MUX_SET_FM() and_l(~REC_MUX_BIT, &GPIO_OUT) #else #if defined(IRIVER_H100_SERIES) #define REC_MUX_BIT (1 << 23) #elif defined(IRIVER_H300_SERIES) #define REC_MUX_BIT (1 << 30) #endif #define REC_MUX_SET_LINE() and_l(~REC_MUX_BIT, &GPIO_OUT) #define REC_MUX_SET_FM() or_l(REC_MUX_BIT, &GPIO_OUT) #endif void pcm_rec_mux(int source) { if (source == 0) REC_MUX_SET_LINE(); /* Line In */ else REC_MUX_SET_FM(); /* FM radio */ or_l(REC_MUX_BIT, &GPIO_ENABLE); or_l(REC_MUX_BIT, &GPIO_FUNCTION); } /* pcm_rec_mux */