2019-04-19 13:21:43 +03:00
// SPDX-License-Identifier: GPL-2.0+
//
// Littlemill audio support
//
// Copyright 2011 Wolfson Microelectronics
2011-11-29 02:05:41 +04:00
# include <sound/soc.h>
# include <sound/soc-dapm.h>
# include <sound/jack.h>
# include <linux/gpio.h>
# include <linux/module.h>
# include "../codecs/wm8994.h"
static int sample_rate = 44100 ;
static int littlemill_set_bias_level ( struct snd_soc_card * card ,
struct snd_soc_dapm_context * dapm ,
enum snd_soc_bias_level level )
{
2015-11-18 10:34:01 +03:00
struct snd_soc_pcm_runtime * rtd ;
struct snd_soc_dai * aif1_dai ;
2011-11-29 02:05:41 +04:00
int ret ;
2019-12-10 03:34:08 +03:00
rtd = snd_soc_get_pcm_runtime ( card , & card - > dai_link [ 0 ] ) ;
2020-03-23 08:20:20 +03:00
aif1_dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2015-11-18 10:34:01 +03:00
2012-04-18 21:20:05 +04:00
if ( dapm - > dev ! = aif1_dai - > dev )
2011-11-29 02:05:41 +04:00
return 0 ;
switch ( level ) {
case SND_SOC_BIAS_PREPARE :
/*
* If we ' ve not already clocked things via hw_params ( )
* then do so now , otherwise these are noops .
*/
if ( dapm - > bias_level = = SND_SOC_BIAS_STANDBY ) {
2012-04-18 21:20:05 +04:00
ret = snd_soc_dai_set_pll ( aif1_dai , WM8994_FLL1 ,
2011-11-29 02:05:41 +04:00
WM8994_FLL_SRC_MCLK2 , 32768 ,
sample_rate * 512 ) ;
if ( ret < 0 ) {
pr_err ( " Failed to start FLL: %d \n " , ret ) ;
return ret ;
}
2012-04-18 21:20:05 +04:00
ret = snd_soc_dai_set_sysclk ( aif1_dai ,
2011-11-29 02:05:41 +04:00
WM8994_SYSCLK_FLL1 ,
sample_rate * 512 ,
SND_SOC_CLOCK_IN ) ;
if ( ret < 0 ) {
pr_err ( " Failed to set SYSCLK: %d \n " , ret ) ;
return ret ;
}
}
break ;
default :
break ;
}
return 0 ;
}
static int littlemill_set_bias_level_post ( struct snd_soc_card * card ,
struct snd_soc_dapm_context * dapm ,
enum snd_soc_bias_level level )
{
2015-11-18 10:34:01 +03:00
struct snd_soc_pcm_runtime * rtd ;
struct snd_soc_dai * aif1_dai ;
2011-11-29 02:05:41 +04:00
int ret ;
2019-12-10 03:34:08 +03:00
rtd = snd_soc_get_pcm_runtime ( card , & card - > dai_link [ 0 ] ) ;
2020-03-23 08:20:20 +03:00
aif1_dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2015-11-18 10:34:01 +03:00
2012-04-18 21:20:05 +04:00
if ( dapm - > dev ! = aif1_dai - > dev )
2011-11-29 02:05:41 +04:00
return 0 ;
switch ( level ) {
case SND_SOC_BIAS_STANDBY :
2012-04-18 21:20:05 +04:00
ret = snd_soc_dai_set_sysclk ( aif1_dai , WM8994_SYSCLK_MCLK2 ,
32768 , SND_SOC_CLOCK_IN ) ;
if ( ret < 0 ) {
pr_err ( " Failed to switch away from FLL1: %d \n " , ret ) ;
return ret ;
}
ret = snd_soc_dai_set_pll ( aif1_dai , WM8994_FLL1 ,
0 , 0 , 0 ) ;
if ( ret < 0 ) {
pr_err ( " Failed to stop FLL1: %d \n " , ret ) ;
return ret ;
}
break ;
2011-11-29 02:05:41 +04:00
default :
break ;
}
dapm - > bias_level = level ;
return 0 ;
}
static int littlemill_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params )
{
2020-07-20 04:18:14 +03:00
struct snd_soc_pcm_runtime * rtd = asoc_substream_to_rtd ( substream ) ;
2020-03-23 08:20:20 +03:00
struct snd_soc_dai * codec_dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2011-11-29 02:05:41 +04:00
int ret ;
sample_rate = params_rate ( params ) ;
ret = snd_soc_dai_set_pll ( codec_dai , WM8994_FLL1 ,
WM8994_FLL_SRC_MCLK2 , 32768 ,
sample_rate * 512 ) ;
if ( ret < 0 ) {
pr_err ( " Failed to start FLL: %d \n " , ret ) ;
return ret ;
}
ret = snd_soc_dai_set_sysclk ( codec_dai ,
WM8994_SYSCLK_FLL1 ,
sample_rate * 512 ,
SND_SOC_CLOCK_IN ) ;
if ( ret < 0 ) {
pr_err ( " Failed to set SYSCLK: %d \n " , ret ) ;
return ret ;
}
return 0 ;
}
2021-07-28 20:25:48 +03:00
static const struct snd_soc_ops littlemill_ops = {
2011-11-29 02:05:41 +04:00
. hw_params = littlemill_hw_params ,
} ;
2012-04-18 21:20:05 +04:00
static const struct snd_soc_pcm_stream baseband_params = {
. formats = SNDRV_PCM_FMTBIT_S32_LE ,
. rate_min = 8000 ,
. rate_max = 8000 ,
. channels_min = 2 ,
. channels_max = 2 ,
} ;
2019-06-06 07:10:41 +03:00
SND_SOC_DAILINK_DEFS ( cpu ,
DAILINK_COMP_ARRAY ( COMP_CPU ( " samsung-i2s.0 " ) ) ,
DAILINK_COMP_ARRAY ( COMP_CODEC ( " wm8994-codec " , " wm8994-aif1 " ) ) ,
DAILINK_COMP_ARRAY ( COMP_PLATFORM ( " samsung-i2s.0 " ) ) ) ;
SND_SOC_DAILINK_DEFS ( baseband ,
DAILINK_COMP_ARRAY ( COMP_CPU ( " wm8994-aif2 " ) ) ,
DAILINK_COMP_ARRAY ( COMP_CODEC ( " wm1250-ev1.1-0027 " ,
" wm1250-ev1 " ) ) ) ;
2011-11-29 02:05:41 +04:00
static struct snd_soc_dai_link littlemill_dai [ ] = {
{
. name = " CPU " ,
. stream_name = " CPU " ,
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM ,
. ops = & littlemill_ops ,
2019-06-06 07:10:41 +03:00
SND_SOC_DAILINK_REG ( cpu ) ,
2011-11-29 02:05:41 +04:00
} ,
2012-04-18 21:20:05 +04:00
{
. name = " Baseband " ,
. stream_name = " Baseband " ,
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM ,
. ignore_suspend = 1 ,
. params = & baseband_params ,
2019-06-06 07:10:41 +03:00
SND_SOC_DAILINK_REG ( baseband ) ,
2012-04-18 21:20:05 +04:00
} ,
2011-11-29 02:05:41 +04:00
} ;
2012-05-12 15:35:44 +04:00
static int bbclk_ev ( struct snd_soc_dapm_widget * w ,
struct snd_kcontrol * kcontrol , int event )
{
struct snd_soc_card * card = w - > dapm - > card ;
2015-11-18 10:34:01 +03:00
struct snd_soc_pcm_runtime * rtd ;
struct snd_soc_dai * aif2_dai ;
2012-05-12 15:35:44 +04:00
int ret ;
2019-12-10 03:34:08 +03:00
rtd = snd_soc_get_pcm_runtime ( card , & card - > dai_link [ 1 ] ) ;
2020-03-23 08:20:20 +03:00
aif2_dai = asoc_rtd_to_cpu ( rtd , 0 ) ;
2015-11-18 10:34:01 +03:00
2012-05-12 15:35:44 +04:00
switch ( event ) {
case SND_SOC_DAPM_PRE_PMU :
ret = snd_soc_dai_set_pll ( aif2_dai , WM8994_FLL2 ,
WM8994_FLL_SRC_BCLK , 64 * 8000 ,
8000 * 256 ) ;
if ( ret < 0 ) {
pr_err ( " Failed to start FLL: %d \n " , ret ) ;
return ret ;
}
ret = snd_soc_dai_set_sysclk ( aif2_dai , WM8994_SYSCLK_FLL2 ,
8000 * 256 ,
SND_SOC_CLOCK_IN ) ;
if ( ret < 0 ) {
pr_err ( " Failed to set SYSCLK: %d \n " , ret ) ;
return ret ;
}
break ;
case SND_SOC_DAPM_POST_PMD :
ret = snd_soc_dai_set_sysclk ( aif2_dai , WM8994_SYSCLK_MCLK2 ,
32768 , SND_SOC_CLOCK_IN ) ;
if ( ret < 0 ) {
pr_err ( " Failed to switch away from FLL2: %d \n " , ret ) ;
return ret ;
}
ret = snd_soc_dai_set_pll ( aif2_dai , WM8994_FLL2 ,
0 , 0 , 0 ) ;
if ( ret < 0 ) {
pr_err ( " Failed to stop FLL2: %d \n " , ret ) ;
return ret ;
}
break ;
default :
return - EINVAL ;
}
return 0 ;
}
2012-07-18 22:16:06 +04:00
static const struct snd_kcontrol_new controls [ ] = {
SOC_DAPM_PIN_SWITCH ( " WM1250 Input " ) ,
SOC_DAPM_PIN_SWITCH ( " WM1250 Output " ) ,
} ;
2011-11-29 02:05:41 +04:00
static struct snd_soc_dapm_widget widgets [ ] = {
SND_SOC_DAPM_HP ( " Headphone " , NULL ) ,
2011-12-02 21:36:06 +04:00
SND_SOC_DAPM_MIC ( " AMIC " , NULL ) ,
SND_SOC_DAPM_MIC ( " DMIC " , NULL ) ,
2012-05-12 15:35:44 +04:00
SND_SOC_DAPM_SUPPLY_S ( " Baseband Clock " , - 1 , SND_SOC_NOPM , 0 , 0 ,
bbclk_ev ,
SND_SOC_DAPM_PRE_PMU | SND_SOC_DAPM_POST_PMD ) ,
2011-11-29 02:05:41 +04:00
} ;
static struct snd_soc_dapm_route audio_paths [ ] = {
{ " Headphone " , NULL , " HPOUT1L " } ,
{ " Headphone " , NULL , " HPOUT1R " } ,
2011-12-02 21:36:06 +04:00
{ " AMIC " , NULL , " MICBIAS1 " } , /* Default for AMICBIAS jumper */
{ " IN1LN " , NULL , " AMIC " } ,
{ " DMIC " , NULL , " MICBIAS2 " } , /* Default for DMICBIAS jumper */
{ " DMIC1DAT " , NULL , " DMIC " } ,
{ " DMIC2DAT " , NULL , " DMIC " } ,
2012-05-12 15:35:44 +04:00
{ " AIF2CLK " , NULL , " Baseband Clock " } ,
2011-11-29 02:05:41 +04:00
} ;
2011-12-02 19:55:52 +04:00
static struct snd_soc_jack littlemill_headset ;
2011-11-29 02:05:41 +04:00
static int littlemill_late_probe ( struct snd_soc_card * card )
{
2015-11-18 10:34:01 +03:00
struct snd_soc_pcm_runtime * rtd ;
2018-01-29 06:12:21 +03:00
struct snd_soc_component * component ;
2015-11-18 10:34:01 +03:00
struct snd_soc_dai * aif1_dai ;
struct snd_soc_dai * aif2_dai ;
2011-11-29 02:05:41 +04:00
int ret ;
2019-12-10 03:34:08 +03:00
rtd = snd_soc_get_pcm_runtime ( card , & card - > dai_link [ 0 ] ) ;
2020-03-23 08:20:20 +03:00
component = asoc_rtd_to_codec ( rtd , 0 ) - > component ;
aif1_dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2015-11-18 10:34:01 +03:00
2019-12-10 03:34:08 +03:00
rtd = snd_soc_get_pcm_runtime ( card , & card - > dai_link [ 1 ] ) ;
2020-03-23 08:20:20 +03:00
aif2_dai = asoc_rtd_to_cpu ( rtd , 0 ) ;
2015-11-18 10:34:01 +03:00
2012-04-18 21:20:05 +04:00
ret = snd_soc_dai_set_sysclk ( aif1_dai , WM8994_SYSCLK_MCLK2 ,
32768 , SND_SOC_CLOCK_IN ) ;
if ( ret < 0 )
return ret ;
ret = snd_soc_dai_set_sysclk ( aif2_dai , WM8994_SYSCLK_MCLK2 ,
2011-11-29 02:05:41 +04:00
32768 , SND_SOC_CLOCK_IN ) ;
if ( ret < 0 )
return ret ;
2015-03-04 12:33:34 +03:00
ret = snd_soc_card_jack_new ( card , " Headset " ,
SND_JACK_HEADSET | SND_JACK_MECHANICAL |
SND_JACK_BTN_0 | SND_JACK_BTN_1 |
SND_JACK_BTN_2 | SND_JACK_BTN_3 |
SND_JACK_BTN_4 | SND_JACK_BTN_5 ,
& littlemill_headset , NULL , 0 ) ;
2011-12-02 19:55:52 +04:00
if ( ret )
return ret ;
/* This will check device compatibility itself */
2018-01-29 06:12:21 +03:00
wm8958_mic_detect ( component , & littlemill_headset , NULL , NULL , NULL , NULL ) ;
2011-12-02 19:55:52 +04:00
2012-02-03 17:15:18 +04:00
/* As will this */
2018-01-29 06:12:21 +03:00
wm8994_mic_detect ( component , & littlemill_headset , 1 ) ;
2012-02-03 17:15:18 +04:00
2011-11-29 02:05:41 +04:00
return 0 ;
}
static struct snd_soc_card littlemill = {
. name = " Littlemill " ,
2011-12-22 06:53:15 +04:00
. owner = THIS_MODULE ,
2011-11-29 02:05:41 +04:00
. dai_link = littlemill_dai ,
. num_links = ARRAY_SIZE ( littlemill_dai ) ,
. set_bias_level = littlemill_set_bias_level ,
. set_bias_level_post = littlemill_set_bias_level_post ,
2012-07-18 22:16:06 +04:00
. controls = controls ,
. num_controls = ARRAY_SIZE ( controls ) ,
2011-11-29 02:05:41 +04:00
. dapm_widgets = widgets ,
. num_dapm_widgets = ARRAY_SIZE ( widgets ) ,
. dapm_routes = audio_paths ,
. num_dapm_routes = ARRAY_SIZE ( audio_paths ) ,
. late_probe = littlemill_late_probe ,
} ;
2012-12-07 18:26:15 +04:00
static int littlemill_probe ( struct platform_device * pdev )
2011-11-29 02:05:41 +04:00
{
struct snd_soc_card * card = & littlemill ;
int ret ;
card - > dev = & pdev - > dev ;
2014-05-21 07:22:17 +04:00
ret = devm_snd_soc_register_card ( & pdev - > dev , card ) ;
2020-02-28 13:11:20 +03:00
if ( ret & & ret ! = - EPROBE_DEFER )
2011-11-29 02:05:41 +04:00
dev_err ( & pdev - > dev , " snd_soc_register_card() failed: %d \n " ,
ret ) ;
2014-05-21 07:22:17 +04:00
return ret ;
2011-11-29 02:05:41 +04:00
}
static struct platform_driver littlemill_driver = {
. driver = {
. name = " littlemill " ,
. pm = & snd_soc_pm_ops ,
} ,
. probe = littlemill_probe ,
} ;
module_platform_driver ( littlemill_driver ) ;
MODULE_DESCRIPTION ( " Littlemill audio support " ) ;
MODULE_AUTHOR ( " Mark Brown <broonie@opensource.wolfsonmicro.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
MODULE_ALIAS ( " platform:littlemill " ) ;