2019-04-19 13:21:46 +03:00
// SPDX-License-Identifier: GPL-2.0
//
// Copyright (C) 2017 Samsung Electronics Co., Ltd.
2017-04-21 20:19:50 +03:00
# include <linux/clk.h>
2019-02-14 12:37:40 +03:00
# include <linux/clk-provider.h>
2017-04-21 20:19:50 +03:00
# include <linux/of.h>
# include <linux/of_device.h>
# include <linux/module.h>
# include <sound/soc.h>
# include <sound/pcm_params.h>
# include "i2s.h"
# include "i2s-regs.h"
struct odroid_priv {
struct snd_soc_card card ;
2017-08-04 13:58:17 +03:00
struct clk * clk_i2s_bus ;
struct clk * sclk_i2s ;
2019-02-14 19:00:11 +03:00
/* Spinlock protecting fields below */
spinlock_t lock ;
unsigned int be_sample_rate ;
bool be_active ;
2017-04-21 20:19:50 +03:00
} ;
2019-02-14 12:37:40 +03:00
static int odroid_card_fe_startup ( struct snd_pcm_substream * substream )
2017-04-21 20:19:50 +03:00
{
struct snd_pcm_runtime * runtime = substream - > runtime ;
snd_pcm_hw_constraint_single ( runtime , SNDRV_PCM_HW_PARAM_CHANNELS , 2 ) ;
2019-02-14 12:37:40 +03:00
2017-04-21 20:19:50 +03:00
return 0 ;
}
2019-02-14 19:00:11 +03:00
static int odroid_card_fe_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 ) ;
2019-02-14 19:00:11 +03:00
struct odroid_priv * priv = snd_soc_card_get_drvdata ( rtd - > card ) ;
unsigned long flags ;
int ret = 0 ;
spin_lock_irqsave ( & priv - > lock , flags ) ;
if ( priv - > be_active & & priv - > be_sample_rate ! = params_rate ( params ) )
ret = - EINVAL ;
spin_unlock_irqrestore ( & priv - > lock , flags ) ;
return ret ;
}
2019-02-14 12:37:40 +03:00
static const struct snd_soc_ops odroid_card_fe_ops = {
. startup = odroid_card_fe_startup ,
2019-02-14 19:00:11 +03:00
. hw_params = odroid_card_fe_hw_params ,
2019-02-14 12:37:40 +03:00
} ;
static int odroid_card_be_hw_params ( struct snd_pcm_substream * substream ,
2017-04-21 20:19:50 +03:00
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 ) ;
2017-04-21 20:19:50 +03:00
struct odroid_priv * priv = snd_soc_card_get_drvdata ( rtd - > card ) ;
2018-03-14 19:41:13 +03:00
unsigned int pll_freq , rclk_freq , rfs ;
2019-02-14 19:00:11 +03:00
unsigned long flags ;
2017-04-21 20:19:50 +03:00
int ret ;
switch ( params_rate ( params ) ) {
case 64000 :
2018-03-14 19:41:13 +03:00
pll_freq = 196608001U ;
rfs = 384 ;
2017-04-21 20:19:50 +03:00
break ;
case 44100 :
case 88200 :
2017-07-21 19:29:20 +03:00
pll_freq = 180633609U ;
2018-03-14 19:41:13 +03:00
rfs = 512 ;
2017-04-21 20:19:50 +03:00
break ;
2018-03-14 19:41:13 +03:00
case 32000 :
2017-04-21 20:19:50 +03:00
case 48000 :
case 96000 :
2017-07-21 19:29:20 +03:00
pll_freq = 196608001U ;
2018-03-14 19:41:13 +03:00
rfs = 512 ;
2017-04-21 20:19:50 +03:00
break ;
default :
return - EINVAL ;
}
2017-08-04 13:58:17 +03:00
ret = clk_set_rate ( priv - > clk_i2s_bus , pll_freq / 2 + 1 ) ;
2017-04-21 20:19:50 +03:00
if ( ret < 0 )
return ret ;
2017-08-04 13:58:17 +03:00
/*
2019-03-12 20:40:06 +03:00
* We add 2 to the rclk_freq value in order to avoid too low clock
2017-08-04 13:58:17 +03:00
* frequency values due to the EPLL output frequency not being exact
* multiple of the audio sampling rate .
*/
2019-03-12 20:40:06 +03:00
rclk_freq = params_rate ( params ) * rfs + 2 ;
2017-04-21 20:19:50 +03:00
2017-08-04 13:58:17 +03:00
ret = clk_set_rate ( priv - > sclk_i2s , rclk_freq ) ;
2017-04-21 20:19:50 +03:00
if ( ret < 0 )
return ret ;
2022-09-20 09:32:16 +03:00
if ( rtd - > dai_link - > num_codecs > 1 ) {
2020-03-23 08:20:20 +03:00
struct snd_soc_dai * codec_dai = asoc_rtd_to_codec ( rtd , 1 ) ;
2017-04-21 20:19:50 +03:00
ret = snd_soc_dai_set_sysclk ( codec_dai , 0 , rclk_freq ,
SND_SOC_CLOCK_IN ) ;
if ( ret < 0 )
return ret ;
}
2019-02-14 19:00:11 +03:00
spin_lock_irqsave ( & priv - > lock , flags ) ;
priv - > be_sample_rate = params_rate ( params ) ;
spin_unlock_irqrestore ( & priv - > lock , flags ) ;
return 0 ;
}
static int odroid_card_be_trigger ( struct snd_pcm_substream * substream , int cmd )
{
2020-07-20 04:18:14 +03:00
struct snd_soc_pcm_runtime * rtd = asoc_substream_to_rtd ( substream ) ;
2019-02-14 19:00:11 +03:00
struct odroid_priv * priv = snd_soc_card_get_drvdata ( rtd - > card ) ;
unsigned long flags ;
spin_lock_irqsave ( & priv - > lock , flags ) ;
switch ( cmd ) {
case SNDRV_PCM_TRIGGER_START :
case SNDRV_PCM_TRIGGER_RESUME :
case SNDRV_PCM_TRIGGER_PAUSE_RELEASE :
priv - > be_active = true ;
break ;
case SNDRV_PCM_TRIGGER_STOP :
case SNDRV_PCM_TRIGGER_SUSPEND :
case SNDRV_PCM_TRIGGER_PAUSE_PUSH :
priv - > be_active = false ;
break ;
}
spin_unlock_irqrestore ( & priv - > lock , flags ) ;
2017-04-21 20:19:50 +03:00
return 0 ;
}
2019-02-14 12:37:40 +03:00
static const struct snd_soc_ops odroid_card_be_ops = {
. hw_params = odroid_card_be_hw_params ,
2019-02-14 19:00:11 +03:00
. trigger = odroid_card_be_trigger ,
2019-02-14 12:37:40 +03:00
} ;
2019-02-15 15:04:22 +03:00
/* DAPM routes for backward compatibility with old DTS */
static const struct snd_soc_dapm_route odroid_dapm_routes [ ] = {
{ " I2S Playback " , NULL , " Mixer DAI TX " } ,
{ " HiFi Playback " , NULL , " Mixer DAI TX " } ,
} ;
2019-06-06 07:10:26 +03:00
SND_SOC_DAILINK_DEFS ( primary ,
DAILINK_COMP_ARRAY ( COMP_EMPTY ( ) ) ,
DAILINK_COMP_ARRAY ( COMP_DUMMY ( ) ) ,
DAILINK_COMP_ARRAY ( COMP_PLATFORM ( " 3830000.i2s " ) ) ) ;
SND_SOC_DAILINK_DEFS ( mixer ,
DAILINK_COMP_ARRAY ( COMP_DUMMY ( ) ) ,
DAILINK_COMP_ARRAY ( COMP_EMPTY ( ) ) ,
DAILINK_COMP_ARRAY ( COMP_DUMMY ( ) ) ) ;
SND_SOC_DAILINK_DEFS ( secondary ,
DAILINK_COMP_ARRAY ( COMP_EMPTY ( ) ) ,
DAILINK_COMP_ARRAY ( COMP_DUMMY ( ) ) ,
DAILINK_COMP_ARRAY ( COMP_PLATFORM ( " 3830000.i2s-sec " ) ) ) ;
2019-02-14 12:37:40 +03:00
static struct snd_soc_dai_link odroid_card_dais [ ] = {
{
/* Primary FE <-> BE link */
. ops = & odroid_card_fe_ops ,
. name = " Primary " ,
. stream_name = " Primary " ,
. dynamic = 1 ,
. dpcm_playback = 1 ,
2019-06-06 07:10:26 +03:00
SND_SOC_DAILINK_REG ( primary ) ,
2019-02-14 12:37:40 +03:00
} , {
/* BE <-> CODECs link */
. name = " I2S Mixer " ,
. ops = & odroid_card_be_ops ,
. no_pcm = 1 ,
. dpcm_playback = 1 ,
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_NB_NF |
SND_SOC_DAIFMT_CBS_CFS ,
2019-06-06 07:10:26 +03:00
SND_SOC_DAILINK_REG ( mixer ) ,
2019-02-14 12:37:40 +03:00
} , {
/* Secondary FE <-> BE link */
. playback_only = 1 ,
. ops = & odroid_card_fe_ops ,
. name = " Secondary " ,
. stream_name = " Secondary " ,
. dynamic = 1 ,
. dpcm_playback = 1 ,
2019-06-06 07:10:26 +03:00
SND_SOC_DAILINK_REG ( secondary ) ,
2019-02-14 12:37:40 +03:00
}
2017-04-21 20:19:50 +03:00
} ;
static int odroid_audio_probe ( struct platform_device * pdev )
{
struct device * dev = & pdev - > dev ;
2019-02-21 12:42:28 +03:00
struct device_node * cpu_dai = NULL ;
struct device_node * cpu , * codec ;
2017-04-21 20:19:50 +03:00
struct odroid_priv * priv ;
struct snd_soc_card * card ;
2019-02-14 12:37:40 +03:00
struct snd_soc_dai_link * link , * codec_link ;
int num_pcms , ret , i ;
2017-04-21 20:19:50 +03:00
priv = devm_kzalloc ( dev , sizeof ( * priv ) , GFP_KERNEL ) ;
if ( ! priv )
return - ENOMEM ;
card = & priv - > card ;
card - > dev = dev ;
card - > owner = THIS_MODULE ;
card - > fully_routed = true ;
2019-02-14 19:00:11 +03:00
spin_lock_init ( & priv - > lock ) ;
2017-04-21 20:19:50 +03:00
snd_soc_card_set_drvdata ( card , priv ) ;
ret = snd_soc_of_parse_card_name ( card , " model " ) ;
if ( ret < 0 )
return ret ;
if ( of_property_read_bool ( dev - > of_node , " samsung,audio-widgets " ) ) {
ret = snd_soc_of_parse_audio_simple_widgets ( card ,
" samsung,audio-widgets " ) ;
if ( ret < 0 )
return ret ;
}
if ( of_property_read_bool ( dev - > of_node , " samsung,audio-routing " ) ) {
ret = snd_soc_of_parse_audio_routing ( card ,
" samsung,audio-routing " ) ;
if ( ret < 0 )
return ret ;
}
2019-02-14 12:37:40 +03:00
card - > dai_link = odroid_card_dais ;
card - > num_links = ARRAY_SIZE ( odroid_card_dais ) ;
2017-04-21 20:19:50 +03:00
cpu = of_get_child_by_name ( dev - > of_node , " cpu " ) ;
codec = of_get_child_by_name ( dev - > of_node , " codec " ) ;
2019-02-14 12:37:40 +03:00
link = card - > dai_link ;
codec_link = & card - > dai_link [ 1 ] ;
2017-04-21 20:19:50 +03:00
2019-02-14 12:37:40 +03:00
/*
* For backwards compatibility create the secondary CPU DAI link only
* if there are 2 CPU DAI entries in the cpu sound - dai property in DT .
2019-02-15 15:04:22 +03:00
* Also add required DAPM routes not available in old DTS .
2019-02-14 12:37:40 +03:00
*/
num_pcms = of_count_phandle_with_args ( cpu , " sound-dai " ,
" #sound-dai-cells " ) ;
2019-02-15 15:04:22 +03:00
if ( num_pcms = = 1 ) {
card - > dapm_routes = odroid_dapm_routes ;
card - > num_dapm_routes = ARRAY_SIZE ( odroid_dapm_routes ) ;
2019-02-14 12:37:40 +03:00
card - > num_links - - ;
2019-02-15 15:04:22 +03:00
}
2019-02-14 12:37:40 +03:00
for ( i = 0 ; i < num_pcms ; i + + , link + = 2 ) {
2023-06-20 05:14:35 +03:00
ret = snd_soc_of_get_dai_name ( cpu , & link - > cpus - > dai_name , i ) ;
2019-02-14 12:37:40 +03:00
if ( ret < 0 )
2019-02-20 14:06:07 +03:00
break ;
2017-04-21 20:19:50 +03:00
}
2019-02-21 12:42:28 +03:00
if ( ret = = 0 ) {
2019-02-20 14:06:07 +03:00
cpu_dai = of_parse_phandle ( cpu , " sound-dai " , 0 ) ;
2019-02-21 12:42:28 +03:00
if ( ! cpu_dai )
ret = - EINVAL ;
}
2017-04-21 20:19:50 +03:00
2019-02-14 12:37:40 +03:00
of_node_put ( cpu ) ;
2019-02-20 14:06:07 +03:00
if ( ret < 0 )
2019-07-13 06:46:14 +03:00
goto err_put_node ;
2019-02-14 12:37:40 +03:00
ret = snd_soc_of_get_dai_link_codecs ( dev , codec , codec_link ) ;
2017-04-21 20:19:50 +03:00
if ( ret < 0 )
2019-02-20 14:06:07 +03:00
goto err_put_cpu_dai ;
2017-04-21 20:19:50 +03:00
2019-02-14 12:37:40 +03:00
/* Set capture capability only for boards with the MAX98090 CODEC */
if ( codec_link - > num_codecs > 1 ) {
card - > dai_link [ 0 ] . dpcm_capture = 1 ;
card - > dai_link [ 1 ] . dpcm_capture = 1 ;
}
2017-08-04 13:58:17 +03:00
2019-02-14 12:37:40 +03:00
priv - > sclk_i2s = of_clk_get_by_name ( cpu_dai , " i2s_opclk1 " ) ;
2017-08-04 13:58:17 +03:00
if ( IS_ERR ( priv - > sclk_i2s ) ) {
ret = PTR_ERR ( priv - > sclk_i2s ) ;
2019-02-20 14:06:07 +03:00
goto err_put_cpu_dai ;
2017-08-04 13:58:17 +03:00
}
2019-02-14 12:37:40 +03:00
priv - > clk_i2s_bus = of_clk_get_by_name ( cpu_dai , " iis " ) ;
2017-08-04 13:58:17 +03:00
if ( IS_ERR ( priv - > clk_i2s_bus ) ) {
ret = PTR_ERR ( priv - > clk_i2s_bus ) ;
goto err_put_sclk ;
}
2017-04-21 20:19:50 +03:00
ret = devm_snd_soc_register_card ( dev , card ) ;
if ( ret < 0 ) {
2021-12-14 05:08:41 +03:00
dev_err_probe ( dev , ret , " snd_soc_register_card() failed \n " ) ;
2017-08-04 13:58:17 +03:00
goto err_put_clk_i2s ;
2017-04-21 20:19:50 +03:00
}
2019-07-13 06:46:15 +03:00
of_node_put ( cpu_dai ) ;
2019-07-13 06:46:14 +03:00
of_node_put ( codec ) ;
2017-04-21 20:19:50 +03:00
return 0 ;
2017-08-04 13:58:17 +03:00
err_put_clk_i2s :
clk_put ( priv - > clk_i2s_bus ) ;
err_put_sclk :
clk_put ( priv - > sclk_i2s ) ;
2019-02-20 14:06:07 +03:00
err_put_cpu_dai :
of_node_put ( cpu_dai ) ;
2019-02-14 12:37:40 +03:00
snd_soc_of_put_dai_link_codecs ( codec_link ) ;
2019-07-13 06:46:14 +03:00
err_put_node :
of_node_put ( codec ) ;
2017-04-21 20:19:50 +03:00
return ret ;
}
2023-03-15 18:06:58 +03:00
static void odroid_audio_remove ( struct platform_device * pdev )
2017-04-21 20:19:50 +03:00
{
struct odroid_priv * priv = platform_get_drvdata ( pdev ) ;
2019-02-14 12:37:40 +03:00
snd_soc_of_put_dai_link_codecs ( & priv - > card . dai_link [ 1 ] ) ;
2017-08-04 13:58:17 +03:00
clk_put ( priv - > sclk_i2s ) ;
clk_put ( priv - > clk_i2s_bus ) ;
2017-04-21 20:19:50 +03:00
}
static const struct of_device_id odroid_audio_of_match [ ] = {
2018-03-07 20:46:25 +03:00
{ . compatible = " hardkernel,odroid-xu3-audio " } ,
{ . compatible = " hardkernel,odroid-xu4-audio " } ,
2017-04-21 20:19:50 +03:00
{ . compatible = " samsung,odroid-xu3-audio " } ,
2018-03-07 20:46:25 +03:00
{ . compatible = " samsung,odroid-xu4-audio " } ,
2017-04-21 20:19:50 +03:00
{ } ,
} ;
MODULE_DEVICE_TABLE ( of , odroid_audio_of_match ) ;
static struct platform_driver odroid_audio_driver = {
. driver = {
. name = " odroid-audio " ,
. of_match_table = odroid_audio_of_match ,
. pm = & snd_soc_pm_ops ,
} ,
. probe = odroid_audio_probe ,
2023-03-15 18:06:58 +03:00
. remove_new = odroid_audio_remove ,
2017-04-21 20:19:50 +03:00
} ;
module_platform_driver ( odroid_audio_driver ) ;
MODULE_AUTHOR ( " Sylwester Nawrocki <s.nawrocki@samsung.com> " ) ;
MODULE_DESCRIPTION ( " Odroid XU3/XU4 audio support " ) ;
MODULE_LICENSE ( " GPL v2 " ) ;