2008-09-05 18:21:39 +08:00
/*
* File : sound / soc / blackfin / bf5xx - i2s . c
* Author : Cliff Cai < Cliff . Cai @ analog . com >
*
* Created : Tue June 06 2008
* Description : Blackfin I2S CPU DAI driver
*
* Modified :
* Copyright 2008 Analog Devices Inc .
*
* Bugs : Enter bugs at http : //blackfin.uclinux.org/
*
* 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 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 , see the file COPYING , or 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 <linux/delay.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/initval.h>
# include <sound/soc.h>
# include <asm/irq.h>
# include <asm/portmux.h>
# include <linux/mutex.h>
# include <linux/gpio.h>
# include "bf5xx-sport.h"
2013-05-28 19:22:14 +02:00
# include "bf5xx-i2s-pcm.h"
2008-09-05 18:21:39 +08:00
struct bf5xx_i2s_port {
u16 tcr1 ;
u16 rcr1 ;
u16 tcr2 ;
u16 rcr2 ;
2009-06-20 11:29:05 -04:00
int configured ;
2013-05-28 19:22:14 +02:00
unsigned int slots ;
unsigned int tx_mask ;
unsigned int rx_mask ;
struct bf5xx_i2s_pcm_data tx_dma_data ;
struct bf5xx_i2s_pcm_data rx_dma_data ;
2008-09-05 18:21:39 +08:00
} ;
static int bf5xx_i2s_set_dai_fmt ( struct snd_soc_dai * cpu_dai ,
unsigned int fmt )
{
2011-03-28 01:45:10 -04:00
struct sport_device * sport_handle = snd_soc_dai_get_drvdata ( cpu_dai ) ;
struct bf5xx_i2s_port * bf5xx_i2s = sport_handle - > private_data ;
2008-09-05 18:21:39 +08:00
int ret = 0 ;
/* interface format:support I2S,slave mode */
switch ( fmt & SND_SOC_DAIFMT_FORMAT_MASK ) {
case SND_SOC_DAIFMT_I2S :
2011-03-28 01:45:10 -04:00
bf5xx_i2s - > tcr1 | = TFSR | TCKFE ;
bf5xx_i2s - > rcr1 | = RFSR | RCKFE ;
bf5xx_i2s - > tcr2 | = TSFSE ;
bf5xx_i2s - > rcr2 | = RSFSE ;
2008-09-27 22:30:15 +08:00
break ;
case SND_SOC_DAIFMT_DSP_A :
2011-03-28 01:45:10 -04:00
bf5xx_i2s - > tcr1 | = TFSR ;
bf5xx_i2s - > rcr1 | = RFSR ;
2008-09-05 18:21:39 +08:00
break ;
case SND_SOC_DAIFMT_LEFT_J :
ret = - EINVAL ;
break ;
default :
2013-05-28 19:22:10 +02:00
dev_err ( cpu_dai - > dev , " %s: Unknown DAI format type \n " ,
__func__ ) ;
2008-09-05 18:21:39 +08:00
ret = - EINVAL ;
break ;
}
switch ( fmt & SND_SOC_DAIFMT_MASTER_MASK ) {
case SND_SOC_DAIFMT_CBM_CFM :
break ;
2008-10-27 17:09:25 +08:00
case SND_SOC_DAIFMT_CBS_CFS :
case SND_SOC_DAIFMT_CBM_CFS :
2008-09-05 18:21:39 +08:00
case SND_SOC_DAIFMT_CBS_CFM :
ret = - EINVAL ;
break ;
default :
2013-05-28 19:22:10 +02:00
dev_err ( cpu_dai - > dev , " %s: Unknown DAI master type \n " ,
__func__ ) ;
2008-09-05 18:21:39 +08:00
ret = - EINVAL ;
break ;
}
return ret ;
}
static int bf5xx_i2s_hw_params ( struct snd_pcm_substream * substream ,
2008-11-18 22:11:38 +00:00
struct snd_pcm_hw_params * params ,
struct snd_soc_dai * dai )
2008-09-05 18:21:39 +08:00
{
2011-03-28 01:45:10 -04:00
struct sport_device * sport_handle = snd_soc_dai_get_drvdata ( dai ) ;
struct bf5xx_i2s_port * bf5xx_i2s = sport_handle - > private_data ;
2008-09-05 18:21:39 +08:00
int ret = 0 ;
2011-03-28 01:45:10 -04:00
bf5xx_i2s - > tcr2 & = ~ 0x1f ;
bf5xx_i2s - > rcr2 & = ~ 0x1f ;
2008-09-05 18:21:39 +08:00
switch ( params_format ( params ) ) {
2011-03-26 03:05:08 -04:00
case SNDRV_PCM_FORMAT_S8 :
bf5xx_i2s - > tcr2 | = 7 ;
bf5xx_i2s - > rcr2 | = 7 ;
sport_handle - > wdsize = 1 ;
2013-11-13 17:15:00 +01:00
break ;
2008-09-05 18:21:39 +08:00
case SNDRV_PCM_FORMAT_S16_LE :
2011-03-28 01:45:10 -04:00
bf5xx_i2s - > tcr2 | = 15 ;
bf5xx_i2s - > rcr2 | = 15 ;
2008-09-27 22:30:15 +08:00
sport_handle - > wdsize = 2 ;
2008-09-05 18:21:39 +08:00
break ;
case SNDRV_PCM_FORMAT_S24_LE :
2011-03-28 01:45:10 -04:00
bf5xx_i2s - > tcr2 | = 23 ;
bf5xx_i2s - > rcr2 | = 23 ;
2008-09-27 22:30:15 +08:00
sport_handle - > wdsize = 3 ;
2008-09-05 18:21:39 +08:00
break ;
case SNDRV_PCM_FORMAT_S32_LE :
2011-03-28 01:45:10 -04:00
bf5xx_i2s - > tcr2 | = 31 ;
bf5xx_i2s - > rcr2 | = 31 ;
2008-09-27 22:30:15 +08:00
sport_handle - > wdsize = 4 ;
2008-09-05 18:21:39 +08:00
break ;
}
2011-03-28 01:45:10 -04:00
if ( ! bf5xx_i2s - > configured ) {
2008-09-05 18:21:39 +08:00
/*
* TX and RX are not independent , they are enabled at the
* same time , even if only one side is running . So , we
* need to configure both of them at the time when the first
* stream is opened .
*
2008-09-27 22:30:15 +08:00
* CPU DAI : slave mode .
2008-09-05 18:21:39 +08:00
*/
2011-03-28 01:45:10 -04:00
bf5xx_i2s - > configured = 1 ;
ret = sport_config_rx ( sport_handle , bf5xx_i2s - > rcr1 ,
bf5xx_i2s - > rcr2 , 0 , 0 ) ;
2008-09-05 18:21:39 +08:00
if ( ret ) {
2013-05-28 19:22:10 +02:00
dev_err ( dai - > dev , " SPORT is busy! \n " ) ;
2008-09-05 18:21:39 +08:00
return - EBUSY ;
}
2011-03-28 01:45:10 -04:00
ret = sport_config_tx ( sport_handle , bf5xx_i2s - > tcr1 ,
bf5xx_i2s - > tcr2 , 0 , 0 ) ;
2008-09-05 18:21:39 +08:00
if ( ret ) {
2013-05-28 19:22:10 +02:00
dev_err ( dai - > dev , " SPORT is busy! \n " ) ;
2008-09-05 18:21:39 +08:00
return - EBUSY ;
}
}
return 0 ;
}
2008-11-18 22:11:38 +00:00
static void bf5xx_i2s_shutdown ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
2008-09-05 18:21:39 +08:00
{
2011-03-28 01:45:10 -04:00
struct sport_device * sport_handle = snd_soc_dai_get_drvdata ( dai ) ;
struct bf5xx_i2s_port * bf5xx_i2s = sport_handle - > private_data ;
2013-05-28 19:22:10 +02:00
dev_dbg ( dai - > dev , " %s enter \n " , __func__ ) ;
2009-06-20 11:29:05 -04:00
/* No active stream, SPORT is allowed to be configured again. */
2009-09-23 11:51:04 -04:00
if ( ! dai - > active )
2011-03-28 01:45:10 -04:00
bf5xx_i2s - > configured = 0 ;
2008-09-27 22:30:15 +08:00
}
2013-05-28 19:22:14 +02:00
static int bf5xx_i2s_set_channel_map ( struct snd_soc_dai * dai ,
unsigned int tx_num , unsigned int * tx_slot ,
unsigned int rx_num , unsigned int * rx_slot )
{
struct sport_device * sport_handle = snd_soc_dai_get_drvdata ( dai ) ;
struct bf5xx_i2s_port * bf5xx_i2s = sport_handle - > private_data ;
unsigned int tx_mapped = 0 , rx_mapped = 0 ;
unsigned int slot ;
int i ;
if ( ( tx_num > BFIN_TDM_DAI_MAX_SLOTS ) | |
( rx_num > BFIN_TDM_DAI_MAX_SLOTS ) )
return - EINVAL ;
for ( i = 0 ; i < tx_num ; i + + ) {
slot = tx_slot [ i ] ;
if ( ( slot < BFIN_TDM_DAI_MAX_SLOTS ) & &
( ! ( tx_mapped & ( 1 < < slot ) ) ) ) {
bf5xx_i2s - > tx_dma_data . map [ i ] = slot ;
tx_mapped | = 1 < < slot ;
} else
return - EINVAL ;
}
for ( i = 0 ; i < rx_num ; i + + ) {
slot = rx_slot [ i ] ;
if ( ( slot < BFIN_TDM_DAI_MAX_SLOTS ) & &
( ! ( rx_mapped & ( 1 < < slot ) ) ) ) {
bf5xx_i2s - > rx_dma_data . map [ i ] = slot ;
rx_mapped | = 1 < < slot ;
} else
return - EINVAL ;
}
return 0 ;
}
static int bf5xx_i2s_set_tdm_slot ( struct snd_soc_dai * dai , unsigned int tx_mask ,
unsigned int rx_mask , int slots , int width )
{
struct sport_device * sport_handle = snd_soc_dai_get_drvdata ( dai ) ;
struct bf5xx_i2s_port * bf5xx_i2s = sport_handle - > private_data ;
if ( slots % 8 ! = 0 | | slots > 8 )
return - EINVAL ;
if ( width ! = 32 )
return - EINVAL ;
bf5xx_i2s - > slots = slots ;
bf5xx_i2s - > tx_mask = tx_mask ;
bf5xx_i2s - > rx_mask = rx_mask ;
bf5xx_i2s - > tx_dma_data . tdm_mode = slots ! = 0 ;
bf5xx_i2s - > rx_dma_data . tdm_mode = slots ! = 0 ;
return sport_set_multichannel ( sport_handle , slots , tx_mask , rx_mask , 0 ) ;
}
2008-09-05 18:21:39 +08:00
# ifdef CONFIG_PM
2008-12-03 18:21:52 +00:00
static int bf5xx_i2s_suspend ( struct snd_soc_dai * dai )
2008-09-05 18:21:39 +08:00
{
2011-03-28 01:45:10 -04:00
struct sport_device * sport_handle = snd_soc_dai_get_drvdata ( dai ) ;
2008-09-05 18:21:39 +08:00
2013-05-28 19:22:10 +02:00
dev_dbg ( dai - > dev , " %s : sport %d \n " , __func__ , dai - > id ) ;
2009-09-16 20:25:12 -04:00
2010-03-17 20:15:21 +00:00
if ( dai - > capture_active )
2009-09-16 20:25:12 -04:00
sport_rx_stop ( sport_handle ) ;
2010-03-17 20:15:21 +00:00
if ( dai - > playback_active )
2009-09-16 20:25:12 -04:00
sport_tx_stop ( sport_handle ) ;
2008-09-05 18:21:39 +08:00
return 0 ;
}
2009-06-20 11:29:57 -04:00
static int bf5xx_i2s_resume ( struct snd_soc_dai * dai )
2008-09-05 18:21:39 +08:00
{
2011-03-28 01:45:10 -04:00
struct sport_device * sport_handle = snd_soc_dai_get_drvdata ( dai ) ;
struct bf5xx_i2s_port * bf5xx_i2s = sport_handle - > private_data ;
2008-09-05 18:21:39 +08:00
int ret ;
2013-05-28 19:22:10 +02:00
dev_dbg ( dai - > dev , " %s : sport %d \n " , __func__ , dai - > id ) ;
2008-09-05 18:21:39 +08:00
2011-03-28 01:45:10 -04:00
ret = sport_config_rx ( sport_handle , bf5xx_i2s - > rcr1 ,
bf5xx_i2s - > rcr2 , 0 , 0 ) ;
2008-09-05 18:21:39 +08:00
if ( ret ) {
2013-05-28 19:22:10 +02:00
dev_err ( dai - > dev , " SPORT is busy! \n " ) ;
2008-09-05 18:21:39 +08:00
return - EBUSY ;
}
2011-03-28 01:45:10 -04:00
ret = sport_config_tx ( sport_handle , bf5xx_i2s - > tcr1 ,
bf5xx_i2s - > tcr2 , 0 , 0 ) ;
2008-09-05 18:21:39 +08:00
if ( ret ) {
2013-05-28 19:22:10 +02:00
dev_err ( dai - > dev , " SPORT is busy! \n " ) ;
2008-09-05 18:21:39 +08:00
return - EBUSY ;
}
2013-05-28 19:22:14 +02:00
return sport_set_multichannel ( sport_handle , bf5xx_i2s - > slots ,
bf5xx_i2s - > tx_mask , bf5xx_i2s - > rx_mask , 0 ) ;
2008-09-05 18:21:39 +08:00
}
# else
# define bf5xx_i2s_suspend NULL
# define bf5xx_i2s_resume NULL
# endif
2013-05-28 19:22:14 +02:00
static int bf5xx_i2s_dai_probe ( struct snd_soc_dai * dai )
{
struct sport_device * sport_handle = snd_soc_dai_get_drvdata ( dai ) ;
struct bf5xx_i2s_port * bf5xx_i2s = sport_handle - > private_data ;
unsigned int i ;
for ( i = 0 ; i < BFIN_TDM_DAI_MAX_SLOTS ; i + + ) {
bf5xx_i2s - > tx_dma_data . map [ i ] = i ;
bf5xx_i2s - > rx_dma_data . map [ i ] = i ;
}
dai - > playback_dma_data = & bf5xx_i2s - > tx_dma_data ;
dai - > capture_dma_data = & bf5xx_i2s - > rx_dma_data ;
return 0 ;
}
2008-09-05 18:21:39 +08:00
# define BF5XX_I2S_RATES (SNDRV_PCM_RATE_8000 | SNDRV_PCM_RATE_11025 |\
SNDRV_PCM_RATE_16000 | SNDRV_PCM_RATE_22050 | \
SNDRV_PCM_RATE_44100 | SNDRV_PCM_RATE_48000 | \
SNDRV_PCM_RATE_96000 )
2011-03-26 03:05:08 -04:00
# define BF5XX_I2S_FORMATS \
( SNDRV_PCM_FMTBIT_S8 | \
SNDRV_PCM_FMTBIT_S16_LE | \
SNDRV_PCM_FMTBIT_S24_LE | \
SNDRV_PCM_FMTBIT_S32_LE )
2008-09-05 18:21:39 +08:00
2011-11-23 11:40:40 +01:00
static const struct snd_soc_dai_ops bf5xx_i2s_dai_ops = {
2013-05-28 19:22:14 +02:00
. shutdown = bf5xx_i2s_shutdown ,
. hw_params = bf5xx_i2s_hw_params ,
. set_fmt = bf5xx_i2s_set_dai_fmt ,
. set_tdm_slot = bf5xx_i2s_set_tdm_slot ,
. set_channel_map = bf5xx_i2s_set_channel_map ,
2009-03-03 09:41:00 +08:00
} ;
2010-03-17 20:15:21 +00:00
static struct snd_soc_dai_driver bf5xx_i2s_dai = {
2013-05-28 19:22:14 +02:00
. probe = bf5xx_i2s_dai_probe ,
2008-09-05 18:21:39 +08:00
. suspend = bf5xx_i2s_suspend ,
. resume = bf5xx_i2s_resume ,
. playback = {
2013-05-28 19:22:14 +02:00
. channels_min = 2 ,
. channels_max = 8 ,
2008-09-05 18:21:39 +08:00
. rates = BF5XX_I2S_RATES ,
. formats = BF5XX_I2S_FORMATS , } ,
. capture = {
2013-05-28 19:22:14 +02:00
. channels_min = 2 ,
. channels_max = 8 ,
2008-09-05 18:21:39 +08:00
. rates = BF5XX_I2S_RATES ,
. formats = BF5XX_I2S_FORMATS , } ,
2009-03-03 09:41:00 +08:00
. ops = & bf5xx_i2s_dai_ops ,
2008-09-05 18:21:39 +08:00
} ;
2010-03-17 20:15:21 +00:00
2013-03-21 03:30:08 -07:00
static const struct snd_soc_component_driver bf5xx_i2s_component = {
. name = " bf5xx-i2s " ,
} ;
2012-12-07 09:26:13 -05:00
static int bf5xx_i2s_probe ( struct platform_device * pdev )
2010-03-17 20:15:21 +00:00
{
2011-03-28 01:45:10 -04:00
struct sport_device * sport_handle ;
int ret ;
/* configure SPORT for I2S */
2013-05-28 19:22:14 +02:00
sport_handle = sport_init ( pdev , 4 , 8 * sizeof ( u32 ) ,
2011-03-28 01:45:10 -04:00
sizeof ( struct bf5xx_i2s_port ) ) ;
if ( ! sport_handle )
return - ENODEV ;
/* register with the ASoC layers */
2013-03-21 03:30:08 -07:00
ret = snd_soc_register_component ( & pdev - > dev , & bf5xx_i2s_component ,
& bf5xx_i2s_dai , 1 ) ;
2011-03-28 01:45:10 -04:00
if ( ret ) {
2013-05-28 19:22:10 +02:00
dev_err ( & pdev - > dev , " Failed to register DAI: %d \n " , ret ) ;
2011-03-28 01:45:10 -04:00
sport_done ( sport_handle ) ;
return ret ;
}
return 0 ;
2010-03-17 20:15:21 +00:00
}
2012-12-07 09:26:13 -05:00
static int bf5xx_i2s_remove ( struct platform_device * pdev )
2010-03-17 20:15:21 +00:00
{
2011-03-28 01:45:10 -04:00
struct sport_device * sport_handle = platform_get_drvdata ( pdev ) ;
2013-05-28 19:22:10 +02:00
dev_dbg ( & pdev - > dev , " %s enter \n " , __func__ ) ;
2011-03-28 01:45:10 -04:00
2013-03-21 03:30:08 -07:00
snd_soc_unregister_component ( & pdev - > dev ) ;
2011-03-28 01:45:10 -04:00
sport_done ( sport_handle ) ;
2010-03-17 20:15:21 +00:00
return 0 ;
}
static struct platform_driver bfin_i2s_driver = {
2011-03-28 01:45:10 -04:00
. probe = bf5xx_i2s_probe ,
2012-12-07 09:26:13 -05:00
. remove = bf5xx_i2s_remove ,
2010-03-17 20:15:21 +00:00
. driver = {
2011-03-28 01:45:09 -04:00
. name = " bfin-i2s " ,
2010-03-17 20:15:21 +00:00
. owner = THIS_MODULE ,
} ,
} ;
2008-09-05 18:21:39 +08:00
2011-11-24 14:44:52 +08:00
module_platform_driver ( bfin_i2s_driver ) ;
2008-12-03 19:26:35 +00:00
2008-09-05 18:21:39 +08:00
/* Module information */
MODULE_AUTHOR ( " Cliff Cai " ) ;
MODULE_DESCRIPTION ( " I2S driver for ADI Blackfin " ) ;
MODULE_LICENSE ( " GPL " ) ;