2008-09-05 14:21:39 +04: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"
struct bf5xx_i2s_port {
u16 tcr1 ;
u16 rcr1 ;
u16 tcr2 ;
u16 rcr2 ;
2009-06-20 19:29:05 +04:00
int configured ;
2008-09-05 14:21:39 +04:00
} ;
static int bf5xx_i2s_set_dai_fmt ( struct snd_soc_dai * cpu_dai ,
unsigned int fmt )
{
2011-03-28 09: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 14:21:39 +04: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 09: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 18:30:15 +04:00
break ;
case SND_SOC_DAIFMT_DSP_A :
2011-03-28 09:45:10 +04:00
bf5xx_i2s - > tcr1 | = TFSR ;
bf5xx_i2s - > rcr1 | = RFSR ;
2008-09-05 14:21:39 +04:00
break ;
case SND_SOC_DAIFMT_LEFT_J :
ret = - EINVAL ;
break ;
default :
2008-10-27 12:09:25 +03:00
printk ( KERN_ERR " %s: Unknown DAI format type \n " , __func__ ) ;
2008-09-05 14:21:39 +04:00
ret = - EINVAL ;
break ;
}
switch ( fmt & SND_SOC_DAIFMT_MASTER_MASK ) {
case SND_SOC_DAIFMT_CBM_CFM :
break ;
2008-10-27 12:09:25 +03:00
case SND_SOC_DAIFMT_CBS_CFS :
case SND_SOC_DAIFMT_CBM_CFS :
2008-09-05 14:21:39 +04:00
case SND_SOC_DAIFMT_CBS_CFM :
ret = - EINVAL ;
break ;
default :
2008-10-27 12:09:25 +03:00
printk ( KERN_ERR " %s: Unknown DAI master type \n " , __func__ ) ;
2008-09-05 14:21:39 +04:00
ret = - EINVAL ;
break ;
}
return ret ;
}
static int bf5xx_i2s_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-09-05 14:21:39 +04:00
{
2011-03-28 09: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 14:21:39 +04:00
int ret = 0 ;
2011-03-28 09:45:10 +04:00
bf5xx_i2s - > tcr2 & = ~ 0x1f ;
bf5xx_i2s - > rcr2 & = ~ 0x1f ;
2008-09-05 14:21:39 +04:00
switch ( params_format ( params ) ) {
2011-03-26 10:05:08 +03:00
case SNDRV_PCM_FORMAT_S8 :
bf5xx_i2s - > tcr2 | = 7 ;
bf5xx_i2s - > rcr2 | = 7 ;
sport_handle - > wdsize = 1 ;
2008-09-05 14:21:39 +04:00
case SNDRV_PCM_FORMAT_S16_LE :
2011-03-28 09:45:10 +04:00
bf5xx_i2s - > tcr2 | = 15 ;
bf5xx_i2s - > rcr2 | = 15 ;
2008-09-27 18:30:15 +04:00
sport_handle - > wdsize = 2 ;
2008-09-05 14:21:39 +04:00
break ;
case SNDRV_PCM_FORMAT_S24_LE :
2011-03-28 09:45:10 +04:00
bf5xx_i2s - > tcr2 | = 23 ;
bf5xx_i2s - > rcr2 | = 23 ;
2008-09-27 18:30:15 +04:00
sport_handle - > wdsize = 3 ;
2008-09-05 14:21:39 +04:00
break ;
case SNDRV_PCM_FORMAT_S32_LE :
2011-03-28 09:45:10 +04:00
bf5xx_i2s - > tcr2 | = 31 ;
bf5xx_i2s - > rcr2 | = 31 ;
2008-09-27 18:30:15 +04:00
sport_handle - > wdsize = 4 ;
2008-09-05 14:21:39 +04:00
break ;
}
2011-03-28 09:45:10 +04:00
if ( ! bf5xx_i2s - > configured ) {
2008-09-05 14:21:39 +04: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 18:30:15 +04:00
* CPU DAI : slave mode .
2008-09-05 14:21:39 +04:00
*/
2011-03-28 09: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 14:21:39 +04:00
if ( ret ) {
pr_err ( " SPORT is busy! \n " ) ;
return - EBUSY ;
}
2011-03-28 09:45:10 +04:00
ret = sport_config_tx ( sport_handle , bf5xx_i2s - > tcr1 ,
bf5xx_i2s - > tcr2 , 0 , 0 ) ;
2008-09-05 14:21:39 +04:00
if ( ret ) {
pr_err ( " SPORT is busy! \n " ) ;
return - EBUSY ;
}
}
return 0 ;
}
2008-11-19 01:11:38 +03:00
static void bf5xx_i2s_shutdown ( struct snd_pcm_substream * substream ,
struct snd_soc_dai * dai )
2008-09-05 14:21:39 +04:00
{
2011-03-28 09: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 14:21:39 +04:00
pr_debug ( " %s enter \n " , __func__ ) ;
2009-06-20 19:29:05 +04:00
/* No active stream, SPORT is allowed to be configured again. */
2009-09-23 19:51:04 +04:00
if ( ! dai - > active )
2011-03-28 09:45:10 +04:00
bf5xx_i2s - > configured = 0 ;
2008-09-27 18:30:15 +04:00
}
2008-09-05 14:21:39 +04:00
# ifdef CONFIG_PM
2008-12-03 21:21:52 +03:00
static int bf5xx_i2s_suspend ( struct snd_soc_dai * dai )
2008-09-05 14:21:39 +04:00
{
2011-03-28 09:45:10 +04:00
struct sport_device * sport_handle = snd_soc_dai_get_drvdata ( dai ) ;
2008-09-05 14:21:39 +04:00
pr_debug ( " %s : sport %d \n " , __func__ , dai - > id ) ;
2009-09-17 04:25:12 +04:00
2010-03-17 23:15:21 +03:00
if ( dai - > capture_active )
2009-09-17 04:25:12 +04:00
sport_rx_stop ( sport_handle ) ;
2010-03-17 23:15:21 +03:00
if ( dai - > playback_active )
2009-09-17 04:25:12 +04:00
sport_tx_stop ( sport_handle ) ;
2008-09-05 14:21:39 +04:00
return 0 ;
}
2009-06-20 19:29:57 +04:00
static int bf5xx_i2s_resume ( struct snd_soc_dai * dai )
2008-09-05 14:21:39 +04:00
{
2011-03-28 09: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 14:21:39 +04:00
int ret ;
pr_debug ( " %s : sport %d \n " , __func__ , dai - > id ) ;
2011-03-28 09:45:10 +04:00
ret = sport_config_rx ( sport_handle , bf5xx_i2s - > rcr1 ,
bf5xx_i2s - > rcr2 , 0 , 0 ) ;
2008-09-05 14:21:39 +04:00
if ( ret ) {
pr_err ( " SPORT is busy! \n " ) ;
return - EBUSY ;
}
2011-03-28 09:45:10 +04:00
ret = sport_config_tx ( sport_handle , bf5xx_i2s - > tcr1 ,
bf5xx_i2s - > tcr2 , 0 , 0 ) ;
2008-09-05 14:21:39 +04:00
if ( ret ) {
pr_err ( " SPORT is busy! \n " ) ;
return - EBUSY ;
}
return 0 ;
}
# else
# define bf5xx_i2s_suspend NULL
# define bf5xx_i2s_resume NULL
# endif
# 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 10:05:08 +03: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 14:21:39 +04:00
2009-03-03 04:41:00 +03:00
static struct snd_soc_dai_ops bf5xx_i2s_dai_ops = {
. shutdown = bf5xx_i2s_shutdown ,
. hw_params = bf5xx_i2s_hw_params ,
. set_fmt = bf5xx_i2s_set_dai_fmt ,
} ;
2010-03-17 23:15:21 +03:00
static struct snd_soc_dai_driver bf5xx_i2s_dai = {
2008-09-05 14:21:39 +04:00
. suspend = bf5xx_i2s_suspend ,
. resume = bf5xx_i2s_resume ,
. playback = {
2008-09-27 18:30:15 +04:00
. channels_min = 1 ,
2008-09-05 14:21:39 +04:00
. channels_max = 2 ,
. rates = BF5XX_I2S_RATES ,
. formats = BF5XX_I2S_FORMATS , } ,
. capture = {
2008-09-27 18:30:15 +04:00
. channels_min = 1 ,
2008-09-05 14:21:39 +04:00
. channels_max = 2 ,
. rates = BF5XX_I2S_RATES ,
. formats = BF5XX_I2S_FORMATS , } ,
2009-03-03 04:41:00 +03:00
. ops = & bf5xx_i2s_dai_ops ,
2008-09-05 14:21:39 +04:00
} ;
2010-03-17 23:15:21 +03:00
2011-03-28 09:45:10 +04:00
static int __devinit bf5xx_i2s_probe ( struct platform_device * pdev )
2010-03-17 23:15:21 +03:00
{
2011-03-28 09:45:10 +04:00
struct sport_device * sport_handle ;
int ret ;
/* configure SPORT for I2S */
sport_handle = sport_init ( pdev , 4 , 2 * sizeof ( u32 ) ,
sizeof ( struct bf5xx_i2s_port ) ) ;
if ( ! sport_handle )
return - ENODEV ;
/* register with the ASoC layers */
ret = snd_soc_register_dai ( & pdev - > dev , & bf5xx_i2s_dai ) ;
if ( ret ) {
pr_err ( " Failed to register DAI: %d \n " , ret ) ;
sport_done ( sport_handle ) ;
return ret ;
}
return 0 ;
2010-03-17 23:15:21 +03:00
}
2011-03-28 09:45:10 +04:00
static int __devexit bf5xx_i2s_remove ( struct platform_device * pdev )
2010-03-17 23:15:21 +03:00
{
2011-03-28 09:45:10 +04:00
struct sport_device * sport_handle = platform_get_drvdata ( pdev ) ;
pr_debug ( " %s enter \n " , __func__ ) ;
2010-03-17 23:15:21 +03:00
snd_soc_unregister_dai ( & pdev - > dev ) ;
2011-03-28 09:45:10 +04:00
sport_done ( sport_handle ) ;
2010-03-17 23:15:21 +03:00
return 0 ;
}
static struct platform_driver bfin_i2s_driver = {
2011-03-28 09:45:10 +04:00
. probe = bf5xx_i2s_probe ,
. remove = __devexit_p ( bf5xx_i2s_remove ) ,
2010-03-17 23:15:21 +03:00
. driver = {
2011-03-28 09:45:09 +04:00
. name = " bfin-i2s " ,
2010-03-17 23:15:21 +03:00
. owner = THIS_MODULE ,
} ,
} ;
2008-09-05 14:21:39 +04:00
2008-12-10 09:47:22 +03:00
static int __init bfin_i2s_init ( void )
2008-12-03 22:26:35 +03:00
{
2010-03-17 23:15:21 +03:00
return platform_driver_register ( & bfin_i2s_driver ) ;
2008-12-03 22:26:35 +03:00
}
static void __exit bfin_i2s_exit ( void )
{
2010-03-17 23:15:21 +03:00
platform_driver_unregister ( & bfin_i2s_driver ) ;
2008-12-03 22:26:35 +03:00
}
2010-03-17 23:15:21 +03:00
module_init ( bfin_i2s_init ) ;
2008-12-03 22:26:35 +03:00
module_exit ( bfin_i2s_exit ) ;
2008-09-05 14:21:39 +04:00
/* Module information */
MODULE_AUTHOR ( " Cliff Cai " ) ;
MODULE_DESCRIPTION ( " I2S driver for ADI Blackfin " ) ;
MODULE_LICENSE ( " GPL " ) ;