2011-04-13 04:24:39 +04:00
/*
* Speyside audio support
*
* Copyright 2011 Wolfson Microelectronics
*
* 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 .
*/
# include <sound/soc.h>
# include <sound/soc-dapm.h>
2011-04-12 11:00:36 +04:00
# include <sound/jack.h>
# include <linux/gpio.h>
2011-07-15 20:38:28 +04:00
# include <linux/module.h>
2011-04-13 04:24:39 +04:00
2011-06-24 15:10:44 +04:00
# include "../codecs/wm8996.h"
2011-04-12 10:42:25 +04:00
# include "../codecs/wm9081.h"
2011-04-13 04:24:39 +04:00
2011-06-24 15:10:44 +04:00
# define WM8996_HPSEL_GPIO 214
2011-12-11 07:31:22 +04:00
# define MCLK_AUDIO_RATE (512 * 48000)
2011-04-12 11:00:36 +04:00
2011-04-12 10:32:03 +04:00
static int speyside_set_bias_level ( struct snd_soc_card * card ,
2011-06-06 22:13:23 +04:00
struct snd_soc_dapm_context * dapm ,
2011-04-12 10:32:03 +04:00
enum snd_soc_bias_level level )
{
2012-08-23 20:05:48 +04:00
struct snd_soc_dai * codec_dai = card - > rtd [ 1 ] . codec_dai ;
2011-04-12 10:32:03 +04:00
int ret ;
2011-06-06 22:13:23 +04:00
if ( dapm - > dev ! = codec_dai - > dev )
return 0 ;
2011-04-12 10:32:03 +04:00
switch ( level ) {
case SND_SOC_BIAS_STANDBY :
2011-06-24 15:10:44 +04:00
ret = snd_soc_dai_set_sysclk ( codec_dai , WM8996_SYSCLK_MCLK2 ,
2011-04-12 10:32:03 +04:00
32768 , SND_SOC_CLOCK_IN ) ;
if ( ret < 0 )
return ret ;
2011-06-24 15:10:44 +04:00
ret = snd_soc_dai_set_pll ( codec_dai , WM8996_FLL_MCLK2 ,
2011-04-12 10:32:03 +04:00
0 , 0 , 0 ) ;
if ( ret < 0 ) {
pr_err ( " Failed to stop FLL \n " ) ;
return ret ;
}
2011-06-06 22:12:44 +04:00
break ;
default :
break ;
}
return 0 ;
}
static int speyside_set_bias_level_post ( struct snd_soc_card * card ,
struct snd_soc_dapm_context * dapm ,
enum snd_soc_bias_level level )
{
2012-08-23 20:05:48 +04:00
struct snd_soc_dai * codec_dai = card - > rtd [ 1 ] . codec_dai ;
2011-06-06 22:12:44 +04:00
int ret ;
if ( dapm - > dev ! = codec_dai - > dev )
return 0 ;
switch ( level ) {
case SND_SOC_BIAS_PREPARE :
if ( card - > dapm . bias_level = = SND_SOC_BIAS_STANDBY ) {
ret = snd_soc_dai_set_pll ( codec_dai , 0 ,
2011-06-24 15:10:44 +04:00
WM8996_FLL_MCLK2 ,
2011-12-11 07:31:22 +04:00
32768 , MCLK_AUDIO_RATE ) ;
2011-06-06 22:12:44 +04:00
if ( ret < 0 ) {
pr_err ( " Failed to start FLL \n " ) ;
return ret ;
}
ret = snd_soc_dai_set_sysclk ( codec_dai ,
2011-06-24 15:10:44 +04:00
WM8996_SYSCLK_FLL ,
2011-12-11 07:31:22 +04:00
MCLK_AUDIO_RATE ,
2011-06-06 22:12:44 +04:00
SND_SOC_CLOCK_IN ) ;
if ( ret < 0 )
return ret ;
}
break ;
2011-04-12 10:32:03 +04:00
default :
break ;
}
2011-06-06 22:12:44 +04:00
card - > dapm . bias_level = level ;
2011-04-12 10:32:03 +04:00
return 0 ;
}
2011-04-12 11:00:36 +04:00
static struct snd_soc_jack speyside_headset ;
/* Headset jack detection DAPM pins */
static struct snd_soc_jack_pin speyside_headset_pins [ ] = {
{
. pin = " Headset Mic " ,
. mask = SND_JACK_MICROPHONE ,
} ,
} ;
/* Default the headphone selection to active high */
static int speyside_jack_polarity ;
static int speyside_get_micbias ( struct snd_soc_dapm_widget * source ,
struct snd_soc_dapm_widget * sink )
{
if ( speyside_jack_polarity & & ( strcmp ( source - > name , " MICB1 " ) = = 0 ) )
return 1 ;
if ( ! speyside_jack_polarity & & ( strcmp ( source - > name , " MICB2 " ) = = 0 ) )
return 1 ;
return 0 ;
}
static void speyside_set_polarity ( struct snd_soc_codec * codec ,
int polarity )
{
speyside_jack_polarity = ! polarity ;
2011-06-24 15:10:44 +04:00
gpio_direction_output ( WM8996_HPSEL_GPIO , speyside_jack_polarity ) ;
2011-04-12 11:00:36 +04:00
/* Re-run DAPM to make sure we're using the correct mic bias */
snd_soc_dapm_sync ( & codec - > dapm ) ;
}
2012-08-23 20:05:48 +04:00
static int speyside_wm0010_init ( struct snd_soc_pcm_runtime * rtd )
{
struct snd_soc_dai * dai = rtd - > codec_dai ;
int ret ;
ret = snd_soc_dai_set_sysclk ( dai , 0 , MCLK_AUDIO_RATE , 0 ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
2011-06-24 15:10:44 +04:00
static int speyside_wm8996_init ( struct snd_soc_pcm_runtime * rtd )
2011-04-12 10:32:03 +04:00
{
struct snd_soc_dai * dai = rtd - > codec_dai ;
2011-04-12 11:00:36 +04:00
struct snd_soc_codec * codec = rtd - > codec ;
int ret ;
2011-06-24 15:10:44 +04:00
ret = snd_soc_dai_set_sysclk ( dai , WM8996_SYSCLK_MCLK2 , 32768 , 0 ) ;
2011-04-12 11:00:36 +04:00
if ( ret < 0 )
return ret ;
2011-06-24 15:10:44 +04:00
ret = gpio_request ( WM8996_HPSEL_GPIO , " HP_SEL " ) ;
2011-04-12 11:00:36 +04:00
if ( ret ! = 0 )
pr_err ( " Failed to request HP_SEL GPIO: %d \n " , ret ) ;
2011-06-24 15:10:44 +04:00
gpio_direction_output ( WM8996_HPSEL_GPIO , speyside_jack_polarity ) ;
2011-04-12 10:32:03 +04:00
2011-04-12 11:00:36 +04:00
ret = snd_soc_jack_new ( codec , " Headset " ,
2011-09-04 18:54:55 +04:00
SND_JACK_LINEOUT | SND_JACK_HEADSET |
SND_JACK_BTN_0 ,
2011-04-12 11:00:36 +04:00
& speyside_headset ) ;
if ( ret )
return ret ;
ret = snd_soc_jack_add_pins ( & speyside_headset ,
ARRAY_SIZE ( speyside_headset_pins ) ,
speyside_headset_pins ) ;
if ( ret )
return ret ;
2011-06-24 15:10:44 +04:00
wm8996_detect ( codec , & speyside_headset , speyside_set_polarity ) ;
2011-04-12 11:00:36 +04:00
return 0 ;
2011-04-12 10:32:03 +04:00
}
2011-04-13 04:37:52 +04:00
static int speyside_late_probe ( struct snd_soc_card * card )
{
snd_soc_dapm_ignore_suspend ( & card - > dapm , " Headphone " ) ;
snd_soc_dapm_ignore_suspend ( & card - > dapm , " Headset Mic " ) ;
snd_soc_dapm_ignore_suspend ( & card - > dapm , " Main AMIC " ) ;
snd_soc_dapm_ignore_suspend ( & card - > dapm , " Main DMIC " ) ;
2011-11-27 19:54:49 +04:00
snd_soc_dapm_ignore_suspend ( & card - > dapm , " Main Speaker " ) ;
2011-04-13 04:37:52 +04:00
snd_soc_dapm_ignore_suspend ( & card - > dapm , " WM1250 Output " ) ;
snd_soc_dapm_ignore_suspend ( & card - > dapm , " WM1250 Input " ) ;
return 0 ;
}
2012-08-23 20:05:48 +04:00
static const struct snd_soc_pcm_stream dsp_codec_params = {
. formats = SNDRV_PCM_FMTBIT_S32_LE ,
. rate_min = 48000 ,
. rate_max = 48000 ,
. channels_min = 2 ,
. channels_max = 2 ,
} ;
2011-04-13 04:24:39 +04:00
static struct snd_soc_dai_link speyside_dai [ ] = {
{
2012-08-23 20:05:48 +04:00
. name = " CPU-DSP " ,
. stream_name = " CPU-DSP " ,
2011-04-13 04:24:39 +04:00
. cpu_dai_name = " samsung-i2s.0 " ,
2012-08-23 20:05:48 +04:00
. codec_dai_name = " wm0010-sdi1 " ,
2011-04-13 04:24:39 +04:00
. platform_name = " samsung-audio " ,
2012-08-23 20:05:48 +04:00
. codec_name = " spi0.0 " ,
. init = speyside_wm0010_init ,
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM ,
} ,
{
. name = " DSP-CODEC " ,
. stream_name = " DSP-CODEC " ,
. cpu_dai_name = " wm0010-sdi2 " ,
. codec_dai_name = " wm8996-aif1 " ,
2011-06-24 15:10:44 +04:00
. codec_name = " wm8996.1-001a " ,
. init = speyside_wm8996_init ,
2011-09-27 19:48:49 +04:00
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM ,
2012-08-23 20:05:48 +04:00
. params = & dsp_codec_params ,
. ignore_suspend = 1 ,
2011-04-13 04:24:39 +04:00
} ,
2011-04-13 01:15:10 +04:00
{
. name = " Baseband " ,
. stream_name = " Baseband " ,
2011-06-24 15:10:44 +04:00
. cpu_dai_name = " wm8996-aif2 " ,
2011-04-13 01:15:10 +04:00
. codec_dai_name = " wm1250-ev1 " ,
. codec_name = " wm1250-ev1.1-0027 " ,
2011-09-27 19:48:49 +04:00
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM ,
2011-04-13 04:37:52 +04:00
. ignore_suspend = 1 ,
2011-04-13 01:15:10 +04:00
} ,
2011-04-13 04:24:39 +04:00
} ;
2011-04-12 10:42:25 +04:00
static int speyside_wm9081_init ( struct snd_soc_dapm_context * dapm )
{
/* At any time the WM9081 is active it will have this clock */
2011-08-24 23:09:01 +04:00
return snd_soc_codec_set_sysclk ( dapm - > codec , WM9081_SYSCLK_MCLK , 0 ,
2011-12-11 07:31:22 +04:00
MCLK_AUDIO_RATE , 0 ) ;
2011-04-12 10:42:25 +04:00
}
static struct snd_soc_aux_dev speyside_aux_dev [ ] = {
{
. name = " wm9081 " ,
. codec_name = " wm9081.1-006c " ,
. init = speyside_wm9081_init ,
} ,
} ;
static struct snd_soc_codec_conf speyside_codec_conf [ ] = {
{
. dev_name = " wm9081.1-006c " ,
. name_prefix = " Sub " ,
} ,
} ;
2011-04-12 11:09:53 +04:00
static const struct snd_kcontrol_new controls [ ] = {
SOC_DAPM_PIN_SWITCH ( " Main Speaker " ) ,
SOC_DAPM_PIN_SWITCH ( " Main DMIC " ) ,
SOC_DAPM_PIN_SWITCH ( " Main AMIC " ) ,
2011-04-13 01:15:10 +04:00
SOC_DAPM_PIN_SWITCH ( " WM1250 Input " ) ,
SOC_DAPM_PIN_SWITCH ( " WM1250 Output " ) ,
2011-08-04 13:13:45 +04:00
SOC_DAPM_PIN_SWITCH ( " Headphone " ) ,
2011-04-12 11:09:53 +04:00
} ;
2011-04-12 10:09:15 +04:00
static struct snd_soc_dapm_widget widgets [ ] = {
SND_SOC_DAPM_HP ( " Headphone " , NULL ) ,
2011-04-12 11:00:36 +04:00
SND_SOC_DAPM_MIC ( " Headset Mic " , NULL ) ,
2011-04-12 10:09:15 +04:00
SND_SOC_DAPM_SPK ( " Main Speaker " , NULL ) ,
SND_SOC_DAPM_MIC ( " Main AMIC " , NULL ) ,
SND_SOC_DAPM_MIC ( " Main DMIC " , NULL ) ,
} ;
static struct snd_soc_dapm_route audio_paths [ ] = {
2011-04-12 11:00:36 +04:00
{ " IN1RN " , NULL , " MICB1 " } ,
{ " IN1RP " , NULL , " MICB1 " } ,
{ " IN1RN " , NULL , " MICB2 " } ,
{ " IN1RP " , NULL , " MICB2 " } ,
{ " MICB1 " , NULL , " Headset Mic " , speyside_get_micbias } ,
{ " MICB2 " , NULL , " Headset Mic " , speyside_get_micbias } ,
2011-04-12 10:09:15 +04:00
{ " IN1LP " , NULL , " MICB2 " } ,
2011-04-12 11:00:36 +04:00
{ " IN1RN " , NULL , " MICB1 " } ,
2011-04-12 10:09:15 +04:00
{ " MICB2 " , NULL , " Main AMIC " } ,
{ " DMIC1DAT " , NULL , " MICB1 " } ,
{ " DMIC2DAT " , NULL , " MICB1 " } ,
{ " MICB1 " , NULL , " Main DMIC " } ,
{ " Headphone " , NULL , " HPOUT1L " } ,
{ " Headphone " , NULL , " HPOUT1R " } ,
2011-04-12 10:42:25 +04:00
{ " Sub IN1 " , NULL , " HPOUT2L " } ,
{ " Sub IN2 " , NULL , " HPOUT2R " } ,
{ " Main Speaker " , NULL , " Sub SPKN " } ,
{ " Main Speaker " , NULL , " Sub SPKP " } ,
2011-04-12 10:09:15 +04:00
{ " Main Speaker " , NULL , " SPKDAT " } ,
} ;
2011-04-13 04:24:39 +04:00
static struct snd_soc_card speyside = {
. name = " Speyside " ,
2011-12-22 06:53:15 +04:00
. owner = THIS_MODULE ,
2011-04-13 04:24:39 +04:00
. dai_link = speyside_dai ,
. num_links = ARRAY_SIZE ( speyside_dai ) ,
2011-04-12 10:42:25 +04:00
. aux_dev = speyside_aux_dev ,
. num_aux_devs = ARRAY_SIZE ( speyside_aux_dev ) ,
. codec_conf = speyside_codec_conf ,
. num_configs = ARRAY_SIZE ( speyside_codec_conf ) ,
2011-04-12 10:09:15 +04:00
2011-04-12 10:32:03 +04:00
. set_bias_level = speyside_set_bias_level ,
2011-06-08 19:11:18 +04:00
. set_bias_level_post = speyside_set_bias_level_post ,
2011-04-12 10:32:03 +04:00
2011-04-12 11:09:53 +04:00
. controls = controls ,
. num_controls = ARRAY_SIZE ( controls ) ,
2011-04-12 10:09:15 +04:00
. dapm_widgets = widgets ,
. num_dapm_widgets = ARRAY_SIZE ( widgets ) ,
. dapm_routes = audio_paths ,
. num_dapm_routes = ARRAY_SIZE ( audio_paths ) ,
2011-11-24 01:33:07 +04:00
. fully_routed = true ,
2011-04-13 04:37:52 +04:00
. late_probe = speyside_late_probe ,
2011-04-13 04:24:39 +04:00
} ;
static __devinit int speyside_probe ( struct platform_device * pdev )
{
struct snd_soc_card * card = & speyside ;
int ret ;
card - > dev = & pdev - > dev ;
ret = snd_soc_register_card ( card ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " snd_soc_register_card() failed: %d \n " ,
ret ) ;
return ret ;
}
return 0 ;
}
static int __devexit speyside_remove ( struct platform_device * pdev )
{
struct snd_soc_card * card = platform_get_drvdata ( pdev ) ;
snd_soc_unregister_card ( card ) ;
return 0 ;
}
static struct platform_driver speyside_driver = {
. driver = {
. name = " speyside " ,
. owner = THIS_MODULE ,
. pm = & snd_soc_pm_ops ,
} ,
. probe = speyside_probe ,
. remove = __devexit_p ( speyside_remove ) ,
} ;
2011-11-23 19:20:13 +04:00
module_platform_driver ( speyside_driver ) ;
2011-04-13 04:24:39 +04:00
MODULE_DESCRIPTION ( " Speyside audio support " ) ;
MODULE_AUTHOR ( " Mark Brown <broonie@opensource.wolfsonmicro.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:speyside " ) ;