2023-06-14 15:22:40 +03:00
// SPDX-License-Identifier: GPL-2.0
//
// Loongson ASoC Audio Machine driver
//
// Copyright (C) 2023 Loongson Technology Corporation Limited
// Author: Yingkun Meng <mengyingkun@loongson.cn>
//
# include <linux/module.h>
# include <sound/soc.h>
# include <sound/soc-acpi.h>
# include <linux/acpi.h>
# include <linux/pci.h>
# include <sound/pcm_params.h>
static char codec_name [ SND_ACPI_I2C_ID_LEN ] ;
struct loongson_card_data {
struct snd_soc_card snd_card ;
unsigned int mclk_fs ;
} ;
static int loongson_card_hw_params ( struct snd_pcm_substream * substream ,
struct snd_pcm_hw_params * params )
{
struct snd_soc_pcm_runtime * rtd = substream - > private_data ;
struct snd_soc_dai * cpu_dai = asoc_rtd_to_cpu ( rtd , 0 ) ;
struct snd_soc_dai * codec_dai = asoc_rtd_to_codec ( rtd , 0 ) ;
struct loongson_card_data * ls_card = snd_soc_card_get_drvdata ( rtd - > card ) ;
int ret , mclk ;
if ( ls_card - > mclk_fs ) {
mclk = ls_card - > mclk_fs * params_rate ( params ) ;
ret = snd_soc_dai_set_sysclk ( cpu_dai , 0 , mclk ,
SND_SOC_CLOCK_OUT ) ;
if ( ret < 0 ) {
dev_err ( codec_dai - > dev , " cpu_dai clock not set \n " ) ;
return ret ;
}
ret = snd_soc_dai_set_sysclk ( codec_dai , 0 , mclk ,
SND_SOC_CLOCK_IN ) ;
if ( ret < 0 ) {
dev_err ( codec_dai - > dev , " codec_dai clock not set \n " ) ;
return ret ;
}
}
return 0 ;
}
static const struct snd_soc_ops loongson_ops = {
. hw_params = loongson_card_hw_params ,
} ;
SND_SOC_DAILINK_DEFS ( analog ,
DAILINK_COMP_ARRAY ( COMP_CPU ( " loongson-i2s " ) ) ,
DAILINK_COMP_ARRAY ( COMP_EMPTY ( ) ) ,
DAILINK_COMP_ARRAY ( COMP_EMPTY ( ) ) ) ;
static struct snd_soc_dai_link loongson_dai_links [ ] = {
{
. name = " Loongson Audio Port " ,
. stream_name = " Loongson Audio " ,
. dai_fmt = SND_SOC_DAIFMT_I2S | SND_SOC_DAIFMT_IB_NF
| SND_SOC_DAIFMT_CBC_CFC ,
SND_SOC_DAILINK_REG ( analog ) ,
. ops = & loongson_ops ,
} ,
} ;
static int loongson_card_parse_acpi ( struct loongson_card_data * data )
{
struct snd_soc_card * card = & data - > snd_card ;
struct fwnode_handle * fwnode = card - > dev - > fwnode ;
struct fwnode_reference_args args ;
const char * codec_dai_name ;
struct acpi_device * adev ;
struct device * phy_dev ;
int ret , i ;
/* fixup platform name based on reference node */
memset ( & args , 0 , sizeof ( args ) ) ;
ret = acpi_node_get_property_reference ( fwnode , " cpu " , 0 , & args ) ;
2023-06-19 12:46:21 +03:00
if ( ret | | ! is_acpi_device_node ( args . fwnode ) ) {
2023-06-14 15:22:40 +03:00
dev_err ( card - > dev , " No matching phy in ACPI table \n " ) ;
2023-06-19 12:46:21 +03:00
return ret ? : - ENOENT ;
2023-06-14 15:22:40 +03:00
}
adev = to_acpi_device_node ( args . fwnode ) ;
phy_dev = acpi_get_first_physical_node ( adev ) ;
if ( ! phy_dev )
return - EPROBE_DEFER ;
for ( i = 0 ; i < card - > num_links ; i + + )
loongson_dai_links [ i ] . platforms - > name = dev_name ( phy_dev ) ;
/* fixup codec name based on reference node */
memset ( & args , 0 , sizeof ( args ) ) ;
ret = acpi_node_get_property_reference ( fwnode , " codec " , 0 , & args ) ;
2023-06-19 12:46:21 +03:00
if ( ret | | ! is_acpi_device_node ( args . fwnode ) ) {
2023-06-14 15:22:40 +03:00
dev_err ( card - > dev , " No matching phy in ACPI table \n " ) ;
2023-06-19 12:46:21 +03:00
return ret ? : - ENOENT ;
2023-06-14 15:22:40 +03:00
}
adev = to_acpi_device_node ( args . fwnode ) ;
snprintf ( codec_name , sizeof ( codec_name ) , " i2c-%s " , acpi_dev_name ( adev ) ) ;
for ( i = 0 ; i < card - > num_links ; i + + )
loongson_dai_links [ i ] . codecs - > name = codec_name ;
device_property_read_string ( card - > dev , " codec-dai-name " ,
& codec_dai_name ) ;
for ( i = 0 ; i < card - > num_links ; i + + )
loongson_dai_links [ i ] . codecs - > dai_name = codec_dai_name ;
return 0 ;
}
static int loongson_card_parse_of ( struct loongson_card_data * data )
{
struct device_node * cpu , * codec ;
struct snd_soc_card * card = & data - > snd_card ;
struct device * dev = card - > dev ;
int ret , i ;
cpu = of_get_child_by_name ( dev - > of_node , " cpu " ) ;
if ( ! cpu ) {
dev_err ( dev , " platform property missing or invalid \n " ) ;
return - EINVAL ;
}
codec = of_get_child_by_name ( dev - > of_node , " codec " ) ;
if ( ! codec ) {
dev_err ( dev , " audio-codec property missing or invalid \n " ) ;
ret = - EINVAL ;
goto err ;
}
2023-06-20 05:14:40 +03:00
for ( i = 0 ; i < card - > num_links ; i + + ) {
ret = snd_soc_of_get_dlc ( cpu , NULL , loongson_dai_links [ i ] . cpus , 0 ) ;
if ( ret < 0 ) {
dev_err ( dev , " getting cpu dlc error (%d) \n " , ret ) ;
goto err ;
}
2023-06-14 15:22:40 +03:00
2023-06-20 05:14:40 +03:00
ret = snd_soc_of_get_dlc ( codec , NULL , loongson_dai_links [ i ] . codecs , 0 ) ;
if ( ret < 0 ) {
dev_err ( dev , " getting codec dlc error (%d) \n " , ret ) ;
goto err ;
}
2023-06-14 15:22:40 +03:00
}
of_node_put ( cpu ) ;
of_node_put ( codec ) ;
return 0 ;
err :
of_node_put ( cpu ) ;
of_node_put ( codec ) ;
return ret ;
}
static int loongson_asoc_card_probe ( struct platform_device * pdev )
{
struct loongson_card_data * ls_priv ;
struct snd_soc_card * card ;
int ret ;
ls_priv = devm_kzalloc ( & pdev - > dev , sizeof ( * ls_priv ) , GFP_KERNEL ) ;
if ( ! ls_priv )
return - ENOMEM ;
card = & ls_priv - > snd_card ;
card - > dev = & pdev - > dev ;
card - > owner = THIS_MODULE ;
card - > dai_link = loongson_dai_links ;
card - > num_links = ARRAY_SIZE ( loongson_dai_links ) ;
snd_soc_card_set_drvdata ( card , ls_priv ) ;
ret = device_property_read_string ( & pdev - > dev , " model " , & card - > name ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Error parsing card name: %d \n " , ret ) ;
return ret ;
}
ret = device_property_read_u32 ( & pdev - > dev , " mclk-fs " , & ls_priv - > mclk_fs ) ;
if ( ret ) {
dev_err ( & pdev - > dev , " Error parsing mclk-fs: %d \n " , ret ) ;
return ret ;
}
if ( has_acpi_companion ( & pdev - > dev ) )
ret = loongson_card_parse_acpi ( ls_priv ) ;
else
ret = loongson_card_parse_of ( ls_priv ) ;
if ( ret < 0 )
return ret ;
ret = devm_snd_soc_register_card ( & pdev - > dev , card ) ;
return ret ;
}
static const struct of_device_id loongson_asoc_dt_ids [ ] = {
{ . compatible = " loongson,ls-audio-card " } ,
{ /* sentinel */ } ,
} ;
MODULE_DEVICE_TABLE ( of , loongson_asoc_dt_ids ) ;
static struct platform_driver loongson_audio_driver = {
. probe = loongson_asoc_card_probe ,
. driver = {
. name = " loongson-asoc-card " ,
. pm = & snd_soc_pm_ops ,
. of_match_table = of_match_ptr ( loongson_asoc_dt_ids ) ,
} ,
} ;
module_platform_driver ( loongson_audio_driver ) ;
MODULE_DESCRIPTION ( " Loongson ASoc Sound Card driver " ) ;
MODULE_AUTHOR ( " Loongson Technology Corporation Limited " ) ;
MODULE_LICENSE ( " GPL " ) ;