2022-05-11 19:23:51 +03:00
// SPDX-License-Identifier: GPL-2.0
//
// Copyright(c) 2021-2022 Intel Corporation. All rights reserved.
//
// Author: Cezary Rojewski <cezary.rojewski@intel.com>
//
# include <linux/module.h>
# include <linux/pm_runtime.h>
# include <sound/soc.h>
# include <sound/hdaudio_ext.h>
# include <sound/hda_i915.h>
# include <sound/hda_codec.h>
# include "hda.h"
static int hda_codec_create_dais ( struct hda_codec * codec , int pcm_count ,
struct snd_soc_dai_driver * * drivers )
{
struct device * dev = & codec - > core . dev ;
struct snd_soc_dai_driver * drvs ;
struct hda_pcm * pcm ;
int i ;
drvs = devm_kcalloc ( dev , pcm_count , sizeof ( * drvs ) , GFP_KERNEL ) ;
if ( ! drvs )
return - ENOMEM ;
pcm = list_first_entry ( & codec - > pcm_list_head , struct hda_pcm , list ) ;
for ( i = 0 ; i < pcm_count ; i + + , pcm = list_next_entry ( pcm , list ) ) {
struct snd_soc_pcm_stream * stream ;
int dir ;
dev_info ( dev , " creating for %s %d \n " , pcm - > name , i ) ;
drvs [ i ] . id = i ;
drvs [ i ] . name = pcm - > name ;
drvs [ i ] . ops = & snd_soc_hda_codec_dai_ops ;
dir = SNDRV_PCM_STREAM_PLAYBACK ;
stream = & drvs [ i ] . playback ;
if ( ! pcm - > stream [ dir ] . substreams ) {
dev_info ( dev , " skipping playback dai for %s \n " , pcm - > name ) ;
goto capture_dais ;
}
stream - > stream_name =
devm_kasprintf ( dev , GFP_KERNEL , " %s %s " , pcm - > name ,
snd_pcm_direction_name ( dir ) ) ;
if ( ! stream - > stream_name )
return - ENOMEM ;
stream - > channels_min = pcm - > stream [ dir ] . channels_min ;
stream - > channels_max = pcm - > stream [ dir ] . channels_max ;
stream - > rates = pcm - > stream [ dir ] . rates ;
stream - > formats = pcm - > stream [ dir ] . formats ;
stream - > sig_bits = pcm - > stream [ dir ] . maxbps ;
capture_dais :
dir = SNDRV_PCM_STREAM_CAPTURE ;
stream = & drvs [ i ] . capture ;
if ( ! pcm - > stream [ dir ] . substreams ) {
dev_info ( dev , " skipping capture dai for %s \n " , pcm - > name ) ;
continue ;
}
stream - > stream_name =
devm_kasprintf ( dev , GFP_KERNEL , " %s %s " , pcm - > name ,
snd_pcm_direction_name ( dir ) ) ;
if ( ! stream - > stream_name )
return - ENOMEM ;
stream - > channels_min = pcm - > stream [ dir ] . channels_min ;
stream - > channels_max = pcm - > stream [ dir ] . channels_max ;
stream - > rates = pcm - > stream [ dir ] . rates ;
stream - > formats = pcm - > stream [ dir ] . formats ;
stream - > sig_bits = pcm - > stream [ dir ] . maxbps ;
}
* drivers = drvs ;
return 0 ;
}
static int hda_codec_register_dais ( struct hda_codec * codec , struct snd_soc_component * component )
{
struct snd_soc_dai_driver * drvs = NULL ;
struct snd_soc_dapm_context * dapm ;
struct hda_pcm * pcm ;
int ret , pcm_count = 0 ;
if ( list_empty ( & codec - > pcm_list_head ) )
return - EINVAL ;
list_for_each_entry ( pcm , & codec - > pcm_list_head , list )
pcm_count + + ;
ret = hda_codec_create_dais ( codec , pcm_count , & drvs ) ;
if ( ret < 0 )
return ret ;
dapm = snd_soc_component_get_dapm ( component ) ;
list_for_each_entry ( pcm , & codec - > pcm_list_head , list ) {
struct snd_soc_dai * dai ;
dai = snd_soc_register_dai ( component , drvs , false ) ;
if ( ! dai ) {
dev_err ( component - > dev , " register dai for %s failed \n " , pcm - > name ) ;
return - EINVAL ;
}
ret = snd_soc_dapm_new_dai_widgets ( dapm , dai ) ;
if ( ret < 0 ) {
dev_err ( component - > dev , " create widgets failed: %d \n " , ret ) ;
snd_soc_unregister_dai ( dai ) ;
return ret ;
}
snd_soc_dai_init_dma_data ( dai , & pcm - > stream [ 0 ] , & pcm - > stream [ 1 ] ) ;
drvs + + ;
}
return 0 ;
}
static void hda_codec_unregister_dais ( struct hda_codec * codec ,
struct snd_soc_component * component )
{
struct snd_soc_dai * dai , * save ;
struct hda_pcm * pcm ;
for_each_component_dais_safe ( component , dai , save ) {
2023-01-31 04:59:50 +03:00
int stream ;
2022-05-11 19:23:51 +03:00
list_for_each_entry ( pcm , & codec - > pcm_list_head , list ) {
if ( strcmp ( dai - > driver - > name , pcm - > name ) )
continue ;
2023-01-31 04:59:50 +03:00
for_each_pcm_streams ( stream )
snd_soc_dapm_free_widget ( snd_soc_dai_get_widget ( dai , stream ) ) ;
2022-05-11 19:23:51 +03:00
snd_soc_unregister_dai ( dai ) ;
break ;
}
}
}
int hda_codec_probe_complete ( struct hda_codec * codec )
{
struct hdac_device * hdev = & codec - > core ;
struct hdac_bus * bus = hdev - > bus ;
int ret ;
ret = snd_hda_codec_build_controls ( codec ) ;
if ( ret < 0 ) {
dev_err ( & hdev - > dev , " unable to create controls %d \n " , ret ) ;
goto out ;
}
/* Bus suspended codecs as it does not manage their pm */
pm_runtime_set_active ( & hdev - > dev ) ;
/* rpm was forbidden in snd_hda_codec_device_new() */
snd_hda_codec_set_power_save ( codec , 2000 ) ;
snd_hda_codec_register ( codec ) ;
out :
/* Complement pm_runtime_get_sync(bus) in probe */
pm_runtime_mark_last_busy ( bus - > dev ) ;
pm_runtime_put_autosuspend ( bus - > dev ) ;
return ret ;
}
EXPORT_SYMBOL_GPL ( hda_codec_probe_complete ) ;
/* Expects codec with usage_count=1 and status=suspended */
static int hda_codec_probe ( struct snd_soc_component * component )
{
struct hda_codec * codec = dev_to_hda_codec ( component - > dev ) ;
struct hdac_device * hdev = & codec - > core ;
struct hdac_bus * bus = hdev - > bus ;
struct hdac_ext_link * hlink ;
hda_codec_patch_t patch ;
int ret ;
# ifdef CONFIG_PM
WARN_ON ( atomic_read ( & hdev - > dev . power . usage_count ) ! = 1 | |
! pm_runtime_status_suspended ( & hdev - > dev ) ) ;
# endif
2022-10-19 19:21:09 +03:00
hlink = snd_hdac_ext_bus_get_hlink_by_addr ( bus , hdev - > addr ) ;
2022-05-11 19:23:51 +03:00
if ( ! hlink ) {
dev_err ( & hdev - > dev , " hdac link not found \n " ) ;
return - EIO ;
}
pm_runtime_get_sync ( bus - > dev ) ;
if ( hda_codec_is_display ( codec ) )
snd_hdac_display_power ( bus , hdev - > addr , true ) ;
snd_hdac_ext_bus_link_get ( bus , hlink ) ;
ret = snd_hda_codec_device_new ( codec - > bus , component - > card - > snd_card , hdev - > addr , codec ,
false ) ;
if ( ret < 0 ) {
dev_err ( & hdev - > dev , " create hda codec failed: %d \n " , ret ) ;
goto device_new_err ;
}
ret = snd_hda_codec_set_name ( codec , codec - > preset - > name ) ;
if ( ret < 0 ) {
dev_err ( & hdev - > dev , " name failed %s \n " , codec - > preset - > name ) ;
goto err ;
}
ret = snd_hdac_regmap_init ( & codec - > core ) ;
if ( ret < 0 ) {
dev_err ( & hdev - > dev , " regmap init failed \n " ) ;
goto err ;
}
patch = ( hda_codec_patch_t ) codec - > preset - > driver_data ;
if ( ! patch ) {
2022-10-10 15:19:55 +03:00
dev_err ( & hdev - > dev , " no patch specified \n " ) ;
2022-05-11 19:23:51 +03:00
ret = - EINVAL ;
goto err ;
}
ret = patch ( codec ) ;
if ( ret < 0 ) {
dev_err ( & hdev - > dev , " patch failed %d \n " , ret ) ;
goto err ;
}
ret = snd_hda_codec_parse_pcms ( codec ) ;
if ( ret < 0 ) {
dev_err ( & hdev - > dev , " unable to map pcms to dai %d \n " , ret ) ;
goto parse_pcms_err ;
}
ret = hda_codec_register_dais ( codec , component ) ;
if ( ret < 0 ) {
dev_err ( & hdev - > dev , " update dais failed: %d \n " , ret ) ;
goto parse_pcms_err ;
}
if ( ! hda_codec_is_display ( codec ) ) {
ret = hda_codec_probe_complete ( codec ) ;
if ( ret < 0 )
goto complete_err ;
}
codec - > core . lazy_cache = true ;
return 0 ;
complete_err :
hda_codec_unregister_dais ( codec , component ) ;
parse_pcms_err :
if ( codec - > patch_ops . free )
codec - > patch_ops . free ( codec ) ;
err :
snd_hda_codec_cleanup_for_unbind ( codec ) ;
device_new_err :
if ( hda_codec_is_display ( codec ) )
snd_hdac_display_power ( bus , hdev - > addr , false ) ;
snd_hdac_ext_bus_link_put ( bus , hlink ) ;
pm_runtime_mark_last_busy ( bus - > dev ) ;
pm_runtime_put_autosuspend ( bus - > dev ) ;
return ret ;
}
/* Leaves codec with usage_count=1 and status=suspended */
static void hda_codec_remove ( struct snd_soc_component * component )
{
struct hda_codec * codec = dev_to_hda_codec ( component - > dev ) ;
struct hdac_device * hdev = & codec - > core ;
struct hdac_bus * bus = hdev - > bus ;
struct hdac_ext_link * hlink ;
2022-07-06 15:02:26 +03:00
bool was_registered = codec - > core . registered ;
2022-05-11 19:23:51 +03:00
/* Don't allow any more runtime suspends */
pm_runtime_forbid ( & hdev - > dev ) ;
hda_codec_unregister_dais ( codec , component ) ;
if ( codec - > patch_ops . free )
codec - > patch_ops . free ( codec ) ;
snd_hda_codec_cleanup_for_unbind ( codec ) ;
pm_runtime_put_noidle ( & hdev - > dev ) ;
/* snd_hdac_device_exit() is only called on bus remove */
pm_runtime_set_suspended ( & hdev - > dev ) ;
if ( hda_codec_is_display ( codec ) )
snd_hdac_display_power ( bus , hdev - > addr , false ) ;
2022-10-19 19:21:09 +03:00
hlink = snd_hdac_ext_bus_get_hlink_by_addr ( bus , hdev - > addr ) ;
2022-05-11 19:23:51 +03:00
if ( hlink )
snd_hdac_ext_bus_link_put ( bus , hlink ) ;
/*
* HDMI card ' s hda_codec_probe_complete ( ) ( see late_probe ( ) ) may
* not be called due to early error , leaving bus uc unbalanced
*/
if ( ! was_registered ) {
pm_runtime_mark_last_busy ( bus - > dev ) ;
pm_runtime_put_autosuspend ( bus - > dev ) ;
}
# ifdef CONFIG_PM
WARN_ON ( atomic_read ( & hdev - > dev . power . usage_count ) ! = 1 | |
! pm_runtime_status_suspended ( & hdev - > dev ) ) ;
# endif
}
static const struct snd_soc_dapm_route hda_dapm_routes [ ] = {
{ " AIF1TX " , NULL , " Codec Input Pin1 " } ,
{ " AIF2TX " , NULL , " Codec Input Pin2 " } ,
{ " AIF3TX " , NULL , " Codec Input Pin3 " } ,
{ " Codec Output Pin1 " , NULL , " AIF1RX " } ,
{ " Codec Output Pin2 " , NULL , " AIF2RX " } ,
{ " Codec Output Pin3 " , NULL , " AIF3RX " } ,
} ;
static const struct snd_soc_dapm_widget hda_dapm_widgets [ ] = {
/* Audio Interface */
SND_SOC_DAPM_AIF_IN ( " AIF1RX " , " Analog Codec Playback " , 0 , SND_SOC_NOPM , 0 , 0 ) ,
SND_SOC_DAPM_AIF_IN ( " AIF2RX " , " Digital Codec Playback " , 0 , SND_SOC_NOPM , 0 , 0 ) ,
SND_SOC_DAPM_AIF_IN ( " AIF3RX " , " Alt Analog Codec Playback " , 0 , SND_SOC_NOPM , 0 , 0 ) ,
SND_SOC_DAPM_AIF_OUT ( " AIF1TX " , " Analog Codec Capture " , 0 , SND_SOC_NOPM , 0 , 0 ) ,
SND_SOC_DAPM_AIF_OUT ( " AIF2TX " , " Digital Codec Capture " , 0 , SND_SOC_NOPM , 0 , 0 ) ,
SND_SOC_DAPM_AIF_OUT ( " AIF3TX " , " Alt Analog Codec Capture " , 0 , SND_SOC_NOPM , 0 , 0 ) ,
/* Input Pins */
SND_SOC_DAPM_INPUT ( " Codec Input Pin1 " ) ,
SND_SOC_DAPM_INPUT ( " Codec Input Pin2 " ) ,
SND_SOC_DAPM_INPUT ( " Codec Input Pin3 " ) ,
/* Output Pins */
SND_SOC_DAPM_OUTPUT ( " Codec Output Pin1 " ) ,
SND_SOC_DAPM_OUTPUT ( " Codec Output Pin2 " ) ,
SND_SOC_DAPM_OUTPUT ( " Codec Output Pin3 " ) ,
} ;
static struct snd_soc_dai_driver card_binder_dai = {
. id = - 1 ,
. name = " codec-probing-DAI " ,
} ;
static int hda_hdev_attach ( struct hdac_device * hdev )
{
struct hda_codec * codec = dev_to_hda_codec ( & hdev - > dev ) ;
struct snd_soc_component_driver * comp_drv ;
comp_drv = devm_kzalloc ( & hdev - > dev , sizeof ( * comp_drv ) , GFP_KERNEL ) ;
if ( ! comp_drv )
return - ENOMEM ;
/*
* It ' s save to rely on dev_name ( ) rather than a copy as component
* driver ' s lifetime is directly tied to hda codec one
*/
comp_drv - > name = dev_name ( & hdev - > dev ) ;
comp_drv - > probe = hda_codec_probe ;
comp_drv - > remove = hda_codec_remove ;
comp_drv - > idle_bias_on = false ;
if ( ! hda_codec_is_display ( codec ) ) {
comp_drv - > dapm_widgets = hda_dapm_widgets ;
comp_drv - > num_dapm_widgets = ARRAY_SIZE ( hda_dapm_widgets ) ;
comp_drv - > dapm_routes = hda_dapm_routes ;
comp_drv - > num_dapm_routes = ARRAY_SIZE ( hda_dapm_routes ) ;
}
return snd_soc_register_component ( & hdev - > dev , comp_drv , & card_binder_dai , 1 ) ;
}
static int hda_hdev_detach ( struct hdac_device * hdev )
{
struct hda_codec * codec = dev_to_hda_codec ( & hdev - > dev ) ;
2022-07-06 15:02:26 +03:00
if ( codec - > core . registered )
2022-05-11 19:23:51 +03:00
cancel_delayed_work_sync ( & codec - > jackpoll_work ) ;
snd_soc_unregister_component ( & hdev - > dev ) ;
return 0 ;
}
const struct hdac_ext_bus_ops soc_hda_ext_bus_ops = {
. hdev_attach = hda_hdev_attach ,
. hdev_detach = hda_hdev_detach ,
} ;
EXPORT_SYMBOL_GPL ( soc_hda_ext_bus_ops ) ;
MODULE_DESCRIPTION ( " HD-Audio codec driver " ) ;
MODULE_AUTHOR ( " Cezary Rojewski <cezary.rojewski@intel.com> " ) ;
MODULE_LICENSE ( " GPL " ) ;