2008-10-03 16:58:58 +02:00
/*
* sam9g20_wm8731 - - SoC audio for AT91SAM9G20 - based
* ATMEL AT91SAM9G20ek board .
*
* Copyright ( C ) 2005 SAN People
* Copyright ( C ) 2008 Atmel
*
* Authors : Sedji Gaouaou < sedji . gaouaou @ atmel . com >
*
* Based on ati_b1_wm8731 . c by :
* Frank Mandarino < fmandarino @ endrelia . com >
* Copyright 2006 Endrelia Technologies Inc .
* Based on corgi . c by :
* Copyright 2005 Wolfson Microelectronics PLC .
* Copyright 2005 Openedhand Ltd .
*
* 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 , write to the Free Software
* Foundation , Inc . , 59 Temple Place , Suite 330 , Boston , MA 02111 - 1307 USA
*/
# include <linux/module.h>
# include <linux/moduleparam.h>
# include <linux/kernel.h>
# include <linux/clk.h>
# include <linux/timer.h>
# include <linux/interrupt.h>
# include <linux/platform_device.h>
2009-02-16 20:49:16 +00:00
# include <linux/i2c.h>
2008-10-03 16:58:58 +02:00
# include <linux/atmel-ssc.h>
# include <sound/core.h>
# include <sound/pcm.h>
# include <sound/pcm_params.h>
# include <sound/soc.h>
2009-02-16 16:04:05 +00:00
# include <asm/mach-types.h>
2008-10-03 16:58:58 +02:00
# include <mach/hardware.h>
# include <mach/gpio.h>
# include "../codecs/wm8731.h"
# include "atmel-pcm.h"
# include "atmel_ssc_dai.h"
2009-02-16 17:51:54 +00:00
# define MCLK_RATE 12000000
2009-07-08 18:18:19 +01:00
/*
* As shipped the board does not have inputs . However , it is relatively
* straightforward to modify the board to hook them up so support is left
* in the driver .
*/
# undef ENABLE_MIC_INPUT
2009-02-16 17:51:54 +00:00
static struct clk * mclk ;
2008-10-03 16:58:58 +02:00
static int at91sam9g20ek_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
2010-03-17 20:15:21 +00:00
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
struct snd_soc_dai * cpu_dai = rtd - > cpu_dai ;
2008-10-03 16:58:58 +02:00
int ret ;
/* set codec DAI configuration */
ret = snd_soc_dai_set_fmt ( codec_dai , SND_SOC_DAIFMT_I2S |
2009-07-08 18:05:51 +01:00
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM ) ;
2008-10-03 16:58:58 +02:00
if ( ret < 0 )
return ret ;
/* set cpu DAI configuration */
ret = snd_soc_dai_set_fmt ( cpu_dai , SND_SOC_DAIFMT_I2S |
2009-07-08 18:05:51 +01:00
SND_SOC_DAIFMT_NB_NF | SND_SOC_DAIFMT_CBM_CFM ) ;
2008-10-03 16:58:58 +02:00
if ( ret < 0 )
return ret ;
return 0 ;
}
static struct snd_soc_ops at91sam9g20ek_ops = {
. hw_params = at91sam9g20ek_hw_params ,
} ;
2009-02-16 17:51:54 +00:00
static int at91sam9g20ek_set_bias_level ( struct snd_soc_card * card ,
enum snd_soc_bias_level level )
{
static int mclk_on ;
int ret = 0 ;
switch ( level ) {
case SND_SOC_BIAS_ON :
case SND_SOC_BIAS_PREPARE :
if ( ! mclk_on )
ret = clk_enable ( mclk ) ;
if ( ret = = 0 )
mclk_on = 1 ;
break ;
case SND_SOC_BIAS_OFF :
case SND_SOC_BIAS_STANDBY :
if ( mclk_on )
clk_disable ( mclk ) ;
mclk_on = 0 ;
break ;
}
return ret ;
}
2008-10-03 16:58:58 +02:00
static const struct snd_soc_dapm_widget at91sam9g20ek_dapm_widgets [ ] = {
SND_SOC_DAPM_MIC ( " Int Mic " , NULL ) ,
SND_SOC_DAPM_SPK ( " Ext Spk " , NULL ) ,
} ;
static const struct snd_soc_dapm_route intercon [ ] = {
/* speaker connected to LHPOUT */
{ " Ext Spk " , NULL , " LHPOUT " } ,
/* mic is connected to Mic Jack, with WM8731 Mic Bias */
{ " MICIN " , NULL , " Mic Bias " } ,
{ " Mic Bias " , NULL , " Int Mic " } ,
} ;
/*
* Logic for a wm8731 as connected on a at91sam9g20ek board .
*/
2010-03-17 20:15:21 +00:00
static int at91sam9g20ek_wm8731_init ( struct snd_soc_pcm_runtime * rtd )
2008-10-03 16:58:58 +02:00
{
2010-03-17 20:15:21 +00:00
struct snd_soc_codec * codec = rtd - > codec ;
struct snd_soc_dai * codec_dai = rtd - > codec_dai ;
2010-11-05 15:53:46 +02:00
struct snd_soc_dapm_context * dapm = & codec - > dapm ;
2009-07-08 18:26:16 +01:00
int ret ;
2008-10-03 16:58:58 +02:00
printk ( KERN_DEBUG
" at91sam9g20ek_wm8731 "
" : at91sam9g20ek_wm8731_init() called \n " ) ;
2011-05-24 11:51:16 +02:00
ret = snd_soc_dai_set_sysclk ( codec_dai , WM8731_SYSCLK_MCLK ,
2009-07-08 18:26:16 +01:00
MCLK_RATE , SND_SOC_CLOCK_IN ) ;
if ( ret < 0 ) {
printk ( KERN_ERR " Failed to set WM8731 SYSCLK: %d \n " , ret ) ;
return ret ;
}
2008-10-03 16:58:58 +02:00
/* Add specific widgets */
2010-11-05 15:53:46 +02:00
snd_soc_dapm_new_controls ( dapm , at91sam9g20ek_dapm_widgets ,
2008-10-03 16:58:58 +02:00
ARRAY_SIZE ( at91sam9g20ek_dapm_widgets ) ) ;
/* Set up specific audio path interconnects */
2010-11-05 15:53:46 +02:00
snd_soc_dapm_add_routes ( dapm , intercon , ARRAY_SIZE ( intercon ) ) ;
2008-10-03 16:58:58 +02:00
/* not connected */
2010-11-05 15:53:46 +02:00
snd_soc_dapm_nc_pin ( dapm , " RLINEIN " ) ;
snd_soc_dapm_nc_pin ( dapm , " LLINEIN " ) ;
2008-10-03 16:58:58 +02:00
2009-07-08 18:18:19 +01:00
# ifdef ENABLE_MIC_INPUT
2010-11-05 15:53:46 +02:00
snd_soc_dapm_enable_pin ( dapm , " Int Mic " ) ;
2009-07-08 18:18:19 +01:00
# else
2010-11-05 15:53:46 +02:00
snd_soc_dapm_nc_pin ( dapm , " Int Mic " ) ;
2009-07-08 18:18:19 +01:00
# endif
/* always connected */
2010-11-05 15:53:46 +02:00
snd_soc_dapm_enable_pin ( dapm , " Ext Spk " ) ;
2008-10-03 16:58:58 +02:00
2010-11-05 15:53:46 +02:00
snd_soc_dapm_sync ( dapm ) ;
2008-10-03 16:58:58 +02:00
return 0 ;
}
static struct snd_soc_dai_link at91sam9g20ek_dai = {
. name = " WM8731 " ,
. stream_name = " WM8731 PCM " ,
2010-03-17 20:15:21 +00:00
. cpu_dai_name = " atmel-ssc-dai.0 " ,
. codec_dai_name = " wm8731-hifi " ,
2008-10-03 16:58:58 +02:00
. init = at91sam9g20ek_wm8731_init ,
2010-08-18 16:25:59 +01:00
. platform_name = " atmel-pcm-audio " ,
2011-03-27 14:35:15 +01:00
. codec_name = " wm8731.0-001b " ,
2008-10-03 16:58:58 +02:00
. ops = & at91sam9g20ek_ops ,
} ;
2008-11-18 20:50:34 +00:00
static struct snd_soc_card snd_soc_at91sam9g20ek = {
2009-02-16 18:00:58 +00:00
. name = " AT91SAMG20-EK " ,
2008-10-03 16:58:58 +02:00
. dai_link = & at91sam9g20ek_dai ,
. num_links = 1 ,
2009-02-16 17:51:54 +00:00
. set_bias_level = at91sam9g20ek_set_bias_level ,
2008-10-03 16:58:58 +02:00
} ;
static struct platform_device * at91sam9g20ek_snd_device ;
static int __init at91sam9g20ek_init ( void )
{
2009-02-16 17:51:54 +00:00
struct clk * pllb ;
2008-10-03 16:58:58 +02:00
int ret ;
2009-10-08 18:19:49 +02:00
if ( ! ( machine_is_at91sam9g20ek ( ) | | machine_is_at91sam9g20ek_2mmc ( ) ) )
2009-02-16 16:04:05 +00:00
return - ENODEV ;
2010-08-18 16:29:37 +01:00
ret = atmel_ssc_set_audio ( 0 ) ;
if ( ret ! = 0 ) {
pr_err ( " Failed to set SSC 0 for audio: %d \n " , ret ) ;
return ret ;
}
2009-02-16 17:51:54 +00:00
/*
* Codec MCLK is supplied by PCK0 - set it up .
*/
mclk = clk_get ( NULL , " pck0 " ) ;
if ( IS_ERR ( mclk ) ) {
printk ( KERN_ERR " ASoC: Failed to get MCLK \n " ) ;
ret = PTR_ERR ( mclk ) ;
goto err ;
}
pllb = clk_get ( NULL , " pllb " ) ;
2010-11-22 18:59:13 +03:00
if ( IS_ERR ( pllb ) ) {
2009-02-16 17:51:54 +00:00
printk ( KERN_ERR " ASoC: Failed to get PLLB \n " ) ;
2010-11-22 18:59:13 +03:00
ret = PTR_ERR ( pllb ) ;
2009-02-16 17:51:54 +00:00
goto err_mclk ;
}
ret = clk_set_parent ( mclk , pllb ) ;
clk_put ( pllb ) ;
if ( ret ! = 0 ) {
printk ( KERN_ERR " ASoC: Failed to set MCLK parent \n " ) ;
goto err_mclk ;
}
clk_set_rate ( mclk , MCLK_RATE ) ;
2008-10-03 16:58:58 +02:00
at91sam9g20ek_snd_device = platform_device_alloc ( " soc-audio " , - 1 ) ;
if ( ! at91sam9g20ek_snd_device ) {
2009-02-16 13:38:11 +00:00
printk ( KERN_ERR " ASoC: Platform device allocation failed \n " ) ;
2008-10-03 16:58:58 +02:00
ret = - ENOMEM ;
2010-11-25 15:11:03 +08:00
goto err_mclk ;
2008-10-03 16:58:58 +02:00
}
platform_set_drvdata ( at91sam9g20ek_snd_device ,
2010-03-17 20:15:21 +00:00
& snd_soc_at91sam9g20ek ) ;
2008-10-03 16:58:58 +02:00
ret = platform_device_add ( at91sam9g20ek_snd_device ) ;
if ( ret ) {
2009-02-16 13:38:11 +00:00
printk ( KERN_ERR " ASoC: Platform device allocation failed \n " ) ;
2010-11-25 15:11:03 +08:00
goto err_device_add ;
2008-10-03 16:58:58 +02:00
}
return ret ;
2010-11-25 15:11:03 +08:00
err_device_add :
platform_device_put ( at91sam9g20ek_snd_device ) ;
2009-02-16 17:51:54 +00:00
err_mclk :
clk_put ( mclk ) ;
mclk = NULL ;
err :
2008-10-03 16:58:58 +02:00
return ret ;
}
static void __exit at91sam9g20ek_exit ( void )
{
platform_device_unregister ( at91sam9g20ek_snd_device ) ;
at91sam9g20ek_snd_device = NULL ;
2009-02-16 17:51:54 +00:00
clk_put ( mclk ) ;
mclk = NULL ;
2008-10-03 16:58:58 +02:00
}
module_init ( at91sam9g20ek_init ) ;
module_exit ( at91sam9g20ek_exit ) ;
/* Module information */
MODULE_AUTHOR ( " Sedji Gaouaou <sedji.gaouaou@atmel.com> " ) ;
MODULE_DESCRIPTION ( " ALSA SoC AT91SAM9G20EK_WM8731 " ) ;
MODULE_LICENSE ( " GPL " ) ;