diff options
author | Michael Sevakis <jethead71@rockbox.org> | 2006-11-06 18:18:05 +0000 |
---|---|---|
committer | Michael Sevakis <jethead71@rockbox.org> | 2006-11-06 18:18:05 +0000 |
commit | 5efee7c94adb691799becb605002e85e13bf11e5 (patch) | |
tree | c8f3e198cf40b5fad4f50ee558195ee906c261c5 /firmware/target | |
parent | 0f5cb94aa4a334366a746fcbb22f3335ca413265 (diff) |
Forgot to use cvs add on a few new files in the fresh checkout I used. woops.
git-svn-id: svn://svn.rockbox.org/rockbox/trunk@11453 a1c6a512-1295-4272-9138-f99709370657
Diffstat (limited to 'firmware/target')
-rw-r--r-- | firmware/target/coldfire/pcm-coldfire.c | 738 |
1 files changed, 738 insertions, 0 deletions
diff --git a/firmware/target/coldfire/pcm-coldfire.c b/firmware/target/coldfire/pcm-coldfire.c new file mode 100644 index 0000000000..6b92f9cc14 --- /dev/null +++ b/firmware/target/coldfire/pcm-coldfire.c @@ -0,0 +1,738 @@ +/*************************************************************************** + * __________ __ ___. + * 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 <stdlib.h> +#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 + +/* 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 pcm_more_callback_type pcm_callback_for_more; +extern bool pcm_playing; +extern bool pcm_paused; + +/* the registered callback function for when more data is available */ +extern pcm_more_callback_type pcm_callback_more_ready; +extern 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)) + +#ifdef HAVE_SPDIF_OUT +#define EBU_DEFPARM ((7 << 12) | (3 << 8) | (1 << 5) | (5 << 2)) +#endif +#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; + + /* Reset the audio FIFO */ +#ifdef HAVE_SPDIF_OUT + EBU1CONFIG = IIS_RESET | EBU_DEFPARM; +#endif + + /* 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); + + /* Also send the audio to S/PDIF */ +#ifdef HAVE_SPDIF_OUT + EBU1CONFIG = EBU_DEFPARM; +#endif + + 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); + +#ifdef HAVE_SPDIF_OUT + EBU1CONFIG = IIS_RESET | EBU_DEFPARM; +#endif +} /* 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); + + /* 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_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; + +#ifdef HAVE_SPDIF_IN + /* PHASECONFIG setup: gain = 3*2^13, source = EBUIN */ + PHASECONFIG = (6 << 3) | (4 << 0); +#endif + + 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); +#ifdef HAVE_SPDIF_OUT + EBU1CONFIG = EBU_DEFPARM; +#endif +} /* pcm_play_pause_pause */ + +void pcm_play_pause_unpause(void) +{ + /* Enable the FIFO and force one write to it */ + pcm_apply_settings(false); +#ifdef HAVE_SPDIF_OUT + EBU1CONFIG = EBU_DEFPARM; +#endif + 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 */ |