2022-10-27 15:35:24 -04:00
// SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause)
//
// This file is provided under a dual BSD/GPLv2 license. When using or
// redistributing this file, you may do so under either license.
//
// Copyright(c) 2022 Intel Corporation. All rights reserved.
//
/*
* Management of HDaudio multi - link ( capabilities , power , coupling )
*/
# include <sound/hdaudio_ext.h>
# include <sound/hda_register.h>
2023-04-04 13:41:14 +03:00
# include <sound/hda-mlink.h>
2022-10-27 15:35:24 -04:00
2023-04-04 13:41:15 +03:00
# include <linux/bitfield.h>
2022-10-27 15:35:24 -04:00
# include <linux/module.h>
2023-04-04 13:41:14 +03:00
# if IS_ENABLED(CONFIG_SND_SOC_SOF_HDA_MLINK)
2022-10-27 15:35:24 -04:00
2023-04-04 13:41:15 +03:00
/**
* struct hdac_ext2_link - HDAudio extended + alternate link
*
* @ hext_link : hdac_ext_link
* @ alt : flag set for alternate extended links
* @ intc : boolean for interrupt capable
* @ ofls : boolean for offload support
* @ lss : boolean for link synchronization capabilities
* @ slcount : sublink count
* @ elid : extended link ID ( AZX_REG_ML_LEPTR_ID_ defines )
* @ elver : extended link version
* @ leptr : extended link pointer
* @ eml_lock : mutual exclusion to access shared registers e . g . CPA / SPA bits
* in LCTL register
* @ base_ptr : pointer to shim / ip / shim_vs space
* @ instance_offset : offset between each of @ slcount instances managed by link
* @ shim_offset : offset to SHIM register base
* @ ip_offset : offset to IP register base
* @ shim_vs_offset : offset to vendor - specific ( VS ) SHIM base
*/
struct hdac_ext2_link {
struct hdac_ext_link hext_link ;
/* read directly from LCAP register */
bool alt ;
bool intc ;
bool ofls ;
bool lss ;
int slcount ;
int elid ;
int elver ;
u32 leptr ;
struct mutex eml_lock ; /* prevent concurrent access to e.g. CPA/SPA */
/* internal values computed from LCAP contents */
void __iomem * base_ptr ;
u32 instance_offset ;
u32 shim_offset ;
u32 ip_offset ;
u32 shim_vs_offset ;
} ;
# define hdac_ext_link_to_ext2(h) container_of(h, struct hdac_ext2_link, hext_link)
# define AZX_REG_SDW_INSTANCE_OFFSET 0x8000
# define AZX_REG_SDW_SHIM_OFFSET 0x0
# define AZX_REG_SDW_IP_OFFSET 0x100
# define AZX_REG_SDW_VS_SHIM_OFFSET 0x6000
/* only one instance supported */
# define AZX_REG_INTEL_DMIC_SHIM_OFFSET 0x0
# define AZX_REG_INTEL_DMIC_IP_OFFSET 0x100
# define AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET 0x6000
# define AZX_REG_INTEL_SSP_INSTANCE_OFFSET 0x1000
# define AZX_REG_INTEL_SSP_SHIM_OFFSET 0x0
# define AZX_REG_INTEL_SSP_IP_OFFSET 0x100
# define AZX_REG_INTEL_SSP_VS_SHIM_OFFSET 0xC00
/* only one instance supported */
# define AZX_REG_INTEL_UAOL_SHIM_OFFSET 0x0
# define AZX_REG_INTEL_UAOL_IP_OFFSET 0x100
# define AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET 0xC00
/* HDAML section - this part follows sequences in the hardware specification,
* including naming conventions and the use of the hdaml_ prefix .
* The code is intentionally minimal with limited dependencies on frameworks or
* helpers . Locking and scanning lists is handled at a higher level
*/
static int hdaml_lnk_enum ( struct device * dev , struct hdac_ext2_link * h2link ,
void __iomem * ml_addr , int link_idx )
{
struct hdac_ext_link * hlink = & h2link - > hext_link ;
u32 base_offset ;
hlink - > lcaps = readl ( ml_addr + AZX_REG_ML_LCAP ) ;
h2link - > alt = FIELD_GET ( AZX_ML_HDA_LCAP_ALT , hlink - > lcaps ) ;
/* handle alternate extensions */
if ( ! h2link - > alt ) {
h2link - > slcount = 1 ;
/*
* LSDIID is initialized by hardware for HDaudio link ,
* it needs to be setup by software for alternate links
*/
hlink - > lsdiid = readw ( ml_addr + AZX_REG_ML_LSDIID ) ;
dev_dbg ( dev , " Link %d: HDAudio - lsdiid=%d \n " ,
link_idx , hlink - > lsdiid ) ;
return 0 ;
}
h2link - > intc = FIELD_GET ( AZX_ML_HDA_LCAP_INTC , hlink - > lcaps ) ;
h2link - > ofls = FIELD_GET ( AZX_ML_HDA_LCAP_OFLS , hlink - > lcaps ) ;
h2link - > lss = FIELD_GET ( AZX_ML_HDA_LCAP_LSS , hlink - > lcaps ) ;
/* read slcount (increment due to zero-based hardware representation */
h2link - > slcount = FIELD_GET ( AZX_ML_HDA_LCAP_SLCOUNT , hlink - > lcaps ) + 1 ;
dev_dbg ( dev , " Link %d: HDAudio extended - sublink count %d \n " ,
link_idx , h2link - > slcount ) ;
/* find IP ID and offsets */
h2link - > leptr = readl ( hlink - > ml_addr + AZX_REG_ML_LEPTR ) ;
h2link - > elid = FIELD_GET ( AZX_REG_ML_LEPTR_ID , h2link - > leptr ) ;
base_offset = FIELD_GET ( AZX_REG_ML_LEPTR_PTR , h2link - > leptr ) ;
h2link - > base_ptr = hlink - > ml_addr + base_offset ;
switch ( h2link - > elid ) {
case AZX_REG_ML_LEPTR_ID_SDW :
h2link - > shim_offset = AZX_REG_SDW_SHIM_OFFSET ;
h2link - > ip_offset = AZX_REG_SDW_IP_OFFSET ;
h2link - > shim_vs_offset = AZX_REG_SDW_VS_SHIM_OFFSET ;
dev_dbg ( dev , " Link %d: HDAudio extended - SoundWire alternate link, leptr.ptr %#x \n " ,
link_idx , base_offset ) ;
break ;
case AZX_REG_ML_LEPTR_ID_INTEL_DMIC :
h2link - > shim_offset = AZX_REG_INTEL_DMIC_SHIM_OFFSET ;
h2link - > ip_offset = AZX_REG_INTEL_DMIC_IP_OFFSET ;
h2link - > shim_vs_offset = AZX_REG_INTEL_DMIC_VS_SHIM_OFFSET ;
dev_dbg ( dev , " Link %d: HDAudio extended - INTEL DMIC alternate link, leptr.ptr %#x \n " ,
link_idx , base_offset ) ;
break ;
case AZX_REG_ML_LEPTR_ID_INTEL_SSP :
h2link - > shim_offset = AZX_REG_INTEL_SSP_SHIM_OFFSET ;
h2link - > ip_offset = AZX_REG_INTEL_SSP_IP_OFFSET ;
h2link - > shim_vs_offset = AZX_REG_INTEL_SSP_VS_SHIM_OFFSET ;
dev_dbg ( dev , " Link %d: HDAudio extended - INTEL SSP alternate link, leptr.ptr %#x \n " ,
link_idx , base_offset ) ;
break ;
case AZX_REG_ML_LEPTR_ID_INTEL_UAOL :
h2link - > shim_offset = AZX_REG_INTEL_UAOL_SHIM_OFFSET ;
h2link - > ip_offset = AZX_REG_INTEL_UAOL_IP_OFFSET ;
h2link - > shim_vs_offset = AZX_REG_INTEL_UAOL_VS_SHIM_OFFSET ;
dev_dbg ( dev , " Link %d: HDAudio extended - INTEL UAOL alternate link, leptr.ptr %#x \n " ,
link_idx , base_offset ) ;
break ;
default :
dev_err ( dev , " Link %d: HDAudio extended - Unsupported alternate link, leptr.id=%#02x value \n " ,
link_idx , h2link - > elid ) ;
return - EINVAL ;
}
return 0 ;
}
/* END HDAML section */
static int hda_ml_alloc_h2link ( struct hdac_bus * bus , int index )
{
struct hdac_ext2_link * h2link ;
struct hdac_ext_link * hlink ;
int ret ;
h2link = kzalloc ( sizeof ( * h2link ) , GFP_KERNEL ) ;
if ( ! h2link )
return - ENOMEM ;
/* basic initialization */
hlink = & h2link - > hext_link ;
hlink - > index = index ;
hlink - > bus = bus ;
hlink - > ml_addr = bus - > mlcap + AZX_ML_BASE + ( AZX_ML_INTERVAL * index ) ;
ret = hdaml_lnk_enum ( bus - > dev , h2link , hlink - > ml_addr , index ) ;
if ( ret < 0 ) {
kfree ( h2link ) ;
return ret ;
}
mutex_init ( & h2link - > eml_lock ) ;
list_add_tail ( & hlink - > list , & bus - > hlink_list ) ;
/*
* HDaudio regular links are powered - on by default , the
* refcount needs to be initialized .
*/
if ( ! h2link - > alt )
hlink - > ref_count = 1 ;
return 0 ;
}
int hda_bus_ml_init ( struct hdac_bus * bus )
2022-10-27 15:35:24 -04:00
{
2023-04-04 13:41:15 +03:00
u32 link_count ;
int ret ;
int i ;
if ( ! bus - > mlcap )
return 0 ;
link_count = readl ( bus - > mlcap + AZX_REG_ML_MLCD ) + 1 ;
dev_dbg ( bus - > dev , " HDAudio Multi-Link count: %d \n " , link_count ) ;
for ( i = 0 ; i < link_count ; i + + ) {
ret = hda_ml_alloc_h2link ( bus , i ) ;
if ( ret < 0 ) {
hda_bus_ml_free ( bus ) ;
return ret ;
}
}
2023-04-04 13:41:13 +03:00
return 0 ;
2022-10-27 15:35:24 -04:00
}
2023-04-04 13:41:15 +03:00
EXPORT_SYMBOL_NS ( hda_bus_ml_init , SND_SOC_SOF_HDA_MLINK ) ;
2022-10-27 15:35:24 -04:00
2022-10-27 15:35:37 -04:00
void hda_bus_ml_free ( struct hdac_bus * bus )
{
2023-04-04 13:41:12 +03:00
struct hdac_ext_link * hlink , * _h ;
2023-04-04 13:41:15 +03:00
struct hdac_ext2_link * h2link ;
2022-10-27 15:35:37 -04:00
if ( ! bus - > mlcap )
return ;
2023-04-04 13:41:12 +03:00
list_for_each_entry_safe ( hlink , _h , & bus - > hlink_list , list ) {
2022-10-27 15:35:37 -04:00
list_del ( & hlink - > list ) ;
2023-04-04 13:41:15 +03:00
h2link = hdac_ext_link_to_ext2 ( hlink ) ;
mutex_destroy ( & h2link - > eml_lock ) ;
kfree ( h2link ) ;
2022-10-27 15:35:37 -04:00
}
}
2023-04-04 13:41:14 +03:00
EXPORT_SYMBOL_NS ( hda_bus_ml_free , SND_SOC_SOF_HDA_MLINK ) ;
2022-10-27 15:35:37 -04:00
2022-10-27 15:35:24 -04:00
void hda_bus_ml_put_all ( struct hdac_bus * bus )
{
struct hdac_ext_link * hlink ;
2023-04-04 13:41:16 +03:00
list_for_each_entry ( hlink , & bus - > hlink_list , list ) {
struct hdac_ext2_link * h2link = hdac_ext_link_to_ext2 ( hlink ) ;
if ( ! h2link - > alt )
snd_hdac_ext_bus_link_put ( bus , hlink ) ;
}
2022-10-27 15:35:24 -04:00
}
2023-04-04 13:41:14 +03:00
EXPORT_SYMBOL_NS ( hda_bus_ml_put_all , SND_SOC_SOF_HDA_MLINK ) ;
2022-10-27 15:35:24 -04:00
2022-10-27 15:35:25 -04:00
void hda_bus_ml_reset_losidv ( struct hdac_bus * bus )
{
struct hdac_ext_link * hlink ;
/* Reset stream-to-link mapping */
list_for_each_entry ( hlink , & bus - > hlink_list , list )
writel ( 0 , hlink - > ml_addr + AZX_REG_ML_LOSIDV ) ;
}
2023-04-04 13:41:14 +03:00
EXPORT_SYMBOL_NS ( hda_bus_ml_reset_losidv , SND_SOC_SOF_HDA_MLINK ) ;
2022-10-27 15:35:25 -04:00
2022-10-27 15:35:36 -04:00
int hda_bus_ml_resume ( struct hdac_bus * bus )
{
struct hdac_ext_link * hlink ;
int ret ;
/* power up links that were active before suspend */
list_for_each_entry ( hlink , & bus - > hlink_list , list ) {
2023-04-04 13:41:16 +03:00
struct hdac_ext2_link * h2link = hdac_ext_link_to_ext2 ( hlink ) ;
if ( ! h2link - > alt & & hlink - > ref_count ) {
2022-10-27 15:35:36 -04:00
ret = snd_hdac_ext_bus_link_power_up ( hlink ) ;
if ( ret < 0 )
return ret ;
}
}
return 0 ;
}
2023-04-04 13:41:14 +03:00
EXPORT_SYMBOL_NS ( hda_bus_ml_resume , SND_SOC_SOF_HDA_MLINK ) ;
2022-10-27 15:35:36 -04:00
int hda_bus_ml_suspend ( struct hdac_bus * bus )
{
2023-04-04 13:41:16 +03:00
struct hdac_ext_link * hlink ;
int ret ;
list_for_each_entry ( hlink , & bus - > hlink_list , list ) {
struct hdac_ext2_link * h2link = hdac_ext_link_to_ext2 ( hlink ) ;
if ( ! h2link - > alt ) {
ret = snd_hdac_ext_bus_link_power_down ( hlink ) ;
if ( ret < 0 )
return ret ;
}
}
return 0 ;
2022-10-27 15:35:36 -04:00
}
2023-04-04 13:41:14 +03:00
EXPORT_SYMBOL_NS ( hda_bus_ml_suspend , SND_SOC_SOF_HDA_MLINK ) ;
2022-10-27 15:35:36 -04:00
2022-10-27 15:35:24 -04:00
# endif
2023-04-04 13:41:14 +03:00
MODULE_LICENSE ( " Dual BSD/GPL " ) ;