blob: 38071bc838b950ce0f3d6760d64f8b7f15d6763d [file] [log] [blame] [edit]
// 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");