| /*************************************************************************** |
| * __________ __ ___. |
| * 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 "system.h" |
| #include "kernel.h" |
| #include "logf.h" |
| #include "audio.h" |
| #include "wm8975.h" |
| #include "file.h" |
| #include "mmu-meg-fx.h" |
| |
| static int pcm_freq = HW_SAMPR_DEFAULT; /* 44.1 is default */ |
| |
| #define GIGABEAT_8000HZ 0x4d |
| #define GIGABEAT_11025HZ 0x32 |
| #define GIGABEAT_12000HZ 0x61 |
| #define GIGABEAT_16000HZ 0x55 |
| #define GIGABEAT_22050HZ 0x36 |
| #define GIGABEAT_24000HZ 0x79 |
| #define GIGABEAT_32000HZ 0x59 |
| #define GIGABEAT_44100HZ 0x22 |
| #define GIGABEAT_48000HZ 0x41 |
| #define GIGABEAT_88200HZ 0x3e |
| #define GIGABEAT_96000HZ 0x5d |
| |
| #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"))); |
| 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(); |
| 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(); |
| } |
| |
| } |
| |
| |
| |
| void pcm_init(void) |
| { |
| pcm_playing = false; |
| pcm_paused = false; |
| pcm_callback_for_more = NULL; |
| |
| audiohw_init(); |
| audiohw_enable_output(true); |
| |
| /* cannot use the WM8975 defaults since our clock is not the same */ |
| /* the input master clock is 16.9344MHz - we can divide exact for that */ |
| 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_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; |
| |
| 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; |
| |
| set_fiq_handler(fiq); |
| enable_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); |
| |
| pcm_playing = true; |
| |
| /* start the IIS */ |
| IISCON |= (1<<0); |
| |
| } |
| |
| |
| |
| /* Disconnect the DMA and wait for the FIFO to clear */ |
| void pcm_play_dma_stop(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); |
| |
| disable_fiq(); |
| |
| } |
| |
| |
| |
| void pcm_play_pause_pause(void) |
| { |
| /* stop servicing refills */ |
| INTMSK |= (1<<19); |
| } |
| |
| |
| |
| void pcm_play_pause_unpause(void) |
| { |
| /* refill buffer and keep going */ |
| INTMSK &= ~(1<<19); |
| } |
| |
| void pcm_set_frequency(unsigned int frequency) |
| { |
| int sr_ctrl; |
| |
| switch(frequency) |
| { |
| case SAMPR_8: |
| sr_ctrl = GIGABEAT_8000HZ; |
| break; |
| case SAMPR_11: |
| sr_ctrl = GIGABEAT_11025HZ; |
| break; |
| case SAMPR_12: |
| sr_ctrl = GIGABEAT_12000HZ; |
| break; |
| case SAMPR_16: |
| sr_ctrl = GIGABEAT_16000HZ; |
| break; |
| case SAMPR_22: |
| sr_ctrl = GIGABEAT_22050HZ; |
| break; |
| case SAMPR_24: |
| sr_ctrl = GIGABEAT_24000HZ; |
| break; |
| case SAMPR_32: |
| sr_ctrl = GIGABEAT_32000HZ; |
| break; |
| default: |
| frequency = SAMPR_44; |
| case SAMPR_44: |
| sr_ctrl = GIGABEAT_44100HZ; |
| break; |
| case SAMPR_48: |
| sr_ctrl = GIGABEAT_48000HZ; |
| break; |
| case SAMPR_88: |
| sr_ctrl = GIGABEAT_88200HZ; |
| break; |
| case SAMPR_96: |
| sr_ctrl = GIGABEAT_96000HZ; |
| break; |
| } |
| |
| audiohw_set_sample_rate(sr_ctrl); |
| pcm_freq = frequency; |
| } |
| |
| |
| |
| size_t pcm_get_bytes_waiting(void) |
| { |
| return (DSTAT2 & 0xFFFFF) * 2; |
| } |
| |
| |
| |
| /* dummy functions for those not actually supporting all this yet */ |
| void pcm_apply_settings(bool reset) |
| { |
| (void)reset; |
| } |
| |
| void pcm_set_monitor(int monitor) |
| { |
| (void)monitor; |
| } |
| /** **/ |
| |
| void pcm_mute(bool mute) |
| { |
| audiohw_mute(mute); |
| if (mute) |
| sleep(HZ/16); |
| } |
| |
| /* |
| * 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; |
| } |
| } |