2019-05-27 08:55:05 +02:00
// SPDX-License-Identifier: GPL-2.0-or-later
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 .
*/
# 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>
2013-10-11 17:24:01 +05:30
# include <linux/of.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>
# 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
2009-02-16 17:51:54 +00:00
static int at91sam9g20ek_set_bias_level ( struct snd_soc_card * card ,
2011-06-06 19:13:23 +01:00
struct snd_soc_dapm_context * dapm ,
2009-02-16 17:51:54 +00:00
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 [ ] = {
2015-04-11 11:18:43 +02:00
/* speaker connected to LHPOUT/RHPOUT */
2008-10-03 16:58:58 +02:00
{ " Ext Spk " , NULL , " LHPOUT " } ,
2015-04-11 11:18:43 +02:00
{ " Ext Spk " , NULL , " RHPOUT " } ,
2008-10-03 16:58:58 +02:00
/* 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_dai * codec_dai = rtd - > codec_dai ;
2018-01-15 08:52:54 +01:00
struct device * dev = rtd - > dev ;
2009-07-08 18:26:16 +01:00
int ret ;
2018-01-15 08:52:54 +01:00
dev_dbg ( dev , " %s called \n " , __func__ ) ;
2008-10-03 16:58:58 +02:00
2011-05-24 11:51:16 +02:00
ret = snd_soc_dai_set_sysclk ( codec_dai , WM8731_SYSCLK_MCLK ,
2018-01-15 08:52:54 +01:00
MCLK_RATE , SND_SOC_CLOCK_IN ) ;
2009-07-08 18:26:16 +01:00
if ( ret < 0 ) {
2018-01-15 08:52:54 +01:00
dev_err ( dev , " Failed to set WM8731 SYSCLK: %d \n " , ret ) ;
2009-07-08 18:26:16 +01:00
return ret ;
}
2014-03-08 15:47:22 +01:00
# ifndef ENABLE_MIC_INPUT
snd_soc_dapm_nc_pin ( & rtd - > card - > dapm , " Int Mic " ) ;
2009-07-08 18:18:19 +01:00
# endif
2008-10-03 16:58:58 +02:00
return 0 ;
}
2019-06-06 13:13:46 +09:00
SND_SOC_DAILINK_DEFS ( pcm ,
DAILINK_COMP_ARRAY ( COMP_CPU ( " at91rm9200_ssc.0 " ) ) ,
2019-06-28 10:47:02 +09:00
DAILINK_COMP_ARRAY ( COMP_CODEC ( " wm8731.0-001b " , " wm8731-hifi " ) ) ,
DAILINK_COMP_ARRAY ( COMP_PLATFORM ( " at91rm9200_ssc.0 " ) ) ) ;
2019-06-06 13:13:46 +09:00
2008-10-03 16:58:58 +02:00
static struct snd_soc_dai_link at91sam9g20ek_dai = {
. name = " WM8731 " ,
. stream_name = " WM8731 PCM " ,
. init = at91sam9g20ek_wm8731_init ,
2015-01-01 17:16:10 +01:00
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBM_CFM ,
2019-06-06 13:13:46 +09:00
SND_SOC_DAILINK_REG ( pcm ) ,
2008-10-03 16:58:58 +02:00
} ;
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 " ,
2011-12-22 21:14:58 +08:00
. owner = THIS_MODULE ,
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 ,
2014-03-08 15:47:22 +01:00
. dapm_widgets = at91sam9g20ek_dapm_widgets ,
. num_dapm_widgets = ARRAY_SIZE ( at91sam9g20ek_dapm_widgets ) ,
. dapm_routes = intercon ,
. num_dapm_routes = ARRAY_SIZE ( intercon ) ,
2015-04-11 11:18:43 +02:00
. fully_routed = true ,
2008-10-03 16:58:58 +02:00
} ;
2012-12-07 09:26:21 -05:00
static int at91sam9g20ek_audio_probe ( struct platform_device * pdev )
2008-10-03 16:58:58 +02:00
{
2012-11-14 18:09:11 +08:00
struct device_node * np = pdev - > dev . of_node ;
struct device_node * codec_np , * cpu_np ;
2009-02-16 17:51:54 +00:00
struct clk * pllb ;
2012-10-11 10:38:15 +08:00
struct snd_soc_card * card = & snd_soc_at91sam9g20ek ;
2008-10-03 16:58:58 +02:00
int ret ;
2012-11-14 18:09:11 +08:00
if ( ! np ) {
2015-01-06 12:14:32 +01:00
return - ENODEV ;
2012-11-14 18:09:11 +08:00
}
2009-02-16 16:04:05 +00:00
2012-11-14 18:09:10 +08:00
ret = atmel_ssc_set_audio ( 0 ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " ssc channel is not valid \n " ) ;
return - EINVAL ;
}
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 ) ) {
2018-01-15 08:52:54 +01:00
dev_err ( & pdev - > dev , " Failed to get MCLK \n " ) ;
2009-02-16 17:51:54 +00:00
ret = PTR_ERR ( mclk ) ;
goto err ;
}
pllb = clk_get ( NULL , " pllb " ) ;
2010-11-22 18:59:13 +03:00
if ( IS_ERR ( pllb ) ) {
2018-01-15 08:52:54 +01:00
dev_err ( & pdev - > dev , " 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 ) {
2018-01-15 08:52:54 +01:00
dev_err ( & pdev - > dev , " Failed to set MCLK parent \n " ) ;
2009-02-16 17:51:54 +00:00
goto err_mclk ;
}
clk_set_rate ( mclk , MCLK_RATE ) ;
2012-10-11 10:38:15 +08:00
card - > dev = & pdev - > dev ;
2012-11-14 18:09:11 +08:00
/* Parse device node info */
2015-01-06 12:14:32 +01:00
ret = snd_soc_of_parse_card_name ( card , " atmel,model " ) ;
if ( ret )
goto err ;
ret = snd_soc_of_parse_audio_routing ( card ,
" atmel,audio-routing " ) ;
if ( ret )
goto err ;
/* Parse codec info */
2019-06-06 13:13:46 +09:00
at91sam9g20ek_dai . codecs - > name = NULL ;
2015-01-06 12:14:32 +01:00
codec_np = of_parse_phandle ( np , " atmel,audio-codec " , 0 ) ;
if ( ! codec_np ) {
dev_err ( & pdev - > dev , " codec info missing \n " ) ;
return - EINVAL ;
}
2019-06-06 13:13:46 +09:00
at91sam9g20ek_dai . codecs - > of_node = codec_np ;
2015-01-06 12:14:32 +01:00
/* Parse dai and platform info */
2019-06-06 13:13:46 +09:00
at91sam9g20ek_dai . cpus - > dai_name = NULL ;
2019-06-28 10:47:02 +09:00
at91sam9g20ek_dai . platforms - > name = NULL ;
2015-01-06 12:14:32 +01:00
cpu_np = of_parse_phandle ( np , " atmel,ssc-controller " , 0 ) ;
if ( ! cpu_np ) {
dev_err ( & pdev - > dev , " dai and pcm info missing \n " ) ;
return - EINVAL ;
2012-11-14 18:09:11 +08:00
}
2019-06-06 13:13:46 +09:00
at91sam9g20ek_dai . cpus - > of_node = cpu_np ;
2019-06-28 10:47:02 +09:00
at91sam9g20ek_dai . platforms - > of_node = cpu_np ;
2015-01-06 12:14:32 +01:00
of_node_put ( codec_np ) ;
of_node_put ( cpu_np ) ;
2012-11-14 18:09:11 +08:00
2012-10-11 10:38:15 +08:00
ret = snd_soc_register_card ( card ) ;
2008-10-03 16:58:58 +02:00
if ( ret ) {
2018-01-15 08:52:54 +01:00
dev_err ( & pdev - > dev , " snd_soc_register_card() failed \n " ) ;
2008-10-03 16:58:58 +02:00
}
return ret ;
2009-02-16 17:51:54 +00:00
err_mclk :
clk_put ( mclk ) ;
mclk = NULL ;
err :
2012-11-14 18:09:10 +08:00
atmel_ssc_put_audio ( 0 ) ;
2008-10-03 16:58:58 +02:00
return ret ;
}
2012-12-07 09:26:21 -05:00
static int at91sam9g20ek_audio_remove ( struct platform_device * pdev )
2008-10-03 16:58:58 +02:00
{
2012-10-11 10:38:15 +08:00
struct snd_soc_card * card = platform_get_drvdata ( pdev ) ;
2013-01-31 11:53:40 +08:00
clk_disable ( mclk ) ;
2009-02-16 17:51:54 +00:00
mclk = NULL ;
2013-01-31 11:53:40 +08:00
snd_soc_unregister_card ( card ) ;
atmel_ssc_put_audio ( 0 ) ;
2012-10-11 10:38:15 +08:00
return 0 ;
2008-10-03 16:58:58 +02:00
}
2012-11-14 18:09:11 +08:00
# ifdef CONFIG_OF
static const struct of_device_id at91sam9g20ek_wm8731_dt_ids [ ] = {
{ . compatible = " atmel,at91sam9g20ek-wm8731-audio " , } ,
{ }
} ;
MODULE_DEVICE_TABLE ( of , at91sam9g20ek_wm8731_dt_ids ) ;
# endif
2012-10-11 10:38:15 +08:00
static struct platform_driver at91sam9g20ek_audio_driver = {
. driver = {
. name = " at91sam9g20ek-audio " ,
2012-11-14 18:09:11 +08:00
. of_match_table = of_match_ptr ( at91sam9g20ek_wm8731_dt_ids ) ,
2012-10-11 10:38:15 +08:00
} ,
. probe = at91sam9g20ek_audio_probe ,
2012-12-07 09:26:21 -05:00
. remove = at91sam9g20ek_audio_remove ,
2012-10-11 10:38:15 +08:00
} ;
module_platform_driver ( at91sam9g20ek_audio_driver ) ;
2008-10-03 16:58:58 +02:00
/* Module information */
MODULE_AUTHOR ( " Sedji Gaouaou <sedji.gaouaou@atmel.com> " ) ;
MODULE_DESCRIPTION ( " ALSA SoC AT91SAM9G20EK_WM8731 " ) ;
2012-10-11 10:38:15 +08:00
MODULE_ALIAS ( " platform:at91sam9g20ek-audio " ) ;
2008-10-03 16:58:58 +02:00
MODULE_LICENSE ( " GPL " ) ;