diff --git a/apps/debug_menu.c b/apps/debug_menu.c index 15c2c93d62..b3b6fe3eca 100644 --- a/apps/debug_menu.c +++ b/apps/debug_menu.c @@ -75,7 +75,6 @@ #include "logfdisp.h" #if CONFIG_CODEC == SWCODEC #include "pcmbuf.h" -#include "pcm_playback.h" #if defined(HAVE_SPDIF_OUT) || defined(HAVE_SPDIF_IN) #include "spdif.h" #endif diff --git a/apps/main.c b/apps/main.c index bc8a12dd4e..e4fd6bc614 100644 --- a/apps/main.c +++ b/apps/main.c @@ -353,6 +353,9 @@ static void init(void) power_init(); set_irq_level(0); +#ifdef CPU_ARM + set_fiq_status(FIQ_ENABLED); +#endif lcd_init(); #ifdef HAVE_REMOTE_LCD lcd_remote_init(); diff --git a/apps/pcmbuf.c b/apps/pcmbuf.c index 7e6954c280..a6b82baf25 100644 --- a/apps/pcmbuf.c +++ b/apps/pcmbuf.c @@ -24,7 +24,7 @@ #include "panic.h" #include #include "pcmbuf.h" -#include "pcm_playback.h" +#include "pcm.h" #include "logf.h" #ifndef SIMULATOR #include "cpu.h" @@ -154,7 +154,6 @@ static void pcmbuf_callback(unsigned char** start, size_t* size) crossfade_chunk = pcmbuf_read; } -process_new_buffer: { /* Send the new buffer to the pcm */ struct pcmbufdesc *pcmbuf_new = pcmbuf_read; @@ -171,10 +170,6 @@ process_new_buffer: } else { - /* There may be more data waiting to flush, try to use it */ - if (pcmbuf_flush_fillpos()) - goto process_new_buffer; - /* No more buffers */ last_chunksize = 0; *realsize = 0; @@ -487,7 +482,12 @@ void pcmbuf_pause(bool pause) if (pause) pcm_mute(true); #endif - pcm_play_pause(!pause); + + if (pcm_is_playing()) + pcm_play_pause(!pause); + else if (!pause) + pcmbuf_play_start(); + #ifdef PCMBUF_MUTING if (!pause) pcm_mute(false); @@ -823,7 +823,8 @@ static bool prepare_insert(size_t length) #endif { logf("pcm starting"); - pcmbuf_play_start(); + if (!(audio_status() & AUDIO_STATUS_PAUSE)) + pcmbuf_play_start(); } } else if (pcmbuf_unplayed_bytes <= pcmbuf_watermark) diff --git a/apps/plugins/SOURCES b/apps/plugins/SOURCES index b725e7f0e5..1bed2b1e3a 100644 --- a/apps/plugins/SOURCES +++ b/apps/plugins/SOURCES @@ -20,6 +20,7 @@ stopwatch.c vbrfix.c viewer.c + #if CONFIG_CODEC == SWCODEC metronome.c #endif diff --git a/apps/recorder/peakmeter.c b/apps/recorder/peakmeter.c index 00591de657..c4bf07146d 100644 --- a/apps/recorder/peakmeter.c +++ b/apps/recorder/peakmeter.c @@ -43,7 +43,7 @@ #include "action.h" #if CONFIG_CODEC == SWCODEC -#include "pcm_playback.h" +#include "pcm.h" #ifdef HAVE_RECORDING #include "pcm_record.h" diff --git a/apps/recorder/recording.c b/apps/recorder/recording.c index fed2e6767e..ac06c93cf9 100644 --- a/apps/recorder/recording.c +++ b/apps/recorder/recording.c @@ -31,7 +31,6 @@ #include "audio.h" #if CONFIG_CODEC == SWCODEC #include "thread.h" -#include "pcm_playback.h" #include "playback.h" #include "enc_config.h" #if defined(HAVE_SPDIF_IN) || defined(HAVE_SPDIF_OUT) diff --git a/apps/settings.c b/apps/settings.c index cacf3e5854..26036c8948 100644 --- a/apps/settings.c +++ b/apps/settings.c @@ -79,7 +79,6 @@ struct system_status global_status; #if CONFIG_CODEC == SWCODEC #include "pcmbuf.h" -#include "pcm_playback.h" #include "dsp.h" #ifdef HAVE_RECORDING #include "enc_config.h" diff --git a/firmware/SOURCES b/firmware/SOURCES index 1079fabec9..2d4bc9a522 100644 --- a/firmware/SOURCES +++ b/firmware/SOURCES @@ -182,9 +182,7 @@ sound.c general.c pcm_sampr.c replaygain.c -#ifndef SIMULATOR -pcm_playback.c -#endif /* SIMULATOR */ +pcm.c #ifdef HAVE_RECORDING enc_base.c #ifndef SIMULATOR @@ -808,7 +806,7 @@ target/arm/pnx0101/iriver-ifp7xx/powermgmt-ifp7xx.c target/arm/pnx0101/iriver-ifp7xx/usb-ifp7xx.c #ifndef BOOTLOADER target/arm/pnx0101/pcm-pnx0101.c -#endif -#endif -#endif +#endif /* BOOTLOADER */ +#endif /* SIMULATOR */ +#endif /* IRIVER_IFP7XX */ diff --git a/firmware/export/audio.h b/firmware/export/audio.h index b55c46a573..84275cca2a 100644 --- a/firmware/export/audio.h +++ b/firmware/export/audio.h @@ -26,7 +26,7 @@ many files. */ #if CONFIG_CODEC == SWCODEC #include "pcm_sampr.h" -#include "pcm_playback.h" +#include "pcm.h" #ifdef HAVE_RECORDING #include "pcm_record.h" #include "id3.h" diff --git a/firmware/export/pcm.h b/firmware/export/pcm.h new file mode 100644 index 0000000000..a875479a2d --- /dev/null +++ b/firmware/export/pcm.h @@ -0,0 +1,122 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2005 by Linus Nielsen Feltzing + * + * 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. + * + ****************************************************************************/ +#ifndef PCM_PLAYBACK_H +#define PCM_PLAYBACK_H + +#include + +/** RAW PCM routines used with playback and recording **/ + +/* Typedef for registered callback */ +typedef void (*pcm_more_callback_type)(unsigned char **start, + size_t *size); +typedef int (*pcm_more_callback_type2)(int status); + +/* set the pcm frequency - use values in hw_sampr_list + * use -1 for the default frequency + */ +void pcm_set_frequency(unsigned int frequency); +/* apply settings to hardware immediately */ +void pcm_apply_settings(void); + +/** RAW PCM playback routines **/ + +/* Reenterable locks for locking and unlocking the playback interrupt */ +void pcm_play_lock(void); +void pcm_play_unlock(void); + +void pcm_init(void); +void pcm_postinit(void); + +/* This is for playing "raw" PCM data */ +void pcm_play_data(pcm_more_callback_type get_more, + unsigned char* start, size_t size); + +void pcm_calculate_peaks(int *left, int *right); +size_t pcm_get_bytes_waiting(void); + +void pcm_play_stop(void); +void pcm_mute(bool mute); +void pcm_play_pause(bool play); +bool pcm_is_paused(void); +bool pcm_is_playing(void); + +/** The following are for internal use between pcm.c and target- + specific portion **/ + +extern unsigned long pcm_curr_sampr; + +/* the registered callback function to ask for more mp3 data */ +extern volatile pcm_more_callback_type pcm_callback_for_more; +extern volatile bool pcm_playing; +extern volatile bool pcm_paused; + +void pcm_play_dma_lock(void); +void pcm_play_dma_unlock(void); +void pcm_play_dma_init(void); +void pcm_play_dma_start(const void *addr, size_t size); +void pcm_play_dma_stop(void); +void pcm_play_dma_pause(bool pause); +void pcm_play_dma_stopped_callback(void); +const void * pcm_play_dma_get_peak_buffer(int *count); + +#ifdef HAVE_RECORDING + +/** RAW PCM recording routines **/ + +/* Reenterable locks for locking and unlocking the recording interrupt */ +void pcm_rec_lock(void); +void pcm_rec_unlock(void); + +/* Initialize pcm recording interface */ +void pcm_init_recording(void); +/* Uninitialze pcm recording interface */ +void pcm_close_recording(void); + +/* Start recording "raw" PCM data */ +void pcm_record_data(pcm_more_callback_type2 more_ready, + void *start, size_t size); + +/* Stop tranferring data into supplied buffer */ +void pcm_stop_recording(void); + +/* Continue transferring data in - call during interrupt handler */ +void pcm_record_more(void *start, size_t size); + +void pcm_calculate_rec_peaks(int *left, int *right); + +/** The following are for internal use between pcm.c and target- + specific portion **/ +extern volatile const void *pcm_rec_peak_addr; +/* the registered callback function for when more data is available */ +extern volatile pcm_more_callback_type2 pcm_callback_more_ready; +/* DMA transfer in is currently active */ +extern volatile bool pcm_recording; + +/* APIs implemented in the target-specific portion */ +void pcm_rec_dma_init(void); +void pcm_rec_dma_close(void); +void pcm_rec_dma_start(void *addr, size_t size); +void pcm_rec_dma_stop(void); +void pcm_rec_dma_stopped_callback(void); +const void * pcm_rec_dma_get_peak_buffer(int *count); + +#endif /* HAVE_RECORDING */ + +#endif /* PCM_PLAYBACK_H */ diff --git a/firmware/export/pcm_playback.h b/firmware/export/pcm_playback.h deleted file mode 100644 index 351b1fa23f..0000000000 --- a/firmware/export/pcm_playback.h +++ /dev/null @@ -1,65 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2005 by Linus Nielsen Feltzing - * - * 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. - * - ****************************************************************************/ -#ifndef PCM_PLAYBACK_H -#define PCM_PLAYBACK_H - -#include - -/* Typedef for registered callback (play and record) */ -typedef void (*pcm_more_callback_type)(unsigned char **start, - size_t *size); -typedef int (*pcm_more_callback_type2)(int status); - -void pcm_init(void); -void pcm_postinit(void); - -/* set the pcm frequency - use values in hw_sampr_list - * use -1 for the default frequency - */ -void pcm_set_frequency(unsigned int frequency); -/* apply settings to hardware immediately */ -void pcm_apply_settings(void); - -/* This is for playing "raw" PCM data */ -void pcm_play_data(pcm_more_callback_type get_more, - unsigned char* start, size_t size); - -void pcm_calculate_peaks(int *left, int *right); -size_t pcm_get_bytes_waiting(void); - -void pcm_play_stop(void); -void pcm_mute(bool mute); -void pcm_play_pause(bool play); -bool pcm_is_paused(void); -bool pcm_is_playing(void); - -/** The following are for internal use between pcm_playback.c and target- - specific portion **/ - -/* the registered callback function to ask for more mp3 data */ -extern volatile pcm_more_callback_type pcm_callback_for_more; -extern volatile bool pcm_playing; -extern volatile bool pcm_paused; - -extern void pcm_play_dma_start(const void *addr, size_t size); -extern void pcm_play_dma_stop(void); -extern void pcm_play_pause_pause(void); -extern void pcm_play_pause_unpause(void); - -#endif /* PCM_PLAYBACK_H */ diff --git a/firmware/export/pcm_record.h b/firmware/export/pcm_record.h index 19c10cb228..814eb73b3a 100644 --- a/firmware/export/pcm_record.h +++ b/firmware/export/pcm_record.h @@ -44,28 +44,6 @@ #define PCMREC_E_CHUNK_OVF 0x80010000 #endif /* DEBUG */ -/** - * RAW pcm data recording - * These calls are nescessary only when using the raw pcm apis directly. - */ - -/* Initialize pcm recording interface */ -void pcm_init_recording(void); -/* Uninitialze pcm recording interface */ -void pcm_close_recording(void); - -/* Start recording "raw" PCM data */ -void pcm_record_data(pcm_more_callback_type2 more_ready, - void *start, size_t size); - -/* Stop tranferring data into supplied buffer */ -void pcm_stop_recording(void); - -/* Continue transferring data in - call during interrupt handler */ -void pcm_record_more(void *start, size_t size); - -void pcm_calculate_rec_peaks(int *left, int *right); - /** General functions for high level codec recording **/ /* pcm_rec_error_clear is deprecated for general use. audio_error_clear should be used */ @@ -83,16 +61,4 @@ int pcm_get_num_unprocessed(void); /* audio.h contains audio_* recording functions */ - -/** The following are for internal use between pcm_record.c and target- - specific portion **/ -/* the registered callback function for when more data is available */ -extern volatile pcm_more_callback_type2 pcm_callback_more_ready; -/* DMA transfer in is currently active */ -extern volatile bool pcm_recording; - -/* APIs implemented in the target-specific portion */ -extern void pcm_rec_dma_start(void *addr, size_t size); -extern void pcm_rec_dma_stop(void); - #endif /* PCM_RECORD_H */ diff --git a/firmware/export/pp5002.h b/firmware/export/pp5002.h index f566b5cd04..730e42b66d 100644 --- a/firmware/export/pp5002.h +++ b/firmware/export/pp5002.h @@ -26,12 +26,28 @@ #define IPOD_LCD_BASE 0xc0001000 -#define IISCONFIG (*(volatile unsigned long *)(0xc0002500)) +/* Processor ID */ +#define PROCESSOR_ID (*(volatile unsigned long *)(0xc4000000)) +#define PROC_ID_CPU 0x55 +#define PROC_ID_COP 0xaa + +#define IISCONFIG (*(volatile unsigned long *)(0xc0002500)) #define IISFIFO_CFG (*(volatile unsigned long *)(0xc000251c)) #define IISFIFO_WR (*(volatile unsigned long *)(0xc0002540)) #define IISFIFO_RD (*(volatile unsigned long *)(0xc0002580)) +/* IISCONFIG bits: */ +#define IIS_TXFIFOEN (1 << 2) +#define IIS_TX_FREE_MASK (0xf << 23) +#define IIS_TX_FREE_COUNT ((IISFIFO_CFG & IIS_TX_FREE_MASK) >> 23) + +/* IISFIFO_CFG bits: */ +#define IIS_IRQTX_REG IISFIFO_CFG +#define IIS_IRQTX (1 << 9) + +#define I2C_BASE 0xc0008000 + #define IDE_BASE 0xc0003000 #define IDE_CFG_STATUS (*(volatile unsigned long *)(0xc0003024)) @@ -103,6 +119,8 @@ #define DMA_OUT_MASK (1 << DMA_OUT_IRQ) #define DMA_IN_MASK (1 << DMA_IN_IRQ) +/* Yes, there is I2S_MASK but this cleans up the pcm code */ +#define IIS_MASK DMA_OUT_MASK #define TIMER1_CFG (*(volatile unsigned long *)(0xcf001100)) #define TIMER1_VAL (*(volatile unsigned long *)(0xcf001104)) diff --git a/firmware/export/pp5020.h b/firmware/export/pp5020.h index af78101583..981ab318c5 100644 --- a/firmware/export/pp5020.h +++ b/firmware/export/pp5020.h @@ -84,7 +84,7 @@ #define TIMER1_IRQ 0 #define TIMER2_IRQ 1 #define MAILBOX_IRQ 4 -#define I2S_IRQ 10 +#define IIS_IRQ 10 #define IDE_IRQ 23 #define USB_IRQ 24 #define FIREWIRE_IRQ 25 @@ -97,7 +97,7 @@ #define TIMER1_MASK (1 << TIMER1_IRQ) #define TIMER2_MASK (1 << TIMER2_IRQ) #define MAILBOX_MASK (1 << MAILBOX_IRQ) -#define I2S_MASK (1 << I2S_IRQ) +#define IIS_MASK (1 << IIS_IRQ) #define IDE_MASK (1 << IDE_IRQ) #define USB_MASK (1 << USB_IRQ) #define FIREWIRE_MASK (1 << FIREWIRE_IRQ) @@ -307,12 +307,96 @@ #define INIT_USB 0x80000000 -/* I2S */ +/* IIS */ #define IISCONFIG (*(volatile unsigned long*)(0x70002800)) #define IISFIFO_CFG (*(volatile unsigned long*)(0x7000280c)) #define IISFIFO_WR (*(volatile unsigned long*)(0x70002840)) #define IISFIFO_RD (*(volatile unsigned long*)(0x70002880)) +/** + * IISCONFIG bits: + * | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | + * | RESET | |TXFIFOEN|RXFIFOEN| | ???? | MS | ???? | + * | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | + * | | | | | | | | | + * | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | + * | | | | | Bus Format[1:0] | Size[1:0] | + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * | | Size Format[2:0] | ???? | ???? | IRQTX | IRQRX | + */ + +/* All IIS formats send MSB first */ +#define IIS_RESET (1 << 31) +#define IIS_TXFIFOEN (1 << 29) +#define IIS_RXFIFOEN (1 << 28) +#define IIS_MASTER (1 << 25) +#define IIS_IRQTX (1 << 1) +#define IIS_IRQRX (1 << 0) + +#define IIS_IRQTX_REG IISCONFIG +#define IIS_IRQRX_REG IISCONFIG + +/* Data format on the IIS bus */ +#define IIS_FORMAT_MASK (0x3 << 10) +#define IIS_FORMAT_IIS (0x0 << 10) /* Standard IIS - leading dummy bit */ +#define IIS_FORMAT_1 (0x1 << 10) +#define IIS_FORMAT_LJUST (0x2 << 10) /* Left justified - no dummy bit */ +#define IIS_FORMAT_3 (0x3 << 10) +/* Other formats not yet known */ + +/* Data size on IIS bus */ +#define IIS_SIZE_MASK (0x3 << 8) +#define IIS_SIZE_16BIT (0x0 << 8) +/* Other sizes not yet known */ + +/* Data size/format on IIS FIFO */ +#define IIS_FIFO_FORMAT_MASK (0x7 << 4) +#define IIS_FIFO_FORMAT_0 (0x0 << 4) +/* Big-endian formats - data sent to the FIFO must be big endian. + * I forgot which is which size but did test them. */ +#define IIS_FIFO_FORMAT_1 (0x1 << 4) +#define IIS_FIFO_FORMAT_2 (0x2 << 4) + /* 32bit-MSB-little endian */ +#define IIS_FIFO_FORMAT_LE32 (0x3 << 4) + /* 16bit-MSB-little endian */ +#define IIS_FIFO_FORMAT_LE16 (0x4 << 4) + +/* FIFO formats 0x5 and above seem equivalent to 0x4 ?? */ + +/** + * IISFIFO_CFG bits: + * | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | + * | | | RXFull[5:0] | + * | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | + * | | | TXFree[5:0] | + * | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | + * | | | | RXCLR | | | | TXCLR | + * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | + * | | | RX_FULL_LVL | | | TX_EMPTY_LVL | + */ + +/* handy macros to extract the FIFO counts */ +#define IIS_RX_FULL_MASK (0x3f << 24) +#define IIS_RX_FULL_COUNT \ + ((IISFIFO_CFG & IIS_RX_FULL_MASK) >> 24) + +#define IIS_TX_FREE_MASK (0x3f << 16) +#define IIS_TX_FREE_COUNT \ + ((IISFIFO_CFG & IIS_TX_FREE_MASK) >> 16) + +#define IIS_RXCLR (1 << 12) +#define IIS_TXCLR (1 << 8) +/* Number of slots */ +#define IIS_RX_FULL_LVL_4 (0x1 << 4) +#define IIS_RX_FULL_LVL_8 (0x2 << 4) +#define IIS_RX_FULL_LVL_12 (0x3 << 4) + +#define IIS_TX_EMPTY_LVL_4 (0x1 << 0) +#define IIS_TX_EMPTY_LVL_8 (0x2 << 0) +#define IIS_TX_EMPTY_LVL_12 (0x3 << 0) + +/* Note: didn't bother to see of levels 0 and 16 actually work */ + /* Serial Controller */ #define SERIAL0 (*(volatile unsigned long*)(0x70006000)) #define SERIAL1 (*(volatile unsigned long*)(0x70006040)) diff --git a/firmware/pcm.c b/firmware/pcm.c new file mode 100644 index 0000000000..6e05d57f0c --- /dev/null +++ b/firmware/pcm.c @@ -0,0 +1,437 @@ +/*************************************************************************** + * __________ __ ___. + * Open \______ \ ____ ____ | | _\_ |__ _______ ___ + * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / + * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < + * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ + * \/ \/ \/ \/ \/ + * $Id$ + * + * Copyright (C) 2007 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" +#include "sound.h" + +/** + * Aspects implemented in the target-specific portion: + * + * ==Playback== + * Public - + * pcm_postinit + * pcm_get_bytes_waiting + * pcm_play_lock + * pcm_play_unlock + * Semi-private - + * pcm_play_dma_init + * pcm_play_dma_init + * pcm_play_dma_start + * pcm_play_dma_stop + * pcm_play_dma_pause + * pcm_play_dma_get_peak_buffer + * Data Read/Written within TSP - + * pcm_curr_sampr (RW) + * pcm_callback_for_more (R) + * pcm_playing (R) + * pcm_paused (R) + * + * ==Recording== + * Public - + * pcm_rec_lock + * pcm_rec_unlock + * Semi-private - + * pcm_rec_dma_init + * pcm_rec_dma_close + * pcm_rec_dma_start + * pcm_rec_dma_stop + * pcm_rec_dma_get_peak_buffer + * Data Read/Written within TSP - + * pcm_rec_peak_addr (RW) + * pcm_callback_more_ready (R) + * pcm_recording (R) + * + * States are set _after_ the target's pcm driver is called so that it may + * know from whence the state is changed. + * + */ + +/* the registered callback function to ask for more mp3 data */ +volatile pcm_more_callback_type pcm_callback_for_more + NOCACHEBSS_ATTR = NULL; +/* PCM playback state */ +volatile bool pcm_playing NOCACHEBSS_ATTR = false; +/* PCM paused state. paused implies playing */ +volatile bool pcm_paused NOCACHEBSS_ATTR = false; +/* samplerate of currently playing audio - undefined if stopped */ +unsigned long pcm_curr_sampr NOCACHEBSS_ATTR = 0; + +/** + * Do peak calculation using distance squared from axis and save a lot + * of jumps and negation. Don't bother with the calculations of left or + * right only as it's never really used and won't save much time. + * + * Used for recording and playback. + */ +static void pcm_peak_peeker(const void *addr, int count, int peaks[2]) +{ + int32_t peak_l = 0, peak_r = 0; + int32_t peaksq_l = 0, peaksq_r = 0; + + do + { + int32_t value = *(int32_t *)addr; + int32_t ch, chsq; +#ifdef ROCKBOX_BIG_ENDIAN + ch = value >> 16; +#else + ch = (int16_t)value; +#endif + chsq = ch*ch; + if (chsq > peaksq_l) + peak_l = ch, peaksq_l = chsq; + +#ifdef ROCKBOX_BIG_ENDIAN + ch = (int16_t)value; +#else + ch = value >> 16; +#endif + chsq = ch*ch; + if (chsq > peaksq_r) + peak_r = ch, peaksq_r = chsq; + + addr += 16; + count -= 4; + } + while (count > 0); + + peaks[0] = abs(peak_l); + peaks[1] = abs(peak_r); +} + +void pcm_calculate_peaks(int *left, int *right) +{ + static int peaks[2] = { 0, 0 }; + static unsigned long last_peak_tick = 0; + static unsigned long frame_period = 0; + + long tick = current_tick; + + /* Throttled peak ahead based on calling period */ + long period = 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 = tick; + + if (pcm_playing && !pcm_paused) + { + const void *addr; + int count, framecount; + + addr = pcm_play_dma_get_peak_buffer(&count); + + framecount = frame_period*pcm_curr_sampr / HZ; + count = MIN(framecount, count); + + if (count > 0) + pcm_peak_peeker(addr, count, peaks); + } + else + { + peaks[0] = peaks[1] = 0; + } + + if (left) + *left = peaks[0]; + + if (right) + *right = peaks[1]; +} + +/**************************************************************************** + * Functions that do not require targeted implementation but only a targeted + * interface + */ + +/* This should only be called at startup before any audio playback or + recording is attempted */ +void pcm_init(void) +{ + logf("pcm_init"); + + pcm_play_dma_stopped_callback(); + + logf(" pcm_play_dma_init"); + pcm_play_dma_init(); +} + +/* Common code to pcm_play_data and pcm_play_pause */ +static void pcm_play_data_start(unsigned char *start, size_t size) +{ + if (!(start && size)) + { + pcm_more_callback_type get_more = pcm_callback_for_more; + size = 0; + if (get_more) + { + logf(" get_more"); + get_more(&start, &size); + } + } + + if (start && size) + { + logf(" pcm_play_dma_start"); + pcm_play_dma_start(start, size); + pcm_playing = true; + pcm_paused = false; + return; + } + + /* Force a stop */ + logf(" pcm_play_dma_stop"); + pcm_play_dma_stop(); + pcm_play_dma_stopped_callback(); +} + +void pcm_play_data(pcm_more_callback_type get_more, + unsigned char *start, size_t size) +{ + logf("pcm_play_data"); + + pcm_play_lock(); + + pcm_callback_for_more = get_more; + + logf(" pcm_play_dma_start"); + pcm_play_data_start(start, size); + + pcm_play_unlock(); +} + +void pcm_play_pause(bool play) +{ + logf("pcm_play_pause: %s", play ? "play" : "pause"); + + pcm_play_lock(); + + if (play == pcm_paused && pcm_playing) + { + if (!play) + { + logf(" pcm_play_dma_pause"); + pcm_play_dma_pause(true); + pcm_paused = true; + } + else if (pcm_get_bytes_waiting() > 0) + { + logf(" pcm_play_dma_pause"); + pcm_play_dma_pause(false); + pcm_paused = false; + } + else + { + logf(" pcm_play_dma_start: no data"); + pcm_play_data_start(NULL, 0); + } + } + else + { + logf(" no change"); + } + + pcm_play_unlock(); +} + +void pcm_play_stop(void) +{ + logf("pcm_play_stop"); + + pcm_play_lock(); + + if (pcm_playing) + { + logf(" pcm_play_dma_stop"); + pcm_play_dma_stop(); + pcm_play_dma_stopped_callback(); + } + else + { + logf(" not playing"); + } + + pcm_play_unlock(); +} + +void pcm_play_dma_stopped_callback(void) +{ + pcm_callback_for_more = NULL; + pcm_paused = false; + pcm_playing = false; +} + +/**/ + +bool pcm_is_playing(void) +{ + return pcm_playing; +} + +bool pcm_is_paused(void) +{ + return pcm_paused; +} + +void pcm_mute(bool mute) +{ +#ifndef SIMULATOR + audiohw_mute(mute); +#endif + + if (mute) + sleep(HZ/16); +} + +#ifdef HAVE_RECORDING +/** Low level pcm recording apis **/ + +/* Next start for recording peaks */ +const volatile void *pcm_rec_peak_addr NOCACHEBSS_ATTR = NULL; +/* the registered callback function for when more data is available */ +volatile pcm_more_callback_type2 + pcm_callback_more_ready NOCACHEBSS_ATTR = NULL; +/* DMA transfer in is currently active */ +volatile bool pcm_recording NOCACHEBSS_ATTR = false; + +/** + * Return recording peaks - From the end of the last peak up to + * current write position. + */ +void pcm_calculate_rec_peaks(int *left, int *right) +{ + static int peaks[2]; + + if (pcm_recording) + { + const void *addr; + int count; + + addr = pcm_rec_dma_get_peak_buffer(&count); + + if (count > 0) + { + pcm_peak_peeker(addr, count, peaks); + + if (addr == pcm_rec_peak_addr) + pcm_rec_peak_addr = (int32_t *)addr + count; + } + } + else + { + peaks[0] = peaks[1] = 0; + } + + if (left) + *left = peaks[0]; + + if (right) + *right = peaks[1]; +} /* pcm_calculate_rec_peaks */ + +/**************************************************************************** + * Functions that do not require targeted implementation but only a targeted + * interface + */ +void pcm_init_recording(void) +{ + logf("pcm_init_recording"); + + pcm_rec_lock(); + + logf(" pcm_rec_dma_init"); + pcm_rec_dma_stopped_callback(); + pcm_rec_dma_init(); + + pcm_rec_unlock(); +} + +void pcm_close_recording(void) +{ + logf("pcm_close_recording"); + + pcm_rec_lock(); + + if (pcm_recording) + { + logf(" pcm_rec_dma_stop"); + pcm_rec_dma_stopped_callback(); + pcm_rec_dma_stop(); + } + + logf(" pcm_rec_dma_close"); + pcm_rec_dma_close(); + + pcm_rec_unlock(); +} + +void pcm_record_data(pcm_more_callback_type2 more_ready, + void *start, size_t size) +{ + logf("pcm_record_data"); + + if (!(start && size)) + { + logf(" no buffer"); + return; + } + + pcm_rec_lock(); + + pcm_callback_more_ready = more_ready; + + logf(" pcm_rec_dma_start"); + pcm_rec_dma_start(start, size); + pcm_recording = true; + + pcm_rec_unlock(); +} /* pcm_record_data */ + +void pcm_stop_recording(void) +{ + logf("pcm_stop_recording"); + + pcm_rec_lock(); + + if (pcm_recording) + { + logf(" pcm_rec_dma_stop"); + pcm_rec_dma_stop(); + pcm_rec_dma_stopped_callback(); + } + + pcm_rec_unlock(); +} /* pcm_stop_recording */ + +void pcm_rec_dma_stopped_callback(void) +{ + pcm_recording = false; + pcm_callback_more_ready = NULL; +} + +#endif /* HAVE_RECORDING */ diff --git a/firmware/pcm_playback.c b/firmware/pcm_playback.c deleted file mode 100644 index d317b3708e..0000000000 --- a/firmware/pcm_playback.c +++ /dev/null @@ -1,283 +0,0 @@ -/*************************************************************************** - * __________ __ ___. - * Open \______ \ ____ ____ | | _\_ |__ _______ ___ - * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / - * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < - * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ - * \/ \/ \/ \/ \/ - * $Id$ - * - * Copyright (C) 2005 by Linus Nielsen Feltzing - * - * 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 "system.h" -#include "kernel.h" -#include "logf.h" -#include "audio.h" -#include "sound.h" - -/** - * APIs implemented in the target-specific portion: - * Public - - * pcm_init - * pcm_get_bytes_waiting - * pcm_calculate_peaks - * Semi-private - - * pcm_play_dma_start - * pcm_play_dma_stop - * pcm_play_pause_pause - * pcm_play_pause_unpause - */ - -/** These items may be implemented target specifically or need to - be shared semi-privately **/ - -/* the registered callback function to ask for more mp3 data */ -volatile pcm_more_callback_type pcm_callback_for_more = NULL; -volatile bool pcm_playing = false; -volatile bool pcm_paused = false; - -void pcm_play_dma_start(const void *addr, size_t size); -void pcm_play_dma_stop(void); -void pcm_play_pause_pause(void); -void pcm_play_pause_unpause(void); - -/** Functions that require targeted implementation **/ - -#if defined(CPU_COLDFIRE) || (CONFIG_CPU == S3C2440) || (CONFIG_CPU == IMX31L) \ - || (CONFIG_CPU == DM320) -/* Implemented in target/... */ -#else -/* dummy functions for those not actually supporting all this yet */ -void pcm_apply_settings(void) -{ -} -/** **/ - -void pcm_mute(bool mute) -{ -#if defined(HAVE_WM8975) || defined(HAVE_WM8758) \ - || defined(HAVE_WM8731) || defined(HAVE_WM8721) - audiohw_mute(mute); -#endif - if (mute) - sleep(HZ/16); -} -#endif /* defined(CPU_COLDFIRE) || (CONFIG_CPU == S3C2440) */ - -#if defined(CPU_COLDFIRE) || (CONFIG_CPU == S3C2440) || defined(CPU_PP) \ - || (CONFIG_CPU == IMX31L) || (CONFIG_CPU == DM320) -/* Implemented in target/... */ -#else -static int pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ - -unsigned short* p IBSS_ATTR; -size_t p_size IBSS_ATTR; - -void pcm_play_dma_start(const void *addr, size_t size) -{ - p = (unsigned short*)addr; - p_size = size; - - pcm_playing = true; -} - -void pcm_play_dma_stop(void) -{ - pcm_playing = false; - if (!audio_status()) - pcm_paused = false; -} - -void pcm_play_pause_pause(void) -{ -} - -void pcm_play_pause_unpause(void) -{ -} - -void pcm_postinit(void) -{ - audiohw_postinit(); -} - -void pcm_set_frequency(unsigned int frequency) -{ - (void)frequency; - pcm_freq = HW_SAMPR_DEFAULT; -} -size_t pcm_get_bytes_waiting(void) -{ - return p_size; -} - -/* - * This function goes directly into the DMA buffer to calculate the left and - * right peak values. To avoid missing peaks it tries to look forward two full - * peek periods (2/HZ sec, 100% overlap), although it's always possible that - * the entire period will not be visible. To reduce CPU load it only looks at - * every third sample, and this can be reduced even further if needed (even - * every tenth sample would still be pretty accurate). - */ - -/* Check for a peak every PEAK_STRIDE samples */ -#define PEAK_STRIDE 3 -/* Up to 1/50th of a second of audio for peak calculation */ -/* This should use NATIVE_FREQUENCY, or eventually an adjustable freq. value */ -#define PEAK_SAMPLES (44100/50) -void pcm_calculate_peaks(int *left, int *right) -{ - short *addr; - short *end; - { - size_t samples = p_size / 4; - addr = p; - - if (samples > PEAK_SAMPLES) - samples = PEAK_SAMPLES - (PEAK_STRIDE - 1); - else - samples -= MIN(PEAK_STRIDE - 1, samples); - - end = &addr[samples * 2]; - } - - if (left && right) { - int left_peak = 0, right_peak = 0; - - while (addr < end) { - int value; - if ((value = addr [0]) > left_peak) - left_peak = value; - else if (-value > left_peak) - left_peak = -value; - - if ((value = addr [PEAK_STRIDE | 1]) > right_peak) - right_peak = value; - else if (-value > right_peak) - right_peak = -value; - - addr = &addr[PEAK_STRIDE * 2]; - } - - *left = left_peak; - *right = right_peak; - } - else if (left || right) { - int peak_value = 0, value; - - if (right) - addr += (PEAK_STRIDE | 1); - - while (addr < end) { - if ((value = addr [0]) > peak_value) - peak_value = value; - else if (-value > peak_value) - peak_value = -value; - - addr += PEAK_STRIDE * 2; - } - - if (left) - *left = peak_value; - else - *right = peak_value; - } -} -#endif /* defined(CPU_COLDFIRE) || (CONFIG_CPU == S3C2440) || defined(CPU_PP) */ - -/**************************************************************************** - * Functions that do not require targeted implementation but only a targeted - * interface - */ - -/* Common code to pcm_play_data and pcm_play_pause - Returns true if DMA playback was started, else false. */ -bool pcm_play_data_start(pcm_more_callback_type get_more, - unsigned char *start, size_t size) -{ - if (!(start && size)) - { - size = 0; - if (get_more) - get_more(&start, &size); - } - - if (start && size) - { - pcm_play_dma_start(start, size); - return true; - } - - return false; -} - -void pcm_play_data(pcm_more_callback_type get_more, - unsigned char *start, size_t size) -{ - pcm_callback_for_more = get_more; - - if (pcm_play_data_start(get_more, start, size) && pcm_paused) - { - pcm_paused = false; - pcm_play_pause(false); - } -} - -void pcm_play_pause(bool play) -{ - bool needs_change = pcm_paused == play; - - /* This needs to be done ahead of the rest to prevent infinite - recursion from pcm_play_data */ - pcm_paused = !play; - - if (pcm_playing && needs_change) - { - if (play) - { - if (pcm_get_bytes_waiting()) - { - logf("unpause"); - pcm_play_pause_unpause(); - } - else - { - logf("unpause, no data waiting"); - if (!pcm_play_data_start(pcm_callback_for_more, NULL, 0)) - { - pcm_play_dma_stop(); - logf("unpause attempted, no data"); - } - } - } - else - { - logf("pause"); - pcm_play_pause_pause(); - } - } /* pcm_playing && needs_change */ -} - -void pcm_play_stop(void) -{ - if (pcm_playing) - pcm_play_dma_stop(); -} - -bool pcm_is_playing(void) -{ - return pcm_playing; -} - -bool pcm_is_paused(void) -{ - return pcm_paused; -} - diff --git a/firmware/pcm_record.c b/firmware/pcm_record.c index a5d0e51c30..ac12fe2ba0 100644 --- a/firmware/pcm_record.c +++ b/firmware/pcm_record.c @@ -34,25 +34,8 @@ /***************************************************************************/ -/** - * APIs implemented in the target tree portion: - * Public - - * pcm_init_recording - * pcm_close_recording - * Semi-private - - * pcm_rec_dma_start - * pcm_rec_dma_stop - */ - -/** These items may be implemented target specifically or need to - be shared semi-privately **/ extern struct thread_entry *codec_thread_p; -/* the registered callback function for when more data is available */ -volatile pcm_more_callback_type2 pcm_callback_more_ready = NULL; -/* DMA transfer in is currently active */ -volatile bool pcm_recording = false; - /** General recording state **/ static bool is_recording; /* We are recording */ static bool is_paused; /* We have paused */ @@ -1790,25 +1773,3 @@ size_t enc_unget_pcm_data(size_t size) return 0; } /* enc_unget_pcm_data */ - -/** Low level pcm recording apis **/ - -/**************************************************************************** - * Functions that do not require targeted implementation but only a targeted - * interface - */ -void pcm_record_data(pcm_more_callback_type2 more_ready, - void *start, size_t size) -{ - if (!(start && size)) - return; - - pcm_callback_more_ready = more_ready; - pcm_rec_dma_start(start, size); -} /* pcm_record_data */ - -void pcm_stop_recording(void) -{ - if (pcm_recording) - pcm_rec_dma_stop(); -} /* pcm_stop_recording */ diff --git a/firmware/sound.c b/firmware/sound.c index 67890f2731..dba732c904 100644 --- a/firmware/sound.c +++ b/firmware/sound.c @@ -31,7 +31,7 @@ #include "dac.h" #include "system.h" #if CONFIG_CODEC == SWCODEC -#include "pcm_playback.h" +#include "pcm.h" #endif #endif diff --git a/firmware/target/arm/crt0-pp.S b/firmware/target/arm/crt0-pp.S index 858f795aad..8fd1e31f09 100644 --- a/firmware/target/arm/crt0-pp.S +++ b/firmware/target/arm/crt0-pp.S @@ -243,10 +243,10 @@ cpu_init: msr cpsr_c, #0xd1 /* IRQ/FIQ disabled */ ldr sp, =fiq_stack /* We'll load the banked FIQ mode registers with useful values here. - These values will be used in the FIQ handler in pcm_playback.c */ - ldr r12, =IIS_CONFIG + These values will be used in the FIQ handler in pcm-pp.c */ + ldr r10, =IIS_CONFIG - ldr r11, =p + ldr r11, =dma_play_data /* Let abort and undefined modes use IRQ stack */ msr cpsr_c, #0xd7 /* IRQ/FIQ disabled */ @@ -328,7 +328,7 @@ vectors: .word data_abort_handler .word reserved_handler .word irq_handler - .word 0 /* fiq handler set in pcm driver */ + .word fiq_handler .text diff --git a/firmware/target/arm/i2s-pp.c b/firmware/target/arm/i2s-pp.c index e06ee835a6..24d901a4c1 100644 --- a/firmware/target/arm/i2s-pp.c +++ b/firmware/target/arm/i2s-pp.c @@ -47,99 +47,40 @@ void i2s_reset(void) } #else /* PP502X */ -/* All I2S formats send MSB first */ - -/* Data format on the I2S bus */ -#define FORMAT_MASK (0x3 << 10) -#define FORMAT_I2S (0x0 << 10) /* Standard I2S - leading dummy bit */ -#define FORMAT_1 (0x1 << 10) -#define FORMAT_LJUST (0x2 << 10) /* Left justified - no dummy bit */ -#define FORMAT_3 (0x3 << 10) -/* Other formats not yet known */ - -/* Data size on I2S bus */ -#define SIZE_MASK (0x3 << 8) -#define SIZE_16BIT (0x0 << 8) -/* Other sizes not yet known */ - -/* Data size/format on I2S FIFO */ -#define FIFO_FORMAT_MASK (0x7 << 4) -#define FIFO_FORMAT_0 (0x0 << 4) -/* Big-endian formats - data sent to the FIFO must be big endian. - * I forgot which is which size but did test them. */ -#define FIFO_FORMAT_1 (0x1 << 4) -#define FIFO_FORMAT_2 (0x2 << 4) - /* 32bit-MSB-little endian */ -#define FIFO_FORMAT_LE32 (0x3 << 4) - /* 16bit-MSB-little endian */ -#define FIFO_FORMAT_LE16 (0x4 << 4) - -/* FIFO formats 0x5 and above seem equivalent to 0x4 ?? */ - -/** - * PP502x - * - * IISCONFIG bits: - * | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | - * | RESET | |TXFIFOEN|RXFIFOEN| | ???? | MS | ???? | - * | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | - * | | | | | | | | | - * | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | - * | | | | | Bus Format[1:0] | Size[1:0] | - * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | - * | | Size Format[2:0] | ???? | ???? | IRQTX | IRQRX | - * - * IISFIFO_CFG bits: - * | 31 | 30 | 29 | 28 | 27 | 26 | 25 | 24 | - * | | Free[6:0] | - * | 23 | 22 | 21 | 20 | 19 | 18 | 17 | 16 | - * | | | | | | | | | - * | 15 | 14 | 13 | 12 | 11 | 10 | 9 | 8 | - * | | | | RXCLR | | | | TXCLR | - * | 7 | 6 | 5 | 4 | 3 | 2 | 1 | 0 | - * | | | RX_ATN_LEVEL | | | TX_ATN_LEVEL | - */ - -/* Are we I2S Master or slave? */ -#define I2S_MASTER (1<<25) - -#define I2S_RESET (0x1 << 31) - /* * Reset the I2S BIT.FORMAT I2S, 16bit, FIFO.FORMAT 32bit */ void i2s_reset(void) { /* I2S soft reset */ - IISCONFIG |= I2S_RESET; - IISCONFIG &= ~I2S_RESET; + IISCONFIG |= IIS_RESET; + IISCONFIG &= ~IIS_RESET; /* BIT.FORMAT */ - IISCONFIG = ((IISCONFIG & ~FORMAT_MASK) | FORMAT_I2S); + IISCONFIG = ((IISCONFIG & ~IIS_FORMAT_MASK) | IIS_FORMAT_IIS); /* BIT.SIZE */ - IISCONFIG = ((IISCONFIG & ~SIZE_MASK) | SIZE_16BIT); + IISCONFIG = ((IISCONFIG & ~IIS_SIZE_MASK) | IIS_SIZE_16BIT); /* FIFO.FORMAT */ /* If BIT.SIZE < FIFO.FORMAT low bits will be 0 */ #ifdef HAVE_AS3514 /* AS3514 can only operate as I2S Slave */ - IISCONFIG |= I2S_MASTER; + IISCONFIG |= IIS_MASTER; /* Set I2S to 44.1kHz */ outl((inl(0x70002808) & ~(0x1ff)) | 33, 0x70002808); outl(7, 0x60006080); - - IISCONFIG = ((IISCONFIG & ~FIFO_FORMAT_MASK) | FIFO_FORMAT_LE16); + IISCONFIG = ((IISCONFIG & ~IIS_FIFO_FORMAT_MASK) | IIS_FIFO_FORMAT_LE16); #else - IISCONFIG = ((IISCONFIG & ~FIFO_FORMAT_MASK) | FIFO_FORMAT_LE32); + IISCONFIG = ((IISCONFIG & ~IIS_FIFO_FORMAT_MASK) | IIS_FIFO_FORMAT_LE32); #endif - /* RX_ATN_LVL=1 == when 12 slots full */ - /* TX_ATN_LVL=1 == when 12 slots empty */ - IISFIFO_CFG |= 0x33; + /* RX_ATN_LVL = when 12 slots full */ + /* TX_ATN_LVL = when 12 slots empty */ + IISFIFO_CFG |= IIS_RX_FULL_LVL_12 | IIS_TX_EMPTY_LVL_12; /* Rx.CLR = 1, TX.CLR = 1 */ - IISFIFO_CFG |= 0x1100; + IISFIFO_CFG |= IIS_RXCLR | IIS_TXCLR; } #endif /* CONFIG_CPU == */ diff --git a/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c b/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c index 73bd9fef14..c29c4b2930 100644 --- a/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c +++ b/firmware/target/arm/imx31/gigabeat-s/pcm-imx31.c @@ -25,69 +25,145 @@ #include "file.h" #include "mmu-imx31.h" +static int pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ + void fiq_handler(void) __attribute__((naked)); +/* Implement separately on recording and playback - simply disable the + specific DMA interrupt. Disable the FIQ itself only temporarily to sync + with the DMA interrupt and restore its previous state. The pcm routines + will call the lockout first then call into these low-level routines. */ +struct dma_lock +{ + int locked; + unsigned long state; +}; + +static struct dma_play_lock = +{ + .locked = 0, + .state = 0, /* Initialize this as disabled */ +}; + +void pcm_play_lock(void) +{ + int status = set_fiq_status(FIQ_DISABLED); + if (++dma_play_lock.locked == 1) + ; /* Mask the DMA interrupt */ + set_fiq_status(status); +} + +void pcm_play_unlock(void) +{ + int status = set_fiq_status(FIQ_DISABLED); + if (--dma_play_lock.locked == 0) + ; /* Unmask the DMA interrupt if enabled */ + set_fiq_status(status); +} + static void _pcm_apply_settings(void) { + if (pcm_freq != pcm_curr_sampr) + { + pcm_curr_sampr = pcm_freq; + /* Change hardware sample rate */ + /* */ + } } void pcm_apply_settings(void) { + /* Lockout FIQ and sync the hardware to the settings */ + int oldstatus = set_fiq_status(FIQ_DISABLED); + _pcm_apply_settings(); + set_fiq_status(oldstatus); } -void pcm_init(void) +void pcm_play_dma_init(void) { + pcm_set_frequency(SAMPR_44); + +#if 0 + /* Do basic init enable output in pcm_postinit if popping is a + problem at boot to enable a lenghy delay and let the boot + process continue */ + audiohw_init(); +#endif } void pcm_postinit(void) { } +/* Connect the DMA and start filling the FIFO */ +static void play_start_pcm(void) +{ +#if 0 + /* unmask DMA interrupt when unlocking */ + dma_play_lock.state = 0; /* Set to allow pcm_play_unlock to unmask interrupt */ +#endif +} + +/* Disconnect the DMA and wait for the FIFO to clear */ +static void play_stop_pcm(void) +{ +#if 0 + /* Keep interrupt masked when unlocking */ + dma_play_lock.state = 0; /* Set to keep pcm_play_unlock from unmasking interrupt */ +#endif +} + void pcm_play_dma_start(const void *addr, size_t size) { (void)addr; (void)size; } -static void pcm_play_dma_stop_fiq(void) -{ -} - -void fiq_handler(void) -{ -} - -/* Disconnect the DMA and wait for the FIFO to clear */ void pcm_play_dma_stop(void) { + play_stop_pcm(); } -void pcm_play_pause_pause(void) +void pcm_play_dma_pause(bool pause) { + if (pause) + { + play_stop_pcm(); + } + else + { + play_start_pcm(); + } } -void pcm_play_pause_unpause(void) +/* Get more samples to play out - call pcm_play_dma_stop and + pcm_play_dma_stopped_callback if the data runs out */ +void fiq_handler(void) { +#if 0 + /* Callback missing or no more DMA to do */ + pcm_play_dma_stop(); + pcm_play_dma_stopped_callback(); +#endif } +/* Set the pcm frequency hardware will use when play is next started or + when pcm_apply_settings is called. Do not apply the setting to the + hardware here but simply cache it. */ void pcm_set_frequency(unsigned int frequency) { - (void)frequency; + pcm_freq = frequency; } +/* Return the number of bytes waiting - full L-R sample pairs only */ size_t pcm_get_bytes_waiting(void) { } -void pcm_mute(bool mute) +/* Return a pointer to the samples and the number of them in *count */ +const void * pcm_play_dma_get_peak_buffer(int *count) { + (void)count; } -/** - * 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) -{ -} +/* Any recording functionality should be implemented similarly */ diff --git a/firmware/target/arm/pcm-pp.c b/firmware/target/arm/pcm-pp.c index f38757ec6c..50e5b272aa 100644 --- a/firmware/target/arm/pcm-pp.c +++ b/firmware/target/arm/pcm-pp.c @@ -23,312 +23,64 @@ #include "audio.h" #include "sound.h" -/* peaks */ -#ifdef HAVE_RECORDING -static unsigned long *rec_peak_addr; -static int rec_peak_left, rec_peak_right; +/** DMA **/ + +#if defined(HAVE_AS3514) +/* E200 uses 16bit FIFO - all others should be able to as well - + i2s-pp.c has to set the right size as well */ +#define SAMPLE_SIZE 16 +#else +#define SAMPLE_SIZE 32 #endif -/** DMA **/ -#ifdef CPU_PP502x -#define FIFO_FREE_COUNT ((IISFIFO_CFG & 0x3f000000) >> 24) -#elif CONFIG_CPU == PP5002 -#define FIFO_FREE_COUNT ((IISFIFO_CFG & 0x7800000) >> 23) +struct dma_data +{ +/* NOTE: The order of size and p is important if you use assembler + optimised fiq handler, so don't change it. */ +#if SAMPLE_SIZE == 16 + uint32_t *p; +#elif SAMPLE_SIZE == 32 + uint16_t *p; #endif + size_t size; +#if NUM_CORES > 1 + unsigned core; +#endif + int locked; + int state; +} __attribute__((packed)); + +extern void *fiq_function; + +/* Dispatch to the proper handler and leave the main vector table alone */ +void fiq_handler(void) ICODE_ATTR __attribute__((naked)); +void fiq_handler(void) +{ + asm volatile ( + "ldr pc, [pc, #-4] \n" + "fiq_function: \n" + ".word 0 \n" + ); +} + +/* TODO: Get simultaneous recording and playback to work. Just needs some tweaking */ /**************************************************************************** ** Playback DMA transfer **/ -static int pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ - -/* NOTE: The order of these two variables is important if you use the iPod - assembler optimised fiq handler, so don't change it. */ -unsigned short* p IBSS_ATTR; -size_t p_size IBSS_ATTR; - -/* ASM optimised FIQ handler. GCC fails to make use of the fact that FIQ mode - has registers r8-r14 banked, and so does not need to be saved. This routine - uses only these registers, and so will never touch the stack unless it - actually needs to do so when calling pcm_callback_for_more. C version is - still included below for reference. - */ -#if 1 -void fiq(void) ICODE_ATTR __attribute__((naked)); -void fiq(void) +struct dma_data dma_play_data NOCACHEBSS_ATTR = { - /* r12 contains IISCONFIG address (set in crt0.S to minimise code in actual - * FIQ handler. r11 contains address of p (also set in crt0.S). Most other - * addresses we need are generated by using offsets with these two. - * r12 + 0x40 is IISFIFO_WR, and r12 + 0x0c is IISFIFO_CFG. - * r8 and r9 contains local copies of p_size and p respectively. - * r10 is a working register. - */ - asm volatile ( -#if CONFIG_CPU == PP5002 - "ldr r10, =0xcf001040 \n\t" /* Some magic from iPodLinux */ - "ldr r10, [r10] \n\t" - "ldr r10, [r12, #0x1c]\n\t" - "bic r10, r10, #0x200 \n\t" /* clear interrupt */ - "str r10, [r12, #0x1c]\n\t" -#else - "ldr r10, [r12] \n\t" - "bic r10, r10, #0x2 \n\t" /* clear interrupt */ - "str r10, [r12] \n\t" -#endif - "ldr r8, [r11, #4] \n\t" /* r8 = p_size */ - "ldr r9, [r11] \n\t" /* r9 = p */ - ".loop: \n\t" - "cmp r8, #0 \n\t" /* is p_size 0? */ - "beq .more_data \n\t" /* if so, ask pcmbuf for more data */ - ".fifo_loop: \n\t" -#if CONFIG_CPU == PP5002 - "ldr r10, [r12, #0x1c]\n\t" /* read IISFIFO_CFG to check FIFO status */ - "and r10, r10, #0x7800000\n\t" - "cmp r10, #0x800000 \n\t" -#else - "ldr r10, [r12, #0x0c]\n\t" /* read IISFIFO_CFG to check FIFO status */ - "and r10, r10, #0x3f0000\n\t" - "cmp r10, #0x10000 \n\t" -#endif - "bls .fifo_full \n\t" /* FIFO full, exit */ - "ldr r10, [r9], #4 \n\t" /* load two samples */ -#ifdef HAVE_AS3514 - "str r10, [r12, #0x40]\n\t" /* write them */ -#else - "mov r10, r10, ror #16\n\t" /* put left sample at the top bits */ - "str r10, [r12, #0x40]\n\t" /* write top sample, lower sample ignored */ - "mov r10, r10, lsl #16\n\t" /* shift lower sample up */ - "str r10, [r12, #0x40]\n\t" /* then write it */ -#endif - "subs r8, r8, #4 \n\t" /* check if we have more samples */ - "bne .fifo_loop \n\t" /* yes, continue */ - ".more_data: \n\t" - "stmdb sp!, { r0-r3, r12, lr}\n\t" /* stack scratch regs and lr */ - "mov r0, r11 \n\t" /* r0 = &p */ - "add r1, r11, #4 \n\t" /* r1 = &p_size */ - "str r9, [r0] \n\t" /* save internal copies of variables back */ - "str r8, [r1] \n\t" - "ldr r2, =pcm_callback_for_more\n\t" - "ldr r2, [r2] \n\t" /* get callback address */ - "cmp r2, #0 \n\t" /* check for null pointer */ - "movne lr, pc \n\t" /* call pcm_callback_for_more */ - "bxne r2 \n\t" - "ldmia sp!, { r0-r3, r12, lr}\n\t" - "ldr r8, [r11, #4] \n\t" /* reload p_size and p */ - "ldr r9, [r11] \n\t" - "cmp r8, #0 \n\t" /* did we actually get more data? */ - "bne .loop \n\t" /* yes, continue to try feeding FIFO */ - ".dma_stop: \n\t" /* no more data, do dma_stop() and exit */ - "ldr r10, =pcm_playing\n\t" - "strb r8, [r10] \n\t" /* pcm_playing = false (r8=0, look above) */ - "ldr r10, =pcm_paused \n\t" - "strb r8, [r10] \n\t" /* pcm_paused = false (r8=0, look above) */ - "ldr r10, [r12] \n\t" -#if CONFIG_CPU == PP5002 - "bic r10, r10, #0x4\n\t" /* disable playback FIFO */ - "str r10, [r12] \n\t" - "ldr r10, [r12, #0x1c] \n\t" - "bic r10, r10, #0x200 \n\t" /* clear interrupt */ - "str r10, [r12, #0x1c] \n\t" -#else - "bic r10, r10, #0x20000002\n\t" /* disable playback FIFO and IRQ */ - "str r10, [r12] \n\t" -#endif - "mrs r10, cpsr \n\t" - "orr r10, r10, #0x40 \n\t" /* disable FIQ */ - "msr cpsr_c, r10 \n\t" - ".exit: \n\t" - "str r8, [r11, #4] \n\t" - "str r9, [r11] \n\t" - "subs pc, lr, #4 \n\t" /* FIQ specific return sequence */ - ".fifo_full: \n\t" /* enable IRQ and exit */ -#if CONFIG_CPU == PP5002 - "ldr r10, [r12, #0x1c]\n\t" - "orr r10, r10, #0x200 \n\t" /* set interrupt */ - "str r10, [r12, #0x1c]\n\t" -#else - "ldr r10, [r12] \n\t" - "orr r10, r10, #0x2 \n\t" /* set interrupt */ - "str r10, [r12] \n\t" -#endif - "b .exit \n\t" - ); -} -#else /* C version for reference */ -void fiq(void) ICODE_ATTR __attribute__ ((interrupt ("FIQ"))); -void fiq(void) -{ - /* Clear interrupt */ -#ifdef CPU_PP502x - IISCONFIG &= ~(1 << 1); -#elif CONFIG_CPU == PP5002 - inl(0xcf001040); - IISFIFO_CFG &= ~(1<<9); + /* Initialize to a locked, stopped state */ + .p = NULL, + .size = 0, +#if NUM_CORES > 1 + .core = 0x00, #endif + .locked = 0, + .state = 0 +}; - do { - while (p_size) { - //if (FIFO_FREE_COUNT < 2) { - if (((IISFIFO_CFG & (0x1f << 16)) >> 16) < 2) { - /* Enable interrupt */ -#ifdef CPU_PP502x - IISCONFIG |= (1 << 1); -#elif CONFIG_CPU == PP5002 - IISFIFO_CFG |= (1<<9); -#endif - return; - } - -#ifdef HAVE_AS3514 - IISFIFO_WR = *(int32_t *)p; - p += 2; -#else - IISFIFO_WR = (*(p++))<<16; - IISFIFO_WR = (*(p++))<<16; -#endif - p_size-=4; - } - - /* p is empty, get some more data */ - if (pcm_callback_for_more) { - pcm_callback_for_more((unsigned char**)&p,&p_size); - } - } while (p_size); - - /* No more data, so disable the FIFO/FIQ */ - pcm_play_dma_stop(); -} -#endif /* ASM / C selection */ - -void pcm_play_dma_start(const void *addr, size_t size) -{ - p=(unsigned short*)addr; - p_size=size; - - pcm_playing = true; - -#ifdef CPU_PP502x - CPU_INT_PRIORITY |= I2S_MASK; /* FIQ priority for I2S */ - CPU_INT_EN = I2S_MASK; /* Enable I2S interrupt */ -#else - /* setup I2S interrupt for FIQ */ - outl(inl(0xcf00102c) | DMA_OUT_MASK, 0xcf00102c); - outl(DMA_OUT_MASK, 0xcf001024); -#endif - - /* Clear the FIQ disable bit in cpsr_c */ - set_fiq_handler(fiq); - enable_fiq(); - -#if defined(CPU_PP502x) - /* Enable playback FIFO */ - IISCONFIG |= (1 << 29); -#elif CONFIG_CPU == PP5002 - IISCONFIG |= 0x4; -#endif - - /* Fill the FIFO - we assume there are enough bytes in the pcm buffer to - fill the 32-byte FIFO. */ - while (p_size > 0) { - if (FIFO_FREE_COUNT < 2) { - /* Enable interrupt */ -#ifdef CPU_PP502x - IISCONFIG |= (1 << 1); -#elif CONFIG_CPU == PP5002 - IISFIFO_CFG |= (1<<9); -#endif - return; - } - -#ifdef HAVE_AS3514 - IISFIFO_WR = *(int32_t *)p; - p += 2; -#else - IISFIFO_WR = (*(p++))<<16; - IISFIFO_WR = (*(p++))<<16; -#endif - p_size-=4; - } -} - -/* Stops the DMA transfer and interrupt */ -void pcm_play_dma_stop(void) -{ - pcm_playing = false; - if (!audio_status()) - pcm_paused = false; - -#if CONFIG_CPU == PP5020 - /* Disable TX interrupt */ - IISCONFIG &= ~(1 << 1); -#elif defined(CPU_PP502x) - /* Disable playback FIFO and interrupt */ - IISCONFIG &= ~((1 << 29) | (1 << 1)); -#elif CONFIG_CPU == PP5002 - /* Disable playback FIFO */ - IISCONFIG &= ~0x4; - - /* Disable the interrupt */ - IISFIFO_CFG &= ~(1<<9); -#endif - - disable_fiq(); -} - -void pcm_play_pause_pause(void) -{ -#if CONFIG_CPU == PP5020 - /* Disable TX interrupt */ - IISCONFIG &= ~(1 << 1); -#elif defined(CPU_PP502x) - /* Disable playback FIFO and interrupt */ - IISCONFIG &= ~((1 << 29) | (1 << 1)); -#elif CONFIG_CPU == PP5002 - /* Disable the interrupt */ - IISFIFO_CFG &= ~(1<<9); - /* Disable playback FIFO */ - IISCONFIG &= ~0x4; -#endif - disable_fiq(); -} - -void pcm_play_pause_unpause(void) -{ - /* Enable the FIFO and fill it */ - - set_fiq_handler(fiq); - enable_fiq(); - -#if defined(CPU_PP502x) - /* Enable playback FIFO */ - IISCONFIG |= (1 << 29); -#elif CONFIG_CPU == PP5002 - IISCONFIG |= 0x4; -#endif - - /* Fill the FIFO - we assume there are enough bytes in the - pcm buffer to fill the 32-byte FIFO. */ - while (p_size > 0) { - if (FIFO_FREE_COUNT < 2) { - /* Enable interrupt */ -#ifdef CPU_PP502x - IISCONFIG |= (1 << 1); -#elif CONFIG_CPU == PP5002 - IISFIFO_CFG |= (1<<9); -#endif - return; - } - -#ifdef HAVE_AS3514 - IISFIFO_WR = *(int32_t *)p; - p += 2; -#else - IISFIFO_WR = (*(p++))<<16; - IISFIFO_WR = (*(p++))<<16; -#endif - p_size-=4; - } -} +static unsigned long pcm_freq NOCACHEDATA_ATTR = HW_SAMPR_DEFAULT; /* 44.1 is default */ void pcm_set_frequency(unsigned int frequency) { @@ -336,16 +88,221 @@ void pcm_set_frequency(unsigned int frequency) pcm_freq = HW_SAMPR_DEFAULT; } -size_t pcm_get_bytes_waiting(void) +void pcm_apply_settings(void) { - return p_size; + pcm_curr_sampr = pcm_freq; } -void pcm_init(void) +/* ASM optimised FIQ handler. GCC fails to make use of the fact that FIQ mode + has registers r8-r14 banked, and so does not need to be saved. This routine + uses only these registers, and so will never touch the stack unless it + actually needs to do so when calling pcm_callback_for_more. C version is + still included below for reference and testing. + */ +#if 1 +void fiq_playback(void) ICODE_ATTR __attribute__((naked)); +void fiq_playback(void) { - pcm_playing = false; - pcm_paused = false; - pcm_callback_for_more = NULL; + /* r10 contains IISCONFIG address (set in crt0.S to minimise code in actual + * FIQ handler. r11 contains address of p (also set in crt0.S). Most other + * addresses we need are generated by using offsets with these two. + * r10 + 0x40 is IISFIFO_WR, and r10 + 0x0c is IISFIFO_CFG. + * r8 and r9 contains local copies of p and size respectively. + * r12 is a working register. + */ + asm volatile ( +#if CONFIG_CPU == PP5002 + "ldr r12, =0xcf001040 \n" /* Some magic from iPodLinux */ + "ldr r12, [r12] \n" +#endif + "ldmia r11, { r8-r9 } \n" /* r8 = p, r9 = size */ + "cmp r9, #0 \n" /* is size 0? */ + "beq .more_data \n" /* if so, ask pcmbuf for more data */ + ".fifo_loop: \n" + "ldr r12, [r10, %[cfg]] \n" /* read IISFIFO_CFG to check FIFO status */ + "ands r12, r12, %[mask] \n" + "beq .exit \n" /* FIFO full, exit */ + "ldr r12, [r8], #4 \n" /* load two samples */ +#if SAMPLE_SIZE == 16 + "str r12, [r10, %[wr]] \n" /* write them */ +#elif SAMPLE_SIZE == 32 + "mov r12, r12, ror #16 \n" /* put left sample at the top bits */ + "str r12, [r10, %[wr]] \n" /* write top sample, lower sample ignored */ + "mov r12, r12, lsl #16 \n" /* shift lower sample up */ + "str r12, [r10, %[wr]] \n" /* then write it */ +#endif + "subs r9, r9, #4 \n" /* check if we have more samples */ + "bne .fifo_loop \n" /* yes, continue */ + ".more_data: \n" + "stmfd sp!, { r0-r3, lr } \n" /* stack scratch regs and lr */ + "ldr r2, =pcm_callback_for_more \n" + "ldr r2, [r2] \n" /* get callback address */ + "cmp r2, #0 \n" /* check for null pointer */ + "stmneia r11, { r8-r9 } \n" /* save internal copies of variables back */ + "movne r0, r11 \n" /* r0 = &p */ + "addne r1, r11, #4 \n" /* r1 = &size */ + "movne lr, pc \n" /* call pcm_callback_for_more */ + "bxne r2 \n" + "ldmia r11, { r8-r9 } \n" /* reload p and size */ + "cmp r9, #0 \n" /* did we actually get more data? */ + "ldmnefd sp!, { r0-r3, lr } \n" + "bne .fifo_loop \n" /* yes, continue to try feeding FIFO */ + "ldr r12, =pcm_play_dma_stop \n" + "mov lr, pc \n" + "bx r12 \n" + "ldr r12, =pcm_play_dma_stopped_callback \n" + "mov lr, pc \n" + "bx r12 \n" + "ldmfd sp!, { r0-r3, lr } \n" + ".exit: \n" /* (r8=0 if stopping, look above) */ + "stmia r11, { r8-r9 } \n" /* save p and size */ + "subs pc, lr, #4 \n" /* FIQ specific return sequence */ + ".ltorg \n" + : /* These must only be integers! No regs */ + : [mask]"i"(IIS_TX_FREE_MASK & (IIS_TX_FREE_MASK-1)), + [cfg]"i"((int)&IISFIFO_CFG - (int)&IISCONFIG), + [wr]"i"((int)&IISFIFO_WR - (int)&IISCONFIG) + ); +} +#else /* C version for reference */ +void fiq_playback(void) __attribute__((interrupt ("FIQ"))) ICODE_ATTR; +/* NOTE: direct stack use forbidden by GCC stack handling bug for FIQ */ +void fiq_playback(void) +{ + register pcm_more_callback_type get_more; + +#if CONFIG_CPU == PP5002 + inl(0xcf001040); +#endif + + do { + while (dma_play_data.size > 0) { + if (IIS_TX_FREE_COUNT < 2) { + return; + } +#if SAMPLE_SIZE == 16 + IISFIFO_WR = *dma_play_data.p++; +#elif SAMPLE_SIZE == 32 + IISFIFO_WR = *dma_play_data.p++ << 16; + IISFIFO_WR = *dma_play_data.p++ << 16; +#endif + dma_play_data.size -= 4; + } + + /* p is empty, get some more data */ + get_more = pcm_callback_for_more; + if (get_more) { + get_more((unsigned char**)&dma_play_data.p, + &dma_play_data.size); + } + } while (dma_play_data.size); + + /* No more data, so disable the FIFO/interrupt */ + pcm_play_dma_stop(); + pcm_play_dma_stopped_callback(); +} +#endif /* ASM / C selection */ + +/* For the locks, FIQ must be disabled because the handler manipulates + IISCONFIG and the operation is not atomic - dual core support + will require other measures */ +void pcm_play_lock(void) +{ + int status = set_fiq_status(FIQ_DISABLED); + + if (++dma_play_data.locked == 1) { + IIS_IRQTX_REG &= ~IIS_IRQTX; + } + + set_fiq_status(status); +} + +void pcm_play_unlock(void) +{ + int status = set_fiq_status(FIQ_DISABLED); + + if (--dma_play_data.locked == 0 && dma_play_data.state != 0) { + IIS_IRQTX_REG |= IIS_IRQTX; + } + + set_fiq_status(status); +} + +static void play_start_pcm(void) +{ + fiq_function = fiq_playback; + pcm_apply_settings(); + + IISCONFIG &= ~IIS_TXFIFOEN; /* Stop transmitting */ + dma_play_data.state = 1; + + /* Fill the FIFO or start when data is used up */ + while (1) { + if (IIS_TX_FREE_COUNT < 2 || dma_play_data.size == 0) { + IISCONFIG |= IIS_TXFIFOEN; /* Start transmitting */ + return; + } + +#if SAMPLE_SIZE == 16 + IISFIFO_WR = *dma_play_data.p++; +#elif SAMPLE_SIZE == 32 + IISFIFO_WR = *dma_play_data.p++ << 16; + IISFIFO_WR = *dma_play_data.p++ << 16; +#endif + dma_play_data.size -= 4; + } +} + +static void play_stop_pcm(void) +{ + /* Disable TX interrupt */ + IIS_IRQTX_REG &= ~IIS_IRQTX; + dma_play_data.state = 0; +} + +void pcm_play_dma_start(const void *addr, size_t size) +{ + dma_play_data.p = (void *)addr; + dma_play_data.size = size; + +#if NUM_CORES > 1 + /* This will become more important later - and different ! */ + dma_play_data.core = processor_id(); /* save initiating core */ +#endif + + CPU_INT_PRIORITY |= IIS_MASK; /* FIQ priority for I2S */ + CPU_INT_EN = IIS_MASK; + + play_start_pcm(); +} + +/* Stops the DMA transfer and interrupt */ +void pcm_play_dma_stop(void) +{ + play_stop_pcm(); + dma_play_data.size = 0; +#if NUM_CORES > 1 + dma_play_data.core = 0; /* no core in control */ +#endif +} + +void pcm_play_dma_pause(bool pause) +{ + if (pause) { + play_stop_pcm(); + } else { + play_start_pcm(); + } +} + +size_t pcm_get_bytes_waiting(void) +{ + return dma_play_data.size & ~3; +} + +void pcm_play_dma_init(void) +{ + pcm_set_frequency(SAMPR_44); /* Initialize default register values. */ audiohw_init(); @@ -357,91 +314,129 @@ void pcm_init(void) audiohw_mute(false); #endif - /* Call pcm_play_dma_stop to initialize everything. */ - pcm_play_dma_stop(); - -#if CONFIG_CPU == PP5020 - /* This processor doesn't like this disabled */ - IISCONFIG |= (1 << 29); + dma_play_data.size = 0; +#if NUM_CORES > 1 + dma_play_data.core = 0; /* no core in control */ #endif + + IISCONFIG |= IIS_TXFIFOEN; } void pcm_postinit(void) { audiohw_postinit(); + pcm_apply_settings(); +} + +const void * pcm_play_dma_get_peak_buffer(int *count) +{ + unsigned long addr = (unsigned long)dma_play_data.p; + size_t cnt = dma_play_data.size; + *count = cnt >> 2; + return (void *)((addr + 2) & ~3); } /**************************************************************************** ** Recording DMA transfer **/ #ifdef HAVE_RECORDING +/* PCM recording interrupt routine lockout */ +static struct dma_data dma_rec_data NOCACHEBSS_ATTR = +{ + /* Initialize to a locked, stopped state */ + .p = NULL, + .size = 0, +#if NUM_CORES > 1 + .core = 0x00, +#endif + .locked = 0, + .state = 0 +}; -#ifdef HAVE_AS3514 -void fiq_record(void) ICODE_ATTR __attribute__((naked)); +/* For the locks, FIQ must be disabled because the handler manipulates + IISCONFIG and the operation is not atomic - dual core support + will require other measures */ +void pcm_rec_lock(void) +{ + int status = set_fiq_status(FIQ_DISABLED); + + if (++dma_rec_data.locked == 1) + IIS_IRQRX_REG &= ~IIS_IRQRX; + + set_fiq_status(status); +} + +void pcm_rec_unlock(void) +{ + int status = set_fiq_status(FIQ_DISABLED); + + if (--dma_rec_data.locked == 0 && dma_rec_data.state != 0) + IIS_IRQRX_REG |= IIS_IRQRX; + + set_fiq_status(status); +} + +/* NOTE: direct stack use forbidden by GCC stack handling bug for FIQ */ +void fiq_record(void) ICODE_ATTR __attribute__((interrupt ("FIQ"))); + +#if defined(SANSA_C200) || defined(SANSA_E200) void fiq_record(void) { register pcm_more_callback_type2 more_ready; - register int32_t value1, value2; - - asm volatile ("stmfd sp!, {r0-r7, ip, lr} \n"); /* Store context */ - - IISCONFIG &= ~(1 << 0); + register int32_t value; if (audio_channels == 2) { /* RX is stereo */ - while (p_size > 0) { - if (FIFO_FREE_COUNT < 2) { - /* enable interrupt */ - IISCONFIG |= (1 << 0); - goto fiq_record_exit; + while (dma_rec_data.size > 0) { + if (IIS_RX_FULL_COUNT < 2) { + return; } /* Discard every other sample since ADC clock is 1/2 LRCK */ - value1 = IISFIFO_RD; - value2 = IISFIFO_RD; + value = IISFIFO_RD; + IISFIFO_RD; - *(int32_t *)p = value1; - p += 2; - p_size -= 4; + *dma_rec_data.p++ = value; + dma_rec_data.size -= 4; /* TODO: Figure out how to do IIS loopback */ if (audio_output_source != AUDIO_SRC_PLAYBACK) { - if ((IISFIFO_CFG & (0x3f << 16)) >= (16 << 16)) { + if (IIS_TX_FREE_COUNT >= 16) { /* Resync the output FIFO - it ran dry */ IISFIFO_WR = 0; IISFIFO_WR = 0; } - IISFIFO_WR = value1; - IISFIFO_WR = value1; + IISFIFO_WR = value; + IISFIFO_WR = value; } } } else { /* RX is left channel mono */ - while (p_size > 0) { - if (FIFO_FREE_COUNT < 2) { - /* enable interrupt */ - IISCONFIG |= (1 << 0); - goto fiq_record_exit; + while (dma_rec_data.size > 0) { + if (IIS_RX_FULL_COUNT < 2) { + return; } /* Discard every other sample since ADC clock is 1/2 LRCK */ - value1 = IISFIFO_RD; - value2 = IISFIFO_RD; - *p++ = value1; - *p++ = value1; - p_size -= 4; + value = IISFIFO_RD; + IISFIFO_RD; + + value = (uint16_t)value | (value << 16); + + *dma_rec_data.p++ = value; + dma_rec_data.size -= 4; if (audio_output_source != AUDIO_SRC_PLAYBACK) { - if ((IISFIFO_CFG & (0x3f << 16)) >= (16 << 16)) { + if (IIS_TX_FREE_COUNT >= 16) { /* Resync the output FIFO - it ran dry */ IISFIFO_WR = 0; IISFIFO_WR = 0; } - value1 = *((int32_t *)p - 1); - IISFIFO_WR = value1; - IISFIFO_WR = value1; + value = *((int32_t *)dma_rec_data.p - 1); + IISFIFO_WR = value; + IISFIFO_WR = value; } } } @@ -451,281 +446,112 @@ void fiq_record(void) if (more_ready == NULL || more_ready(0) < 0) { /* Finished recording */ pcm_rec_dma_stop(); + pcm_rec_dma_stopped_callback(); } - -fiq_record_exit: - asm volatile("ldmfd sp!, {r0-r7, ip, lr} \n" /* Restore context */ - "subs pc, lr, #4 \n"); /* Return from FIQ */ } #else -static short peak_l, peak_r IBSS_ATTR; - -/* Temporary to stop playback crashing after record */ -void fiq_record(void) ICODE_ATTR __attribute__((naked)); void fiq_record(void) { - asm volatile ("stmfd sp!, {r0-r7, r11, ip, lr} \n"); /* Store context */ - - register short value; register pcm_more_callback_type2 more_ready; - register int status = 0; - /* Clear interrupt */ -#ifdef CPU_PP502x - IISCONFIG &= ~(1 << 0); -#elif CONFIG_CPU == PP5002 - /* TODO */ -#endif - - while (p_size > 0) { - if (FIFO_FREE_COUNT < 2) { - /* enable interrupt */ -#ifdef CPU_PP502x - IISCONFIG |= (1 << 0); -#elif CONFIG_CPU == PP5002 - /* TODO */ -#endif - goto fiq_record_exit; + while (dma_rec_data.size > 0) { + if (IIS_RX_FULL_COUNT < 2) { + return; } - value = (unsigned short)(IISFIFO_RD >> 16); - if (value > peak_l) peak_l = value; - else if (-value > peak_l) peak_l = -value; - *(p++) = value; - - value = (unsigned short)(IISFIFO_RD >> 16); - if (value > peak_r) peak_r = value; - else if (-value > peak_r) peak_r = -value; - *(p++) = value; - - p_size -= 4; - - /* If we have filled the current chunk, start a new one */ - if (p_size == 0) { - rec_peak_left = peak_l; - rec_peak_right = peak_r; - peak_l = peak_r = 0; - } +#if SAMPLE_SIZE == 16 + *dma_rec_data.p++ = IISFIFO_RD; +#elif SAMPLE_SIZE == 32 + *dma_rec_data.p++ = IISFIFO_RD >> 16; + *dma_rec_data.p++ = IISFIFO_RD >> 16; +#endif + dma_rec_data.size -= 4; } more_ready = pcm_callback_more_ready; - if (more_ready != NULL && more_ready(status) >= 0) - goto fiq_record_exit; - - /* Finished recording */ - pcm_rec_dma_stop(); - -fiq_record_exit: - asm volatile("ldmfd sp!, {r0-r7, r11, ip, lr} \n" /* Restore context */ - "subs pc, lr, #4 \n"); /* Return from FIQ */ + if (more_ready == NULL || more_ready(0) < 0) { + /* Finished recording */ + pcm_rec_dma_stop(); + pcm_rec_dma_stopped_callback(); + } } -#endif /* HAVE_AS3514 */ +#endif /* SANSA_E200 */ /* Continue transferring data in */ void pcm_record_more(void *start, size_t size) { - rec_peak_addr = start; /* Start peaking at dest */ - p = start; /* Start of RX buffer */ - p_size = size; /* Bytes to transfer */ -#ifdef CPU_PP502x - IISCONFIG |= (1 << 0); -#elif CONFIG_CPU == PP5002 - /* TODO */ -#endif + pcm_rec_peak_addr = start; /* Start peaking at dest */ + dma_rec_data.p = start; /* Start of RX buffer */ + dma_rec_data.size = size; /* Bytes to transfer */ } void pcm_rec_dma_stop(void) { - logf("pcm_rec_dma_stop"); + /* disable interrupt */ + IIS_IRQRX_REG &= ~IIS_IRQRX; - disable_fiq(); + dma_rec_data.state = 0; + dma_rec_data.size = 0; +#if NUM_CORES > 1 + dma_rec_data.core = 0x00; +#endif - /* clear interrupt, disable fifo */ - IISCONFIG &= ~((1 << 28) | (1 << 0)); - - /* clear rx fifo */ - IISFIFO_CFG |= (1 << 12); - - pcm_recording = false; + /* disable fifo */ + IISCONFIG &= ~IIS_RXFIFOEN; + IISFIFO_CFG |= IIS_RXCLR; } void pcm_rec_dma_start(void *addr, size_t size) { - logf("pcm_rec_dma_start"); + pcm_rec_dma_stop(); - pcm_recording = true; - -#ifndef HAVE_AS3514 - peak_l = peak_r = 0; + pcm_rec_peak_addr = addr; + dma_rec_data.p = addr; + dma_rec_data.size = size; +#if NUM_CORES > 1 + /* This will become more important later - and different ! */ + dma_rec_data.core = processor_id(); /* save initiating core */ #endif + /* setup FIQ handler */ + fiq_function = fiq_record; - p_size = size; - p = addr; + /* interrupt on full fifo, enable record fifo interrupt */ + dma_rec_data.state = 1; - /* setup FIQ */ - CPU_INT_PRIORITY |= I2S_MASK; - CPU_INT_EN = I2S_MASK; + /* enable RX FIFO */ + IISCONFIG |= IIS_RXFIFOEN; - /* interrupt on full fifo, enable record fifo */ - IISCONFIG |= (1 << 28) | (1 << 0); - - set_fiq_handler(fiq_record); - enable_fiq(); + /* enable IIS interrupt as FIQ */ + CPU_INT_PRIORITY |= IIS_MASK; + CPU_INT_EN = IIS_MASK; } -void pcm_close_recording(void) +void pcm_rec_dma_close(void) { - logf("pcm_close_recording"); pcm_rec_dma_stop(); } /* pcm_close_recording */ -void pcm_init_recording(void) +void pcm_rec_dma_init(void) { - logf("pcm_init_recording"); - - pcm_recording = false; - pcm_callback_more_ready = NULL; - -#ifdef CPU_PP502x #if defined(IPOD_COLOR) || defined (IPOD_4G) /* The usual magic from IPL - I'm guessing this configures the headphone socket to be input or output - in this case, input. */ GPIOI_OUTPUT_VAL &= ~0x40; GPIOA_OUTPUT_VAL &= ~0x4; #endif - /* Setup the recording FIQ handler */ - set_fiq_handler(fiq_record); -#endif pcm_rec_dma_stop(); } /* pcm_init */ -void pcm_calculate_rec_peaks(int *left, int *right) +const void * pcm_rec_dma_get_peak_buffer(int *count) { -#ifdef HAVE_AS3514 - if (pcm_recording) - { - unsigned long *start = rec_peak_addr; - unsigned long *end = (unsigned long *)p; + unsigned long addr = (unsigned long)pcm_rec_peak_addr; + unsigned long end = (unsigned long)dma_rec_data.p; + *count = (end >> 2) - (addr >> 2); + return (void *)(addr & ~3); +} /* pcm_rec_dma_get_peak_buffer */ - if (start < end) - { - unsigned long *addr = start; - long peak_l = 0, peak_r = 0; - long peaksq_l = 0, peaksq_r = 0; - - do - { - long value = *addr; - long ch, chsq; - - ch = (int16_t)value; - chsq = ch*ch; - if (chsq > peaksq_l) - peak_l = ch, peaksq_l = chsq; - - ch = value >> 16; - chsq = ch*ch; - if (chsq > peaksq_r) - peak_r = ch, peaksq_r = chsq; - - addr += 4; - } - while (addr < end); - - if (start == rec_peak_addr) - rec_peak_addr = end; - - rec_peak_left = abs(peak_l); - rec_peak_right = abs(peak_r); - } - } - else - { - rec_peak_left = rec_peak_right = 0; - } -#endif /* HAVE_AS3514 */ - - if (left) - *left = rec_peak_left; - - if (right) - *right = rec_peak_right; -} #endif /* HAVE_RECORDING */ - -/* - * This function goes directly into the DMA buffer to calculate the left and - * right peak values. To avoid missing peaks it tries to look forward two full - * peek periods (2/HZ sec, 100% overlap), although it's always possible that - * the entire period will not be visible. To reduce CPU load it only looks at - * every third sample, and this can be reduced even further if needed (even - * every tenth sample would still be pretty accurate). - */ - -/* Check for a peak every PEAK_STRIDE samples */ -#define PEAK_STRIDE 3 -/* Up to 1/50th of a second of audio for peak calculation */ -/* This should use NATIVE_FREQUENCY, or eventually an adjustable freq. value */ -#define PEAK_SAMPLES (44100/50) -void pcm_calculate_peaks(int *left, int *right) -{ - short *addr; - short *end; - { - size_t samples = p_size / 4; - addr = p; - - if (samples > PEAK_SAMPLES) - samples = PEAK_SAMPLES - (PEAK_STRIDE - 1); - else - samples -= MIN(PEAK_STRIDE - 1, samples); - - end = &addr[samples * 2]; - } - - if (left && right) { - int left_peak = 0, right_peak = 0; - - while (addr < end) { - int value; - if ((value = addr [0]) > left_peak) - left_peak = value; - else if (-value > left_peak) - left_peak = -value; - - if ((value = addr [PEAK_STRIDE | 1]) > right_peak) - right_peak = value; - else if (-value > right_peak) - right_peak = -value; - - addr = &addr[PEAK_STRIDE * 2]; - } - - *left = left_peak; - *right = right_peak; - } - else if (left || right) { - int peak_value = 0, value; - - if (right) - addr += (PEAK_STRIDE | 1); - - while (addr < end) { - if ((value = addr [0]) > peak_value) - peak_value = value; - else if (-value > peak_value) - peak_value = -value; - - addr += PEAK_STRIDE * 2; - } - - if (left) - *left = peak_value; - else - *right = peak_value; - } -} diff --git a/firmware/target/arm/pnx0101/pcm-pnx0101.c b/firmware/target/arm/pnx0101/pcm-pnx0101.c index 8b076cc918..adfc752e8e 100644 --- a/firmware/target/arm/pnx0101/pcm-pnx0101.c +++ b/firmware/target/arm/pnx0101/pcm-pnx0101.c @@ -25,9 +25,36 @@ short __attribute__((section(".dmabuf"))) dma_buf_left[DMA_BUF_SAMPLES]; short __attribute__((section(".dmabuf"))) dma_buf_right[DMA_BUF_SAMPLES]; -/* From pcm_playback.c */ -extern unsigned short* p; -extern size_t p_size; +static int pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ + +unsigned short* p IBSS_ATTR; +size_t p_size IBSS_ATTR; + +void pcm_play_lock(void) +{ +} + +void pcm_play_unlock(void) +{ +} + +void pcm_play_dma_start(const void *addr, size_t size) +{ + pcm_apply_settings(); + + p = (unsigned short*)addr; + p_size = size; +} + +void pcm_play_dma_stop(void) +{ +} + +void pcm_play_dma_pause(bool pause) +{ + if (!pause) + pcm_apply_settings(); +} static inline void fill_dma_buf(int offset) { @@ -85,7 +112,8 @@ static inline void fill_dma_buf(int offset) &p_size); } while (p_size); - pcm_playing = false; + + pcm_play_dma_stopped_callback(); } if (l < lend) @@ -117,9 +145,7 @@ void pcm_init(void) { int i; - pcm_playing = false; - pcm_paused = false; - pcm_callback_for_more = NULL; + pcm_set_frequency(HW_SAMPR_DEFAULT); memset(dma_buf_left, 0, sizeof(dma_buf_left)); memset(dma_buf_right, 0, sizeof(dma_buf_right)); @@ -159,3 +185,32 @@ void pcm_init(void) DMAR10(1) |= 1; } +void pcm_postinit(void) +{ + audiohw_postinit(); + pcm_apply_settings(); +} + +void pcm_set_frequency(unsigned int frequency) +{ + (void)frequency; + pcm_freq = HW_SAMPR_DEFAULT; +} + +void pcm_apply_settings(void) +{ + pcm_curr_sampr = pcm_freq; +} + +size_t pcm_get_bytes_waiting(void) +{ + return p_size & ~3; +} + +const void * pcm_play_dma_get_peak_buffer(int *count) +{ + unsigned long addr = (unsigned long)p; + size_t cnt = p_size; + *count = cnt >> 2; + return (void *)((addr + 2) & ~3); +} diff --git a/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c b/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c index 57873faaff..a38b4e424e 100644 --- a/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c +++ b/firmware/target/arm/s3c2440/gigabeat-fx/pcm-meg-fx.c @@ -25,31 +25,42 @@ #include "file.h" #include "mmu-meg-fx.h" +/* All exact rates for 16.9344MHz clock */ #define GIGABEAT_11025HZ (0x19 << 1) #define GIGABEAT_22050HZ (0x1b << 1) #define GIGABEAT_44100HZ (0x11 << 1) #define GIGABEAT_88200HZ (0x1f << 1) -static int pcm_freq = 0; /* 44.1 is default */ +/* PCM interrupt routine lockout */ +static struct +{ + int locked; + unsigned long state; +} dma_play_lock = +{ + .locked = 0, + .state = (0<<19) +}; + +/* Last samplerate set by pcm_set_frequency */ +static unsigned long pcm_freq = 0; /* 44.1 is default */ +/* Samplerate control for audio codec */ static int sr_ctrl = 0; -#define FIFO_COUNT ((IISFCON >> 6) & 0x01F) + +#define FIFO_COUNT ((IISFCON >> 6) & 0x3F) /* Setup for the DMA controller */ #define DMA_CONTROL_SETUP ((1<<31) | (1<<29) | (1<<23) | (1<<22) | (1<<20)) /* DMA count has hit zero - no more data */ /* Get more data from the callback and top off the FIFO */ -/* Uses explicitly coded prologue/epilogue code to get around complier bugs - in order to be able to use the stack */ -void fiq_handler(void) __attribute__((naked)); +void fiq_handler(void) __attribute__((interrupt ("FIQ"))); static void _pcm_apply_settings(void) { - static int last_freqency = 0; - - if (pcm_freq != last_freqency) + if (pcm_freq != pcm_curr_sampr) { - last_freqency = pcm_freq; + pcm_curr_sampr = pcm_freq; audiohw_set_frequency(sr_ctrl); } } @@ -61,29 +72,50 @@ void pcm_apply_settings(void) set_fiq_status(oldstatus); } -void pcm_init(void) +/* For the locks, DMA interrupt must be disabled because the handler + manipulates INTMSK and the operation is not atomic */ +void pcm_play_lock(void) { - pcm_playing = false; - pcm_paused = false; - pcm_callback_for_more = NULL; + int status = set_fiq_status(FIQ_DISABLED); + if (++dma_play_lock.locked == 1) + INTMSK |= (1<<19); /* Mask the DMA interrupt */ + set_fiq_status(status); +} +void pcm_play_unlock(void) +{ + int status = set_fiq_status(FIQ_DISABLED); + if (--dma_play_lock.locked == 0) + INTMSK &= ~dma_play_lock.state; /* Unmask the DMA interrupt if enabled */ + set_fiq_status(status); +} + +void pcm_play_dma_init(void) +{ pcm_set_frequency(SAMPR_44); /* slave */ IISMOD |= (1<<8); + /* RX,TX off,idle */ + IISCON |= (1<<3) | (1<<2); + audiohw_init(); /* init GPIO */ GPCCON = (GPCCON & ~(3<<14)) | (1<<14); - GPCDAT |= 1<<7; - GPECON |= 0x2aa; + GPCDAT |= (1<<7); + /* GPE4=I2SDO, GPE3=I2SDI, GPE2=CDCLK, GPE1=I2SSCLK, GPE0=I2SLRCK */ + GPECON = (GPECON & ~0x3ff) | 0x2aa; /* Do not service DMA requests, yet */ + /* clear any pending int and mask it */ - INTMSK |= (1<<19); /* mask the interrupt */ - SRCPND = (1<<19); /* clear any pending interrupts */ - INTMOD |= (1<<19); /* connect to FIQ */ + INTMSK |= (1<<19); + SRCPND = (1<<19); + + /* connect to FIQ */ + INTMOD |= (1<<19); } void pcm_postinit(void) @@ -92,56 +124,22 @@ void pcm_postinit(void) pcm_apply_settings(); } -void pcm_play_dma_start(const void *addr, size_t size) +/* Connect the DMA and start filling the FIFO */ +static void play_start_pcm(void) { - addr = (void *)((unsigned long)addr & ~3); /* Align data */ - size &= ~3; /* Size must be multiple of 4 */ - - /* sanity check: bad pointer or too small file */ - if (NULL == addr || size == 0) return; - - disable_fiq(); - - /* Enable the IIS clock */ - CLKCON |= (1<<17); - - /* IIS interface setup and set to idle */ - IISCON = (1<<5) | (1<<3); - - /* slave, transmit mode, 16 bit samples - MCLK 384fs - use 16.9344Mhz - - BCLK 32fs */ - IISMOD = (1<<9) | (1<<8) | (2<<6) | (1<<3) | (1<<2) | (1<<0); - - /* connect DMA to the FIFO and enable the FIFO */ - IISFCON = (1<<15) | (1<<13); - - /* set DMA dest */ - DIDST2 = (int)&IISFIFO; - - /* IIS is on the APB bus, INT when TC reaches 0, fixed dest addr */ - DIDSTC2 = 0x03; - - /* How many transfers to make - we transfer half-word at a time = 2 bytes */ - /* DMA control: CURR_TC int, single service mode, I2SSDO int, HW trig */ - /* no auto-reload, half-word (16bit) */ - DCON2 = DMA_CONTROL_SETUP | (size / 2); - - /* set DMA source and options */ - DISRC2 = (unsigned long)addr + 0x30000000; - DISRCC2 = 0x00; /* memory is on AHB bus, increment addresses */ - /* clear pending DMA interrupt */ - SRCPND = 1<<19; - - pcm_playing = true; + SRCPND = (1<<19); _pcm_apply_settings(); - /* unmask the DMA interrupt */ - INTMSK &= ~(1<<19); - /* Flush any pending writes */ - clean_dcache_range(addr, size); + clean_dcache_range((void*)DISRC2, (DCON2 & 0xFFFFF) * 2); + + /* unmask DMA interrupt when unlocking */ + dma_play_lock.state = (1<<19); + + /* turn on the request */ + IISCON |= (1<<5); /* Activate the channel */ DMASKTRIG2 = 0x2; @@ -151,120 +149,127 @@ void pcm_play_dma_start(const void *addr, size_t size) /* start the IIS */ IISCON |= (1<<0); - - enable_fiq(); } -static void pcm_play_dma_stop_fiq(void) +/* Disconnect the DMA and wait for the FIFO to clear */ +static void play_stop_pcm(void) { - INTMSK |= (1<<19); /* mask the DMA interrupt */ - IISCON &= ~(1<<5); /* disable fifo request */ - DMASKTRIG2 = 0x4; /* De-Activate the DMA channel */ + /* Mask DMA interrupt */ + INTMSK |= (1<<19); + + /* De-Activate the DMA channel */ + DMASKTRIG2 = 0x4; /* are we playing? wait for the chunk to finish */ - if (pcm_playing) + if (dma_play_lock.state != 0) { - /* wait for the FIFO to empty before turning things off */ - while (IISCON & (1<<7)) ; - - pcm_playing = false; - if (!audio_status()) - pcm_paused = false; + /* wait for the FIFO to empty and DMA to stop */ + while ((IISCON & (1<<7)) || (DMASKTRIG2 & 0x2)); } + /* Keep interrupt masked when unlocking */ + dma_play_lock.state = 0; + + /* turn off the request */ + IISCON &= ~(1<<5); + + /* turn on the idle */ + IISCON |= (1<<3); + + /* stop the IIS */ + IISCON &= ~(1<<0); +} + +void pcm_play_dma_start(const void *addr, size_t size) +{ + /* Enable the IIS clock */ + CLKCON |= (1<<17); + + /* stop any DMA in progress - idle IIS */ + play_stop_pcm(); + + /* slave, transmit mode, 16 bit samples - MCLK 384fs - use 16.9344Mhz - + BCLK 32fs */ + IISMOD = (1<<9) | (1<<8) | (2<<6) | (1<<3) | (1<<2) | (1<<0); + + /* connect DMA to the FIFO and enable the FIFO */ + IISFCON = (1<<15) | (1<<13); + + /* set DMA dest */ + DIDST2 = (unsigned int)&IISFIFO; + + /* IIS is on the APB bus, INT when TC reaches 0, fixed dest addr */ + DIDSTC2 = 0x03; + + /* set DMA source and options */ + DISRC2 = (unsigned int)addr + 0x30000000; + /* How many transfers to make - we transfer half-word at a time = 2 bytes */ + /* DMA control: CURR_TC int, single service mode, I2SSDO int, HW trig */ + /* no auto-reload, half-word (16bit) */ + DCON2 = DMA_CONTROL_SETUP | (size / 2); + DISRCC2 = 0x00; /* memory is on AHB bus, increment addresses */ + + play_start_pcm(); +} + +/* Promptly stop DMA transfers and stop IIS */ +void pcm_play_dma_stop(void) +{ + play_stop_pcm(); + /* Disconnect the IIS clock */ CLKCON &= ~(1<<17); } +void pcm_play_dma_pause(bool pause) +{ + if (pause) + { + /* pause playback on current buffer */ + play_stop_pcm(); + } + else + { + /* restart playback on current buffer */ + /* make sure we're aligned on left channel - skip any right + channel sample left waiting */ + DISRC2 = (DCSRC2 + 2) & ~0x3; + DCON2 = DMA_CONTROL_SETUP | (DSTAT2 & 0xFFFFE); + play_start_pcm(); + } +} + void fiq_handler(void) { - /* r0-r7 are probably not all used by GCC but there's no way to know - otherwise this whole thing must be assembly */ - asm volatile ("stmfd sp!, {r0-r7, ip, lr} \n" /* Store context */ - "sub sp, sp, #8 \n"); /* Reserve stack */ + static unsigned char *start; + static size_t size; register pcm_more_callback_type get_more; /* No stack for this */ - unsigned char *next_start; /* sp + #0 */ - size_t next_size; /* sp + #4 */ /* clear any pending interrupt */ SRCPND = (1<<19); /* Buffer empty. Try to get more. */ get_more = pcm_callback_for_more; - if (get_more == NULL) + size = 0; + + if (get_more == NULL || (get_more(&start, &size), size == 0)) { - /* Callback missing */ - pcm_play_dma_stop_fiq(); - goto fiq_exit; + /* Callback missing or no more DMA to do */ + pcm_play_dma_stop(); + pcm_play_dma_stopped_callback(); } - - next_size = 0; - get_more(&next_start, &next_size); - - if (next_size == 0) + else { - /* No more DMA to do */ - pcm_play_dma_stop_fiq(); - goto fiq_exit; + /* Flush any pending cache writes */ + clean_dcache_range(start, size); + + /* set the new DMA values */ + DCON2 = DMA_CONTROL_SETUP | (size >> 1); + DISRC2 = (unsigned int)start + 0x30000000; + + /* Re-Activate the channel */ + DMASKTRIG2 = 0x2; } - - /* Flush any pending cache writes */ - clean_dcache_range(next_start, next_size); - - /* set the new DMA values */ - DCON2 = DMA_CONTROL_SETUP | (next_size >> 1); - DISRC2 = (unsigned long)next_start + 0x30000000; - - /* Re-Activate the channel */ - DMASKTRIG2 = 0x2; - -fiq_exit: - asm volatile("add sp, sp, #8 \n" /* Cleanup stack */ - "ldmfd sp!, {r0-r7, ip, lr} \n" /* Restore context */ - "subs pc, lr, #4 \n"); /* Return from FIQ */ -} - -/* Disconnect the DMA and wait for the FIFO to clear */ -void pcm_play_dma_stop(void) -{ - disable_fiq(); - pcm_play_dma_stop_fiq(); -} - -void pcm_play_pause_pause(void) -{ - /* stop servicing refills */ - int oldstatus = set_fiq_status(FIQ_DISABLED); - INTMSK |= (1<<19); /* mask interrupt request */ - IISCON &= ~(1<<5); /* turn off FIFO request */ - DMASKTRIG2 = 0x4; /* stop DMA at end of atomic transfer */ - - if (pcm_playing) - { - /* playing - wait for the FIFO to empty */ - while (IISCON & (1<<7)) ; - } - - set_fiq_status(oldstatus); -} - -void pcm_play_pause_unpause(void) -{ - /* refill buffer and keep going */ - int oldstatus = set_fiq_status(FIQ_DISABLED); - _pcm_apply_settings(); - if (pcm_playing) - { - /* make sure we're aligned on left channel - skip any right channel - sample left waiting */ - DISRC2 = (DCSRC2 + 2) & ~0x3; - DCON2 = (DSTAT2 & 0xFFFFE); - - SRCPND = (1<<19); /* clear pending DMA interrupt */ - INTMSK &= ~(1<<19); /* unmask interrupt request */ - IISCON |= (1<<5); /* enable FIFO request */ - } - set_fiq_status(oldstatus); } void pcm_set_frequency(unsigned int frequency) @@ -296,89 +301,10 @@ size_t pcm_get_bytes_waiting(void) return (DSTAT2 & 0xFFFFE) * 2; } -/** **/ - -void pcm_mute(bool mute) +const void * pcm_play_dma_get_peak_buffer(int *count) { - audiohw_mute(mute); - if (mute) - sleep(HZ/16); + unsigned long addr = DCSRC2; + int cnt = DSTAT2; + *count = (cnt & 0xFFFFF) >> 1; + return (void *)((addr + 2) & ~3); } - -/** - * 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) -{ - static unsigned long last_peak_tick = 0; - static unsigned long frame_period = 0; - static int peaks_l = 0, peaks_r = 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) - { - unsigned long *addr = (unsigned long *)DCSRC2; - long samples = DSTAT2; - long samp_frames; - - addr = (unsigned long *)((unsigned long)addr & ~3); - samples &= 0xFFFFE; - samp_frames = frame_period*pcm_freq/(HZ/2); - samples = MIN(samp_frames, samples) >> 1; - - if (samples > 0) - { - long peak_l = 0, peak_r = 0; - long peaksq_l = 0, peaksq_r = 0; - - addr -= 0x30000000 >> 2; - addr = (long *)((long)addr & ~3); - - do - { - long value = *addr; - long ch, chsq; - - ch = (int16_t)value; - chsq = ch*ch; - if (chsq > peaksq_l) - peak_l = ch, peaksq_l = chsq; - - ch = value >> 16; - chsq = ch*ch; - if (chsq > peaksq_r) - peak_r = ch, peaksq_r = chsq; - - addr += 4; - } - while ((samples -= 4) > 0); - - peaks_l = abs(peak_l); - peaks_r = abs(peak_r); - } - } - else - { - peaks_l = peaks_r = 0; - } - - if (left) - *left = peaks_l; - - if (right) - *right = peaks_r; -} /* pcm_calculate_peaks */ diff --git a/firmware/target/arm/system-arm.h b/firmware/target/arm/system-arm.h index c3af652ebc..774cdbcff4 100644 --- a/firmware/target/arm/system-arm.h +++ b/firmware/target/arm/system-arm.h @@ -74,12 +74,6 @@ static inline uint32_t swap_odd_even32(uint32_t value) return value; } -static inline void set_fiq_handler(void(*fiq_handler)(void)) -{ - /* Install the FIQ handler */ - *((unsigned int*)(15*4)) = (unsigned int)fiq_handler; -} - static inline void enable_fiq(void) { /* Clear FIQ disable bit */ diff --git a/firmware/target/arm/system-pp502x.c b/firmware/target/arm/system-pp502x.c index 576459d6c1..8110cfc127 100644 --- a/firmware/target/arm/system-pp502x.c +++ b/firmware/target/arm/system-pp502x.c @@ -214,6 +214,7 @@ void system_init(void) outl(inl(0x70000024) | 0xc0, 0x70000024); DEV_RS = 0; DEV_OFF_MASK = 0; + STRAP_OPT_A = 0x80; #endif #if !defined(SANSA_E200) && !defined(SANSA_C200) diff --git a/firmware/target/arm/system-target.h b/firmware/target/arm/system-target.h index 6b35a49c97..7a1ff4f79a 100644 --- a/firmware/target/arm/system-target.h +++ b/firmware/target/arm/system-target.h @@ -70,6 +70,20 @@ static inline unsigned int current_core(void) return core; } +/* Return the actual ID instead of core index */ +static inline unsigned int processor_id(void) +{ + unsigned char id; + + asm volatile ( + "ldrb %0, [%1] \n" + : "=r"(id) + : "r"(&PROCESSOR_ID) + ); + + return id; +} + #ifdef BOOTLOADER /* All addresses within rockbox are in IRAM in the bootloader so are therefore uncached */ diff --git a/firmware/target/coldfire/pcm-coldfire.c b/firmware/target/coldfire/pcm-coldfire.c index 315479f115..6d79ed08f1 100644 --- a/firmware/target/coldfire/pcm-coldfire.c +++ b/firmware/target/coldfire/pcm-coldfire.c @@ -26,17 +26,6 @@ #include "spdif.h" #endif -/* peaks */ -static unsigned long *rec_peak_addr; -enum -{ - PLAY_PEAK_LEFT = 0, - PLAY_PEAK_RIGHT, - REC_PEAK_LEFT, - REC_PEAK_RIGHT -}; -static int peaks[4]; /* p-l, p-r, r-l, r-r */ - #define IIS_PLAY_DEFPARM ( (freq_ent[FPARM_CLOCKSEL] << 12) | \ (IIS_PLAY & (7 << 8)) | \ (4 << 2) ) /* 64 bit clocks / word clock */ @@ -51,6 +40,12 @@ static int peaks[4]; /* p-l, p-r, r-l, r-r */ #define IIS_PLAY IIS2CONFIG #endif +struct dma_lock +{ + int locked; + unsigned long state; +}; + static bool is_playback_monitoring(void) { return (IIS_PLAY & (7 << 8)) == (3 << 8); @@ -93,7 +88,7 @@ static const unsigned char pcm_freq_parms[HW_NUM_FREQ][3] = }; #endif -static int pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ +static unsigned long pcm_freq = 0; /* 44.1 is default */ static const unsigned char *freq_ent = pcm_freq_parms[HW_FREQ_DEFAULT]; /* set frequency used by the audio hardware */ @@ -126,13 +121,12 @@ void pcm_set_frequency(unsigned int frequency) /* apply audio settings */ bool _pcm_apply_settings(bool clear_reset) { - static int last_pcm_freq = 0; bool did_reset = false; unsigned long iis_play_defparm = IIS_PLAY_DEFPARM; - if (pcm_freq != last_pcm_freq) + if (pcm_freq != pcm_curr_sampr) { - last_pcm_freq = pcm_freq; + pcm_curr_sampr = pcm_freq; /* Reprogramming bits 15-12 requires FIFO to be in a reset condition - Users Manual 17-8, Note 11 */ or_l(IIS_FIFO_RESET, &IIS_PLAY); @@ -159,6 +153,14 @@ bool _pcm_apply_settings(bool clear_reset) return did_reset; } /* _pcm_apply_settings */ +/* apply audio setting with all DMA interrupts disabled */ +void _pcm_apply_settings_irq_lock(bool clear_reset) +{ + int level = set_irq_level(DMA_IRQ_LEVEL); + _pcm_apply_settings(clear_reset); + set_irq_level(level); +} + /* This clears the reset bit to enable monitoring immediately if monitoring recording sources or always if playback is in progress - we might be switching samplerates on the fly */ @@ -176,76 +178,8 @@ void pcm_apply_settings(void) set_irq_level(level); } /* 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) +void pcm_play_dma_init(void) { - int level; - - logf("pcm_play_dma_start"); - - addr = (void *)((unsigned long)addr & ~3); /* Align data */ - size &= ~3; /* Size must be multiple of 4 */ - - /* If a tranfer is going, prevent an interrupt while setting up - a new one */ - level = set_irq_level(DMA_IRQ_LEVEL); - - pcm_playing = true; - - /* Set up DMA transfer */ - SAR0 = (unsigned long)addr; /* Source address */ - DAR0 = (unsigned long)&PDOR3; /* Destination address */ - BCR0 = (unsigned long)size; /* Bytes to transfer */ - - /* Enable the FIFO and force one write to it */ - _pcm_apply_settings(is_playback_monitoring()); - - DCR0 = DMA_INT | DMA_EEXT | DMA_CS | DMA_AA | - DMA_SINC | DMA_SSIZE(DMA_SIZE_LINE) | DMA_START; - - set_irq_level(level); -} /* pcm_play_dma_start */ - -/* Stops the DMA transfer and interrupt */ -static void pcm_play_dma_stop_irq(void) -{ - pcm_playing = false; - if (!audio_status()) - pcm_paused = false; - - DSR0 = 1; - DCR0 = 0; - - /* Place TX FIFO in reset condition if playback monitoring is on. - Recording monitoring something else should not be stopped. */ - iis_play_reset_if_playback(true); -} /* pcm_play_dma_stop_irq */ - -void pcm_play_dma_stop(void) -{ - int level = set_irq_level(DMA_IRQ_LEVEL); - - logf("pcm_play_dma_stop"); - - pcm_play_dma_stop_irq(); - - set_irq_level(level); -} /* pcm_play_dma_stop */ - -void pcm_init(void) -{ - logf("pcm_init"); - - pcm_playing = false; - pcm_paused = false; - pcm_callback_for_more = NULL; - AUDIOGLOB = (1 << 8) /* IIS1 fifo auto sync */ | (1 << 7) /* PDIR2 fifo auto sync */ #ifdef HAVE_SPDIF_OUT @@ -259,8 +193,6 @@ void pcm_init(void) /* Call pcm_play_dma_stop to initialize everything. */ pcm_play_dma_stop(); - /* Call pcm_close_recording to put in closed state */ - pcm_close_recording(); /* Setup Coldfire I2S before initializing hardware or changing other settings. */ @@ -282,14 +214,95 @@ void pcm_init(void) #endif /* Enable interrupt at level 6, priority 0 */ ICR6 = (6 << 2); - and_l(~(1 << 14), &IMR); /* bit 14 is DMA0 */ -} /* pcm_init */ +} /* pcm_play_dma_init */ void pcm_postinit(void) { audiohw_postinit(); } +/** DMA **/ + +/**************************************************************************** + ** Playback DMA transfer + **/ +/* For the locks, DMA interrupt must be disabled when manipulating the lock + if the handler ever calls these - right now things are arranged so it + doesn't */ +static struct dma_lock dma_play_lock = +{ + .locked = 0, + .state = (0 << 14) /* bit 14 is DMA0 */ +}; + +void pcm_play_lock(void) +{ + if (++dma_play_lock.locked == 1) + or_l((1 << 14), &IMR); +} + +void pcm_play_unlock(void) +{ + if (--dma_play_lock.locked == 0) + and_l(~dma_play_lock.state, &IMR); +} + +/* 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"); + + /* stop any DMA in progress */ + DSR0 = 1; + DCR0 = 0; + + /* Set up DMA transfer */ + SAR0 = (unsigned long)addr; /* Source address */ + DAR0 = (unsigned long)&PDOR3; /* Destination address */ + BCR0 = (unsigned long)size; /* Bytes to transfer */ + + /* Enable the FIFO and force one write to it */ + _pcm_apply_settings_irq_lock(is_playback_monitoring()); + + DCR0 = DMA_INT | DMA_EEXT | DMA_CS | DMA_AA | + DMA_SINC | DMA_SSIZE(DMA_SIZE_LINE) | DMA_START; + + dma_play_lock.state = (1 << 14); +} /* pcm_play_dma_start */ + +/* Stops the DMA transfer and interrupt */ +void pcm_play_dma_stop(void) +{ + DSR0 = 1; + DCR0 = 0; + BCR0 = 0; + + /* Place TX FIFO in reset condition if playback monitoring is on. + Recording monitoring something else should not be stopped. */ + iis_play_reset_if_playback(true); + + dma_play_lock.state = (0 << 14); +} /* pcm_play_dma_stop */ + +void pcm_play_dma_pause(bool pause) +{ + if (pause) + { + /* pause playback on current buffer */ + and_l(~DMA_EEXT, &DCR0); + iis_play_reset_if_playback(true); + dma_play_lock.state = (0 << 14); + } + else + { + /* restart playback on current buffer */ + /* Enable the FIFO and force one write to it */ + _pcm_apply_settings_irq_lock(is_playback_monitoring()); + or_l(DMA_EEXT | DMA_START, &DCR0); + dma_play_lock.state = (1 << 14); + } +} /* pcm_play_dma_pause */ + size_t pcm_get_bytes_waiting(void) { return BCR0 & 0xffffff; @@ -341,29 +354,54 @@ void DMA0(void) #endif } - pcm_play_dma_stop_irq(); + /* Stop interrupt and futher transfers */ + pcm_play_dma_stop(); + /* Inform PCM that we're done */ + pcm_play_dma_stopped_callback(); } /* DMA0 */ +const void * pcm_play_dma_get_peak_buffer(int *count) +{ + unsigned long addr = SAR0; + int cnt = BCR0; + *count = (cnt & 0xffffff) >> 2; + return (void *)((addr + 2) & ~3); +} /* pcm_play_dma_get_peak_buffer */ + /**************************************************************************** ** Recording DMA transfer **/ +static struct dma_lock dma_rec_lock = +{ + .locked = 0, + .state = (0 << 15) /* bit 15 is DMA1 */ +}; + +/* For the locks, DMA interrupt must be disabled when manipulating the lock + if the handler ever calls these - right now things are arranged so it + doesn't */ +void pcm_rec_lock(void) +{ + if (++dma_rec_lock.locked == 1) + or_l((1 << 15), &IMR); +} + +void pcm_rec_unlock(void) +{ + if (--dma_rec_lock.locked == 0) + and_l(~dma_rec_lock.state, &IMR); +} + void pcm_rec_dma_start(void *addr, size_t size) { - int level; - logf("pcm_rec_dma_start"); - - addr = (void *)((unsigned long)addr & ~3); /* Align data */ - size &= ~3; /* Size must be multiple of 4 */ - - /* No DMA1 interrupts while setting up a new transfer */ - level = set_irq_level(DMA_IRQ_LEVEL); - - pcm_recording = true; + /* stop any DMA in progress */ + and_l(~DMA_EEXT, &DCR1); + DSR1 = 1; and_l(~PDIR2_FIFO_RESET, &DATAINCONTROL); /* Clear TX FIFO reset bit if the source is not set to monitor playback otherwise maintain independence between playback and recording. */ - _pcm_apply_settings(!is_playback_monitoring()); + _pcm_apply_settings_irq_lock(!is_playback_monitoring()); /* Start the DMA transfer.. */ #ifdef HAVE_SPDIF_REC @@ -371,74 +409,48 @@ void pcm_rec_dma_start(void *addr, size_t size) INTERRUPTCLEAR = (1 << 25) | (1 << 24) | (1 << 23) | (1 << 22); #endif - SAR1 = (unsigned long)&PDIR2; /* Source address */ - rec_peak_addr = (unsigned long *)addr; /* Start peaking at dest */ - DAR1 = (unsigned long)addr; /* Destination address */ - BCR1 = (unsigned long)size; /* Bytes to transfer */ + SAR1 = (unsigned long)&PDIR2; /* Source address */ + pcm_rec_peak_addr = (unsigned long *)addr; /* Start peaking at dest */ + DAR1 = (unsigned long)addr; /* Destination address */ + BCR1 = (unsigned long)size; /* Bytes to transfer */ DCR1 = DMA_INT | DMA_EEXT | DMA_CS | DMA_AA | DMA_DINC | - DMA_DSIZE(DMA_SIZE_LINE) /* | DMA_START */; + DMA_DSIZE(DMA_SIZE_LINE) | DMA_START; - set_irq_level(level); -} /* pcm_dma_start */ - -static void pcm_rec_dma_stop_irq(void) -{ - DSR1 = 1; /* Clear interrupt */ - DCR1 = 0; - pcm_recording = false; - or_l(PDIR2_FIFO_RESET, &DATAINCONTROL); - - iis_play_reset_if_playback(false); -} /* pcm_rec_dma_stop_irq */ + dma_rec_lock.state = (1 << 15); +} /* pcm_rec_dma_start */ void pcm_rec_dma_stop(void) { - int level = set_irq_level(DMA_IRQ_LEVEL); + DSR1 = 1; /* Clear interrupt */ + DCR1 = 0; + BCR1 = 0; + or_l(PDIR2_FIFO_RESET, &DATAINCONTROL); - logf("pcm_rec_dma_stop"); + iis_play_reset_if_playback(false); - pcm_rec_dma_stop_irq(); - - set_irq_level(level); + dma_rec_lock.state = (0 << 15); } /* pcm_rec_dma_stop */ -void pcm_init_recording(void) +void pcm_rec_dma_init(void) { - int level = set_irq_level(DMA_IRQ_LEVEL); - - logf("pcm_init_recording"); - - pcm_recording = false; - pcm_callback_more_ready = NULL; - - DIVR1 = 55; /* DMA1 is mapped into vector 55 in system.c */ - DMACONFIG = 1; /* DMA0Req = PDOR3, DMA1Req = PDIR2 */ + DIVR1 = 55; /* DMA1 is mapped into vector 55 in system.c */ + DMACONFIG = 1; /* DMA0Req = PDOR3, DMA1Req = PDIR2 */ and_l(0xffff00ff, &DMAROUTE); or_l(DMA1_REQ_AUDIO_2, &DMAROUTE); - pcm_rec_dma_stop_irq(); + pcm_rec_dma_stop(); - ICR7 = (6 << 2) | (1 << 0); /* Enable interrupt at level 6, priority 1 */ - and_l(~(1 << 15), &IMR); /* bit 15 is DMA1 */ - - set_irq_level(level); + /* Enable interrupt at level 6, priority 1 */ + ICR7 = (6 << 2) | (1 << 0); } /* pcm_init_recording */ -void pcm_close_recording(void) +void pcm_rec_dma_close(void) { - int level = set_irq_level(DMA_IRQ_LEVEL); - - logf("pcm_close_recording"); - - pcm_rec_dma_stop_irq(); - and_l(0xffff00ff, &DMAROUTE); - ICR7 = 0x00; /* Disable interrupt */ - or_l((1 << 15), &IMR); /* bit 15 is DMA1 */ - - set_irq_level(level); -} /* pcm_close_recording */ + ICR7 = 0x00; /* Disable interrupt */ + dma_rec_lock.state = (0 << 15); +} /* pcm_rec_dma_close */ /* DMA1 Interrupt is called when the DMA has finished transfering a chunk into the caller's buffer */ @@ -485,175 +497,25 @@ void DMA1(void) logf("DMA1 done:%04x %d", res, status); #endif /* Finished recording */ - pcm_rec_dma_stop_irq(); + pcm_rec_dma_stop(); + /* Inform PCM that we're done */ + pcm_rec_dma_stopped_callback(); } /* DMA1 */ /* Continue transferring data in - call from interrupt callback */ void pcm_record_more(void *start, size_t size) { - rec_peak_addr = (unsigned long *)start; /* Start peaking at dest */ - DAR1 = (unsigned long)start; /* Destination address */ - BCR1 = (unsigned long)size; /* Bytes to transfer */ + pcm_rec_peak_addr = (unsigned long *)start; /* Start peaking at dest */ + DAR1 = (unsigned long)start; /* Destination address */ + BCR1 = (unsigned long)size; /* Bytes to transfer */ or_l(DMA_EEXT, &DCR1); /* Enable peripheral request */ -} +} /* pcm_record_more */ -void pcm_mute(bool mute) +const void * pcm_rec_dma_get_peak_buffer(int *count) { - audiohw_mute(mute); - if (mute) - sleep(HZ/16); -} /* pcm_mute */ - -void pcm_play_pause_pause(void) -{ - /* Disable DMA peripheral request. */ - int level = set_irq_level(DMA_IRQ_LEVEL); - - and_l(~DMA_EEXT, &DCR0); - iis_play_reset_if_playback(true); - - set_irq_level(level); -} /* pcm_play_pause_pause */ - -void pcm_play_pause_unpause(void) -{ - int level = set_irq_level(DMA_IRQ_LEVEL); - - /* Enable the FIFO and force one write to it */ - _pcm_apply_settings(is_playback_monitoring()); - or_l(DMA_EEXT | DMA_START, &DCR0); - - set_irq_level(level); -} /* pcm_play_pause_unpause */ - -/** - * Do peak calculation using distance squared from axis and save a lot - * of jumps and negation. Don't bother with the calculations of left or - * right only as it's never really used and won't save much time. - */ -static void pcm_peak_peeker(unsigned long *addr, unsigned long *end, - int peaks[2]) -{ - long peak_l = 0, peak_r = 0; - long peaksq_l = 0, peaksq_r = 0; - - do - { - long value = *addr; - long ch, chsq; - - ch = value >> 16; - chsq = ch*ch; - if (chsq > peaksq_l) - peak_l = ch, peaksq_l = chsq; - - ch = (short)value; - chsq = ch*ch; - if (chsq > peaksq_r) - peak_r = ch, peaksq_r = chsq; - - addr += 4; - } - while (addr < end); - - peaks[0] = abs(peak_l); - peaks[1] = abs(peak_r); -} /* pcm_peak_peeker */ - -/** - * 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) -{ - static unsigned long last_peak_tick = 0; - static unsigned long frame_period = 0; - - long samples, samp_frames; - unsigned long *addr; - - /* 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) - { - /* Snapshot as quickly as possible */ - asm volatile ( - "move.l %c[sar0], %[start] \n" - "move.l %c[bcr0], %[count] \n" - : [start]"=r"(addr), [count]"=r"(samples) - : [sar0]"p"(&SAR0), [bcr0]"p"(&BCR0) - ); - - samples &= 0xfffffc; - samp_frames = frame_period*pcm_freq/(HZ/4); - samples = MIN(samp_frames, samples) >> 2; - - if (samples > 0) - { - addr = (long *)((long)addr & ~3); - pcm_peak_peeker(addr, addr + samples, &peaks[PLAY_PEAK_LEFT]); - } - } - else - { - peaks[PLAY_PEAK_LEFT] = peaks[PLAY_PEAK_RIGHT] = 0; - } - - if (left) - *left = peaks[PLAY_PEAK_LEFT]; - - if (right) - *right = peaks[PLAY_PEAK_RIGHT]; -} /* pcm_calculate_peaks */ - -/** - * Return recording peaks - From the end of the last peak up to - * current write position. - */ -void pcm_calculate_rec_peaks(int *left, int *right) -{ - if (pcm_recording) - { - unsigned long *addr, *end; - - /* Snapshot as quickly as possible */ - asm volatile ( - "move.l %c[start], %[addr] \n" - "move.l %c[dar1], %[end] \n" - "and.l %[mask], %[addr] \n" - "and.l %[mask], %[end] \n" - : [addr]"=r"(addr), [end]"=r"(end) - : [start]"p"(&rec_peak_addr), [dar1]"p"(&DAR1), [mask]"r"(~3) - ); - - if (addr < end) - { - pcm_peak_peeker(addr, end, &peaks[REC_PEAK_LEFT]); - - if (addr == rec_peak_addr) - rec_peak_addr = end; - } - } - else - { - peaks[REC_PEAK_LEFT] = peaks[REC_PEAK_RIGHT] = 0; - } - - if (left) - *left = peaks[REC_PEAK_LEFT]; - - if (right) - *right = peaks[REC_PEAK_RIGHT]; -} /* pcm_calculate_rec_peaks */ + unsigned long addr = (unsigned long)pcm_rec_peak_addr; + unsigned long end = DAR1; + addr >>= 2; + *count = (end >> 2) - addr; + return (void *)(addr << 2); +} /* pcm_rec_dma_get_peak_buffer */ diff --git a/uisimulator/sdl/sound.c b/uisimulator/sdl/sound.c index 6016676f70..3636939f06 100644 --- a/uisimulator/sdl/sound.c +++ b/uisimulator/sdl/sound.c @@ -26,20 +26,12 @@ #include "kernel.h" #include "sound.h" -#ifdef HAVE_RECORDING -#ifndef REC_SAMPR_CAPS -#define REC_SAMPR_CAPS SAMPR_CAP_44 -#endif -#endif - +#include "pcm.h" #include "pcm_sampr.h" #include "SDL.h" -static bool pcm_playing; -static bool pcm_paused; static int cvt_status = -1; static unsigned long pcm_frequency = SAMPR_44; -static unsigned long pcm_curr_frequency = SAMPR_44; static Uint8* pcm_data; static size_t pcm_data_size; @@ -63,66 +55,56 @@ extern bool debug_audio; #define MIN(a, b) (((a) < (b)) ? (a) : (b)) #endif +void pcm_play_lock(void) +{ + SDL_LockAudio(); +} + +void pcm_play_unlock(void) +{ + SDL_UnlockAudio(); +} + static void pcm_apply_settings_nolock(void) { cvt_status = SDL_BuildAudioCVT(&cvt, AUDIO_S16SYS, 2, pcm_frequency, obtained.format, obtained.channels, obtained.freq); - pcm_curr_frequency = pcm_frequency; + pcm_curr_sampr = pcm_frequency; if (cvt_status < 0) { - cvt.len_ratio = (double)obtained.freq / (double)pcm_curr_frequency; + cvt.len_ratio = (double)obtained.freq / (double)pcm_curr_sampr; } } void pcm_apply_settings(void) { - SDL_LockAudio(); + pcm_play_lock(); pcm_apply_settings_nolock(); - SDL_UnlockAudio(); + pcm_play_unlock(); } -static void sdl_dma_start_nolock(const void *addr, size_t size) +void pcm_play_dma_start(const void *addr, size_t size) { - pcm_playing = false; - pcm_apply_settings_nolock(); pcm_data = (Uint8 *) addr; pcm_data_size = size; - pcm_playing = true; - SDL_PauseAudio(0); } -static void sdl_dma_stop_nolock(void) +void pcm_play_dma_stop(void) { - pcm_playing = false; - SDL_PauseAudio(1); - - pcm_paused = false; } -static void (*callback_for_more)(unsigned char**, size_t*) = NULL; -void pcm_play_data(void (*get_more)(unsigned char** start, size_t* size), - unsigned char* start, size_t size) +void pcm_play_dma_pause(bool pause) { - SDL_LockAudio(); - - callback_for_more = get_more; - - if (!(start && size)) { - if (get_more) - get_more(&start, &size); - } - - if (start && size) { - sdl_dma_start_nolock(start, size); - } - - SDL_UnlockAudio(); + if (pause) + SDL_PauseAudio(1); + else + SDL_PauseAudio(0); } size_t pcm_get_bytes_waiting(void) @@ -130,74 +112,6 @@ size_t pcm_get_bytes_waiting(void) return pcm_data_size; } -void pcm_mute(bool mute) -{ - (void) mute; -} - -void pcm_play_stop(void) -{ - SDL_LockAudio(); - if (pcm_playing) { - sdl_dma_stop_nolock(); - } - SDL_UnlockAudio(); -} - -void pcm_play_pause(bool play) -{ - size_t next_size; - Uint8 *next_start; - - SDL_LockAudio(); - - if (!pcm_playing) { - SDL_UnlockAudio(); - return; - } - - if(pcm_paused && play) { - if (pcm_get_bytes_waiting()) { - printf("unpause\n"); - pcm_apply_settings_nolock(); - SDL_PauseAudio(0); - } else { - printf("unpause, no data waiting\n"); - - void (*get_more)(unsigned char**, size_t*) = callback_for_more; - - if (get_more) { - get_more(&next_start, &next_size); - } - - if (next_start && next_size) { - sdl_dma_start_nolock(next_start, next_size); - } else { - sdl_dma_stop_nolock(); - printf("unpause attempted, no data\n"); - } - } - } else if(!pcm_paused && !play) { - printf("pause\n"); - - SDL_PauseAudio(1); - } - - pcm_paused = !play; - - SDL_UnlockAudio(); -} - -bool pcm_is_paused(void) -{ - return pcm_paused; -} - -bool pcm_is_playing(void) -{ - return pcm_playing; -} - void pcm_set_frequency(unsigned int frequency) { switch (frequency) @@ -222,70 +136,6 @@ void pcm_set_frequency(unsigned int frequency) pcm_frequency = frequency; } -/* - * This function goes directly into the DMA buffer to calculate the left and - * right peak values. To avoid missing peaks it tries to look forward two full - * peek periods (2/HZ sec, 100% overlap), although it's always possible that - * the entire period will not be visible. To reduce CPU load it only looks at - * every third sample, and this can be reduced even further if needed (even - * every tenth sample would still be pretty accurate). - */ - -#define PEAK_SAMPLES (44100*2/HZ) /* 44100 samples * 2 / 100 Hz tick */ -#define PEAK_STRIDE 3 /* every 3rd sample is plenty... */ - -void pcm_calculate_peaks(int *left, int *right) -{ - long samples = (long) pcm_data_size / 4; - short *addr = (short *) pcm_data; - - if (samples > PEAK_SAMPLES) - samples = PEAK_SAMPLES; - - samples /= PEAK_STRIDE; - - if (left && right) { - int left_peak = 0, right_peak = 0, value; - - while (samples--) { - if ((value = addr [0]) > left_peak) - left_peak = value; - else if (-value > left_peak) - left_peak = -value; - - if ((value = addr [PEAK_STRIDE | 1]) > right_peak) - right_peak = value; - else if (-value > right_peak) - right_peak = -value; - - addr += PEAK_STRIDE * 2; - } - - *left = left_peak; - *right = right_peak; - } - else if (left || right) { - int peak_value = 0, value; - - if (right) - addr += (PEAK_STRIDE | 1); - - while (samples--) { - if ((value = addr [0]) > peak_value) - peak_value = value; - else if (-value > peak_value) - peak_value = -value; - - addr += PEAK_STRIDE * 2; - } - - if (left) - *left = peak_value; - else - *right = peak_value; - } -} - extern int sim_volume; /* in firmware/sound.c */ void write_to_soundcard(struct pcm_udata *udata) { if (cvt.needed) { @@ -380,8 +230,8 @@ void sdl_audio_callback(struct pcm_udata *udata, Uint8 *stream, int len) if ((ssize_t)pcm_data_size <= 0) { pcm_data_size = 0; - if (callback_for_more) - callback_for_more(&pcm_data, &pcm_data_size); + if (pcm_callback_for_more) + pcm_callback_for_more(&pcm_data, &pcm_data_size); } if (pcm_data_size > 0) { @@ -400,30 +250,42 @@ void sdl_audio_callback(struct pcm_udata *udata, Uint8 *stream, int len) len -= udata->num_out; } else { DEBUGF("sdl_audio_callback: No Data.\n"); - sdl_dma_stop_nolock(); + pcm_play_dma_stop(); + pcm_play_dma_stopped_callback(); break; } } } +const void * pcm_play_dma_get_peak_buffer(int *count) +{ + uintptr_t addr = (uintptr_t)pcm_data; + *count = pcm_data_size / 4; + return (void *)((addr + 2) & ~3); +} + #ifdef HAVE_RECORDING -void pcm_init_recording(void) +void pcm_rec_lock(void) { } -void pcm_close_recording(void) +void pcm_rec_unlock(void) { } -void pcm_record_data(void (*more_ready)(void* start, size_t size), - void *start, size_t size) +void pcm_rec_dma_init(void) { - (void)more_ready; - (void)start; - (void)size; } -void pcm_stop_recording(void) +void pcm_rec_dma_close(void) +{ +} + +void pcm_rec_dma_start(void *start, size_t size) +{ +} + +void pcm_rec_dma_stop(void) { } @@ -433,22 +295,20 @@ void pcm_record_more(void *start, size_t size) (void)size; } -void pcm_calculate_rec_peaks(int *left, int *right) -{ - if (left) - *left = 0; - if (right) - *right = 0; -} - unsigned long pcm_rec_status(void) { return 0; } +const void * pcm_rec_dma_get_peak_buffer(int *count) +{ + *count = 0; + return NULL; +} + #endif /* HAVE_RECORDING */ -int pcm_init(void) +void pcm_play_dma_init(void) { SDL_AudioSpec wanted_spec; udata.debug = NULL; @@ -470,7 +330,7 @@ int pcm_init(void) /* Open the audio device and start playing sound! */ if(SDL_OpenAudio(&wanted_spec, &obtained) < 0) { fprintf(stderr, "Unable to open audio: %s\n", SDL_GetError()); - return -1; + return; } switch (obtained.format) @@ -488,16 +348,12 @@ int pcm_init(void) default: fprintf(stderr, "Unknown sample format obtained: %u\n", (unsigned)obtained.format); - return -1; + return; } pcm_sample_bytes = obtained.channels * pcm_channel_bytes; pcm_apply_settings_nolock(); - - sdl_dma_stop_nolock(); - - return 0; } void pcm_postinit(void)