2b6dfdb34e
Change-Id: I6c66c7496db3db78e5c959414464826134dbe200
300 lines
9 KiB
C
300 lines
9 KiB
C
/***************************************************************************
|
|
* __________ __ ___.
|
|
* Open \______ \ ____ ____ | | _\_ |__ _______ ___
|
|
* Source | _// _ \_/ ___\| |/ /| __ \ / _ \ \/ /
|
|
* Jukebox | | ( <_> ) \___| < | \_\ ( <_> > < <
|
|
* Firmware |____|_ /\____/ \___ >__|_ \|___ /\____/__/\_ \
|
|
* \/ \/ \/ \/ \/
|
|
* $Id$
|
|
*
|
|
* Copyright (C) 2011 Marcin Bukat
|
|
* Copyright (C) 2011 Andrew Ryabinin
|
|
*
|
|
* 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 "panic.h"
|
|
#include "audiohw.h"
|
|
#include "sound.h"
|
|
#include "pcm-internal.h"
|
|
|
|
static int locked = 0;
|
|
|
|
/* Mask the DMA interrupt */
|
|
void pcm_play_lock(void)
|
|
{
|
|
if (++locked == 1)
|
|
{
|
|
int old = disable_irq_save();
|
|
INTC_IMR &= ~(1<<12); /* mask HDMA interrupt */
|
|
restore_irq(old);
|
|
}
|
|
}
|
|
|
|
/* Unmask the DMA interrupt if enabled */
|
|
void pcm_play_unlock(void)
|
|
{
|
|
if(--locked == 0)
|
|
{
|
|
int old = disable_irq_save();
|
|
INTC_IMR |= (1<<12); /* unmask HDMA interrupt */
|
|
restore_irq(old);
|
|
}
|
|
}
|
|
|
|
void pcm_play_dma_stop(void)
|
|
{
|
|
HDMA_CON0 = 0x00;
|
|
HDMA_ISR = 0x00;
|
|
|
|
locked = 1;
|
|
}
|
|
|
|
static void hdma_i2s_transfer(const void *addr, size_t size)
|
|
{
|
|
SCU_CLKCFG &= ~CLKCFG_HDMA; /* enable HDMA clock */
|
|
|
|
commit_discard_dcache_range(addr, size);
|
|
|
|
HDMA_ISRC0 = (uint32_t)addr; /* source address */
|
|
HDMA_IDST0 = (uint32_t)&I2S_TXR; /* i2s tx fifo */
|
|
HDMA_ICNT0 = (uint16_t)((size>>2) - 1); /* number of dma transactions
|
|
* of transfer size bytes
|
|
* (zero based)
|
|
*/
|
|
|
|
HDMA_ISR = ((1<<13) | /* mask ch1 accumulation overflow irq */
|
|
(1<<12) | /* mask ch0 accumulation overflow irq */
|
|
(1<<11) | /* mask ch1 page count down irq */
|
|
(0<<10) | /* UNMASK ch0 page count down irq */
|
|
(1<<9) | /* mask ch0 transfer irq */
|
|
(1<<8) | /* mask ch1 transfer irq */
|
|
(0<<5) | /* clear ch1 accumulation overflow flag */
|
|
(0<<4) | /* clear ch0 accumulation overflow flag */
|
|
(0<<3) | /* clear ch1 count down to zero flag */
|
|
(0<<2) | /* clear ch0 count down to zero flag */
|
|
(0<<1) | /* clear ch1 active flag */
|
|
(0<<0)); /* clear ch0 active flag */
|
|
|
|
HDMA_ISCNT0 = 0x07; /* slice size in transfer size units (zero base) */
|
|
|
|
HDMA_IPNCNTD0 = 0x01; /* page count */
|
|
|
|
HDMA_CON0 = ((0<<23) | /* page mode */
|
|
(1<<22) | /* slice mode */
|
|
(1<<21) | /* DMA enable */
|
|
(1<<18) | /* generate interrupt */
|
|
(0<<16) | /* on-the-fly is not supported by rk27xx */
|
|
(5<<13) | /* transfer mode inc8 */
|
|
(6<<9) | /* external hdreq from i2s tx */
|
|
(0<<7) | /* increment source address */
|
|
(1<<5) | /* fixed destination address */
|
|
(2<<3) | /* transfer size = 32bits word */
|
|
(0<<1) | /* command of software DMA (not relevant) */
|
|
(1<<0)); /* hardware trigger DMA mode */
|
|
}
|
|
|
|
void pcm_play_dma_start(const void *addr, size_t size)
|
|
{
|
|
/* Stop any DMA in progress */
|
|
pcm_play_dma_stop();
|
|
|
|
/* kick in DMA transfer */
|
|
hdma_i2s_transfer(addr, size);
|
|
}
|
|
|
|
/* pause DMA transfer by disabling clock to DMA module */
|
|
void pcm_play_dma_pause(bool pause)
|
|
{
|
|
if(pause)
|
|
{
|
|
SCU_CLKCFG |= CLKCFG_HDMA;
|
|
locked = 1;
|
|
}
|
|
else
|
|
{
|
|
SCU_CLKCFG &= ~CLKCFG_HDMA;
|
|
locked = 0;
|
|
}
|
|
}
|
|
|
|
static void i2s_init(void)
|
|
{
|
|
#if defined(HAVE_RK27XX_CODEC)
|
|
/* iomux I2S internal */
|
|
SCU_IOMUXA_CON &= ~(1<<19); /* i2s external bit */
|
|
SCU_IOMUXB_CON &= ~((1<<4) | /* i2s_mclk */
|
|
(1<<3) | /* i2s_sdo */
|
|
(1<<2) | /* i2s_sdi */
|
|
(1<<1) | /* i2s_lrck */
|
|
(1<<0)); /* i2s_bck */
|
|
#else
|
|
/* iomux I2S external */
|
|
SCU_IOMUXA_CON |= (1<<19); /* i2s external bit */
|
|
SCU_IOMUXB_CON |= ((1<<4) | /* i2s_mclk */
|
|
(1<<3) | /* i2s_sdo */
|
|
(1<<2) | /* i2s_sdi */
|
|
(1<<1) | /* i2s_lrck */
|
|
(1<<0)); /* i2s_bck */
|
|
#endif
|
|
|
|
/* enable i2s clocks */
|
|
SCU_CLKCFG &= ~(CLKCFG_PCLK_I2S | CLKCFG_I2S);
|
|
|
|
/* configure I2S module */
|
|
I2S_IER = 0; /* disable all i2s interrupts */
|
|
|
|
I2S_TXCTL = (1<<16) | /* LRCK/SCLK = 64 */
|
|
(4<<8) | /* MCLK/SCLK = 4 */
|
|
(1<<4) | /* 16bit samples */
|
|
(0<<3) | /* stereo */
|
|
(0<<1) | /* I2S IF */
|
|
#ifdef CODEC_SLAVE
|
|
(1<<0); /* master mode */
|
|
#else
|
|
(0<<0); /* slave mode */
|
|
#endif
|
|
|
|
/* the fifo is 16x32bits according to my tests
|
|
* while the docs state 32x32bits
|
|
*/
|
|
I2S_FIFOSTS = (1<<18) | /* Tx trigger level half full */
|
|
(1<<16); /* Rx trigger level half full */
|
|
|
|
I2S_OPR = (1<<17) | /* reset Tx */
|
|
(1<<16) | /* reset Rx */
|
|
(0<<6) | /* HDMA Req1 enable */
|
|
(1<<5) | /* HDMA Req2 disable */
|
|
(0<<4) | /* Req1 for Tx fifo */
|
|
(1<<3) | /* Req2 for Rx fifo */
|
|
(0<<2) | /* normal operation */
|
|
#ifdef CODEC_SLAVE
|
|
(1<<1) | /* start Tx (master mode) */
|
|
(0<<0); /* do not start Rx (master mode) */
|
|
/* setting Rx bit to 1 result in choppy audio */
|
|
#else
|
|
(0<<1) | /* not used in slave mode */
|
|
(0<<0); /* not used in slave mode */
|
|
#endif
|
|
}
|
|
|
|
#ifdef CODEC_SLAVE
|
|
/* When codec is slave we need to setup i2s MCLK clock using codec pll.
|
|
* The MCLK frequency is 256*codec frequency as i2s setup is:
|
|
* LRCK/SCLK = 64 and MCLK/SCLK = 4 (see i2s_init() for reference)
|
|
*
|
|
* PLL output frequency:
|
|
* Fout = ((Fref / (CLKR+1)) * (CLKF+1)) / (CLKOD+1)
|
|
* Fref = 24 MHz
|
|
*/
|
|
static void set_codec_freq(unsigned int freq)
|
|
{
|
|
long timeout;
|
|
|
|
/* {CLKR, CLKF, CLKOD, CODECPLL_DIV} */
|
|
static const unsigned int pcm_freq_params[HW_NUM_FREQ][4] =
|
|
{
|
|
[HW_FREQ_96] = {24, 255, 4, 1},
|
|
[HW_FREQ_48] = {24, 127, 4, 1},
|
|
[HW_FREQ_44] = {24, 293, 4, 4},
|
|
[HW_FREQ_32] = {24, 127, 4, 2},
|
|
[HW_FREQ_24] = {24, 127, 4, 3},
|
|
[HW_FREQ_22] = {24, 146, 4, 4},
|
|
[HW_FREQ_16] = {24, 127, 5, 4},
|
|
[HW_FREQ_12] = {24, 127, 4, 7},
|
|
[HW_FREQ_11] = {24, 146, 4, 9},
|
|
[HW_FREQ_8] = {24, 127, 5, 9},
|
|
};
|
|
/* select divider output from codec pll */
|
|
SCU_DIVCON1 &= ~((1<<9) | (0xF<<5));
|
|
SCU_DIVCON1 |= (pcm_freq_params[freq][3]<<5);
|
|
|
|
/* Codec PLL power up */
|
|
SCU_PLLCON3 &= ~(1<<22);
|
|
|
|
SCU_PLLCON3 = (1<<24) | /* Saturation behavior enable */
|
|
(1<<23) | /* Enable fast locking circuit */
|
|
(pcm_freq_params[freq][0]<<16) | /* CLKR factor */
|
|
(pcm_freq_params[freq][1]<<4) | /* CLKF factor */
|
|
(pcm_freq_params[freq][2]<<1) ; /* CLKOD factor */
|
|
|
|
/* wait for CODEC PLL lock with 10 ms timeout
|
|
* datasheet states that pll lock should take approx. 0.3 ms
|
|
*/
|
|
timeout = current_tick + (HZ/100);
|
|
while (!(SCU_STATUS & (1<<2)))
|
|
if (TIME_AFTER(current_tick, timeout))
|
|
break;
|
|
|
|
}
|
|
#endif
|
|
|
|
void pcm_play_dma_init(void)
|
|
{
|
|
/* unmask HDMA interrupt in INTC */
|
|
INTC_IMR |= (1<<12);
|
|
INTC_IECR |= (1<<12);
|
|
|
|
audiohw_preinit();
|
|
|
|
i2s_init();
|
|
}
|
|
|
|
void pcm_play_dma_postinit(void)
|
|
{
|
|
audiohw_postinit();
|
|
}
|
|
|
|
void pcm_dma_apply_settings(void)
|
|
{
|
|
#ifdef CODEC_SLAVE
|
|
set_codec_freq(pcm_fsel);
|
|
#endif
|
|
|
|
audiohw_set_frequency(pcm_fsel);
|
|
}
|
|
|
|
size_t pcm_get_bytes_waiting(void)
|
|
{
|
|
/* current terminate count is in transfer size units (4bytes here) */
|
|
return (HDMA_CCNT0 & 0xffff)<<2;
|
|
}
|
|
|
|
/* audio DMA ISR called when chunk from callers buffer has been transfered */
|
|
void INT_HDMA(void)
|
|
{
|
|
const void *start;
|
|
size_t size;
|
|
|
|
if (pcm_play_dma_complete_callback(PCM_DMAST_OK, &start, &size))
|
|
{
|
|
hdma_i2s_transfer(start, size);
|
|
pcm_play_dma_status_callback(PCM_DMAST_STARTED);
|
|
}
|
|
}
|
|
|
|
const void * pcm_play_dma_get_peak_buffer(int *count)
|
|
{
|
|
uint32_t addr;
|
|
|
|
int old = disable_irq_save();
|
|
addr = HDMA_CSRC0;
|
|
*count = ((HDMA_CCNT0 & 0xffff)<<2);
|
|
restore_interrupt(old);
|
|
|
|
return (void*)addr;
|
|
}
|
|
|
|
/****************************************************************************
|
|
** Recording DMA transfer
|
|
**/
|
|
/* TODO */
|