diff options
Diffstat (limited to 'sound/soc/sdca/sdca_functions.c')
-rw-r--r-- | sound/soc/sdca/sdca_functions.c | 171 |
1 files changed, 171 insertions, 0 deletions
diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c new file mode 100644 index 000000000000..38071bc838b9 --- /dev/null +++ b/sound/soc/sdca/sdca_functions.c @@ -0,0 +1,171 @@ +// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) +// Copyright(c) 2024 Intel Corporation + +/* + * The MIPI SDCA specification is available for public downloads at + * https://www.mipi.org/mipi-sdca-v1-0-download + */ + +#define dev_fmt(fmt) "%s: " fmt, __func__ + +#include <linux/acpi.h> +#include <linux/device.h> +#include <linux/module.h> +#include <linux/property.h> +#include <linux/soundwire/sdw.h> +#include <linux/types.h> +#include <sound/sdca.h> +#include <sound/sdca_function.h> + +static int patch_sdca_function_type(u32 interface_revision, u32 *function_type) +{ + /* + * Unfortunately early SDCA specifications used different indices for Functions, + * for backwards compatibility we have to reorder the values found + */ + if (interface_revision < 0x0801) { + switch (*function_type) { + case 1: + *function_type = SDCA_FUNCTION_TYPE_SMART_AMP; + break; + case 2: + *function_type = SDCA_FUNCTION_TYPE_SMART_MIC; + break; + case 3: + *function_type = SDCA_FUNCTION_TYPE_SPEAKER_MIC; + break; + case 4: + *function_type = SDCA_FUNCTION_TYPE_UAJ; + break; + case 5: + *function_type = SDCA_FUNCTION_TYPE_RJ; + break; + case 6: + *function_type = SDCA_FUNCTION_TYPE_HID; + break; + default: + return -EINVAL; + } + } + + return 0; +} + +static const char *get_sdca_function_name(u32 function_type) +{ + switch (function_type) { + case SDCA_FUNCTION_TYPE_SMART_AMP: + return SDCA_FUNCTION_TYPE_SMART_AMP_NAME; + case SDCA_FUNCTION_TYPE_SMART_MIC: + return SDCA_FUNCTION_TYPE_SMART_MIC_NAME; + case SDCA_FUNCTION_TYPE_UAJ: + return SDCA_FUNCTION_TYPE_UAJ_NAME; + case SDCA_FUNCTION_TYPE_HID: + return SDCA_FUNCTION_TYPE_HID_NAME; + case SDCA_FUNCTION_TYPE_SIMPLE_AMP: + return SDCA_FUNCTION_TYPE_SIMPLE_AMP_NAME; + case SDCA_FUNCTION_TYPE_SIMPLE_MIC: + return SDCA_FUNCTION_TYPE_SIMPLE_MIC_NAME; + case SDCA_FUNCTION_TYPE_SPEAKER_MIC: + return SDCA_FUNCTION_TYPE_SPEAKER_MIC_NAME; + case SDCA_FUNCTION_TYPE_RJ: + return SDCA_FUNCTION_TYPE_RJ_NAME; + case SDCA_FUNCTION_TYPE_IMP_DEF: + return SDCA_FUNCTION_TYPE_IMP_DEF_NAME; + default: + return NULL; + } +} + +static int find_sdca_function(struct acpi_device *adev, void *data) +{ + struct fwnode_handle *function_node = acpi_fwnode_handle(adev); + struct sdca_device_data *sdca_data = data; + struct device *dev = &adev->dev; + struct fwnode_handle *control5; /* used to identify function type */ + const char *function_name; + u32 function_type; + int func_index; + u64 addr; + int ret; + + if (sdca_data->num_functions >= SDCA_MAX_FUNCTION_COUNT) { + dev_err(dev, "maximum number of functions exceeded\n"); + return -EINVAL; + } + + ret = acpi_get_local_u64_address(adev->handle, &addr); + if (ret < 0) + return ret; + + if (!addr || addr > 0x7) { + dev_err(dev, "invalid addr: 0x%llx\n", addr); + return -ENODEV; + } + + /* + * Extracting the topology type for an SDCA function is a + * convoluted process. + * The Function type is only visible as a result of a read + * from a control. In theory this would mean reading from the hardware, + * but the SDCA/DisCo specs defined the notion of "DC value" - a constant + * represented with a DSD subproperty. + * Drivers have to query the properties for the control + * SDCA_CONTROL_ENTITY_0_FUNCTION_TOPOLOGY (0x05) + */ + control5 = fwnode_get_named_child_node(function_node, + "mipi-sdca-control-0x5-subproperties"); + if (!control5) + return -ENODEV; + + ret = fwnode_property_read_u32(control5, "mipi-sdca-control-dc-value", + &function_type); + + fwnode_handle_put(control5); + + if (ret < 0) { + dev_err(dev, "function type only supported as DisCo constant\n"); + return ret; + } + + ret = patch_sdca_function_type(sdca_data->interface_revision, &function_type); + if (ret < 0) { + dev_err(dev, "SDCA version %#x invalid function type %d\n", + sdca_data->interface_revision, function_type); + return ret; + } + + function_name = get_sdca_function_name(function_type); + if (!function_name) { + dev_err(dev, "invalid SDCA function type %d\n", function_type); + return -EINVAL; + } + + dev_info(dev, "SDCA function %s (type %d) at 0x%llx\n", + function_name, function_type, addr); + + /* store results */ + func_index = sdca_data->num_functions; + sdca_data->sdca_func[func_index].adr = addr; + sdca_data->sdca_func[func_index].type = function_type; + sdca_data->sdca_func[func_index].name = function_name; + sdca_data->num_functions++; + + return 0; +} + +void sdca_lookup_functions(struct sdw_slave *slave) +{ + struct device *dev = &slave->dev; + struct acpi_device *adev = to_acpi_device_node(dev->fwnode); + + if (!adev) { + dev_info(dev, "No matching ACPI device found, ignoring peripheral\n"); + return; + } + acpi_dev_for_each_child(adev, find_sdca_function, &slave->sdca_data); +} +EXPORT_SYMBOL_NS(sdca_lookup_functions, "SND_SOC_SDCA"); + +MODULE_LICENSE("Dual BSD/GPL"); +MODULE_DESCRIPTION("SDCA library"); |