/*************************************************************************** * __________ __ ___. * Open \______ \ ____ ____ | | _\_ |__ _______ ___ * Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ / * Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < < * Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \ * \/ \/ \/ \/ \/ * $Id$ * * Copyright © 2008-2009 Rafaël Carré * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or (at your option) any later version. * * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * ****************************************************************************/ #include "system.h" #include "audio.h" #include "string.h" #include "as3525.h" #include "pl081.h" #include "dma-target.h" #include "clock-target.h" #include "panic.h" #include "as3514.h" #include "audiohw.h" #include "mmu-arm.h" #define MAX_TRANSFER (4*((1<<11)-1)) /* maximum data we can transfer via DMA * i.e. 32 bits at once (size of I2SO_DATA) * and the number of 32bits words has to * fit in 11 bits of DMA register */ static unsigned char *dma_start_addr; static size_t dma_size; /* in 4*32 bits */ static void dma_callback(void); static int locked = 0; static int play_irq_state; /* Mask the DMA interrupt */ void pcm_play_lock(void) { if(++locked == 1) play_irq_state = disable_irq_save(); } /* Unmask the DMA interrupt if enabled */ void pcm_play_unlock(void) { if(--locked == 0) restore_irq(play_irq_state); } static void play_start_pcm(void) { const unsigned char* addr = dma_start_addr; size_t size = dma_size; if(size > MAX_TRANSFER) size = MAX_TRANSFER; dma_size -= size; dma_start_addr += size; clean_dcache_range((void*)addr, size); /* force write back */ dma_enable_channel(1, (void*)addr, (void*)I2SOUT_DATA, DMA_PERI_I2SOUT, DMAC_FLOWCTRL_DMAC_MEM_TO_PERI, true, false, size >> 2, DMA_S1, dma_callback); } static void dma_callback(void) { if(!dma_size) { pcm_play_get_more_callback((void **)&dma_start_addr, &dma_size); if (!dma_size) return; } play_start_pcm(); } void pcm_play_dma_start(const void *addr, size_t size) { dma_size = size; dma_start_addr = (unsigned char*)addr; CGU_PERI |= CGU_I2SOUT_APB_CLOCK_ENABLE; CGU_AUDIO |= (1<<11); dma_retain(); play_start_pcm(); } void pcm_play_dma_stop(void) { dma_disable_channel(1); dma_size = 0; dma_release(); CGU_PERI &= ~CGU_I2SOUT_APB_CLOCK_ENABLE; CGU_AUDIO &= ~(1<<11); } void pcm_play_dma_pause(bool pause) { if(pause) dma_disable_channel(1); else play_start_pcm(); } void pcm_play_dma_init(void) { CGU_PERI |= CGU_I2SOUT_APB_CLOCK_ENABLE; I2SOUT_CONTROL = (1<<6)|(1<<3) /* enable dma, stereo */; audiohw_preinit(); } void pcm_postinit(void) { audiohw_postinit(); } /* divider is 9 bits but the highest one (for 8kHz) fit in 8 bits */ static const unsigned char divider[SAMPR_NUM_FREQ] = { [HW_FREQ_96] = ((AS3525_MCLK_FREQ/128 + SAMPR_96/2) / SAMPR_96) - 1, [HW_FREQ_88] = ((AS3525_MCLK_FREQ/128 + SAMPR_88/2) / SAMPR_88) - 1, [HW_FREQ_64] = ((AS3525_MCLK_FREQ/128 + SAMPR_64/2) / SAMPR_64) - 1, [HW_FREQ_48] = ((AS3525_MCLK_FREQ/128 + SAMPR_48/2) / SAMPR_48) - 1, [HW_FREQ_44] = ((AS3525_MCLK_FREQ/128 + SAMPR_44/2) / SAMPR_44) - 1, [HW_FREQ_32] = ((AS3525_MCLK_FREQ/128 + SAMPR_32/2) / SAMPR_32) - 1, [HW_FREQ_24] = ((AS3525_MCLK_FREQ/128 + SAMPR_24/2) / SAMPR_24) - 1, [HW_FREQ_22] = ((AS3525_MCLK_FREQ/128 + SAMPR_22/2) / SAMPR_22) - 1, [HW_FREQ_16] = ((AS3525_MCLK_FREQ/128 + SAMPR_16/2) / SAMPR_16) - 1, [HW_FREQ_12] = ((AS3525_MCLK_FREQ/128 + SAMPR_12/2) / SAMPR_12) - 1, [HW_FREQ_11] = ((AS3525_MCLK_FREQ/128 + SAMPR_11/2) / SAMPR_11) - 1, [HW_FREQ_8 ] = ((AS3525_MCLK_FREQ/128 + SAMPR_8 /2) / SAMPR_8 ) - 1, }; static inline unsigned char mclk_divider(void) { return divider[pcm_fsel]; } void pcm_dma_apply_settings(void) { int cgu_audio = CGU_AUDIO; /* read register */ cgu_audio &= ~(3 << 0); /* clear i2sout MCLK_SEL */ cgu_audio |= (AS3525_MCLK_SEL << 0); /* set i2sout MCLK_SEL */ cgu_audio &= ~(0x1ff << 2); /* clear i2sout divider */ cgu_audio |= mclk_divider() << 2; /* set new i2sout divider */ CGU_AUDIO = cgu_audio; /* write back register */ } size_t pcm_get_bytes_waiting(void) { return dma_size; } const void * pcm_play_dma_get_peak_buffer(int *count) { pcm_play_lock(); unsigned char *addr = (void*)DMAC_CH_SRC_ADDR(1); *count = (dma_size - (addr - dma_start_addr)) >> 2; pcm_play_unlock(); return AS3525_UNCACHED_ADDR(addr); } #ifdef HAVE_PCM_DMA_ADDRESS void * pcm_dma_addr(void *addr) { if (addr != NULL) addr = AS3525_UNCACHED_ADDR(addr); return addr; } #endif /**************************************************************************** ** Recording DMA transfer **/ #ifdef HAVE_RECORDING static int rec_locked = 0; static unsigned char *rec_dma_start_addr; static size_t rec_dma_size, rec_dma_transfer_size; static void rec_dma_callback(void); #if CONFIG_CPU == AS3525 /* points to the samples which need to be duplicated into the right channel */ static int16_t *mono_samples; #endif static int rec_irq_state; void pcm_rec_lock(void) { if(++rec_locked == 1) rec_irq_state = disable_irq_save(); } void pcm_rec_unlock(void) { if(--rec_locked == 0) restore_irq(rec_irq_state); } static void rec_dma_start(void) { rec_dma_transfer_size = rec_dma_size; /* We are limited to 8188 DMA transfers, and the recording core asks for * 8192 bytes. Avoid splitting 8192 bytes transfers in 8188 + 4 */ if(rec_dma_transfer_size > 4096) rec_dma_transfer_size = 4096; dma_enable_channel(1, (void*)I2SIN_DATA, rec_dma_start_addr, DMA_PERI_I2SIN, DMAC_FLOWCTRL_DMAC_PERI_TO_MEM, false, true, rec_dma_transfer_size >> 2, DMA_S4, rec_dma_callback); } /* if needed, duplicate samples of the working channel until the given bound */ static inline void mono2stereo(int16_t *end) { #if CONFIG_CPU == AS3525 if(audio_channels != 1) /* only for microphone */ return; #if 0 /* load pointer in a register and avoid updating it in each loop */ register int16_t *samples = mono_samples; do { int16_t left = *samples++; // load 1 sample of the left-channel *samples++ = left; // copy it in the right-channel } while(samples != end); mono_samples = samples; /* update pointer */ #else /* gcc doesn't use pre indexing : let's save 1 cycle */ int16_t left; asm ( "1: ldrh %0, [%1], #2 \n" // load 1 sample of the left-channel " strh %0, [%1], #2 \n" // copy it in the right-channel " cmp %1, %2 \n" // are we finished? " bne 1b \n" : "=r"(left), "+r"(mono_samples) : "r"(end) : "memory" ); #endif /* C / ASM */ #else /* microphone recording is stereo on as3525v2 */ (void)end; #endif } static void rec_dma_callback(void) { rec_dma_size -= rec_dma_transfer_size; rec_dma_start_addr += rec_dma_transfer_size; /* the 2nd channel is silent when recording microphone on as3525v1 */ mono2stereo(AS3525_UNCACHED_ADDR((int16_t*)rec_dma_start_addr)); if(!rec_dma_size) { pcm_rec_more_ready_callback(0, (void **)&rec_dma_start_addr, &rec_dma_size); if(rec_dma_size == 0) return; dump_dcache_range(rec_dma_start_addr, rec_dma_size); #if CONFIG_CPU == AS3525 mono_samples = AS3525_UNCACHED_ADDR((int16_t*)rec_dma_start_addr); #endif } rec_dma_start(); } void pcm_rec_dma_stop(void) { dma_disable_channel(1); dma_release(); rec_dma_size = 0; I2SIN_CONTROL &= ~(1<<11); /* disable dma */ CGU_AUDIO &= ~((1<<23)|(1<<11)); CGU_PERI &= ~(CGU_I2SIN_APB_CLOCK_ENABLE|CGU_I2SOUT_APB_CLOCK_ENABLE); } void pcm_rec_dma_start(void *addr, size_t size) { dump_dcache_range(addr, size); rec_dma_start_addr = addr; #if CONFIG_CPU == AS3525 mono_samples = AS3525_UNCACHED_ADDR(addr); #endif rec_dma_size = size; dma_retain(); CGU_PERI |= CGU_I2SIN_APB_CLOCK_ENABLE|CGU_I2SOUT_APB_CLOCK_ENABLE; CGU_AUDIO |= ((1<<23)|(1<<11)); I2SIN_CONTROL |= (1<<11)|(1<<5); /* enable dma, 14bits samples */ rec_dma_start(); } void pcm_rec_dma_close(void) { } void pcm_rec_dma_init(void) { int cgu_audio = CGU_AUDIO; /* read register */ cgu_audio &= ~(3 << 12); /* clear i2sin MCLK_SEL */ cgu_audio |= (AS3525_MCLK_SEL << 12); /* set i2sin MCLK_SEL */ cgu_audio &= ~(0x1ff << 14); /* clear i2sin divider */ cgu_audio |= mclk_divider() << 14; /* set new i2sin divider */ CGU_AUDIO = cgu_audio; /* write back register */ /* i2c clk src = I2SOUTIF, sdata src = AFE, * data valid at positive edge of SCLK */ I2SIN_CONTROL = (1<<2); I2SIN_MASK = 0; /* disables all interrupts */ } const void * pcm_rec_dma_get_peak_buffer(void) { pcm_rec_lock(); int16_t *addr = AS3525_UNCACHED_ADDR((int16_t *)DMAC_CH_DST_ADDR(1)); mono2stereo(addr); pcm_rec_unlock(); return addr; } #endif /* HAVE_RECORDING */