2024-03-19 09:30:16 +01:00
// SPDX-License-Identifier: GPL-2.0-only
/*
* Copyright ( c ) 2023 - 2024 Intel Corporation
*
* Authors : Cezary Rojewski < cezary . rojewski @ intel . com >
* Amadeusz Slawinski < amadeuszx . slawinski @ linux . intel . com >
*/
# define pr_fmt(fmt) "ACPI: NHLT: " fmt
# include <linux/acpi.h>
# include <linux/errno.h>
# include <linux/export.h>
# include <linux/minmax.h>
# include <linux/printk.h>
# include <linux/types.h>
# include <acpi/nhlt.h>
2024-03-19 09:30:18 +01:00
static struct acpi_table_nhlt * acpi_gbl_nhlt ;
2024-03-19 09:30:16 +01:00
2024-03-19 09:30:18 +01:00
static struct acpi_table_nhlt empty_nhlt = {
2024-03-19 09:30:16 +01:00
. header = {
. signature = ACPI_SIG_NHLT ,
} ,
} ;
/**
* acpi_nhlt_get_gbl_table - Retrieve a pointer to the first NHLT table .
*
* If there is no NHLT in the system , acpi_gbl_nhlt will instead point to an
* empty table .
*
* Return : ACPI status code of the operation .
*/
acpi_status acpi_nhlt_get_gbl_table ( void )
{
acpi_status status ;
status = acpi_get_table ( ACPI_SIG_NHLT , 0 , ( struct acpi_table_header * * ) ( & acpi_gbl_nhlt ) ) ;
if ( ! acpi_gbl_nhlt )
acpi_gbl_nhlt = & empty_nhlt ;
return status ;
}
EXPORT_SYMBOL_GPL ( acpi_nhlt_get_gbl_table ) ;
/**
* acpi_nhlt_put_gbl_table - Release the global NHLT table .
*/
void acpi_nhlt_put_gbl_table ( void )
{
acpi_put_table ( ( struct acpi_table_header * ) acpi_gbl_nhlt ) ;
}
EXPORT_SYMBOL_GPL ( acpi_nhlt_put_gbl_table ) ;
/**
* acpi_nhlt_endpoint_match - Verify if an endpoint matches criteria .
* @ ep : the endpoint to check .
* @ link_type : the hardware link type , e . g . : PDM or SSP .
* @ dev_type : the device type .
* @ dir : stream direction .
* @ bus_id : the ID of virtual bus hosting the endpoint .
*
* Either of @ link_type , @ dev_type , @ dir or @ bus_id may be set to a negative
* value to ignore the parameter when matching .
*
* Return : % true if endpoint matches specified criteria or % false otherwise .
*/
2024-03-19 09:30:18 +01:00
bool acpi_nhlt_endpoint_match ( const struct acpi_nhlt_endpoint * ep ,
2024-03-19 09:30:16 +01:00
int link_type , int dev_type , int dir , int bus_id )
{
return ep & &
( link_type < 0 | | ep - > link_type = = link_type ) & &
( dev_type < 0 | | ep - > device_type = = dev_type ) & &
( bus_id < 0 | | ep - > virtual_bus_id = = bus_id ) & &
( dir < 0 | | ep - > direction = = dir ) ;
}
EXPORT_SYMBOL_GPL ( acpi_nhlt_endpoint_match ) ;
/**
* acpi_nhlt_tb_find_endpoint - Search a NHLT table for an endpoint .
* @ tb : the table to search .
* @ link_type : the hardware link type , e . g . : PDM or SSP .
* @ dev_type : the device type .
* @ dir : stream direction .
* @ bus_id : the ID of virtual bus hosting the endpoint .
*
* Either of @ link_type , @ dev_type , @ dir or @ bus_id may be set to a negative
* value to ignore the parameter during the search .
*
* Return : A pointer to endpoint matching the criteria , % NULL if not found or
* an ERR_PTR ( ) otherwise .
*/
2024-03-19 09:30:18 +01:00
struct acpi_nhlt_endpoint *
acpi_nhlt_tb_find_endpoint ( const struct acpi_table_nhlt * tb ,
2024-03-19 09:30:16 +01:00
int link_type , int dev_type , int dir , int bus_id )
{
2024-03-19 09:30:18 +01:00
struct acpi_nhlt_endpoint * ep ;
2024-03-19 09:30:16 +01:00
for_each_nhlt_endpoint ( tb , ep )
if ( acpi_nhlt_endpoint_match ( ep , link_type , dev_type , dir , bus_id ) )
return ep ;
return NULL ;
}
EXPORT_SYMBOL_GPL ( acpi_nhlt_tb_find_endpoint ) ;
/**
* acpi_nhlt_find_endpoint - Search all NHLT tables for an endpoint .
* @ link_type : the hardware link type , e . g . : PDM or SSP .
* @ dev_type : the device type .
* @ dir : stream direction .
* @ bus_id : the ID of virtual bus hosting the endpoint .
*
* Either of @ link_type , @ dev_type , @ dir or @ bus_id may be set to a negative
* value to ignore the parameter during the search .
*
* Return : A pointer to endpoint matching the criteria , % NULL if not found or
* an ERR_PTR ( ) otherwise .
*/
2024-03-19 09:30:18 +01:00
struct acpi_nhlt_endpoint *
2024-03-19 09:30:16 +01:00
acpi_nhlt_find_endpoint ( int link_type , int dev_type , int dir , int bus_id )
{
/* TODO: Currently limited to table of index 0. */
return acpi_nhlt_tb_find_endpoint ( acpi_gbl_nhlt , link_type , dev_type , dir , bus_id ) ;
}
EXPORT_SYMBOL_GPL ( acpi_nhlt_find_endpoint ) ;
/**
* acpi_nhlt_endpoint_find_fmtcfg - Search endpoint ' s formats configuration space
* for a specific format .
* @ ep : the endpoint to search .
* @ ch : number of channels .
* @ rate : samples per second .
* @ vbps : valid bits per sample .
* @ bps : bits per sample .
*
* Return : A pointer to format matching the criteria , % NULL if not found or
* an ERR_PTR ( ) otherwise .
*/
2024-03-19 09:30:18 +01:00
struct acpi_nhlt_format_config *
acpi_nhlt_endpoint_find_fmtcfg ( const struct acpi_nhlt_endpoint * ep ,
2024-03-19 09:30:16 +01:00
u16 ch , u32 rate , u16 vbps , u16 bps )
{
2024-03-19 09:30:18 +01:00
struct acpi_nhlt_wave_formatext * wav ;
struct acpi_nhlt_format_config * fmt ;
2024-03-19 09:30:16 +01:00
for_each_nhlt_endpoint_fmtcfg ( ep , fmt ) {
wav = & fmt - > format ;
if ( wav - > valid_bits_per_sample = = vbps & &
wav - > samples_per_sec = = rate & &
wav - > bits_per_sample = = bps & &
wav - > channel_count = = ch )
return fmt ;
}
return NULL ;
}
EXPORT_SYMBOL_GPL ( acpi_nhlt_endpoint_find_fmtcfg ) ;
/**
* acpi_nhlt_tb_find_fmtcfg - Search a NHLT table for a specific format .
* @ tb : the table to search .
* @ link_type : the hardware link type , e . g . : PDM or SSP .
* @ dev_type : the device type .
* @ dir : stream direction .
* @ bus_id : the ID of virtual bus hosting the endpoint .
*
* @ ch : number of channels .
* @ rate : samples per second .
* @ vbps : valid bits per sample .
* @ bps : bits per sample .
*
* Either of @ link_type , @ dev_type , @ dir or @ bus_id may be set to a negative
* value to ignore the parameter during the search .
*
* Return : A pointer to format matching the criteria , % NULL if not found or
* an ERR_PTR ( ) otherwise .
*/
2024-03-19 09:30:18 +01:00
struct acpi_nhlt_format_config *
acpi_nhlt_tb_find_fmtcfg ( const struct acpi_table_nhlt * tb ,
2024-03-19 09:30:16 +01:00
int link_type , int dev_type , int dir , int bus_id ,
u16 ch , u32 rate , u16 vbps , u16 bps )
{
2024-03-19 09:30:18 +01:00
struct acpi_nhlt_format_config * fmt ;
struct acpi_nhlt_endpoint * ep ;
2024-03-19 09:30:16 +01:00
for_each_nhlt_endpoint ( tb , ep ) {
if ( ! acpi_nhlt_endpoint_match ( ep , link_type , dev_type , dir , bus_id ) )
continue ;
fmt = acpi_nhlt_endpoint_find_fmtcfg ( ep , ch , rate , vbps , bps ) ;
if ( fmt )
return fmt ;
}
return NULL ;
}
EXPORT_SYMBOL_GPL ( acpi_nhlt_tb_find_fmtcfg ) ;
/**
* acpi_nhlt_find_fmtcfg - Search all NHLT tables for a specific format .
* @ link_type : the hardware link type , e . g . : PDM or SSP .
* @ dev_type : the device type .
* @ dir : stream direction .
* @ bus_id : the ID of virtual bus hosting the endpoint .
*
* @ ch : number of channels .
* @ rate : samples per second .
* @ vbps : valid bits per sample .
* @ bps : bits per sample .
*
* Either of @ link_type , @ dev_type , @ dir or @ bus_id may be set to a negative
* value to ignore the parameter during the search .
*
* Return : A pointer to format matching the criteria , % NULL if not found or
* an ERR_PTR ( ) otherwise .
*/
2024-03-19 09:30:18 +01:00
struct acpi_nhlt_format_config *
2024-03-19 09:30:16 +01:00
acpi_nhlt_find_fmtcfg ( int link_type , int dev_type , int dir , int bus_id ,
u16 ch , u32 rate , u16 vbps , u16 bps )
{
/* TODO: Currently limited to table of index 0. */
return acpi_nhlt_tb_find_fmtcfg ( acpi_gbl_nhlt , link_type , dev_type , dir , bus_id ,
ch , rate , vbps , bps ) ;
}
EXPORT_SYMBOL_GPL ( acpi_nhlt_find_fmtcfg ) ;
static bool acpi_nhlt_config_is_micdevice ( struct acpi_nhlt_config * cfg )
{
return cfg - > capabilities_size > = sizeof ( struct acpi_nhlt_micdevice_config ) ;
}
static bool acpi_nhlt_config_is_vendor_micdevice ( struct acpi_nhlt_config * cfg )
{
struct acpi_nhlt_vendor_micdevice_config * devcfg = __acpi_nhlt_config_caps ( cfg ) ;
return cfg - > capabilities_size > = sizeof ( * devcfg ) & &
cfg - > capabilities_size = = struct_size ( devcfg , mics , devcfg - > mics_count ) ;
}
/**
* acpi_nhlt_endpoint_mic_count - Retrieve number of digital microphones for a PDM endpoint .
* @ ep : the endpoint to return microphones count for .
*
* Return : A number of microphones or an error code if an invalid endpoint is provided .
*/
2024-03-19 09:30:18 +01:00
int acpi_nhlt_endpoint_mic_count ( const struct acpi_nhlt_endpoint * ep )
2024-03-19 09:30:16 +01:00
{
union acpi_nhlt_device_config * devcfg ;
2024-03-19 09:30:18 +01:00
struct acpi_nhlt_format_config * fmt ;
2024-03-19 09:30:16 +01:00
struct acpi_nhlt_config * cfg ;
u16 max_ch = 0 ;
if ( ! ep | | ep - > link_type ! = ACPI_NHLT_LINKTYPE_PDM )
return - EINVAL ;
/* Find max number of channels based on formats configuration. */
for_each_nhlt_endpoint_fmtcfg ( ep , fmt )
max_ch = max ( fmt - > format . channel_count , max_ch ) ;
cfg = __acpi_nhlt_endpoint_config ( ep ) ;
devcfg = __acpi_nhlt_config_caps ( cfg ) ;
/* If @ep is not a mic array, fallback to channels count. */
if ( ! acpi_nhlt_config_is_micdevice ( cfg ) | |
devcfg - > gen . config_type ! = ACPI_NHLT_CONFIGTYPE_MICARRAY )
return max_ch ;
switch ( devcfg - > mic . array_type ) {
case ACPI_NHLT_ARRAYTYPE_LINEAR2_SMALL :
case ACPI_NHLT_ARRAYTYPE_LINEAR2_BIG :
return 2 ;
case ACPI_NHLT_ARRAYTYPE_LINEAR4_GEO1 :
case ACPI_NHLT_ARRAYTYPE_PLANAR4_LSHAPED :
case ACPI_NHLT_ARRAYTYPE_LINEAR4_GEO2 :
return 4 ;
case ACPI_NHLT_ARRAYTYPE_VENDOR :
if ( ! acpi_nhlt_config_is_vendor_micdevice ( cfg ) )
return - EINVAL ;
return devcfg - > vendor_mic . mics_count ;
default :
pr_warn ( " undefined mic array type: %#x \n " , devcfg - > mic . array_type ) ;
return max_ch ;
}
}
EXPORT_SYMBOL_GPL ( acpi_nhlt_endpoint_mic_count ) ;