2019-04-19 13:22:02 +03:00
// SPDX-License-Identifier: GPL-2.0+
//
// Tobermory audio support
//
// Copyright 2011 Wolfson Microelectronics
2011-04-25 21:30:45 +04:00
# include <sound/soc.h>
# include <sound/soc-dapm.h>
# include <sound/jack.h>
# include <linux/gpio.h>
2011-07-15 20:38:28 +04:00
# include <linux/module.h>
2011-04-25 21:30:45 +04:00
# include "../codecs/wm8962.h"
2011-09-23 19:05:00 +04:00
static int sample_rate = 44100 ;
2011-11-30 17:30:27 +04:00
static int tobermory_set_bias_level ( struct snd_soc_card * card ,
2011-04-25 21:30:45 +04:00
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 * codec_dai ;
2011-04-25 21:30:45 +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
codec_dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2015-11-18 10:34:01 +03:00
2011-08-21 15:20:00 +04:00
if ( dapm - > dev ! = codec_dai - > dev )
return 0 ;
2011-04-25 21:30:45 +04:00
switch ( level ) {
case SND_SOC_BIAS_PREPARE :
if ( dapm - > bias_level = = SND_SOC_BIAS_STANDBY ) {
ret = snd_soc_dai_set_pll ( codec_dai , WM8962_FLL ,
WM8962_FLL_MCLK , 32768 ,
2011-09-23 19:05:00 +04:00
sample_rate * 512 ) ;
2011-04-25 21:30:45 +04:00
if ( ret < 0 )
2011-06-30 01:07:24 +04:00
pr_err ( " Failed to start FLL: %d \n " , ret ) ;
2011-04-25 21:30:45 +04:00
ret = snd_soc_dai_set_sysclk ( codec_dai ,
WM8962_SYSCLK_FLL ,
2011-09-23 19:05:00 +04:00
sample_rate * 512 ,
2011-04-25 21:30:45 +04:00
SND_SOC_CLOCK_IN ) ;
2011-06-30 01:07:24 +04:00
if ( ret < 0 ) {
2011-08-04 05:54:17 +04:00
pr_err ( " Failed to set SYSCLK: %d \n " , ret ) ;
2014-01-30 22:26:07 +04:00
snd_soc_dai_set_pll ( codec_dai , WM8962_FLL ,
0 , 0 , 0 ) ;
2011-04-25 21:30:45 +04:00
return ret ;
2011-06-30 01:07:24 +04:00
}
2011-04-25 21:30:45 +04:00
}
break ;
default :
break ;
}
return 0 ;
}
2011-11-30 17:30:27 +04:00
static int tobermory_set_bias_level_post ( struct snd_soc_card * card ,
2011-04-25 21:30:45 +04:00
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 * codec_dai ;
2011-04-25 21:30:45 +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
codec_dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2015-11-18 10:34:01 +03:00
2011-08-21 15:20:00 +04:00
if ( dapm - > dev ! = codec_dai - > dev )
return 0 ;
2011-04-25 21:30:45 +04:00
switch ( level ) {
case SND_SOC_BIAS_STANDBY :
ret = snd_soc_dai_set_sysclk ( codec_dai , WM8962_SYSCLK_MCLK ,
32768 , SND_SOC_CLOCK_IN ) ;
2011-06-30 01:07:24 +04:00
if ( ret < 0 ) {
pr_err ( " Failed to switch away from FLL: %d \n " , ret ) ;
2011-04-25 21:30:45 +04:00
return ret ;
2011-06-30 01:07:24 +04:00
}
2011-04-25 21:30:45 +04:00
ret = snd_soc_dai_set_pll ( codec_dai , WM8962_FLL ,
0 , 0 , 0 ) ;
if ( ret < 0 ) {
2011-06-30 01:07:24 +04:00
pr_err ( " Failed to stop FLL: %d \n " , ret ) ;
2011-04-25 21:30:45 +04:00
return ret ;
}
break ;
default :
break ;
}
dapm - > bias_level = level ;
return 0 ;
}
2011-11-30 17:30:27 +04:00
static int tobermory_hw_params ( struct snd_pcm_substream * substream ,
2011-04-25 21:30:45 +04:00
struct snd_pcm_hw_params * params )
{
2011-09-23 19:05:00 +04:00
sample_rate = params_rate ( params ) ;
2011-04-25 21:30:45 +04:00
return 0 ;
}
2021-07-28 20:25:48 +03:00
static const struct snd_soc_ops tobermory_ops = {
2011-11-30 17:30:27 +04:00
. hw_params = tobermory_hw_params ,
2011-04-25 21:30:45 +04:00
} ;
2019-06-06 07:09:36 +03:00
SND_SOC_DAILINK_DEFS ( cpu ,
DAILINK_COMP_ARRAY ( COMP_CPU ( " samsung-i2s.0 " ) ) ,
DAILINK_COMP_ARRAY ( COMP_CODEC ( " wm8962.1-001a " , " wm8962 " ) ) ,
DAILINK_COMP_ARRAY ( COMP_PLATFORM ( " samsung-i2s.0 " ) ) ) ;
2011-11-30 17:30:27 +04:00
static struct snd_soc_dai_link tobermory_dai [ ] = {
2011-04-25 21:30:45 +04:00
{
. name = " CPU " ,
. stream_name = " CPU " ,
2011-09-27 19:42:27 +04:00
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF
| SND_SOC_DAIFMT_CBM_CFM ,
2011-11-30 17:30:27 +04:00
. ops = & tobermory_ops ,
2019-06-06 07:09:36 +03:00
SND_SOC_DAILINK_REG ( cpu ) ,
2011-04-25 21:30:45 +04:00
} ,
} ;
static const struct snd_kcontrol_new controls [ ] = {
SOC_DAPM_PIN_SWITCH ( " Main Speaker " ) ,
2011-09-23 19:46:24 +04:00
SOC_DAPM_PIN_SWITCH ( " DMIC " ) ,
2011-04-25 21:30:45 +04:00
} ;
2022-03-30 23:42:27 +03:00
static const struct snd_soc_dapm_widget widgets [ ] = {
2011-04-25 21:30:45 +04:00
SND_SOC_DAPM_HP ( " Headphone " , NULL ) ,
SND_SOC_DAPM_MIC ( " Headset Mic " , NULL ) ,
SND_SOC_DAPM_MIC ( " DMIC " , NULL ) ,
2011-09-23 19:23:11 +04:00
SND_SOC_DAPM_MIC ( " AMIC " , NULL ) ,
2011-04-25 21:30:45 +04:00
SND_SOC_DAPM_SPK ( " Main Speaker " , NULL ) ,
} ;
2022-03-30 23:42:27 +03:00
static const struct snd_soc_dapm_route audio_paths [ ] = {
2011-04-25 21:30:45 +04:00
{ " Headphone " , NULL , " HPOUTL " } ,
{ " Headphone " , NULL , " HPOUTR " } ,
{ " Main Speaker " , NULL , " SPKOUTL " } ,
{ " Main Speaker " , NULL , " SPKOUTR " } ,
2011-09-23 19:22:48 +04:00
{ " Headset Mic " , NULL , " MICBIAS " } ,
{ " IN4L " , NULL , " Headset Mic " } ,
{ " IN4R " , NULL , " Headset Mic " } ,
2011-04-25 21:30:45 +04:00
2011-09-23 19:23:11 +04:00
{ " AMIC " , NULL , " MICBIAS " } ,
{ " IN1L " , NULL , " AMIC " } ,
{ " IN1R " , NULL , " AMIC " } ,
2011-09-23 19:22:48 +04:00
{ " DMIC " , NULL , " MICBIAS " } ,
{ " DMICDAT " , NULL , " DMIC " } ,
2011-04-25 21:30:45 +04:00
} ;
2011-11-30 17:30:27 +04:00
static struct snd_soc_jack tobermory_headset ;
2011-04-25 21:30:45 +04:00
/* Headset jack detection DAPM pins */
2011-11-30 17:30:27 +04:00
static struct snd_soc_jack_pin tobermory_headset_pins [ ] = {
2011-04-25 21:30:45 +04:00
{
. pin = " Headset Mic " ,
. mask = SND_JACK_MICROPHONE ,
} ,
{
. pin = " Headphone " ,
. mask = SND_JACK_MICROPHONE ,
} ,
} ;
2011-11-30 17:30:27 +04:00
static int tobermory_late_probe ( struct snd_soc_card * card )
2011-04-25 21:30:45 +04:00
{
2015-11-18 10:34:01 +03:00
struct snd_soc_pcm_runtime * rtd ;
2018-01-29 06:07:59 +03:00
struct snd_soc_component * component ;
2015-11-18 10:34:01 +03:00
struct snd_soc_dai * codec_dai ;
2011-04-25 21:30:45 +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 ;
codec_dai = asoc_rtd_to_codec ( rtd , 0 ) ;
2015-11-18 10:34:01 +03:00
2011-04-25 21:30:45 +04:00
ret = snd_soc_dai_set_sysclk ( codec_dai , WM8962_SYSCLK_MCLK ,
32768 , SND_SOC_CLOCK_IN ) ;
if ( ret < 0 )
return ret ;
2022-04-08 07:11:14 +03:00
ret = snd_soc_card_jack_new_pins ( card , " Headset " , SND_JACK_HEADSET |
SND_JACK_BTN_0 , & tobermory_headset ,
tobermory_headset_pins ,
ARRAY_SIZE ( tobermory_headset_pins ) ) ;
2011-04-25 21:30:45 +04:00
if ( ret )
return ret ;
2018-01-29 06:07:59 +03:00
wm8962_mic_detect ( component , & tobermory_headset ) ;
2011-04-25 21:30:45 +04:00
return 0 ;
}
2011-11-30 17:30:27 +04:00
static struct snd_soc_card tobermory = {
. name = " Tobermory " ,
2011-12-22 06:53:15 +04:00
. owner = THIS_MODULE ,
2011-11-30 17:30:27 +04:00
. dai_link = tobermory_dai ,
. num_links = ARRAY_SIZE ( tobermory_dai ) ,
2011-04-25 21:30:45 +04:00
2011-11-30 17:30:27 +04:00
. set_bias_level = tobermory_set_bias_level ,
. set_bias_level_post = tobermory_set_bias_level_post ,
2011-04-25 21:30:45 +04:00
. controls = controls ,
. num_controls = ARRAY_SIZE ( controls ) ,
. 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-25 21:30:45 +04:00
2011-11-30 17:30:27 +04:00
. late_probe = tobermory_late_probe ,
2011-04-25 21:30:45 +04:00
} ;
2012-12-07 18:26:15 +04:00
static int tobermory_probe ( struct platform_device * pdev )
2011-04-25 21:30:45 +04:00
{
2011-11-30 17:30:27 +04:00
struct snd_soc_card * card = & tobermory ;
2011-04-25 21:30:45 +04:00
int ret ;
card - > dev = & pdev - > dev ;
2014-05-21 07:22:17 +04:00
ret = devm_snd_soc_register_card ( & pdev - > dev , card ) ;
2021-12-14 05:08:41 +03:00
if ( ret )
dev_err_probe ( & pdev - > dev , ret , " snd_soc_register_card() failed \n " ) ;
2011-04-25 21:30:45 +04:00
2014-05-21 07:22:17 +04:00
return ret ;
2011-04-25 21:30:45 +04:00
}
2011-11-30 17:30:27 +04:00
static struct platform_driver tobermory_driver = {
2011-04-25 21:30:45 +04:00
. driver = {
2011-11-30 17:30:27 +04:00
. name = " tobermory " ,
2011-04-25 21:30:45 +04:00
. pm = & snd_soc_pm_ops ,
} ,
2011-11-30 17:30:27 +04:00
. probe = tobermory_probe ,
2011-04-25 21:30:45 +04:00
} ;
2011-11-30 17:30:27 +04:00
module_platform_driver ( tobermory_driver ) ;
2011-04-25 21:30:45 +04:00
2011-11-30 17:30:27 +04:00
MODULE_DESCRIPTION ( " Tobermory audio support " ) ;
2011-04-25 21:30:45 +04:00
MODULE_AUTHOR ( " Mark Brown <broonie@opensource.wolfsonmicro.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;
2011-11-30 17:30:27 +04:00
MODULE_ALIAS ( " platform:tobermory " ) ;