| /*************************************************************************** |
| * __________ __ ___. |
| * 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 |
| /* setup I2S interrupt for FIQ */ |
| outl(inl(0x6000402c) | I2S_MASK, 0x6000402c); |
| outl(I2S_MASK, 0x60004024); |
| #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); |
| outl(I2S_MASK, 0x60004024); |
| |
| /* interrupt on full fifo */ |
| outl(inl(0x70002800) | 0x1, 0x70002800); |
| |
| /* enable record fifo */ |
| outl(inl(0x70002800) | 0x10000000, 0x70002800); |
| |
| 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; |
| } |
| } |