From 6077e5b7c85c0d6f5963e4aadb215faf2c4d10d2 Mon Sep 17 00:00:00 2001 From: Michael Sevakis Date: Sat, 6 Oct 2007 22:27:27 +0000 Subject: [PATCH] Unify PCM interface just above the hardware driver level for all targets including the sims. Perform lockout of audio callback when changing states. Weird new playback or recording trouble? Check before and after this revision first though things seem quite sound. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@15006 a1c6a512-1295-4272-9138-f99709370657 --- apps/debug_menu.c | 1 - apps/main.c | 3 + apps/pcmbuf.c | 17 +- apps/plugins/SOURCES | 1 + apps/recorder/peakmeter.c | 2 +- apps/recorder/recording.c | 1 - apps/settings.c | 1 - firmware/SOURCES | 10 +- firmware/export/audio.h | 2 +- firmware/export/pcm.h | 122 +++ firmware/export/pcm_playback.h | 65 -- firmware/export/pcm_record.h | 34 - firmware/export/pp5002.h | 20 +- firmware/export/pp5020.h | 90 +- firmware/pcm.c | 437 ++++++++ firmware/pcm_playback.c | 283 ------ firmware/pcm_record.c | 39 - firmware/sound.c | 2 +- firmware/target/arm/crt0-pp.S | 8 +- firmware/target/arm/i2s-pp.c | 81 +- .../target/arm/imx31/gigabeat-s/pcm-imx31.c | 120 ++- firmware/target/arm/pcm-pp.c | 956 +++++++----------- firmware/target/arm/pnx0101/pcm-pnx0101.c | 69 +- .../arm/s3c2440/gigabeat-fx/pcm-meg-fx.c | 396 +++----- firmware/target/arm/system-arm.h | 6 - firmware/target/arm/system-pp502x.c | 1 + firmware/target/arm/system-target.h | 14 + firmware/target/coldfire/pcm-coldfire.c | 488 ++++----- uisimulator/sdl/sound.c | 252 +---- 29 files changed, 1656 insertions(+), 1865 deletions(-) create mode 100644 firmware/export/pcm.h delete mode 100644 firmware/export/pcm_playback.h create mode 100644 firmware/pcm.c delete mode 100644 firmware/pcm_playback.c 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)