169ebdbda7
Also add PortalPlayer SoC version to the HW info debug screen. git-svn-id: svn://svn.rockbox.org/rockbox/trunk@12575 a1c6a512-1295-4272-9138-f99709370657
583 lines
16 KiB
C
583 lines
16 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2006 by Michael Sevakis
|
|
*
|
|
* All files in this archive are subject to the GNU General Public License.
|
|
* See the file COPYING in the source tree root for full license agreement.
|
|
*
|
|
* This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
|
* KIND, either express or implied.
|
|
*
|
|
****************************************************************************/
|
|
#include <stdlib.h>
|
|
#include "system.h"
|
|
#include "kernel.h"
|
|
#include "logf.h"
|
|
#include "audio.h"
|
|
#if defined(HAVE_WM8975)
|
|
#include "wm8975.h"
|
|
#elif defined(HAVE_WM8758)
|
|
#include "wm8758.h"
|
|
#elif defined(HAVE_WM8731) || defined(HAVE_WM8721)
|
|
#include "wm8731l.h"
|
|
#endif
|
|
|
|
|
|
|
|
/* peaks */
|
|
#ifdef HAVE_RECORDING
|
|
static unsigned long *rec_peak_addr;
|
|
static int rec_peak_left, rec_peak_right;
|
|
#endif
|
|
|
|
/** DMA **/
|
|
#if CONFIG_CPU == PP5020
|
|
#define FIFO_FREE_COUNT ((IISFIFO_CFG & 0x3f000000) >> 24)
|
|
#elif CONFIG_CPU == PP5002
|
|
#define FIFO_FREE_COUNT ((IISFIFO_CFG & 0x7800000) >> 23)
|
|
#elif CONFIG_CPU == PP5024
|
|
#define FIFO_FREE_COUNT 4 /* TODO: make this sensible */
|
|
#endif
|
|
|
|
/****************************************************************************
|
|
** 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 CONFIG_CPU == PP5020 || CONFIG_CPU == PP5002
|
|
void fiq(void) ICODE_ATTR __attribute__((naked));
|
|
void fiq(void)
|
|
{
|
|
/* 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 */
|
|
"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 */
|
|
"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, [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 /* !(CONFIG_CPU == PP5020 || CONFIG_CPU == PP5002) */
|
|
void fiq(void) ICODE_ATTR __attribute__ ((interrupt ("FIQ")));
|
|
void fiq(void)
|
|
{
|
|
/* Clear interrupt */
|
|
#if CONFIG_CPU == PP5020
|
|
IISCONFIG &= ~0x2;
|
|
#elif CONFIG_CPU == PP5002
|
|
inl(0xcf001040);
|
|
IISFIFO_CFG &= ~(1<<9);
|
|
#endif
|
|
|
|
do {
|
|
while (p_size) {
|
|
if (FIFO_FREE_COUNT < 2) {
|
|
/* Enable interrupt */
|
|
#if CONFIG_CPU == PP5020
|
|
IISCONFIG |= 0x2;
|
|
#elif CONFIG_CPU == PP5002
|
|
IISFIFO_CFG |= (1<<9);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
IISFIFO_WR = (*(p++))<<16;
|
|
IISFIFO_WR = (*(p++))<<16;
|
|
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 /* CONFIG_CPU == PP5020 || CONFIG_CPU == PP5002 */
|
|
|
|
void pcm_play_dma_start(const void *addr, size_t size)
|
|
{
|
|
p=(unsigned short*)addr;
|
|
p_size=size;
|
|
|
|
pcm_playing = true;
|
|
|
|
#if CONFIG_CPU == PP5020
|
|
CPU_INT_PRIORITY |= I2S_MASK; /* FIQ priority for I2S */
|
|
CPU_INT_EN = I2S_MASK; /* Enable I2S interrupt */
|
|
#elif CONFIG_CPU == PP5024
|
|
#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();
|
|
|
|
/* Enable playback FIFO */
|
|
#if CONFIG_CPU == PP5020
|
|
IISCONFIG |= 0x20000000;
|
|
#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 */
|
|
#if CONFIG_CPU == PP5020
|
|
IISCONFIG |= 0x2;
|
|
#elif CONFIG_CPU == PP5002
|
|
IISFIFO_CFG |= (1<<9);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
IISFIFO_WR = (*(p++))<<16;
|
|
IISFIFO_WR = (*(p++))<<16;
|
|
p_size-=4;
|
|
}
|
|
}
|
|
|
|
/* Stops the DMA transfer and interrupt */
|
|
void pcm_play_dma_stop(void)
|
|
{
|
|
pcm_playing = false;
|
|
|
|
#if CONFIG_CPU == PP5020
|
|
|
|
/* Disable playback FIFO */
|
|
IISCONFIG &= ~0x20000000;
|
|
|
|
/* Disable the interrupt */
|
|
IISCONFIG &= ~0x2;
|
|
|
|
#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 the interrupt */
|
|
IISCONFIG &= ~0x2;
|
|
/* Disable playback FIFO */
|
|
IISCONFIG &= ~0x20000000;
|
|
#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();
|
|
|
|
/* Enable playback FIFO */
|
|
#if CONFIG_CPU == PP5020
|
|
IISCONFIG |= 0x20000000;
|
|
#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 */
|
|
#if CONFIG_CPU == PP5020
|
|
IISCONFIG |= 0x2;
|
|
#elif CONFIG_CPU == PP5002
|
|
IISFIFO_CFG |= (1<<9);
|
|
#endif
|
|
return;
|
|
}
|
|
|
|
IISFIFO_WR = (*(p++))<<16;
|
|
IISFIFO_WR = (*(p++))<<16;
|
|
p_size-=4;
|
|
}
|
|
}
|
|
|
|
void pcm_set_frequency(unsigned int frequency)
|
|
{
|
|
(void)frequency;
|
|
pcm_freq = HW_SAMPR_DEFAULT;
|
|
}
|
|
|
|
size_t pcm_get_bytes_waiting(void)
|
|
{
|
|
return p_size;
|
|
}
|
|
|
|
#ifdef HAVE_PP5024_CODEC
|
|
void pcm_init(void)
|
|
{
|
|
}
|
|
#else
|
|
void pcm_init(void)
|
|
{
|
|
pcm_playing = false;
|
|
pcm_paused = false;
|
|
pcm_callback_for_more = NULL;
|
|
|
|
/* Initialize default register values. */
|
|
audiohw_init();
|
|
|
|
/* Power on */
|
|
audiohw_enable_output(true);
|
|
|
|
/* Unmute the master channel (DAC should be at zero point now). */
|
|
audiohw_mute(false);
|
|
|
|
/* Call pcm_play_dma_stop to initialize everything. */
|
|
pcm_play_dma_stop();
|
|
}
|
|
#endif /* HAVE_PP5024_CODEC */
|
|
|
|
|
|
/****************************************************************************
|
|
** Recording DMA transfer
|
|
**/
|
|
#ifdef HAVE_RECORDING
|
|
static short peak_l, peak_r IBSS_ATTR;
|
|
|
|
void fiq_record(void) ICODE_ATTR __attribute__ ((interrupt ("FIQ")));
|
|
void fiq_record(void)
|
|
{
|
|
short value;
|
|
pcm_more_callback_type2 more_ready;
|
|
int status = 0;
|
|
|
|
/* Clear interrupt */
|
|
#if CONFIG_CPU == PP5020
|
|
IISCONFIG &= ~0x01;
|
|
#elif CONFIG_CPU == PP5002
|
|
/* TODO */
|
|
#endif
|
|
|
|
while (p_size > 0) {
|
|
if (FIFO_FREE_COUNT < 2) {
|
|
/* enable interrupt */
|
|
#if CONFIG_CPU == PP5020
|
|
IISCONFIG |= 0x01;
|
|
#elif CONFIG_CPU == PP5002
|
|
/* TODO */
|
|
#endif
|
|
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;
|
|
}
|
|
}
|
|
|
|
more_ready = pcm_callback_more_ready;
|
|
|
|
if (more_ready != NULL && more_ready(status) >= 0)
|
|
return;
|
|
|
|
/* Finished recording */
|
|
pcm_rec_dma_stop();
|
|
}
|
|
|
|
/* Continue transferring data in */
|
|
void pcm_record_more(void *start, size_t size)
|
|
{
|
|
rec_peak_addr = (unsigned long *)start; /* Start peaking at dest */
|
|
p = start;
|
|
p_size = size; /* Bytes to transfer */
|
|
#if CONFIG_CPU == PP5020
|
|
IISCONFIG |= 0x01;
|
|
#elif CONFIG_CPU == PP5002
|
|
/* TODO */
|
|
#endif
|
|
}
|
|
|
|
void pcm_rec_dma_stop(void)
|
|
{
|
|
logf("pcm_rec_dma_stop");
|
|
|
|
/* disable fifo */
|
|
IISCONFIG &= ~0x10000000;
|
|
|
|
disable_fiq();
|
|
|
|
pcm_recording = false;
|
|
}
|
|
|
|
void pcm_rec_dma_start(void *addr, size_t size)
|
|
{
|
|
logf("pcm_rec_dma_start");
|
|
|
|
pcm_recording = true;
|
|
|
|
peak_l = peak_r = 0;
|
|
p_size = size;
|
|
p = addr;
|
|
|
|
/* setup FIQ */
|
|
outl(inl(0x6000402c) | I2S_MASK, 0x6000402c);
|
|
CPU_INT_EN = I2S_MASK;
|
|
|
|
/* interrupt on full fifo */
|
|
IISCONFIG |= 0x1;
|
|
|
|
/* enable record fifo */
|
|
IISCONFIG |= 0x10000000;
|
|
|
|
set_fiq_handler(fiq_record);
|
|
enable_fiq();
|
|
}
|
|
|
|
void pcm_close_recording(void)
|
|
{
|
|
logf("pcm_close_recording");
|
|
|
|
pcm_rec_dma_stop();
|
|
|
|
#if (CONFIG_CPU == PP5020)
|
|
disable_fiq();
|
|
|
|
/* disable fifo */
|
|
IISCONFIG &= ~0x10000000;
|
|
|
|
/* Clear interrupt */
|
|
IISCONFIG &= ~0x01;
|
|
#endif
|
|
} /* pcm_close_recording */
|
|
|
|
void pcm_init_recording(void)
|
|
{
|
|
logf("pcm_init_recording");
|
|
|
|
pcm_recording = false;
|
|
pcm_callback_more_ready = NULL;
|
|
|
|
#if (CONFIG_CPU == PP5020)
|
|
#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 */
|
|
*((unsigned int*)(15*4)) = (unsigned int)&fiq_record;
|
|
#endif
|
|
|
|
pcm_rec_dma_stop();
|
|
} /* pcm_init */
|
|
|
|
void pcm_calculate_rec_peaks(int *left, int *right)
|
|
{
|
|
*left = rec_peak_left;
|
|
*right = rec_peak_right;
|
|
}
|
|
#endif
|
|
|
|
/*
|
|
* 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;
|
|
}
|
|
}
|