2019-04-19 12:22:00 +02:00
// SPDX-License-Identifier: GPL-2.0+
//
// Speyside audio support
//
// Copyright 2011 Wolfson Microelectronics
2011-04-12 17:24:39 -07:00
# include <sound/soc.h>
# include <sound/soc-dapm.h>
2011-04-12 00:00:36 -07:00
# include <sound/jack.h>
# include <linux/gpio.h>
2011-07-15 12:38:28 -04:00
# include <linux/module.h>
2011-04-12 17:24:39 -07:00
2011-06-24 12:10:44 +01:00
# include "../codecs/wm8996.h"
2011-04-11 23:42:25 -07:00
# include "../codecs/wm9081.h"
2011-04-12 17:24:39 -07:00
2011-06-24 12:10:44 +01:00
# define WM8996_HPSEL_GPIO 214
2011-12-11 11:31:22 +08:00
# define MCLK_AUDIO_RATE (512 * 48000)
2011-04-12 00:00:36 -07:00
2011-04-11 23:32:03 -07:00
static int speyside_set_bias_level ( struct snd_soc_card * card ,
2011-06-06 19:13:23 +01:00
struct snd_soc_dapm_context * dapm ,
2011-04-11 23:32:03 -07:00
enum snd_soc_bias_level level )
{
2015-11-18 02:34:01 -05:00
struct snd_soc_pcm_runtime * rtd ;
struct snd_soc_dai * codec_dai ;
2011-04-11 23:32:03 -07:00
int ret ;
2019-12-10 09:34:08 +09:00
rtd = snd_soc_get_pcm_runtime ( card , & card - > dai_link [ 1 ] ) ;
2020-03-23 14:20:20 +09:00
codec_dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2015-11-18 02:34:01 -05:00
2011-06-06 19:13:23 +01:00
if ( dapm - > dev ! = codec_dai - > dev )
return 0 ;
2011-04-11 23:32:03 -07:00
switch ( level ) {
case SND_SOC_BIAS_STANDBY :
2011-06-24 12:10:44 +01:00
ret = snd_soc_dai_set_sysclk ( codec_dai , WM8996_SYSCLK_MCLK2 ,
2011-04-11 23:32:03 -07:00
32768 , SND_SOC_CLOCK_IN ) ;
if ( ret < 0 )
return ret ;
2011-06-24 12:10:44 +01:00
ret = snd_soc_dai_set_pll ( codec_dai , WM8996_FLL_MCLK2 ,
2011-04-11 23:32:03 -07:00
0 , 0 , 0 ) ;
if ( ret < 0 ) {
pr_err ( " Failed to stop FLL \n " ) ;
return ret ;
}
2011-06-06 19:12:44 +01: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 )
{
2015-11-18 02:34:01 -05:00
struct snd_soc_pcm_runtime * rtd ;
struct snd_soc_dai * codec_dai ;
2011-06-06 19:12:44 +01:00
int ret ;
2019-12-10 09:34:08 +09:00
rtd = snd_soc_get_pcm_runtime ( card , & card - > dai_link [ 1 ] ) ;
2020-03-23 14:20:20 +09:00
codec_dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2015-11-18 02:34:01 -05:00
2011-06-06 19:12:44 +01:00
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 12:10:44 +01:00
WM8996_FLL_MCLK2 ,
2011-12-11 11:31:22 +08:00
32768 , MCLK_AUDIO_RATE ) ;
2011-06-06 19:12:44 +01:00
if ( ret < 0 ) {
pr_err ( " Failed to start FLL \n " ) ;
return ret ;
}
ret = snd_soc_dai_set_sysclk ( codec_dai ,
2011-06-24 12:10:44 +01:00
WM8996_SYSCLK_FLL ,
2011-12-11 11:31:22 +08:00
MCLK_AUDIO_RATE ,
2011-06-06 19:12:44 +01:00
SND_SOC_CLOCK_IN ) ;
if ( ret < 0 )
return ret ;
}
break ;
2011-04-11 23:32:03 -07:00
default :
break ;
}
2011-06-06 19:12:44 +01:00
card - > dapm . bias_level = level ;
2011-04-11 23:32:03 -07:00
return 0 ;
}
2011-04-12 00:00:36 -07: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 ;
}
2018-01-29 03:08:15 +00:00
static void speyside_set_polarity ( struct snd_soc_component * component ,
2011-04-12 00:00:36 -07:00
int polarity )
{
speyside_jack_polarity = ! polarity ;
2011-06-24 12:10:44 +01:00
gpio_direction_output ( WM8996_HPSEL_GPIO , speyside_jack_polarity ) ;
2011-04-12 00:00:36 -07:00
/* Re-run DAPM to make sure we're using the correct mic bias */
2018-01-29 03:08:15 +00:00
snd_soc_dapm_sync ( snd_soc_component_get_dapm ( component ) ) ;
2011-04-12 00:00:36 -07:00
}
2012-08-23 17:05:48 +01:00
static int speyside_wm0010_init ( struct snd_soc_pcm_runtime * rtd )
{
2020-03-23 14:20:20 +09:00
struct snd_soc_dai * dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2012-08-23 17:05:48 +01:00
int ret ;
ret = snd_soc_dai_set_sysclk ( dai , 0 , MCLK_AUDIO_RATE , 0 ) ;
if ( ret < 0 )
return ret ;
return 0 ;
}
2011-06-24 12:10:44 +01:00
static int speyside_wm8996_init ( struct snd_soc_pcm_runtime * rtd )
2011-04-11 23:32:03 -07:00
{
2020-03-23 14:20:20 +09:00
struct snd_soc_dai * dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2018-01-29 03:08:15 +00:00
struct snd_soc_component * component = dai - > component ;
2011-04-12 00:00:36 -07:00
int ret ;
2011-06-24 12:10:44 +01:00
ret = snd_soc_dai_set_sysclk ( dai , WM8996_SYSCLK_MCLK2 , 32768 , 0 ) ;
2011-04-12 00:00:36 -07:00
if ( ret < 0 )
return ret ;
2011-06-24 12:10:44 +01:00
ret = gpio_request ( WM8996_HPSEL_GPIO , " HP_SEL " ) ;
2011-04-12 00:00:36 -07:00
if ( ret ! = 0 )
pr_err ( " Failed to request HP_SEL GPIO: %d \n " , ret ) ;
2011-06-24 12:10:44 +01:00
gpio_direction_output ( WM8996_HPSEL_GPIO , speyside_jack_polarity ) ;
2011-04-11 23:32:03 -07:00
2015-03-04 10:33:38 +01:00
ret = snd_soc_card_jack_new ( rtd - > card , " Headset " , SND_JACK_LINEOUT |
SND_JACK_HEADSET | SND_JACK_BTN_0 ,
& speyside_headset , speyside_headset_pins ,
ARRAY_SIZE ( speyside_headset_pins ) ) ;
2011-04-12 00:00:36 -07:00
if ( ret )
return ret ;
2018-01-29 03:08:15 +00:00
wm8996_detect ( component , & speyside_headset , speyside_set_polarity ) ;
2011-04-12 00:00:36 -07:00
return 0 ;
2011-04-11 23:32:03 -07:00
}
2011-04-12 17:37:52 -07: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 15:54:49 +00:00
snd_soc_dapm_ignore_suspend ( & card - > dapm , " Main Speaker " ) ;
2011-04-12 17:37:52 -07:00
snd_soc_dapm_ignore_suspend ( & card - > dapm , " WM1250 Output " ) ;
snd_soc_dapm_ignore_suspend ( & card - > dapm , " WM1250 Input " ) ;
return 0 ;
}
2012-08-23 17:05:48 +01: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 ,
} ;
2019-06-06 13:09:50 +09:00
SND_SOC_DAILINK_DEFS ( cpu_dsp ,
DAILINK_COMP_ARRAY ( COMP_CPU ( " samsung-i2s.0 " ) ) ,
DAILINK_COMP_ARRAY ( COMP_CODEC ( " spi0.0 " , " wm0010-sdi1 " ) ) ,
DAILINK_COMP_ARRAY ( COMP_PLATFORM ( " samsung-i2s.0 " ) ) ) ;
SND_SOC_DAILINK_DEFS ( dsp_codec ,
DAILINK_COMP_ARRAY ( COMP_CPU ( " wm0010-sdi2 " ) ) ,
DAILINK_COMP_ARRAY ( COMP_CODEC ( " wm8996.1-001a " , " wm8996-aif1 " ) ) ) ;
SND_SOC_DAILINK_DEFS ( baseband ,
DAILINK_COMP_ARRAY ( COMP_CPU ( " wm8996-aif2 " ) ) ,
DAILINK_COMP_ARRAY ( COMP_CODEC ( " wm1250-ev1.1-0027 " , " wm1250-ev1 " ) ) ) ;
2011-04-12 17:24:39 -07:00
static struct snd_soc_dai_link speyside_dai [ ] = {
{
2012-08-23 17:05:48 +01:00
. name = " CPU-DSP " ,
. stream_name = " CPU-DSP " ,
. init = speyside_wm0010_init ,
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM ,
2019-06-06 13:09:50 +09:00
SND_SOC_DAILINK_REG ( cpu_dsp ) ,
2012-08-23 17:05:48 +01:00
} ,
{
. name = " DSP-CODEC " ,
. stream_name = " DSP-CODEC " ,
2011-06-24 12:10:44 +01:00
. init = speyside_wm8996_init ,
2011-09-27 16:48:49 +01:00
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM ,
2012-08-23 17:05:48 +01:00
. params = & dsp_codec_params ,
. ignore_suspend = 1 ,
2019-06-06 13:09:50 +09:00
SND_SOC_DAILINK_REG ( dsp_codec ) ,
2011-04-12 17:24:39 -07:00
} ,
2011-04-12 14:15:10 -07:00
{
. name = " Baseband " ,
. stream_name = " Baseband " ,
2011-09-27 16:48:49 +01:00
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM ,
2011-04-12 17:37:52 -07:00
. ignore_suspend = 1 ,
2019-06-06 13:09:50 +09:00
SND_SOC_DAILINK_REG ( baseband ) ,
2011-04-12 14:15:10 -07:00
} ,
2011-04-12 17:24:39 -07:00
} ;
2014-08-19 15:51:23 +02:00
static int speyside_wm9081_init ( struct snd_soc_component * component )
2011-04-11 23:42:25 -07:00
{
/* At any time the WM9081 is active it will have this clock */
2018-01-29 03:08:15 +00:00
return snd_soc_component_set_sysclk ( component , WM9081_SYSCLK_MCLK , 0 ,
2011-12-11 11:31:22 +08:00
MCLK_AUDIO_RATE , 0 ) ;
2011-04-11 23:42:25 -07:00
}
static struct snd_soc_aux_dev speyside_aux_dev [ ] = {
{
2019-08-08 14:54:15 +09:00
. dlc = COMP_AUX ( " wm9081.1-006c " ) ,
2011-04-11 23:42:25 -07:00
. init = speyside_wm9081_init ,
} ,
} ;
static struct snd_soc_codec_conf speyside_codec_conf [ ] = {
{
2019-12-13 09:55:44 +09:00
. dlc = COMP_CODEC_CONF ( " wm9081.1-006c " ) ,
2011-04-11 23:42:25 -07:00
. name_prefix = " Sub " ,
} ,
} ;
2011-04-12 00:09:53 -07: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-12 14:15:10 -07:00
SOC_DAPM_PIN_SWITCH ( " WM1250 Input " ) ,
SOC_DAPM_PIN_SWITCH ( " WM1250 Output " ) ,
2011-08-04 18:13:45 +09:00
SOC_DAPM_PIN_SWITCH ( " Headphone " ) ,
2011-04-12 00:09:53 -07:00
} ;
2022-03-30 22:42:27 +02:00
static const struct snd_soc_dapm_widget widgets [ ] = {
2011-04-11 23:09:15 -07:00
SND_SOC_DAPM_HP ( " Headphone " , NULL ) ,
2011-04-12 00:00:36 -07:00
SND_SOC_DAPM_MIC ( " Headset Mic " , NULL ) ,
2011-04-11 23:09:15 -07:00
SND_SOC_DAPM_SPK ( " Main Speaker " , NULL ) ,
SND_SOC_DAPM_MIC ( " Main AMIC " , NULL ) ,
SND_SOC_DAPM_MIC ( " Main DMIC " , NULL ) ,
} ;
2022-03-30 22:42:27 +02:00
static const struct snd_soc_dapm_route audio_paths [ ] = {
2011-04-12 00:00:36 -07: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-11 23:09:15 -07:00
{ " IN1LP " , NULL , " MICB2 " } ,
2011-04-12 00:00:36 -07:00
{ " IN1RN " , NULL , " MICB1 " } ,
2011-04-11 23:09:15 -07:00
{ " MICB2 " , NULL , " Main AMIC " } ,
{ " DMIC1DAT " , NULL , " MICB1 " } ,
{ " DMIC2DAT " , NULL , " MICB1 " } ,
{ " MICB1 " , NULL , " Main DMIC " } ,
{ " Headphone " , NULL , " HPOUT1L " } ,
{ " Headphone " , NULL , " HPOUT1R " } ,
2011-04-11 23:42:25 -07:00
{ " Sub IN1 " , NULL , " HPOUT2L " } ,
{ " Sub IN2 " , NULL , " HPOUT2R " } ,
{ " Main Speaker " , NULL , " Sub SPKN " } ,
{ " Main Speaker " , NULL , " Sub SPKP " } ,
2011-04-11 23:09:15 -07:00
{ " Main Speaker " , NULL , " SPKDAT " } ,
} ;
2011-04-12 17:24:39 -07:00
static struct snd_soc_card speyside = {
. name = " Speyside " ,
2011-12-22 10:53:15 +08:00
. owner = THIS_MODULE ,
2011-04-12 17:24:39 -07:00
. dai_link = speyside_dai ,
. num_links = ARRAY_SIZE ( speyside_dai ) ,
2011-04-11 23:42:25 -07: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-11 23:09:15 -07:00
2011-04-11 23:32:03 -07:00
. set_bias_level = speyside_set_bias_level ,
2011-06-08 16:11:18 +01:00
. set_bias_level_post = speyside_set_bias_level_post ,
2011-04-11 23:32:03 -07:00
2011-04-12 00:09:53 -07:00
. controls = controls ,
. num_controls = ARRAY_SIZE ( controls ) ,
2011-04-11 23:09:15 -07:00
. dapm_widgets = widgets ,
. num_dapm_widgets = ARRAY_SIZE ( widgets ) ,
. dapm_routes = audio_paths ,
. num_dapm_routes = ARRAY_SIZE ( audio_paths ) ,
2011-11-23 21:33:07 +00:00
. fully_routed = true ,
2011-04-12 17:37:52 -07:00
. late_probe = speyside_late_probe ,
2011-04-12 17:24:39 -07:00
} ;
2012-12-07 09:26:15 -05:00
static int speyside_probe ( struct platform_device * pdev )
2011-04-12 17:24:39 -07:00
{
struct snd_soc_card * card = & speyside ;
int ret ;
card - > dev = & pdev - > dev ;
2014-05-21 08:52:17 +05:30
ret = devm_snd_soc_register_card ( & pdev - > dev , card ) ;
2021-12-14 11:08:41 +09:00
if ( ret )
dev_err_probe ( & pdev - > dev , ret , " snd_soc_register_card() failed \n " ) ;
2011-04-12 17:24:39 -07:00
2014-05-21 08:52:17 +05:30
return ret ;
2011-04-12 17:24:39 -07:00
}
static struct platform_driver speyside_driver = {
. driver = {
. name = " speyside " ,
. pm = & snd_soc_pm_ops ,
} ,
. probe = speyside_probe ,
} ;
2011-11-23 15:20:13 +00:00
module_platform_driver ( speyside_driver ) ;
2011-04-12 17:24:39 -07:00
MODULE_DESCRIPTION ( " Speyside audio support " ) ;
MODULE_AUTHOR ( " Mark Brown <broonie@opensource.wolfsonmicro.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:speyside " ) ;