/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright (C) 2006 by Michael Sevakis * * All files in this archive are subject to the GNU General Public License. * See the file COPYING in the source tree root for full license agreement. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include #include "system.h" #include "kernel.h" #include "logf.h" #include "audio.h" #include "sound.h" #include "file.h" #include "mmu-meg-fx.h" #define GIGABEAT_8000HZ (0x02 << 1) #define GIGABEAT_11025HZ (0x19 << 1) #define GIGABEAT_22050HZ (0x1b << 1) #define GIGABEAT_44100HZ (0x11 << 1) #define GIGABEAT_88200HZ (0x1f << 1) static int pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ static int sr_ctrl = GIGABEAT_44100HZ; #define FIFO_COUNT ((IISFCON >> 6) & 0x01F) /* number of bytes in FIFO */ #define IIS_FIFO_SIZE 64 /* Setup for the DMA controller */ #define DMA_CONTROL_SETUP ((1<<31) | (1<<29) | (1<<23) | (1<<22) | (1<<20)) unsigned short * p; size_t p_size; /* DMA count has hit zero - no more data */ /* Get more data from the callback and top off the FIFO */ //void fiq(void) __attribute__ ((interrupt ("naked"))); void fiq(void) ICODE_ATTR __attribute__ ((interrupt ("FIQ"))); static void _pcm_apply_settings(void) { static int last_freqency = 0; if (pcm_freq != last_freqency) { last_freqency = pcm_freq; audiohw_set_frequency(sr_ctrl); } } void pcm_apply_settings(void) { int oldstatus = set_fiq_status(FIQ_DISABLED); _pcm_apply_settings(); set_fiq_status(oldstatus); } void pcm_init(void) { pcm_playing = false; pcm_paused = false; pcm_callback_for_more = NULL; audiohw_init(); audiohw_enable_output(true); pcm_set_frequency(SAMPR_44); /* init GPIO */ GPCCON = (GPCCON & ~(3<<14)) | (1<<14); GPCDAT |= 1<<7; GPECON |= 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 */ } void pcm_postinit(void) { audiohw_postinit(); } void pcm_play_dma_start(const void *addr, size_t size) { /* sanity check: bad pointer or too small file */ if (NULL == addr || size <= IIS_FIFO_SIZE) return; disable_fiq(); p = (unsigned short *)addr; p_size = size; /* 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 - 384fs - use 16.9344Mhz */ IISMOD = (1<<9) | (1<<8) | (2<<6) | (1<<3) | (1<<2); /* 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 | (p_size / 2); /* set DMA source and options */ DISRC2 = (int)p + 0x30000000; DISRCC2 = 0x00; /* memory is on AHB bus, increment addresses */ /* clear pending DMA interrupt */ SRCPND = 1<<19; pcm_playing = true; _pcm_apply_settings(); set_fiq_handler(fiq); /* unmask the DMA interrupt */ INTMSK &= ~(1<<19); /* Flush any pending writes */ clean_dcache_range(addr, size); /* Activate the channel */ DMASKTRIG2 = 0x2; /* turn off the idle */ IISCON &= ~(1<<3); /* start the IIS */ IISCON |= (1<<0); enable_fiq(); } static void pcm_play_dma_stop_fiq(void) { /* mask the DMA interrupt */ INTMSK |= (1<<19); /* are we playing? wait for the chunk to finish */ if (pcm_playing) { /* wait for the FIFO to empty before turning things off */ while (IISCON & (1<<7)) ; pcm_playing = false; } /* De-Activate the DMA channel */ DMASKTRIG2 = 0x4; /* Disconnect the IIS clock */ CLKCON &= ~(1<<17); } void fiq(void) { /* clear any pending interrupt */ SRCPND = (1<<19); /* Buffer empty. Try to get more. */ if (pcm_callback_for_more) { pcm_callback_for_more((unsigned char**)&p, &p_size); } else { /* callback func is missing? */ pcm_play_dma_stop_fiq(); return; } if (p_size) { /* Flush any pending cache writes */ clean_dcache_range(p, p_size); /* set the new DMA values */ DCON2 = DMA_CONTROL_SETUP | (p_size >> 1); DISRC2 = (int)p + 0x30000000; /* Re-Activate the channel */ DMASKTRIG2 = 0x2; } else { /* No more DMA to do */ pcm_play_dma_stop_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); 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(); INTMSK &= ~(1<<19); set_fiq_status(oldstatus); } void pcm_set_frequency(unsigned int frequency) { switch(frequency) { case SAMPR_8: sr_ctrl = GIGABEAT_8000HZ; break; case SAMPR_11: sr_ctrl = GIGABEAT_11025HZ; break; case SAMPR_22: sr_ctrl = GIGABEAT_22050HZ; break; default: frequency = SAMPR_44; case SAMPR_44: sr_ctrl = GIGABEAT_44100HZ; break; case SAMPR_88: sr_ctrl = GIGABEAT_88200HZ; break; } pcm_freq = frequency; } size_t pcm_get_bytes_waiting(void) { return (DSTAT2 & 0xFFFFF) * 2; } #if 0 void pcm_set_monitor(int monitor) { (void)monitor; } #endif /** **/ void pcm_mute(bool mute) { audiohw_mute(mute); if (mute) sleep(HZ/16); } /** * 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; 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 */