2008-04-25 15:55:19 +04:00
/*
* omap - mcbsp . c - - OMAP ALSA SoC DAI driver using McBSP port
*
* Copyright ( C ) 2008 Nokia Corporation
*
2009-04-17 15:42:26 +04:00
* Contact : Jarkko Nikula < jhnikula @ gmail . com >
* Peter Ujfalusi < peter . ujfalusi @ nokia . com >
2008-04-25 15:55:19 +04:00
*
* This program is free software ; you can redistribute it and / or
* modify it under the terms of the GNU General Public License
* version 2 as published by the Free Software Foundation .
*
* This program is distributed in the hope that it will be useful , but
* WITHOUT ANY WARRANTY ; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE . See the GNU
* General Public License for more details .
*
* You should have received a copy of the GNU General Public License
* along with this program ; if not , write to the Free Software
* Foundation , Inc . , 51 Franklin St , Fifth Floor , Boston , MA
* 02110 - 1301 USA
*
*/
# include <linux/init.h>
# include <linux/module.h>
# include <linux/device.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/initval.h>
# include <sound/soc.h>
2009-10-20 20:40:47 +04:00
# include <plat/control.h>
# include <plat/dma.h>
# include <plat/mcbsp.h>
2008-04-25 15:55:19 +04:00
# include "omap-mcbsp.h"
# include "omap-pcm.h"
2008-11-12 18:05:51 +03:00
# define OMAP_MCBSP_RATES (SNDRV_PCM_RATE_8000_96000)
2008-04-25 15:55:19 +04:00
2010-02-22 15:21:12 +03:00
# define OMAP_MCBSP_SOC_SINGLE_S16_EXT(xname, xmin, xmax, \
xhandler_get , xhandler_put ) \
{ . iface = SNDRV_CTL_ELEM_IFACE_MIXER , . name = xname , \
. info = omap_mcbsp_st_info_volsw , \
. get = xhandler_get , . put = xhandler_put , \
. private_value = ( unsigned long ) & ( struct soc_mixer_control ) \
{ . min = xmin , . max = xmax } }
2008-04-25 15:55:19 +04:00
struct omap_mcbsp_data {
unsigned int bus_id ;
struct omap_mcbsp_reg_cfg regs ;
2008-10-20 16:29:59 +04:00
unsigned int fmt ;
2008-04-25 15:55:19 +04:00
/*
* Flags indicating is the bus already activated and configured by
* another substream
*/
int active ;
int configured ;
2009-11-09 22:02:15 +03:00
unsigned int in_freq ;
int clk_div ;
2008-04-25 15:55:19 +04:00
} ;
# define to_mcbsp(priv) container_of((priv), struct omap_mcbsp_data, bus_id)
static struct omap_mcbsp_data mcbsp_data [ NUM_LINKS ] ;
/*
* Stream DMA parameters . DMA request line and port address are set runtime
* since they are different between OMAP1 and later OMAPs
*/
2008-10-09 16:57:21 +04:00
static struct omap_pcm_dma_data omap_mcbsp_dai_dma_params [ NUM_LINKS ] [ 2 ] ;
2008-04-25 15:55:19 +04:00
# if defined(CONFIG_ARCH_OMAP15XX) || defined(CONFIG_ARCH_OMAP16XX)
static const int omap1_dma_reqs [ ] [ 2 ] = {
{ OMAP_DMA_MCBSP1_TX , OMAP_DMA_MCBSP1_RX } ,
{ OMAP_DMA_MCBSP2_TX , OMAP_DMA_MCBSP2_RX } ,
{ OMAP_DMA_MCBSP3_TX , OMAP_DMA_MCBSP3_RX } ,
} ;
static const unsigned long omap1_mcbsp_port [ ] [ 2 ] = {
{ OMAP1510_MCBSP1_BASE + OMAP_MCBSP_REG_DXR1 ,
OMAP1510_MCBSP1_BASE + OMAP_MCBSP_REG_DRR1 } ,
{ OMAP1510_MCBSP2_BASE + OMAP_MCBSP_REG_DXR1 ,
OMAP1510_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1 } ,
{ OMAP1510_MCBSP3_BASE + OMAP_MCBSP_REG_DXR1 ,
OMAP1510_MCBSP3_BASE + OMAP_MCBSP_REG_DRR1 } ,
} ;
# else
static const int omap1_dma_reqs [ ] [ 2 ] = { } ;
static const unsigned long omap1_mcbsp_port [ ] [ 2 ] = { } ;
# endif
2008-10-09 16:57:20 +04:00
2010-02-12 23:26:48 +03:00
# if defined(CONFIG_ARCH_OMAP2) || defined(CONFIG_ARCH_OMAP3)
2008-10-09 16:57:20 +04:00
static const int omap24xx_dma_reqs [ ] [ 2 ] = {
2008-04-25 15:55:19 +04:00
{ OMAP24XX_DMA_MCBSP1_TX , OMAP24XX_DMA_MCBSP1_RX } ,
{ OMAP24XX_DMA_MCBSP2_TX , OMAP24XX_DMA_MCBSP2_RX } ,
2010-02-12 23:26:48 +03:00
# if defined(CONFIG_ARCH_OMAP2430) || defined(CONFIG_ARCH_OMAP3)
2008-10-09 16:57:20 +04:00
{ OMAP24XX_DMA_MCBSP3_TX , OMAP24XX_DMA_MCBSP3_RX } ,
{ OMAP24XX_DMA_MCBSP4_TX , OMAP24XX_DMA_MCBSP4_RX } ,
{ OMAP24XX_DMA_MCBSP5_TX , OMAP24XX_DMA_MCBSP5_RX } ,
# endif
2008-04-25 15:55:19 +04:00
} ;
2008-10-09 16:57:20 +04:00
# else
static const int omap24xx_dma_reqs [ ] [ 2 ] = { } ;
# endif
# if defined(CONFIG_ARCH_OMAP2420)
2008-04-25 15:55:19 +04:00
static const unsigned long omap2420_mcbsp_port [ ] [ 2 ] = {
{ OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR1 ,
OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR1 } ,
{ OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR1 ,
OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR1 } ,
} ;
# else
static const unsigned long omap2420_mcbsp_port [ ] [ 2 ] = { } ;
# endif
2008-10-09 16:57:20 +04:00
# if defined(CONFIG_ARCH_OMAP2430)
static const unsigned long omap2430_mcbsp_port [ ] [ 2 ] = {
{ OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR ,
OMAP24XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR } ,
{ OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR ,
OMAP24XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR } ,
{ OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DXR ,
OMAP2430_MCBSP3_BASE + OMAP_MCBSP_REG_DRR } ,
{ OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DXR ,
OMAP2430_MCBSP4_BASE + OMAP_MCBSP_REG_DRR } ,
{ OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DXR ,
OMAP2430_MCBSP5_BASE + OMAP_MCBSP_REG_DRR } ,
} ;
# else
static const unsigned long omap2430_mcbsp_port [ ] [ 2 ] = { } ;
# endif
2010-02-12 23:26:48 +03:00
# if defined(CONFIG_ARCH_OMAP3)
2008-10-09 16:57:20 +04:00
static const unsigned long omap34xx_mcbsp_port [ ] [ 2 ] = {
{ OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DXR ,
OMAP34XX_MCBSP1_BASE + OMAP_MCBSP_REG_DRR } ,
{ OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DXR ,
OMAP34XX_MCBSP2_BASE + OMAP_MCBSP_REG_DRR } ,
{ OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DXR ,
OMAP34XX_MCBSP3_BASE + OMAP_MCBSP_REG_DRR } ,
{ OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DXR ,
OMAP34XX_MCBSP4_BASE + OMAP_MCBSP_REG_DRR } ,
{ OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DXR ,
OMAP34XX_MCBSP5_BASE + OMAP_MCBSP_REG_DRR } ,
} ;
# else
static const unsigned long omap34xx_mcbsp_port [ ] [ 2 ] = { } ;
# endif
2009-08-20 17:18:25 +04:00
static void omap_mcbsp_set_threshold ( struct snd_pcm_substream * substream )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_dai * cpu_dai = rtd - > dai - > cpu_dai ;
struct omap_mcbsp_data * mcbsp_data = to_mcbsp ( cpu_dai - > private_data ) ;
2009-08-20 17:18:26 +04:00
int dma_op_mode = omap_mcbsp_get_dma_op_mode ( mcbsp_data - > bus_id ) ;
int samples ;
/* TODO: Currently, MODE_ELEMENT == MODE_FRAME */
if ( dma_op_mode = = MCBSP_DMA_MODE_THRESHOLD )
samples = snd_pcm_lib_period_bytes ( substream ) > > 1 ;
else
samples = 1 ;
2009-08-20 17:18:25 +04:00
/* Configure McBSP internal buffer usage */
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
omap_mcbsp_set_tx_threshold ( mcbsp_data - > bus_id , samples - 1 ) ;
else
omap_mcbsp_set_rx_threshold ( mcbsp_data - > bus_id , samples - 1 ) ;
}
2008-11-19 01:11:38 +03:00
static int omap_mcbsp_dai_startup ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
2008-04-25 15:55:19 +04:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2008-07-07 19:08:07 +04:00
struct snd_soc_dai * cpu_dai = rtd - > dai - > cpu_dai ;
2008-04-25 15:55:19 +04:00
struct omap_mcbsp_data * mcbsp_data = to_mcbsp ( cpu_dai - > private_data ) ;
2009-08-20 17:18:25 +04:00
int bus_id = mcbsp_data - > bus_id ;
2008-04-25 15:55:19 +04:00
int err = 0 ;
2009-08-20 17:18:25 +04:00
if ( ! cpu_dai - > active )
err = omap_mcbsp_request ( bus_id ) ;
if ( cpu_is_omap343x ( ) ) {
2009-08-20 17:18:26 +04:00
int dma_op_mode = omap_mcbsp_get_dma_op_mode ( bus_id ) ;
2009-08-20 17:18:25 +04:00
int max_period ;
2009-03-27 16:32:01 +03:00
/*
* McBSP2 in OMAP3 has 1024 * 32 - bit internal audio buffer .
* Set constraint for minimum buffer size to the same than FIFO
* size in order to avoid underruns in playback startup because
* HW is keeping the DMA request active until FIFO is filled .
*/
2009-08-20 17:18:25 +04:00
if ( bus_id = = 1 )
snd_pcm_hw_constraint_minmax ( substream - > runtime ,
SNDRV_PCM_HW_PARAM_BUFFER_BYTES ,
4096 , UINT_MAX ) ;
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
max_period = omap_mcbsp_get_max_tx_threshold ( bus_id ) ;
else
max_period = omap_mcbsp_get_max_rx_threshold ( bus_id ) ;
max_period + + ;
max_period < < = 1 ;
2009-08-20 17:18:26 +04:00
if ( dma_op_mode = = MCBSP_DMA_MODE_THRESHOLD )
snd_pcm_hw_constraint_minmax ( substream - > runtime ,
2009-08-20 17:18:25 +04:00
SNDRV_PCM_HW_PARAM_PERIOD_BYTES ,
32 , max_period ) ;
2009-03-27 16:32:01 +03:00
}
2008-04-25 15:55:19 +04:00
return err ;
}
2008-11-19 01:11:38 +03:00
static void omap_mcbsp_dai_shutdown ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
2008-04-25 15:55:19 +04:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2008-07-07 19:08:07 +04:00
struct snd_soc_dai * cpu_dai = rtd - > dai - > cpu_dai ;
2008-04-25 15:55:19 +04:00
struct omap_mcbsp_data * mcbsp_data = to_mcbsp ( cpu_dai - > private_data ) ;
if ( ! cpu_dai - > active ) {
omap_mcbsp_free ( mcbsp_data - > bus_id ) ;
mcbsp_data - > configured = 0 ;
}
}
2008-11-19 01:11:38 +03:00
static int omap_mcbsp_dai_trigger ( struct snd_pcm_substream * substream , int cmd ,
struct snd_soc_dai * dai )
2008-04-25 15:55:19 +04:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2008-07-07 19:08:07 +04:00
struct snd_soc_dai * cpu_dai = rtd - > dai - > cpu_dai ;
2008-04-25 15:55:19 +04:00
struct omap_mcbsp_data * mcbsp_data = to_mcbsp ( cpu_dai - > private_data ) ;
2009-08-07 10:59:47 +04:00
int err = 0 , play = ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK ) ;
2008-04-25 15:55:19 +04:00
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
2009-08-07 10:59:47 +04:00
mcbsp_data - > active + + ;
omap_mcbsp_start ( mcbsp_data - > bus_id , play , ! play ) ;
2008-04-25 15:55:19 +04:00
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
2009-08-07 10:59:47 +04:00
omap_mcbsp_stop ( mcbsp_data - > bus_id , play , ! play ) ;
mcbsp_data - > active - - ;
2008-04-25 15:55:19 +04:00
break ;
default :
err = - EINVAL ;
}
return err ;
}
2010-03-03 16:08:09 +03:00
static snd_pcm_sframes_t omap_mcbsp_dai_delay (
struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_dai * cpu_dai = rtd - > dai - > cpu_dai ;
struct omap_mcbsp_data * mcbsp_data = to_mcbsp ( cpu_dai - > private_data ) ;
u16 fifo_use ;
snd_pcm_sframes_t delay ;
if ( substream - > stream = = SNDRV_PCM_STREAM_PLAYBACK )
fifo_use = omap_mcbsp_get_tx_delay ( mcbsp_data - > bus_id ) ;
else
fifo_use = omap_mcbsp_get_rx_delay ( mcbsp_data - > bus_id ) ;
/*
* Divide the used locations with the channel count to get the
* FIFO usage in samples ( don ' t care about partial samples in the
* buffer ) .
*/
delay = fifo_use / substream - > runtime - > channels ;
return delay ;
}
2008-04-25 15:55:19 +04:00
static int omap_mcbsp_dai_hw_params ( struct snd_pcm_substream * substream ,
2008-11-19 01:11:38 +03:00
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
2008-04-25 15:55:19 +04:00
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2008-07-07 19:08:07 +04:00
struct snd_soc_dai * cpu_dai = rtd - > dai - > cpu_dai ;
2008-04-25 15:55:19 +04:00
struct omap_mcbsp_data * mcbsp_data = to_mcbsp ( cpu_dai - > private_data ) ;
struct omap_mcbsp_reg_cfg * regs = & mcbsp_data - > regs ;
int dma , bus_id = mcbsp_data - > bus_id , id = cpu_dai - > id ;
2009-08-20 17:18:25 +04:00
int wlen , channels , wpf , sync_mode = OMAP_DMA_SYNC_ELEMENT ;
2008-04-25 15:55:19 +04:00
unsigned long port ;
2009-11-09 22:02:15 +03:00
unsigned int format , div , framesize , master ;
2008-04-25 15:55:19 +04:00
if ( cpu_class_is_omap1 ( ) ) {
dma = omap1_dma_reqs [ bus_id ] [ substream - > stream ] ;
port = omap1_mcbsp_port [ bus_id ] [ substream - > stream ] ;
} else if ( cpu_is_omap2420 ( ) ) {
2008-10-09 16:57:20 +04:00
dma = omap24xx_dma_reqs [ bus_id ] [ substream - > stream ] ;
2008-04-25 15:55:19 +04:00
port = omap2420_mcbsp_port [ bus_id ] [ substream - > stream ] ;
2008-10-09 16:57:20 +04:00
} else if ( cpu_is_omap2430 ( ) ) {
dma = omap24xx_dma_reqs [ bus_id ] [ substream - > stream ] ;
port = omap2430_mcbsp_port [ bus_id ] [ substream - > stream ] ;
} else if ( cpu_is_omap343x ( ) ) {
dma = omap24xx_dma_reqs [ bus_id ] [ substream - > stream ] ;
port = omap34xx_mcbsp_port [ bus_id ] [ substream - > stream ] ;
2009-08-20 17:18:25 +04:00
omap_mcbsp_dai_dma_params [ id ] [ substream - > stream ] . set_threshold =
omap_mcbsp_set_threshold ;
2009-08-20 17:18:26 +04:00
/* TODO: Currently, MODE_ELEMENT == MODE_FRAME */
if ( omap_mcbsp_get_dma_op_mode ( bus_id ) = =
MCBSP_DMA_MODE_THRESHOLD )
sync_mode = OMAP_DMA_SYNC_FRAME ;
2008-04-25 15:55:19 +04:00
} else {
return - ENODEV ;
}
2008-10-09 16:57:21 +04:00
omap_mcbsp_dai_dma_params [ id ] [ substream - > stream ] . name =
substream - > stream ? " Audio Capture " : " Audio Playback " ;
2008-04-25 15:55:19 +04:00
omap_mcbsp_dai_dma_params [ id ] [ substream - > stream ] . dma_req = dma ;
omap_mcbsp_dai_dma_params [ id ] [ substream - > stream ] . port_addr = port ;
2009-08-20 17:18:25 +04:00
omap_mcbsp_dai_dma_params [ id ] [ substream - > stream ] . sync_mode = sync_mode ;
2010-02-23 00:09:19 +03:00
omap_mcbsp_dai_dma_params [ id ] [ substream - > stream ] . data_type =
OMAP_DMA_DATA_TYPE_S16 ;
2010-03-19 17:52:55 +03:00
snd_soc_dai_set_dma_data ( cpu_dai , substream ,
& omap_mcbsp_dai_dma_params [ id ] [ substream - > stream ] ) ;
2008-04-25 15:55:19 +04:00
if ( mcbsp_data - > configured ) {
/* McBSP already configured by another stream */
return 0 ;
}
2009-04-15 16:38:55 +04:00
format = mcbsp_data - > fmt & SND_SOC_DAIFMT_FORMAT_MASK ;
wpf = channels = params_channels ( params ) ;
2010-03-19 13:27:31 +03:00
if ( channels = = 2 & & ( format = = SND_SOC_DAIFMT_I2S | |
format = = SND_SOC_DAIFMT_LEFT_J ) ) {
2009-11-09 22:02:15 +03:00
/* Use dual-phase frames */
regs - > rcr2 | = RPHASE ;
regs - > xcr2 | = XPHASE ;
/* Set 1 word per (McBSP) frame for phase1 and phase2 */
wpf - - ;
regs - > rcr2 | = RFRLEN2 ( wpf - 1 ) ;
regs - > xcr2 | = XFRLEN2 ( wpf - 1 ) ;
2008-04-25 15:55:19 +04:00
}
2009-11-09 22:02:15 +03:00
regs - > rcr1 | = RFRLEN1 ( wpf - 1 ) ;
regs - > xcr1 | = XFRLEN1 ( wpf - 1 ) ;
2008-04-25 15:55:19 +04:00
switch ( params_format ( params ) ) {
case SNDRV_PCM_FORMAT_S16_LE :
/* Set word lengths */
2008-10-20 16:29:59 +04:00
wlen = 16 ;
2008-04-25 15:55:19 +04:00
regs - > rcr2 | = RWDLEN2 ( OMAP_MCBSP_WORD_16 ) ;
regs - > rcr1 | = RWDLEN1 ( OMAP_MCBSP_WORD_16 ) ;
regs - > xcr2 | = XWDLEN2 ( OMAP_MCBSP_WORD_16 ) ;
regs - > xcr1 | = XWDLEN1 ( OMAP_MCBSP_WORD_16 ) ;
break ;
default :
/* Unsupported PCM format */
return - EINVAL ;
}
2009-11-09 22:02:15 +03:00
/* In McBSP master modes, FRAME (i.e. sample rate) is generated
* by _counting_ BCLKs . Calculate frame size in BCLKs */
master = mcbsp_data - > fmt & SND_SOC_DAIFMT_MASTER_MASK ;
if ( master = = SND_SOC_DAIFMT_CBS_CFS ) {
div = mcbsp_data - > clk_div ? mcbsp_data - > clk_div : 1 ;
framesize = ( mcbsp_data - > in_freq / div ) / params_rate ( params ) ;
if ( framesize < wlen * channels ) {
printk ( KERN_ERR " %s: not enough bandwidth for desired rate and "
" channels \n " , __func__ ) ;
return - EINVAL ;
}
} else
framesize = wlen * channels ;
2008-10-20 16:29:59 +04:00
/* Set FS period and length in terms of bit clock periods */
2009-04-15 16:38:55 +04:00
switch ( format ) {
2008-10-20 16:29:59 +04:00
case SND_SOC_DAIFMT_I2S :
2010-03-19 13:27:31 +03:00
case SND_SOC_DAIFMT_LEFT_J :
2009-11-09 22:02:15 +03:00
regs - > srgr2 | = FPER ( framesize - 1 ) ;
regs - > srgr1 | = FWID ( ( framesize > > 1 ) - 1 ) ;
2008-10-20 16:29:59 +04:00
break ;
2009-04-15 16:38:56 +04:00
case SND_SOC_DAIFMT_DSP_A :
2008-12-22 11:21:36 +03:00
case SND_SOC_DAIFMT_DSP_B :
2009-11-09 22:02:15 +03:00
regs - > srgr2 | = FPER ( framesize - 1 ) ;
2009-04-15 14:48:16 +04:00
regs - > srgr1 | = FWID ( 0 ) ;
2008-10-20 16:29:59 +04:00
break ;
}
2008-04-25 15:55:19 +04:00
omap_mcbsp_config ( bus_id , & mcbsp_data - > regs ) ;
mcbsp_data - > configured = 1 ;
return 0 ;
}
/*
* This must be called before _set_clkdiv and _set_sysclk since McBSP register
* cache is initialized here
*/
2008-07-07 19:08:07 +04:00
static int omap_mcbsp_dai_set_dai_fmt ( struct snd_soc_dai * cpu_dai ,
2008-04-25 15:55:19 +04:00
unsigned int fmt )
{
struct omap_mcbsp_data * mcbsp_data = to_mcbsp ( cpu_dai - > private_data ) ;
struct omap_mcbsp_reg_cfg * regs = & mcbsp_data - > regs ;
2009-04-15 14:48:16 +04:00
unsigned int temp_fmt = fmt ;
2008-04-25 15:55:19 +04:00
if ( mcbsp_data - > configured )
return 0 ;
2008-10-20 16:29:59 +04:00
mcbsp_data - > fmt = fmt ;
2008-04-25 15:55:19 +04:00
memset ( regs , 0 , sizeof ( * regs ) ) ;
/* Generic McBSP register settings */
regs - > spcr2 | = XINTM ( 3 ) | FREE ;
regs - > spcr1 | = RINTM ( 3 ) ;
2009-08-20 17:18:23 +04:00
/* RFIG and XFIG are not defined in 34xx */
if ( ! cpu_is_omap34xx ( ) ) {
regs - > rcr2 | = RFIG ;
regs - > xcr2 | = XFIG ;
}
2009-01-29 14:29:46 +03:00
if ( cpu_is_omap2430 ( ) | | cpu_is_omap34xx ( ) ) {
2009-08-23 13:24:26 +04:00
regs - > xccr = DXENDLY ( 1 ) | XDMAEN | XDISABLE ;
regs - > rccr = RFULL_CYCLE | RDMAEN | RDISABLE ;
2009-01-29 14:29:46 +03:00
}
2008-04-25 15:55:19 +04:00
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
/* 1-bit data delay */
regs - > rcr2 | = RDATDLY ( 1 ) ;
regs - > xcr2 | = XDATDLY ( 1 ) ;
break ;
2010-03-19 13:27:31 +03:00
case SND_SOC_DAIFMT_LEFT_J :
/* 0-bit data delay */
regs - > rcr2 | = RDATDLY ( 0 ) ;
regs - > xcr2 | = XDATDLY ( 0 ) ;
regs - > spcr1 | = RJUST ( 2 ) ;
/* Invert FS polarity configuration */
temp_fmt ^ = SND_SOC_DAIFMT_NB_IF ;
break ;
2009-04-15 16:38:56 +04:00
case SND_SOC_DAIFMT_DSP_A :
/* 1-bit data delay */
regs - > rcr2 | = RDATDLY ( 1 ) ;
regs - > xcr2 | = XDATDLY ( 1 ) ;
/* Invert FS polarity configuration */
temp_fmt ^ = SND_SOC_DAIFMT_NB_IF ;
break ;
2008-12-22 11:21:36 +03:00
case SND_SOC_DAIFMT_DSP_B :
2008-10-02 13:37:06 +04:00
/* 0-bit data delay */
regs - > rcr2 | = RDATDLY ( 0 ) ;
regs - > xcr2 | = XDATDLY ( 0 ) ;
2009-04-15 14:48:16 +04:00
/* Invert FS polarity configuration */
temp_fmt ^ = SND_SOC_DAIFMT_NB_IF ;
2008-10-02 13:37:06 +04:00
break ;
2008-04-25 15:55:19 +04:00
default :
/* Unsupported data format */
return - EINVAL ;
}
switch ( fmt & SND_SOC_DAIFMT_MASTER_MASK ) {
case SND_SOC_DAIFMT_CBS_CFS :
/* McBSP master. Set FS and bit clocks as outputs */
regs - > pcr0 | = FSXM | FSRM |
CLKXM | CLKRM ;
/* Sample rate generator drives the FS */
regs - > srgr2 | = FSGM ;
break ;
case SND_SOC_DAIFMT_CBM_CFM :
/* McBSP slave */
break ;
default :
/* Unsupported master/slave configuration */
return - EINVAL ;
}
/* Set bit clock (CLKX/CLKR) and FS polarities */
2009-04-15 14:48:16 +04:00
switch ( temp_fmt & SND_SOC_DAIFMT_INV_MASK ) {
2008-04-25 15:55:19 +04:00
case SND_SOC_DAIFMT_NB_NF :
/*
* Normal BCLK + FS .
* FS active low . TX data driven on falling edge of bit clock
* and RX data sampled on rising edge of bit clock .
*/
regs - > pcr0 | = FSXP | FSRP |
CLKXP | CLKRP ;
break ;
case SND_SOC_DAIFMT_NB_IF :
regs - > pcr0 | = CLKXP | CLKRP ;
break ;
case SND_SOC_DAIFMT_IB_NF :
regs - > pcr0 | = FSXP | FSRP ;
break ;
case SND_SOC_DAIFMT_IB_IF :
break ;
default :
return - EINVAL ;
}
return 0 ;
}
2008-07-07 19:08:07 +04:00
static int omap_mcbsp_dai_set_clkdiv ( struct snd_soc_dai * cpu_dai ,
2008-04-25 15:55:19 +04:00
int div_id , int div )
{
struct omap_mcbsp_data * mcbsp_data = to_mcbsp ( cpu_dai - > private_data ) ;
struct omap_mcbsp_reg_cfg * regs = & mcbsp_data - > regs ;
if ( div_id ! = OMAP_MCBSP_CLKGDV )
return - ENODEV ;
2009-11-09 22:02:15 +03:00
mcbsp_data - > clk_div = div ;
2008-04-25 15:55:19 +04:00
regs - > srgr1 | = CLKGDV ( div - 1 ) ;
return 0 ;
}
static int omap_mcbsp_dai_set_clks_src ( struct omap_mcbsp_data * mcbsp_data ,
int clk_id )
{
int sel_bit ;
2008-10-09 16:57:20 +04:00
u16 reg , reg_devconf1 = OMAP243X_CONTROL_DEVCONF1 ;
2008-04-25 15:55:19 +04:00
if ( cpu_class_is_omap1 ( ) ) {
/* OMAP1's can use only external source clock */
if ( unlikely ( clk_id = = OMAP_MCBSP_SYSCLK_CLKS_FCLK ) )
return - EINVAL ;
else
return 0 ;
}
2008-10-09 16:57:20 +04:00
if ( cpu_is_omap2420 ( ) & & mcbsp_data - > bus_id > 1 )
return - EINVAL ;
if ( cpu_is_omap343x ( ) )
reg_devconf1 = OMAP343X_CONTROL_DEVCONF1 ;
2008-04-25 15:55:19 +04:00
switch ( mcbsp_data - > bus_id ) {
case 0 :
reg = OMAP2_CONTROL_DEVCONF0 ;
sel_bit = 2 ;
break ;
case 1 :
reg = OMAP2_CONTROL_DEVCONF0 ;
sel_bit = 6 ;
break ;
2008-10-09 16:57:20 +04:00
case 2 :
reg = reg_devconf1 ;
sel_bit = 0 ;
break ;
case 3 :
reg = reg_devconf1 ;
sel_bit = 2 ;
break ;
case 4 :
reg = reg_devconf1 ;
sel_bit = 4 ;
break ;
2008-04-25 15:55:19 +04:00
default :
return - EINVAL ;
}
2008-10-09 16:57:20 +04:00
if ( clk_id = = OMAP_MCBSP_SYSCLK_CLKS_FCLK )
omap_ctrl_writel ( omap_ctrl_readl ( reg ) & ~ ( 1 < < sel_bit ) , reg ) ;
else
omap_ctrl_writel ( omap_ctrl_readl ( reg ) | ( 1 < < sel_bit ) , reg ) ;
2008-04-25 15:55:19 +04:00
return 0 ;
}
2009-08-28 16:35:35 +04:00
static int omap_mcbsp_dai_set_rcvr_src ( struct omap_mcbsp_data * mcbsp_data ,
int clk_id )
{
int sel_bit , set = 0 ;
u16 reg = OMAP2_CONTROL_DEVCONF0 ;
if ( cpu_class_is_omap1 ( ) )
return - EINVAL ; /* TODO: Can this be implemented for OMAP1? */
if ( mcbsp_data - > bus_id ! = 0 )
return - EINVAL ;
switch ( clk_id ) {
case OMAP_MCBSP_CLKR_SRC_CLKX :
set = 1 ;
case OMAP_MCBSP_CLKR_SRC_CLKR :
sel_bit = 3 ;
break ;
case OMAP_MCBSP_FSR_SRC_FSX :
set = 1 ;
case OMAP_MCBSP_FSR_SRC_FSR :
sel_bit = 4 ;
break ;
default :
return - EINVAL ;
}
if ( set )
omap_ctrl_writel ( omap_ctrl_readl ( reg ) | ( 1 < < sel_bit ) , reg ) ;
else
omap_ctrl_writel ( omap_ctrl_readl ( reg ) & ~ ( 1 < < sel_bit ) , reg ) ;
return 0 ;
}
2008-07-07 19:08:07 +04:00
static int omap_mcbsp_dai_set_dai_sysclk ( struct snd_soc_dai * cpu_dai ,
2008-04-25 15:55:19 +04:00
int clk_id , unsigned int freq ,
int dir )
{
struct omap_mcbsp_data * mcbsp_data = to_mcbsp ( cpu_dai - > private_data ) ;
struct omap_mcbsp_reg_cfg * regs = & mcbsp_data - > regs ;
int err = 0 ;
2009-11-09 22:02:15 +03:00
mcbsp_data - > in_freq = freq ;
2008-04-25 15:55:19 +04:00
switch ( clk_id ) {
case OMAP_MCBSP_SYSCLK_CLK :
regs - > srgr2 | = CLKSM ;
break ;
case OMAP_MCBSP_SYSCLK_CLKS_FCLK :
case OMAP_MCBSP_SYSCLK_CLKS_EXT :
err = omap_mcbsp_dai_set_clks_src ( mcbsp_data , clk_id ) ;
break ;
case OMAP_MCBSP_SYSCLK_CLKX_EXT :
regs - > srgr2 | = CLKSM ;
case OMAP_MCBSP_SYSCLK_CLKR_EXT :
regs - > pcr0 | = SCLKME ;
break ;
2009-08-28 16:35:35 +04:00
case OMAP_MCBSP_CLKR_SRC_CLKR :
case OMAP_MCBSP_CLKR_SRC_CLKX :
case OMAP_MCBSP_FSR_SRC_FSR :
case OMAP_MCBSP_FSR_SRC_FSX :
err = omap_mcbsp_dai_set_rcvr_src ( mcbsp_data , clk_id ) ;
break ;
2008-04-25 15:55:19 +04:00
default :
err = - ENODEV ;
}
return err ;
}
2009-03-03 04:41:00 +03:00
static struct snd_soc_dai_ops omap_mcbsp_dai_ops = {
. startup = omap_mcbsp_dai_startup ,
. shutdown = omap_mcbsp_dai_shutdown ,
. trigger = omap_mcbsp_dai_trigger ,
2010-03-03 16:08:09 +03:00
. delay = omap_mcbsp_dai_delay ,
2009-03-03 04:41:00 +03:00
. hw_params = omap_mcbsp_dai_hw_params ,
. set_fmt = omap_mcbsp_dai_set_dai_fmt ,
. set_clkdiv = omap_mcbsp_dai_set_clkdiv ,
. set_sysclk = omap_mcbsp_dai_set_dai_sysclk ,
} ;
2008-10-09 16:57:22 +04:00
# define OMAP_MCBSP_DAI_BUILDER(link_id) \
{ \
2008-11-21 15:31:33 +03:00
. name = " omap-mcbsp-dai- " # link_id , \
2008-10-09 16:57:22 +04:00
. id = ( link_id ) , \
. playback = { \
2008-11-25 13:45:09 +03:00
. channels_min = 1 , \
2009-11-09 22:02:15 +03:00
. channels_max = 16 , \
2008-10-09 16:57:22 +04:00
. rates = OMAP_MCBSP_RATES , \
. formats = SNDRV_PCM_FMTBIT_S16_LE , \
} , \
. capture = { \
2008-11-25 13:45:09 +03:00
. channels_min = 1 , \
2009-11-09 22:02:15 +03:00
. channels_max = 16 , \
2008-10-09 16:57:22 +04:00
. rates = OMAP_MCBSP_RATES , \
. formats = SNDRV_PCM_FMTBIT_S16_LE , \
} , \
2009-03-03 04:41:00 +03:00
. ops = & omap_mcbsp_dai_ops , \
2008-10-09 16:57:22 +04:00
. private_data = & mcbsp_data [ ( link_id ) ] . bus_id , \
}
struct snd_soc_dai omap_mcbsp_dai [ ] = {
OMAP_MCBSP_DAI_BUILDER ( 0 ) ,
OMAP_MCBSP_DAI_BUILDER ( 1 ) ,
# if NUM_LINKS >= 3
OMAP_MCBSP_DAI_BUILDER ( 2 ) ,
# endif
# if NUM_LINKS == 5
OMAP_MCBSP_DAI_BUILDER ( 3 ) ,
OMAP_MCBSP_DAI_BUILDER ( 4 ) ,
# endif
2008-04-25 15:55:19 +04:00
} ;
2008-10-09 16:57:22 +04:00
2008-04-25 15:55:19 +04:00
EXPORT_SYMBOL_GPL ( omap_mcbsp_dai ) ;
2010-02-22 15:21:12 +03:00
int omap_mcbsp_st_info_volsw ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_info * uinfo )
{
struct soc_mixer_control * mc =
( struct soc_mixer_control * ) kcontrol - > private_value ;
int max = mc - > max ;
int min = mc - > min ;
uinfo - > type = SNDRV_CTL_ELEM_TYPE_INTEGER ;
uinfo - > count = 1 ;
uinfo - > value . integer . min = min ;
uinfo - > value . integer . max = max ;
return 0 ;
}
# define OMAP_MCBSP_ST_SET_CHANNEL_VOLUME(id, channel) \
static int \
omap_mcbsp # # id # # _set_st_ch # # channel # # _volume ( struct snd_kcontrol * kc , \
struct snd_ctl_elem_value * uc ) \
{ \
struct soc_mixer_control * mc = \
( struct soc_mixer_control * ) kc - > private_value ; \
int max = mc - > max ; \
int min = mc - > min ; \
int val = uc - > value . integer . value [ 0 ] ; \
\
if ( val < min | | val > max ) \
return - EINVAL ; \
\
/* OMAP McBSP implementation uses index values 0..4 */ \
return omap_st_set_chgain ( ( id ) - 1 , channel , val ) ; \
}
# define OMAP_MCBSP_ST_GET_CHANNEL_VOLUME(id, channel) \
static int \
omap_mcbsp # # id # # _get_st_ch # # channel # # _volume ( struct snd_kcontrol * kc , \
struct snd_ctl_elem_value * uc ) \
{ \
s16 chgain ; \
\
if ( omap_st_get_chgain ( ( id ) - 1 , channel , & chgain ) ) \
return - EAGAIN ; \
\
uc - > value . integer . value [ 0 ] = chgain ; \
return 0 ; \
}
OMAP_MCBSP_ST_SET_CHANNEL_VOLUME ( 2 , 0 )
OMAP_MCBSP_ST_SET_CHANNEL_VOLUME ( 2 , 1 )
OMAP_MCBSP_ST_SET_CHANNEL_VOLUME ( 3 , 0 )
OMAP_MCBSP_ST_SET_CHANNEL_VOLUME ( 3 , 1 )
OMAP_MCBSP_ST_GET_CHANNEL_VOLUME ( 2 , 0 )
OMAP_MCBSP_ST_GET_CHANNEL_VOLUME ( 2 , 1 )
OMAP_MCBSP_ST_GET_CHANNEL_VOLUME ( 3 , 0 )
OMAP_MCBSP_ST_GET_CHANNEL_VOLUME ( 3 , 1 )
static int omap_mcbsp_st_put_mode ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct soc_mixer_control * mc =
( struct soc_mixer_control * ) kcontrol - > private_value ;
u8 value = ucontrol - > value . integer . value [ 0 ] ;
if ( value = = omap_st_is_enabled ( mc - > reg ) )
return 0 ;
if ( value )
omap_st_enable ( mc - > reg ) ;
else
omap_st_disable ( mc - > reg ) ;
return 1 ;
}
static int omap_mcbsp_st_get_mode ( struct snd_kcontrol * kcontrol ,
struct snd_ctl_elem_value * ucontrol )
{
struct soc_mixer_control * mc =
( struct soc_mixer_control * ) kcontrol - > private_value ;
ucontrol - > value . integer . value [ 0 ] = omap_st_is_enabled ( mc - > reg ) ;
return 0 ;
}
static const struct snd_kcontrol_new omap_mcbsp2_st_controls [ ] = {
SOC_SINGLE_EXT ( " McBSP2 Sidetone Switch " , 1 , 0 , 1 , 0 ,
omap_mcbsp_st_get_mode , omap_mcbsp_st_put_mode ) ,
OMAP_MCBSP_SOC_SINGLE_S16_EXT ( " McBSP2 Sidetone Channel 0 Volume " ,
- 32768 , 32767 ,
omap_mcbsp2_get_st_ch0_volume ,
omap_mcbsp2_set_st_ch0_volume ) ,
OMAP_MCBSP_SOC_SINGLE_S16_EXT ( " McBSP2 Sidetone Channel 1 Volume " ,
- 32768 , 32767 ,
omap_mcbsp2_get_st_ch1_volume ,
omap_mcbsp2_set_st_ch1_volume ) ,
} ;
static const struct snd_kcontrol_new omap_mcbsp3_st_controls [ ] = {
SOC_SINGLE_EXT ( " McBSP3 Sidetone Switch " , 2 , 0 , 1 , 0 ,
omap_mcbsp_st_get_mode , omap_mcbsp_st_put_mode ) ,
OMAP_MCBSP_SOC_SINGLE_S16_EXT ( " McBSP3 Sidetone Channel 0 Volume " ,
- 32768 , 32767 ,
omap_mcbsp3_get_st_ch0_volume ,
omap_mcbsp3_set_st_ch0_volume ) ,
OMAP_MCBSP_SOC_SINGLE_S16_EXT ( " McBSP3 Sidetone Channel 1 Volume " ,
- 32768 , 32767 ,
omap_mcbsp3_get_st_ch1_volume ,
omap_mcbsp3_set_st_ch1_volume ) ,
} ;
int omap_mcbsp_st_add_controls ( struct snd_soc_codec * codec , int mcbsp_id )
{
if ( ! cpu_is_omap34xx ( ) )
return - ENODEV ;
switch ( mcbsp_id ) {
case 1 : /* McBSP 2 */
return snd_soc_add_controls ( codec , omap_mcbsp2_st_controls ,
ARRAY_SIZE ( omap_mcbsp2_st_controls ) ) ;
case 2 : /* McBSP 3 */
return snd_soc_add_controls ( codec , omap_mcbsp3_st_controls ,
ARRAY_SIZE ( omap_mcbsp3_st_controls ) ) ;
default :
break ;
}
return - EINVAL ;
}
EXPORT_SYMBOL_GPL ( omap_mcbsp_st_add_controls ) ;
2008-12-10 09:59:33 +03:00
static int __init snd_omap_mcbsp_init ( void )
2008-12-03 22:26:35 +03:00
{
return snd_soc_register_dais ( omap_mcbsp_dai ,
ARRAY_SIZE ( omap_mcbsp_dai ) ) ;
}
2008-12-10 09:59:33 +03:00
module_init ( snd_omap_mcbsp_init ) ;
2008-12-03 22:26:35 +03:00
2008-12-10 09:59:33 +03:00
static void __exit snd_omap_mcbsp_exit ( void )
2008-12-03 22:26:35 +03:00
{
snd_soc_unregister_dais ( omap_mcbsp_dai , ARRAY_SIZE ( omap_mcbsp_dai ) ) ;
}
2008-12-10 09:59:33 +03:00
module_exit ( snd_omap_mcbsp_exit ) ;
2008-12-03 22:26:35 +03:00
2009-04-17 15:42:26 +04:00
MODULE_AUTHOR ( " Jarkko Nikula <jhnikula@gmail.com> " ) ;
2008-04-25 15:55:19 +04:00
MODULE_DESCRIPTION ( " OMAP I2S SoC Interface " ) ;
MODULE_LICENSE ( " GPL " ) ;