| /*************************************************************************** |
| * __________ __ ___. |
| * 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" |
| #include "pcm-internal.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 void *dma_start_addr; /* Pointer to callback buffer */ |
| static size_t dma_start_size; /* Size of callback buffer */ |
| static void *dma_sub_addr; /* Pointer to sub buffer */ |
| static size_t dma_rem_size; /* Remaining size - in 4*32 bits */ |
| static size_t play_sub_size; /* size of current subtransfer */ |
| static void dma_callback(void); |
| static int locked = 0; |
| static bool is_playing = false; |
| static bool play_callback_pending = false; |
| |
| /* Mask the DMA interrupt */ |
| void pcm_play_lock(void) |
| { |
| ++locked; |
| } |
| |
| /* Unmask the DMA interrupt if enabled */ |
| void pcm_play_unlock(void) |
| { |
| if(--locked == 0 && is_playing) |
| { |
| int old = disable_irq_save(); |
| if(play_callback_pending) |
| { |
| play_callback_pending = false; |
| dma_callback(); |
| } |
| restore_irq(old); |
| } |
| } |
| |
| static void play_start_pcm(void) |
| { |
| const void *addr = dma_sub_addr; |
| size_t size = dma_rem_size; |
| if(size > MAX_TRANSFER) |
| size = MAX_TRANSFER; |
| |
| play_sub_size = size; |
| |
| 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) |
| { |
| dma_sub_addr += play_sub_size; |
| dma_rem_size -= play_sub_size; |
| play_sub_size = 0; /* Might get called again if locked */ |
| |
| if(locked) |
| { |
| play_callback_pending = is_playing; |
| return; |
| } |
| |
| if(!dma_rem_size) |
| { |
| pcm_play_get_more_callback(&dma_start_addr, &dma_start_size); |
| |
| if (!dma_start_size) |
| return; |
| |
| dma_sub_addr = dma_start_addr; |
| dma_rem_size = dma_start_size; |
| |
| /* force writeback */ |
| clean_dcache_range(dma_start_addr, dma_start_size); |
| play_start_pcm(); |
| pcm_play_dma_started_callback(); |
| } |
| else |
| { |
| play_start_pcm(); |
| } |
| } |
| |
| void pcm_play_dma_start(const void *addr, size_t size) |
| { |
| dma_start_addr = (void*)addr; |
| dma_start_size = size; |
| dma_sub_addr = dma_start_addr; |
| dma_rem_size = size; |
| |
| bitset32(&CGU_PERI, CGU_I2SOUT_APB_CLOCK_ENABLE); |
| CGU_AUDIO |= (1<<11); |
| |
| dma_retain(); |
| |
| is_playing = true; |
| |
| /* force writeback */ |
| clean_dcache_range(dma_start_addr, dma_start_size); |
| play_start_pcm(); |
| } |
| |
| void pcm_play_dma_stop(void) |
| { |
| is_playing = false; |
| dma_disable_channel(1); |
| |
| /* Ensure byte counts read back 0 */ |
| DMAC_CH_SRC_ADDR(1) = 0; |
| dma_start_addr = NULL; |
| dma_start_size = 0; |
| dma_rem_size = 0; |
| |
| dma_release(); |
| |
| bitclr32(&CGU_PERI, CGU_I2SOUT_APB_CLOCK_ENABLE); |
| CGU_AUDIO &= ~(1<<11); |
| |
| play_callback_pending = false; |
| } |
| |
| void pcm_play_dma_pause(bool pause) |
| { |
| is_playing = !pause; |
| |
| if(pause) |
| { |
| dma_pause_channel(1); |
| |
| /* if producer's buffer finished, upper layer starts anew */ |
| if (dma_rem_size == 0) |
| play_callback_pending = false; |
| } |
| else |
| { |
| if (play_sub_size != 0) |
| dma_resume_channel(1); |
| /* else unlock calls the callback if sub buffers remain */ |
| } |
| } |
| |
| void pcm_play_dma_init(void) |
| { |
| bitset32(&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 &= ~(1 << 23); /* clear I2SI_MCLK_EN */ |
| cgu_audio &= ~(1 << 24); /* clear I2SI_MCLK2PAD_EN */ |
| CGU_AUDIO = cgu_audio; /* write back register */ |
| } |
| |
| size_t pcm_get_bytes_waiting(void) |
| { |
| int oldstatus = disable_irq_save(); |
| size_t addr = DMAC_CH_SRC_ADDR(1); |
| size_t start_addr = (size_t)dma_start_addr; |
| size_t start_size = dma_start_size; |
| restore_interrupt(oldstatus); |
| |
| return start_size - addr + start_addr; |
| } |
| |
| const void * pcm_play_dma_get_peak_buffer(int *count) |
| { |
| int oldstatus = disable_irq_save(); |
| size_t addr = DMAC_CH_SRC_ADDR(1); |
| size_t start_addr = (size_t)dma_start_addr; |
| size_t start_size = dma_start_size; |
| restore_interrupt(oldstatus); |
| |
| *count = (start_size - addr + start_addr) >> 2; |
| return (void*)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 bool is_recording = false; |
| static bool rec_callback_pending = false; |
| static void *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 |
| |
| |
| void pcm_rec_lock(void) |
| { |
| ++rec_locked; |
| } |
| |
| |
| void pcm_rec_unlock(void) |
| { |
| if(--rec_locked == 0 && is_recording) |
| { |
| int old = disable_irq_save(); |
| if(rec_callback_pending) |
| { |
| rec_callback_pending = false; |
| rec_dma_callback(); |
| } |
| restore_irq(old); |
| } |
| } |
| |
| |
| 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 CONFIG_CPU == AS3525 |
| /* if needed, duplicate samples of the working channel until the given bound */ |
| static inline void mono2stereo(int16_t *end) |
| { |
| 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 */ |
| } |
| #endif /* CONFIG_CPU == AS3525 */ |
| |
| static void rec_dma_callback(void) |
| { |
| if(rec_dma_transfer_size) |
| { |
| rec_dma_size -= rec_dma_transfer_size; |
| rec_dma_start_addr += rec_dma_transfer_size; |
| |
| /* don't act like we just transferred data when we are called from |
| * pcm_rec_unlock() */ |
| rec_dma_transfer_size = 0; |
| |
| #if CONFIG_CPU == AS3525 |
| /* the 2nd channel is silent when recording microphone on as3525v1 */ |
| mono2stereo(AS3525_UNCACHED_ADDR((int16_t*)rec_dma_start_addr)); |
| #endif |
| |
| if(locked) |
| { |
| rec_callback_pending = is_recording; |
| return; |
| } |
| } |
| |
| if(!rec_dma_size) |
| { |
| pcm_rec_more_ready_callback(0, &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) |
| { |
| is_recording = false; |
| dma_disable_channel(1); |
| dma_release(); |
| rec_dma_size = 0; |
| |
| I2SIN_CONTROL &= ~(1<<11); /* disable dma */ |
| |
| CGU_AUDIO &= ~(1<<11); |
| bitclr32(&CGU_PERI, CGU_I2SIN_APB_CLOCK_ENABLE | |
| CGU_I2SOUT_APB_CLOCK_ENABLE); |
| |
| rec_callback_pending = false; |
| } |
| |
| |
| 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(); |
| |
| bitset32(&CGU_PERI, CGU_I2SIN_APB_CLOCK_ENABLE | |
| CGU_I2SOUT_APB_CLOCK_ENABLE); |
| CGU_AUDIO |= (1<<11); |
| |
| I2SIN_CONTROL |= (1<<11)|(1<<5); /* enable dma, 14bits samples */ |
| |
| is_recording = true; |
| |
| rec_dma_start(); |
| } |
| |
| |
| void pcm_rec_dma_close(void) |
| { |
| } |
| |
| |
| void pcm_rec_dma_init(void) |
| { |
| /* 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) |
| { |
| #if CONFIG_CPU == AS3525 |
| /* |
| * We need to prevent the DMA callback from kicking in while we are |
| * faking the right channel with data from left channel. |
| */ |
| |
| int old = disable_irq_save(); |
| int16_t *addr = AS3525_UNCACHED_ADDR((int16_t *)DMAC_CH_DST_ADDR(1)); |
| mono2stereo(addr); |
| restore_irq(old); |
| |
| return addr; |
| |
| #else |
| /* Microphone recording is stereo on as3525v2 */ |
| return AS3525_UNCACHED_ADDR((int16_t *)DMAC_CH_DST_ADDR(1)); |
| #endif |
| } |
| |
| #endif /* HAVE_RECORDING */ |