2006-12-29 02:49:12 +00:00
|
|
|
/***************************************************************************
|
|
|
|
* __________ __ ___.
|
|
|
|
* 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.
|
|
|
|
*
|
|
|
|
****************************************************************************/
|
2007-05-02 22:33:24 +00:00
|
|
|
#include <stdlib.h>
|
2006-12-29 02:49:12 +00:00
|
|
|
#include "system.h"
|
|
|
|
#include "kernel.h"
|
|
|
|
#include "logf.h"
|
|
|
|
#include "audio.h"
|
2007-03-11 06:21:43 +00:00
|
|
|
#include "sound.h"
|
2006-12-29 02:49:12 +00:00
|
|
|
#include "file.h"
|
2007-01-04 11:36:25 +00:00
|
|
|
#include "mmu-meg-fx.h"
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
#define GIGABEAT_11025HZ (0x19 << 1)
|
|
|
|
#define GIGABEAT_22050HZ (0x1b << 1)
|
|
|
|
#define GIGABEAT_44100HZ (0x11 << 1)
|
|
|
|
#define GIGABEAT_88200HZ (0x1f << 1)
|
2007-01-18 13:48:06 +00:00
|
|
|
|
2007-05-03 18:08:00 +00:00
|
|
|
static int pcm_freq = 0; /* 44.1 is default */
|
|
|
|
static int sr_ctrl = 0;
|
2006-12-29 02:49:12 +00:00
|
|
|
#define FIFO_COUNT ((IISFCON >> 6) & 0x01F)
|
|
|
|
|
|
|
|
/* 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 */
|
2007-05-03 12:39:36 +00:00
|
|
|
/* 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));
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
static void _pcm_apply_settings(void)
|
|
|
|
{
|
|
|
|
static int last_freqency = 0;
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
if (pcm_freq != last_freqency)
|
2006-12-29 02:49:12 +00:00
|
|
|
{
|
2007-05-02 22:33:24 +00:00
|
|
|
last_freqency = pcm_freq;
|
|
|
|
audiohw_set_frequency(sr_ctrl);
|
2006-12-29 02:49:12 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
void pcm_apply_settings(void)
|
|
|
|
{
|
|
|
|
int oldstatus = set_fiq_status(FIQ_DISABLED);
|
|
|
|
_pcm_apply_settings();
|
|
|
|
set_fiq_status(oldstatus);
|
|
|
|
}
|
2006-12-29 02:49:12 +00:00
|
|
|
|
|
|
|
void pcm_init(void)
|
|
|
|
{
|
|
|
|
pcm_playing = false;
|
|
|
|
pcm_paused = false;
|
|
|
|
pcm_callback_for_more = NULL;
|
2007-01-04 11:36:25 +00:00
|
|
|
|
2007-01-16 03:36:32 +00:00
|
|
|
pcm_set_frequency(SAMPR_44);
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-06-24 20:41:27 +00:00
|
|
|
/* slave */
|
|
|
|
IISMOD |= (1<<8);
|
|
|
|
|
2007-05-03 18:08:00 +00:00
|
|
|
audiohw_init();
|
|
|
|
|
2006-12-29 02:49:12 +00:00
|
|
|
/* init GPIO */
|
|
|
|
GPCCON = (GPCCON & ~(3<<14)) | (1<<14);
|
|
|
|
GPCDAT |= 1<<7;
|
|
|
|
GPECON |= 0x2aa;
|
|
|
|
|
2007-01-16 03:36:32 +00:00
|
|
|
/* Do not service DMA requests, yet */
|
2006-12-29 02:49:12 +00:00
|
|
|
/* 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 */
|
|
|
|
}
|
|
|
|
|
2007-03-11 06:21:43 +00:00
|
|
|
void pcm_postinit(void)
|
|
|
|
{
|
|
|
|
audiohw_postinit();
|
2007-05-03 18:08:00 +00:00
|
|
|
pcm_apply_settings();
|
2007-03-11 06:21:43 +00:00
|
|
|
}
|
2006-12-29 02:49:12 +00:00
|
|
|
|
|
|
|
void pcm_play_dma_start(const void *addr, size_t size)
|
|
|
|
{
|
2007-05-03 12:39:36 +00:00
|
|
|
addr = (void *)((unsigned long)addr & ~3); /* Align data */
|
|
|
|
size &= ~3; /* Size must be multiple of 4 */
|
|
|
|
|
2006-12-29 02:49:12 +00:00
|
|
|
/* sanity check: bad pointer or too small file */
|
2007-06-24 20:41:27 +00:00
|
|
|
if (NULL == addr || size == 0) return;
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
disable_fiq();
|
|
|
|
|
2006-12-29 02:49:12 +00:00
|
|
|
/* Enable the IIS clock */
|
2007-01-04 11:36:25 +00:00
|
|
|
CLKCON |= (1<<17);
|
2006-12-29 02:49:12 +00:00
|
|
|
|
|
|
|
/* IIS interface setup and set to idle */
|
|
|
|
IISCON = (1<<5) | (1<<3);
|
|
|
|
|
2007-06-24 20:41:27 +00:00
|
|
|
/* 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);
|
2006-12-29 02:49:12 +00:00
|
|
|
|
|
|
|
/* connect DMA to the FIFO and enable the FIFO */
|
|
|
|
IISFCON = (1<<15) | (1<<13);
|
|
|
|
|
|
|
|
/* set DMA dest */
|
2007-01-04 11:36:25 +00:00
|
|
|
DIDST2 = (int)&IISFIFO;
|
2006-12-29 02:49:12 +00:00
|
|
|
|
|
|
|
/* IIS is on the APB bus, INT when TC reaches 0, fixed dest addr */
|
|
|
|
DIDSTC2 = 0x03;
|
2007-01-04 11:36:25 +00:00
|
|
|
|
2006-12-29 02:49:12 +00:00
|
|
|
/* 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) */
|
2007-05-03 12:39:36 +00:00
|
|
|
DCON2 = DMA_CONTROL_SETUP | (size / 2);
|
2006-12-29 02:49:12 +00:00
|
|
|
|
|
|
|
/* set DMA source and options */
|
2007-05-03 12:39:36 +00:00
|
|
|
DISRC2 = (unsigned long)addr + 0x30000000;
|
2006-12-29 02:49:12 +00:00
|
|
|
DISRCC2 = 0x00; /* memory is on AHB bus, increment addresses */
|
|
|
|
|
|
|
|
/* clear pending DMA interrupt */
|
|
|
|
SRCPND = 1<<19;
|
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
pcm_playing = true;
|
|
|
|
|
|
|
|
_pcm_apply_settings();
|
|
|
|
|
2006-12-29 02:49:12 +00:00
|
|
|
/* unmask the DMA interrupt */
|
|
|
|
INTMSK &= ~(1<<19);
|
|
|
|
|
2007-01-04 11:36:25 +00:00
|
|
|
/* Flush any pending writes */
|
|
|
|
clean_dcache_range(addr, size);
|
|
|
|
|
2006-12-29 02:49:12 +00:00
|
|
|
/* Activate the channel */
|
|
|
|
DMASKTRIG2 = 0x2;
|
2007-01-04 11:36:25 +00:00
|
|
|
|
2006-12-29 02:49:12 +00:00
|
|
|
/* turn off the idle */
|
|
|
|
IISCON &= ~(1<<3);
|
|
|
|
|
|
|
|
/* start the IIS */
|
|
|
|
IISCON |= (1<<0);
|
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
enable_fiq();
|
2006-12-29 02:49:12 +00:00
|
|
|
}
|
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
static void pcm_play_dma_stop_fiq(void)
|
2006-12-29 02:49:12 +00:00
|
|
|
{
|
2007-06-24 20:41:27 +00:00
|
|
|
INTMSK |= (1<<19); /* mask the DMA interrupt */
|
|
|
|
IISCON &= ~(1<<5); /* disable fifo request */
|
|
|
|
DMASKTRIG2 = 0x4; /* De-Activate the DMA channel */
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-01-16 03:36:32 +00:00
|
|
|
/* 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)) ;
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-01-16 03:36:32 +00:00
|
|
|
pcm_playing = false;
|
2007-05-19 19:30:08 +00:00
|
|
|
pcm_paused = false;
|
2007-01-16 03:36:32 +00:00
|
|
|
}
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-01-16 03:36:32 +00:00
|
|
|
/* Disconnect the IIS clock */
|
2007-01-04 11:36:25 +00:00
|
|
|
CLKCON &= ~(1<<17);
|
2007-05-02 22:33:24 +00:00
|
|
|
}
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-03 12:39:36 +00:00
|
|
|
void fiq_handler(void)
|
2007-05-02 22:33:24 +00:00
|
|
|
{
|
2007-05-03 12:39:36 +00:00
|
|
|
/* 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 */
|
|
|
|
register pcm_more_callback_type get_more; /* No stack for this */
|
|
|
|
unsigned char *next_start; /* sp + #0 */
|
|
|
|
size_t next_size; /* sp + #4 */
|
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
/* clear any pending interrupt */
|
|
|
|
SRCPND = (1<<19);
|
|
|
|
|
|
|
|
/* Buffer empty. Try to get more. */
|
2007-05-03 12:39:36 +00:00
|
|
|
get_more = pcm_callback_for_more;
|
|
|
|
if (get_more == NULL)
|
2007-05-02 22:33:24 +00:00
|
|
|
{
|
2007-05-03 12:39:36 +00:00
|
|
|
/* Callback missing */
|
2007-05-02 22:33:24 +00:00
|
|
|
pcm_play_dma_stop_fiq();
|
2007-05-03 12:39:36 +00:00
|
|
|
goto fiq_exit;
|
2007-05-02 22:33:24 +00:00
|
|
|
}
|
|
|
|
|
2007-05-03 12:39:36 +00:00
|
|
|
next_size = 0;
|
|
|
|
get_more(&next_start, &next_size);
|
2007-05-02 22:33:24 +00:00
|
|
|
|
2007-05-03 12:39:36 +00:00
|
|
|
if (next_size == 0)
|
2007-05-02 22:33:24 +00:00
|
|
|
{
|
|
|
|
/* No more DMA to do */
|
|
|
|
pcm_play_dma_stop_fiq();
|
2007-05-03 12:39:36 +00:00
|
|
|
goto fiq_exit;
|
2007-05-02 22:33:24 +00:00
|
|
|
}
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-03 12:39:36 +00:00
|
|
|
/* 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 */
|
2006-12-29 02:49:12 +00:00
|
|
|
}
|
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
/* Disconnect the DMA and wait for the FIFO to clear */
|
|
|
|
void pcm_play_dma_stop(void)
|
|
|
|
{
|
|
|
|
disable_fiq();
|
|
|
|
pcm_play_dma_stop_fiq();
|
|
|
|
}
|
2006-12-29 02:49:12 +00:00
|
|
|
|
|
|
|
void pcm_play_pause_pause(void)
|
|
|
|
{
|
2007-01-16 03:36:32 +00:00
|
|
|
/* stop servicing refills */
|
2007-05-02 22:33:24 +00:00
|
|
|
int oldstatus = set_fiq_status(FIQ_DISABLED);
|
2007-06-24 20:41:27 +00:00
|
|
|
INTMSK |= (1<<19); /* mask interrupt request */
|
|
|
|
IISCON &= ~(1<<5); /* turn off FIFO request */
|
|
|
|
DMASKTRIG2 = 0x4; /* stop DMA at end of atomic transfer */
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-06-24 20:41:27 +00:00
|
|
|
if (pcm_playing)
|
|
|
|
{
|
|
|
|
/* playing - wait for the FIFO to empty */
|
|
|
|
while (IISCON & (1<<7)) ;
|
|
|
|
}
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-06-24 20:41:27 +00:00
|
|
|
set_fiq_status(oldstatus);
|
|
|
|
}
|
2006-12-29 02:49:12 +00:00
|
|
|
|
|
|
|
void pcm_play_pause_unpause(void)
|
|
|
|
{
|
2007-01-16 03:36:32 +00:00
|
|
|
/* refill buffer and keep going */
|
2007-05-02 22:33:24 +00:00
|
|
|
int oldstatus = set_fiq_status(FIQ_DISABLED);
|
|
|
|
_pcm_apply_settings();
|
2007-06-24 20:41:27 +00:00
|
|
|
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 */
|
|
|
|
}
|
2007-05-02 22:33:24 +00:00
|
|
|
set_fiq_status(oldstatus);
|
2006-12-29 02:49:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
void pcm_set_frequency(unsigned int frequency)
|
|
|
|
{
|
|
|
|
switch(frequency)
|
|
|
|
{
|
|
|
|
case SAMPR_11:
|
2007-01-18 13:48:06 +00:00
|
|
|
sr_ctrl = GIGABEAT_11025HZ;
|
|
|
|
break;
|
2006-12-29 02:49:12 +00:00
|
|
|
case SAMPR_22:
|
2007-01-18 13:48:06 +00:00
|
|
|
sr_ctrl = GIGABEAT_22050HZ;
|
|
|
|
break;
|
2006-12-29 02:49:12 +00:00
|
|
|
default:
|
2007-01-18 13:48:06 +00:00
|
|
|
frequency = SAMPR_44;
|
2006-12-29 02:49:12 +00:00
|
|
|
case SAMPR_44:
|
2007-01-18 13:48:06 +00:00
|
|
|
sr_ctrl = GIGABEAT_44100HZ;
|
|
|
|
break;
|
2006-12-29 02:49:12 +00:00
|
|
|
case SAMPR_88:
|
2007-01-18 13:48:06 +00:00
|
|
|
sr_ctrl = GIGABEAT_88200HZ;
|
|
|
|
break;
|
2006-12-29 02:49:12 +00:00
|
|
|
}
|
2007-01-18 13:48:06 +00:00
|
|
|
|
2006-12-29 02:49:12 +00:00
|
|
|
pcm_freq = frequency;
|
|
|
|
}
|
|
|
|
|
|
|
|
size_t pcm_get_bytes_waiting(void)
|
|
|
|
{
|
2007-06-24 20:41:27 +00:00
|
|
|
/* lie a little and only return full pairs */
|
|
|
|
return (DSTAT2 & 0xFFFFE) * 2;
|
2006-12-29 02:49:12 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
/** **/
|
|
|
|
|
|
|
|
void pcm_mute(bool mute)
|
|
|
|
{
|
|
|
|
audiohw_mute(mute);
|
|
|
|
if (mute)
|
|
|
|
sleep(HZ/16);
|
|
|
|
}
|
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
/**
|
|
|
|
* Return playback peaks - Peaks ahead in the DMA buffer based upon the
|
|
|
|
* calling period to attempt to compensate for
|
|
|
|
* delay.
|
2006-12-29 02:49:12 +00:00
|
|
|
*/
|
|
|
|
void pcm_calculate_peaks(int *left, int *right)
|
|
|
|
{
|
2007-05-02 22:33:24 +00:00
|
|
|
static unsigned long last_peak_tick = 0;
|
|
|
|
static unsigned long frame_period = 0;
|
|
|
|
static int peaks_l = 0, peaks_r = 0;
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
/* Throttled peak ahead based on calling period */
|
|
|
|
unsigned long period = current_tick - last_peak_tick;
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
/* Keep reasonable limits on period */
|
|
|
|
if (period < 1)
|
|
|
|
period = 1;
|
|
|
|
else if (period > HZ/5)
|
|
|
|
period = HZ/5;
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
frame_period = (3*frame_period + period) >> 2;
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
last_peak_tick = current_tick;
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
if (pcm_playing && !pcm_paused)
|
|
|
|
{
|
|
|
|
unsigned long *addr = (unsigned long *)DCSRC2;
|
|
|
|
long samples = DSTAT2;
|
|
|
|
long samp_frames;
|
|
|
|
|
2007-06-24 20:41:27 +00:00
|
|
|
addr = (unsigned long *)((unsigned long)addr & ~3);
|
2007-05-02 22:33:24 +00:00
|
|
|
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);
|
2006-12-29 02:49:12 +00:00
|
|
|
}
|
|
|
|
}
|
2007-05-02 22:33:24 +00:00
|
|
|
else
|
|
|
|
{
|
|
|
|
peaks_l = peaks_r = 0;
|
|
|
|
}
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
if (left)
|
|
|
|
*left = peaks_l;
|
2006-12-29 02:49:12 +00:00
|
|
|
|
2007-05-02 22:33:24 +00:00
|
|
|
if (right)
|
|
|
|
*right = peaks_r;
|
|
|
|
} /* pcm_calculate_peaks */
|