Unverified Commit 996bf834 authored by Pierre-Louis Bossart's avatar Pierre-Louis Bossart Committed by Mark Brown
Browse files

ASoC: SDCA: Add code to parse Function information



Add a helper function to parse all the Function and Entity
information from ACPI. In SDCA each device may have several Functions
and each corresponds to a specific audio capability such as say
amplifier playback or microphone capture. Each Function then contains
a number of Entities that represent individual parts of the audio
signal chain and are linked together in a graph similar to DAPM.

Signed-off-by: default avatarPierre-Louis Bossart <pierre-louis.bossart@linux.dev>
Signed-off-by: default avatarCharles Keepax <ckeepax@opensource.cirrus.com>
Reviewed-by: default avatarPierre-Louis Bossart <pierre-louis.bossart@linux.dev>
Link: https://patch.msgid.link/20250205113801.3699902-3-ckeepax@opensource.cirrus.com


Signed-off-by: default avatarMark Brown <broonie@kernel.org>
parent 629dd55c
Loading
Loading
Loading
Loading
+2 −0
Original line number Diff line number Diff line
@@ -18,11 +18,13 @@ struct sdw_slave;

/**
 * struct sdca_function_desc - short descriptor for an SDCA Function
 * @node: firmware node for the Function.
 * @name: Human-readable string.
 * @type: Function topology type.
 * @adr: ACPI address (used for SDCA register access).
 */
struct sdca_function_desc {
	struct fwnode_handle *node;
	const char *name;
	u32 type;
	u8 adr;
+95 −0
Original line number Diff line number Diff line
@@ -11,6 +11,15 @@

#include <linux/bits.h>

struct device;
struct sdca_function_desc;

/*
 * The addressing space for SDCA relies on 7 bits for Entities, so a
 * maximum of 128 Entities per function can be represented.
 */
#define SDCA_MAX_ENTITY_COUNT 128

/**
 * enum sdca_function_type - SDCA Function Type codes
 * @SDCA_FUNCTION_TYPE_SMART_AMP: Amplifier with protection features.
@@ -90,4 +99,90 @@ enum sdca_entity0_controls {
	SDCA_CTL_ENTITY_0_FUNCTION_BUSY			= BIT(7),
};

/**
 * enum sdca_entity_type - SDCA Entity Type codes
 * @SDCA_ENTITY_TYPE_IT: Input Terminal.
 * @SDCA_ENTITY_TYPE_OT: Output Terminal.
 * @SDCA_ENTITY_TYPE_MU: Mixer Unit.
 * @SDCA_ENTITY_TYPE_SU: Selector Unit.
 * @SDCA_ENTITY_TYPE_FU: Feature Unit.
 * @SDCA_ENTITY_TYPE_XU: Extension Unit.
 * @SDCA_ENTITY_TYPE_CS: Clock Source.
 * @SDCA_ENTITY_TYPE_CX: Clock selector.
 * @SDCA_ENTITY_TYPE_PDE: Power-Domain Entity.
 * @SDCA_ENTITY_TYPE_GE: Group Entity.
 * @SDCA_ENTITY_TYPE_SPE: Security & Privacy Entity.
 * @SDCA_ENTITY_TYPE_CRU: Channel Remapping Unit.
 * @SDCA_ENTITY_TYPE_UDMPU: Up-Down Mixer Processing Unit.
 * @SDCA_ENTITY_TYPE_MFPU: Multi-Function Processing Unit.
 * @SDCA_ENTITY_TYPE_SMPU: Smart Microphone Processing Unit.
 * @SDCA_ENTITY_TYPE_SAPU: Smart Amp Processing Unit.
 * @SDCA_ENTITY_TYPE_PPU: Posture Processing Unit.
 * @SDCA_ENTITY_TYPE_TG: Tone Generator.
 * @SDCA_ENTITY_TYPE_HIDE: Human Interface Device Entity.
 *
 * SDCA Entity Types from SDCA specification v1.0 Section 6.1.2
 * all Entity Types not described are reserved.
 */
enum sdca_entity_type {
	SDCA_ENTITY_TYPE_IT				= 0x02,
	SDCA_ENTITY_TYPE_OT				= 0x03,
	SDCA_ENTITY_TYPE_MU				= 0x05,
	SDCA_ENTITY_TYPE_SU				= 0x06,
	SDCA_ENTITY_TYPE_FU				= 0x07,
	SDCA_ENTITY_TYPE_XU				= 0x0A,
	SDCA_ENTITY_TYPE_CS				= 0x0B,
	SDCA_ENTITY_TYPE_CX				= 0x0C,
	SDCA_ENTITY_TYPE_PDE				= 0x11,
	SDCA_ENTITY_TYPE_GE				= 0x12,
	SDCA_ENTITY_TYPE_SPE				= 0x13,
	SDCA_ENTITY_TYPE_CRU				= 0x20,
	SDCA_ENTITY_TYPE_UDMPU				= 0x21,
	SDCA_ENTITY_TYPE_MFPU				= 0x22,
	SDCA_ENTITY_TYPE_SMPU				= 0x23,
	SDCA_ENTITY_TYPE_SAPU				= 0x24,
	SDCA_ENTITY_TYPE_PPU				= 0x25,
	SDCA_ENTITY_TYPE_TG				= 0x30,
	SDCA_ENTITY_TYPE_HIDE				= 0x31,
};

/**
 * struct sdca_entity - information for one SDCA Entity
 * @label: String such as "OT 12".
 * @id: Identifier used for addressing.
 * @type: Type code for the Entity.
 * @sources: Dynamically allocated array pointing to each input Entity
 * connected to this Entity.
 * @num_sources: Number of sources for the Entity.
 */
struct sdca_entity {
	const char *label;
	int id;
	enum sdca_entity_type type;

	struct sdca_entity **sources;
	int num_sources;
};

/**
 * struct sdca_function_data - top-level information for one SDCA function
 * @desc: Pointer to short descriptor from initial parsing.
 * @entities: Dynamically allocated array of Entities.
 * @num_entities: Number of Entities reported in this Function.
 * @busy_max_delay: Maximum Function busy delay in microseconds, before an
 * error should be reported.
 */
struct sdca_function_data {
	struct sdca_function_desc *desc;

	struct sdca_entity *entities;
	int num_entities;

	unsigned int busy_max_delay;
};

int sdca_parse_function(struct device *dev,
			struct sdca_function_desc *desc,
			struct sdca_function_data *function);

#endif
+268 −0
Original line number Diff line number Diff line
@@ -18,6 +18,11 @@
#include <sound/sdca.h>
#include <sound/sdca_function.h>

/*
 * Should be long enough to encompass all the MIPI DisCo properties.
 */
#define SDCA_PROPERTY_LENGTH 64

static int patch_sdca_function_type(u32 interface_revision, u32 *function_type)
{
	/*
@@ -150,6 +155,7 @@ static int find_sdca_function(struct acpi_device *adev, void *data)
	sdca_data->function[function_index].adr = addr;
	sdca_data->function[function_index].type = function_type;
	sdca_data->function[function_index].name = function_name;
	sdca_data->function[function_index].node = function_node;
	sdca_data->num_functions++;

	return 0;
@@ -179,5 +185,267 @@ void sdca_lookup_functions(struct sdw_slave *slave)
}
EXPORT_SYMBOL_NS(sdca_lookup_functions, "SND_SOC_SDCA");

static int find_sdca_entity(struct device *dev,
			    struct fwnode_handle *function_node,
			    struct fwnode_handle *entity_node,
			    struct sdca_entity *entity)
{
	u32 tmp;
	int ret;

	ret = fwnode_property_read_string(entity_node, "mipi-sdca-entity-label",
					  &entity->label);
	if (ret) {
		dev_err(dev, "%pfwP: entity %#x: label missing: %d\n",
			function_node, entity->id, ret);
		return ret;
	}

	ret = fwnode_property_read_u32(entity_node, "mipi-sdca-entity-type", &tmp);
	if (ret) {
		dev_err(dev, "%s: type missing: %d\n", entity->label, ret);
		return ret;
	}

	entity->type = tmp;

	dev_info(dev, "%s: entity %#x type %#x\n",
		 entity->label, entity->id, entity->type);

	return 0;
}

static int find_sdca_entities(struct device *dev,
			      struct fwnode_handle *function_node,
			      struct sdca_function_data *function)
{
	struct sdca_entity *entities;
	u32 *entity_list;
	int num_entities;
	int i, ret;

	num_entities = fwnode_property_count_u32(function_node,
						 "mipi-sdca-entity-id-list");
	if (num_entities <= 0) {
		dev_err(dev, "%pfwP: entity id list missing: %d\n",
			function_node, num_entities);
		return -EINVAL;
	} else if (num_entities > SDCA_MAX_ENTITY_COUNT) {
		dev_err(dev, "%pfwP: maximum number of entities exceeded\n",
			function_node);
		return -EINVAL;
	}

	entities = devm_kcalloc(dev, num_entities, sizeof(*entities), GFP_KERNEL);
	if (!entities)
		return -ENOMEM;

	entity_list = kcalloc(num_entities, sizeof(*entity_list), GFP_KERNEL);
	if (!entity_list)
		return -ENOMEM;

	fwnode_property_read_u32_array(function_node, "mipi-sdca-entity-id-list",
				       entity_list, num_entities);

	for (i = 0; i < num_entities; i++)
		entities[i].id = entity_list[i];

	kfree(entity_list);

	/* now read subproperties */
	for (i = 0; i < num_entities; i++) {
		char entity_property[SDCA_PROPERTY_LENGTH];
		struct fwnode_handle *entity_node;

		/* DisCo uses upper-case for hex numbers */
		snprintf(entity_property, sizeof(entity_property),
			 "mipi-sdca-entity-id-0x%X-subproperties", entities[i].id);

		entity_node = fwnode_get_named_child_node(function_node, entity_property);
		if (!entity_node) {
			dev_err(dev, "%pfwP: entity node %s not found\n",
				function_node, entity_property);
			return -EINVAL;
		}

		ret = find_sdca_entity(dev, function_node, entity_node, &entities[i]);
		fwnode_handle_put(entity_node);
		if (ret)
			return ret;
	}

	function->num_entities = num_entities;
	function->entities = entities;

	return 0;
}

static struct sdca_entity *find_sdca_entity_by_label(struct sdca_function_data *function,
						     const char *entity_label)
{
	int i;

	for (i = 0; i < function->num_entities; i++) {
		struct sdca_entity *entity = &function->entities[i];

		if (!strcmp(entity->label, entity_label))
			return entity;
	}

	return NULL;
}

static int find_sdca_entity_connection(struct device *dev,
				       struct sdca_function_data *function,
				       struct fwnode_handle *entity_node,
				       struct sdca_entity *entity)
{
	struct sdca_entity **pins;
	int num_pins, pin;
	u64 pin_list;
	int i, ret;

	ret = fwnode_property_read_u64(entity_node, "mipi-sdca-input-pin-list", &pin_list);
	if (ret == -EINVAL) {
		/* Allow missing pin lists, assume no pins. */
		dev_warn(dev, "%s: missing pin list\n", entity->label);
		return 0;
	} else if (ret) {
		dev_err(dev, "%s: failed to read pin list: %d\n", entity->label, ret);
		return ret;
	} else if (pin_list & BIT(0)) {
		/*
		 * Each bit set in the pin-list refers to an entity_id in this
		 * Function. Entity 0 is an illegal connection since it is used
		 * for Function-level configurations.
		 */
		dev_err(dev, "%s: pin 0 used as input\n", entity->label);
		return -EINVAL;
	} else if (!pin_list) {
		return 0;
	}

	num_pins = hweight64(pin_list);
	pins = devm_kcalloc(dev, num_pins, sizeof(*pins), GFP_KERNEL);
	if (!pins)
		return -ENOMEM;

	i = 0;
	for_each_set_bit(pin, (unsigned long *)&pin_list, BITS_PER_TYPE(pin_list)) {
		char pin_property[SDCA_PROPERTY_LENGTH];
		struct fwnode_handle *connected_node;
		struct sdca_entity *connected_entity;
		const char *connected_label;

		snprintf(pin_property, sizeof(pin_property), "mipi-sdca-input-pin-%d", pin);

		connected_node = fwnode_get_named_child_node(entity_node, pin_property);
		if (!connected_node) {
			dev_err(dev, "%s: pin node %s not found\n",
				entity->label, pin_property);
			return -EINVAL;
		}

		ret = fwnode_property_read_string(connected_node, "mipi-sdca-entity-label",
						  &connected_label);
		if (ret) {
			dev_err(dev, "%s: pin %d label missing: %d\n",
				entity->label, pin, ret);
			fwnode_handle_put(connected_node);
			return ret;
		}

		connected_entity = find_sdca_entity_by_label(function, connected_label);
		if (!connected_entity) {
			dev_err(dev, "%s: failed to find entity with label %s\n",
				entity->label, connected_label);
			fwnode_handle_put(connected_node);
			return -EINVAL;
		}

		pins[i] = connected_entity;

		dev_info(dev, "%s -> %s\n", connected_entity->label, entity->label);

		i++;
		fwnode_handle_put(connected_node);
	}

	entity->num_sources = num_pins;
	entity->sources = pins;

	return 0;
}

static int find_sdca_connections(struct device *dev,
				 struct fwnode_handle *function_node,
				 struct sdca_function_data *function)
{
	int i;

	for (i = 0; i < function->num_entities; i++) {
		struct sdca_entity *entity = &function->entities[i];
		char entity_property[SDCA_PROPERTY_LENGTH];
		struct fwnode_handle *entity_node;
		int ret;

		/* DisCo uses upper-case for hex numbers */
		snprintf(entity_property, sizeof(entity_property),
			 "mipi-sdca-entity-id-0x%X-subproperties",
			 entity->id);

		entity_node = fwnode_get_named_child_node(function_node, entity_property);
		if (!entity_node) {
			dev_err(dev, "%pfwP: entity node %s not found\n",
				function_node, entity_property);
			return -EINVAL;
		}

		ret = find_sdca_entity_connection(dev, function, entity_node, entity);
		fwnode_handle_put(entity_node);
		if (ret)
			return ret;
	}

	return 0;
}

/**
 * sdca_parse_function - parse ACPI DisCo for a Function
 * @dev: Pointer to device against which function data will be allocated.
 * @function_desc: Pointer to the Function short descriptor.
 * @function: Pointer to the Function information, to be populated.
 *
 * Return: Returns 0 for success.
 */
int sdca_parse_function(struct device *dev,
			struct sdca_function_desc *function_desc,
			struct sdca_function_data *function)
{
	u32 tmp;
	int ret;

	function->desc = function_desc;

	ret = fwnode_property_read_u32(function_desc->node,
				       "mipi-sdca-function-busy-max-delay", &tmp);
	if (!ret)
		function->busy_max_delay = tmp;

	dev_info(dev, "%pfwP: name %s delay %dus\n", function->desc->node,
		 function->desc->name, function->busy_max_delay);

	ret = find_sdca_entities(dev, function_desc->node, function);
	if (ret)
		return ret;

	ret = find_sdca_connections(dev, function_desc->node, function);
	if (ret)
		return ret;

	return 0;
}
EXPORT_SYMBOL_NS(sdca_parse_function, "SND_SOC_SDCA");

MODULE_LICENSE("Dual BSD/GPL");
MODULE_DESCRIPTION("SDCA library");