diff mbox series

[03/11] ASoC: SDCA: add initial module

Message ID 20241016102333.294448-4-yung-chuan.liao@linux.intel.com (mailing list archive)
State Accepted
Commit 3a513da1ae33972e59efeef7908061f1f24af480
Headers show
Series ASoC/soundwire: add initial support for SDCA | expand

Commit Message

Bard Liao Oct. 16, 2024, 10:23 a.m. UTC
From: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>

Add new module for SDCA (SoundWire Device Class for Audio) support.
For now just add a parser to identify the SDCA revision and the
function mask.

Note that the SDCA definitions and related MIPI DisCo properties are
defined only for ACPI platforms and extracted with _DSD helpers. There
is currently no support for Device Tree in the specification, the
'depends on ACPI' reflects this design limitation. This might change
in a future revision of the specification but for SDCA 1.0 ACPI is the
only supported type of platform firmware.

The SDCA library is defined with static inline fallbacks, which will
allow for unconditional addition of SDCA support in common parts of
the code.

The design follows a four-step process:

1) Basic information related to Functions is extracted from MIPI DisCo
tables and stored in the 'struct sdw_slave'. Devm_ based memory
allocation is not allowed at this point prior to a driver probe, so we only
store the function node, address and type.

2) When a codec driver probes, it will register subdevices for each
Function identified in phase 1)

3) a driver will probe for each subdevice and addition parsing/memory
allocation takes place at this level. devm_ based allocation is highly
encouraged to make error handling manageable.

4) Before the peripheral device becomes physically attached, register
access is not permitted and the regmaps are cache-only. When
peripheral device is enumerated, the bus level uses the
'update_status' notification; after optional device-level
initialization, the codec driver will notify each of the subdevices so
that they can start interacting with the hardware.

Note that the context extracted in 1) should be arguably be handled
completely in the codec driver probe. That would however make it
difficult to use the ACPI information for machine quirks, and
e.g. select different machine driver and topologies as done for the
RT712_VB handling later in the series. To make the implementation of
quirks simpler, this patchset extracts a minimal amount of context
(interface revision and number/type of Functions) before the codec
driver probe, and stores this context in the scope of the 'struct
sdw_slave'.

The SDCA library can also be used in a vendor-specific driver without
creating subdevices, e.g. to retrieve the 'initialization-table'
values to write platform-specific values as needed.

For more technical details, the SDCA specification is available for
public downloads at https://www.mipi.org/mipi-sdca-v1-0-download

Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Reviewed-by: Péter Ujfalusi <peter.ujfalusi@linux.intel.com>
Signed-off-by: Bard Liao <yung-chuan.liao@linux.intel.com>
---
 include/linux/soundwire/sdw.h   |   3 +
 include/sound/sdca.h            |  54 ++++++++++
 include/sound/sdca_function.h   |  55 ++++++++++
 sound/soc/Kconfig               |   1 +
 sound/soc/Makefile              |   1 +
 sound/soc/sdca/Kconfig          |  11 ++
 sound/soc/sdca/Makefile         |   5 +
 sound/soc/sdca/sdca_device.c    |  24 +++++
 sound/soc/sdca/sdca_functions.c | 173 ++++++++++++++++++++++++++++++++
 9 files changed, 327 insertions(+)
 create mode 100644 include/sound/sdca.h
 create mode 100644 include/sound/sdca_function.h
 create mode 100644 sound/soc/sdca/Kconfig
 create mode 100644 sound/soc/sdca/Makefile
 create mode 100644 sound/soc/sdca/sdca_device.c
 create mode 100644 sound/soc/sdca/sdca_functions.c
diff mbox series

Patch

diff --git a/include/linux/soundwire/sdw.h b/include/linux/soundwire/sdw.h
index 283c8bfdde49..49d690f3d29a 100644
--- a/include/linux/soundwire/sdw.h
+++ b/include/linux/soundwire/sdw.h
@@ -10,6 +10,7 @@ 
 #include <linux/irqdomain.h>
 #include <linux/mod_devicetable.h>
 #include <linux/bitfield.h>
+#include <sound/sdca.h>
 
 struct sdw_bus;
 struct sdw_slave;
@@ -663,6 +664,7 @@  struct sdw_slave_ops {
  * @is_mockup_device: status flag used to squelch errors in the command/control
  * protocol for SoundWire mockup devices
  * @sdw_dev_lock: mutex used to protect callbacks/remove races
+ * @sdca_data: structure containing all device data for SDCA helpers
  */
 struct sdw_slave {
 	struct sdw_slave_id id;
@@ -686,6 +688,7 @@  struct sdw_slave {
 	bool first_interrupt_done;
 	bool is_mockup_device;
 	struct mutex sdw_dev_lock; /* protect callbacks/remove races */
+	struct sdca_device_data sdca_data;
 };
 
 #define dev_to_sdw_dev(_dev) container_of(_dev, struct sdw_slave, dev)
diff --git a/include/sound/sdca.h b/include/sound/sdca.h
new file mode 100644
index 000000000000..34473ca4c789
--- /dev/null
+++ b/include/sound/sdca.h
@@ -0,0 +1,54 @@ 
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * The MIPI SDCA specification is available for public downloads at
+ * https://www.mipi.org/mipi-sdca-v1-0-download
+ *
+ * Copyright(c) 2024 Intel Corporation
+ */
+
+#ifndef __SDCA_H__
+#define __SDCA_H__
+
+struct sdw_slave;
+
+#define SDCA_MAX_FUNCTION_COUNT 8
+
+/**
+ * sdca_device_desc - short descriptor for an SDCA Function
+ * @adr: ACPI address (used for SDCA register access)
+ * @type: Function topology type
+ * @name: human-readable string
+ */
+struct sdca_function_desc {
+	u64 adr;
+	u32 type;
+	const char *name;
+};
+
+/**
+ * sdca_device_data - structure containing all SDCA related information
+ * @sdca_interface_revision: value read from _DSD property, mainly to check
+ * for changes between silicon versions
+ * @num_functions: total number of supported SDCA functions. Invalid/unsupported
+ * functions will be skipped.
+ * @sdca_func: array of function descriptors
+ */
+struct sdca_device_data {
+	u32 interface_revision;
+	int num_functions;
+	struct sdca_function_desc sdca_func[SDCA_MAX_FUNCTION_COUNT];
+};
+
+#if IS_ENABLED(CONFIG_ACPI) && IS_ENABLED(CONFIG_SND_SOC_SDCA)
+
+void sdca_lookup_functions(struct sdw_slave *slave);
+void sdca_lookup_interface_revision(struct sdw_slave *slave);
+
+#else
+
+static inline void sdca_lookup_functions(struct sdw_slave *slave) {}
+static inline void sdca_lookup_interface_revision(struct sdw_slave *slave) {}
+
+#endif
+
+#endif
diff --git a/include/sound/sdca_function.h b/include/sound/sdca_function.h
new file mode 100644
index 000000000000..a01eec86b9a6
--- /dev/null
+++ b/include/sound/sdca_function.h
@@ -0,0 +1,55 @@ 
+/* SPDX-License-Identifier: (GPL-2.0-only OR BSD-3-Clause) */
+/*
+ * The MIPI SDCA specification is available for public downloads at
+ * https://www.mipi.org/mipi-sdca-v1-0-download
+ *
+ * Copyright(c) 2024 Intel Corporation
+ */
+
+#ifndef __SDCA_FUNCTION_H__
+#define __SDCA_FUNCTION_H__
+
+/*
+ * SDCA Function Types from SDCA specification v1.0a Section 5.1.2
+ * all Function types not described are reserved
+ * Note that SIMPLE_AMP, SIMPLE_MIC and SIMPLE_JACK Function Types
+ * are NOT defined in SDCA 1.0a, but they were defined in earlier
+ * drafts and are planned for 1.1.
+ */
+
+enum sdca_function_type {
+	SDCA_FUNCTION_TYPE_SMART_AMP	= 0x01,	/* Amplifier with protection features */
+	SDCA_FUNCTION_TYPE_SIMPLE_AMP	= 0x02,	/* subset of SmartAmp */
+	SDCA_FUNCTION_TYPE_SMART_MIC	= 0x03,	/* Smart microphone with acoustic triggers */
+	SDCA_FUNCTION_TYPE_SIMPLE_MIC	= 0x04,	/* subset of SmartMic */
+	SDCA_FUNCTION_TYPE_SPEAKER_MIC	= 0x05,	/* Combination of SmartMic and SmartAmp */
+	SDCA_FUNCTION_TYPE_UAJ		= 0x06,	/* 3.5mm Universal Audio jack */
+	SDCA_FUNCTION_TYPE_RJ		= 0x07,	/* Retaskable jack */
+	SDCA_FUNCTION_TYPE_SIMPLE_JACK	= 0x08,	/* Subset of UAJ */
+	SDCA_FUNCTION_TYPE_HID		= 0x0A,	/* Human Interface Device, for e.g. buttons */
+	SDCA_FUNCTION_TYPE_IMP_DEF	= 0x1F,	/* Implementation-defined function */
+};
+
+/* Human-readable names used for kernel logs and Function device registration/bind */
+#define	SDCA_FUNCTION_TYPE_SMART_AMP_NAME	"SmartAmp"
+#define	SDCA_FUNCTION_TYPE_SIMPLE_AMP_NAME	"SimpleAmp"
+#define	SDCA_FUNCTION_TYPE_SMART_MIC_NAME	"SmartMic"
+#define	SDCA_FUNCTION_TYPE_SIMPLE_MIC_NAME	"SimpleMic"
+#define	SDCA_FUNCTION_TYPE_SPEAKER_MIC_NAME	"SpeakerMic"
+#define	SDCA_FUNCTION_TYPE_UAJ_NAME		"UAJ"
+#define	SDCA_FUNCTION_TYPE_RJ_NAME		"RJ"
+#define	SDCA_FUNCTION_TYPE_SIMPLE_NAME		"SimpleJack"
+#define	SDCA_FUNCTION_TYPE_HID_NAME		"HID"
+
+enum sdca_entity0_controls {
+	SDCA_CONTROL_ENTITY_0_COMMIT_GROUP_MASK		= 0x01,
+	SDCA_CONTROL_ENTITY_0_INTSTAT_CLEAR		= 0x02,
+	SDCA_CONTROL_ENTITY_0_INT_ENABLE		= 0x03,
+	SDCA_CONTROL_ENTITY_0_FUNCTION_SDCA_VERSION	= 0x04,
+	SDCA_CONTROL_ENTITY_0_FUNCTION_TOPOLOGY		= 0x05,
+	SDCA_CONTROL_ENTITY_0_FUNCTION_MANUFACTURER_ID	= 0x06,
+	SDCA_CONTROL_ENTITY_0_FUNCTION_ID		= 0x07,
+	SDCA_CONTROL_ENTITY_0_FUNCTION_VERSION		= 0x08
+};
+
+#endif
diff --git a/sound/soc/Kconfig b/sound/soc/Kconfig
index e87bd15a8b43..8e01b421fe8d 100644
--- a/sound/soc/Kconfig
+++ b/sound/soc/Kconfig
@@ -108,6 +108,7 @@  source "sound/soc/pxa/Kconfig"
 source "sound/soc/qcom/Kconfig"
 source "sound/soc/rockchip/Kconfig"
 source "sound/soc/samsung/Kconfig"
+source "sound/soc/sdca/Kconfig"
 source "sound/soc/sh/Kconfig"
 source "sound/soc/sof/Kconfig"
 source "sound/soc/spear/Kconfig"
diff --git a/sound/soc/Makefile b/sound/soc/Makefile
index 775bb38c2ed4..5307b0b62a93 100644
--- a/sound/soc/Makefile
+++ b/sound/soc/Makefile
@@ -61,6 +61,7 @@  obj-$(CONFIG_SND_SOC)	+= pxa/
 obj-$(CONFIG_SND_SOC)	+= qcom/
 obj-$(CONFIG_SND_SOC)	+= rockchip/
 obj-$(CONFIG_SND_SOC)	+= samsung/
+obj-$(CONFIG_SND_SOC)	+= sdca/
 obj-$(CONFIG_SND_SOC)	+= sh/
 obj-$(CONFIG_SND_SOC)	+= sof/
 obj-$(CONFIG_SND_SOC)	+= spear/
diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig
new file mode 100644
index 000000000000..07f6822fa614
--- /dev/null
+++ b/sound/soc/sdca/Kconfig
@@ -0,0 +1,11 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+
+config SND_SOC_SDCA
+	tristate "ASoC SDCA library"
+	depends on ACPI
+	help
+	  This option enables support for the MIPI SoundWire Device
+	  Class for Audio (SDCA).
+
+config SND_SOC_SDCA_OPTIONAL
+	def_tristate SND_SOC_SDCA || !SND_SOC_SDCA
diff --git a/sound/soc/sdca/Makefile b/sound/soc/sdca/Makefile
new file mode 100644
index 000000000000..c296bd5a0a7c
--- /dev/null
+++ b/sound/soc/sdca/Makefile
@@ -0,0 +1,5 @@ 
+# SPDX-License-Identifier: GPL-2.0-only
+
+snd-soc-sdca-objs	:= sdca_functions.o sdca_device.o
+
+obj-$(CONFIG_SND_SOC_SDCA)	+= snd-soc-sdca.o
diff --git a/sound/soc/sdca/sdca_device.c b/sound/soc/sdca/sdca_device.c
new file mode 100644
index 000000000000..58f5f6f0f723
--- /dev/null
+++ b/sound/soc/sdca/sdca_device.c
@@ -0,0 +1,24 @@ 
+// 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
+ */
+
+#include <linux/acpi.h>
+#include <linux/soundwire/sdw.h>
+#include <sound/sdca.h>
+
+void sdca_lookup_interface_revision(struct sdw_slave *slave)
+{
+	struct fwnode_handle *fwnode = slave->dev.fwnode;
+
+	/*
+	 * if this property is not present, then the sdca_interface_revision will
+	 * remain zero, which will be considered as 'not defined' or 'invalid'.
+	 */
+	fwnode_property_read_u32(fwnode, "mipi-sdw-sdca-interface-revision",
+				 &slave->sdca_data.interface_revision);
+}
+EXPORT_SYMBOL_NS(sdca_lookup_interface_revision, SND_SOC_SDCA);
diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c
new file mode 100644
index 000000000000..a6ad57430dd4
--- /dev/null
+++ b/sound/soc/sdca/sdca_functions.c
@@ -0,0 +1,173 @@ 
+// 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
+ */
+
+#include <linux/acpi.h>
+#include <linux/soundwire/sdw.h>
+#include <sound/sdca.h>
+#include <sound/sdca_function.h>
+
+static int patch_sdca_function_type(struct device *dev,
+				    u32 interface_revision,
+				    u32 *function_type,
+				    const char **function_name)
+{
+	unsigned long function_type_patch = 0;
+
+	/*
+	 * Unfortunately early SDCA specifications used different indices for Functions,
+	 * for backwards compatibility we have to reorder the values found
+	 */
+	if (interface_revision >= 0x0801)
+		goto skip_early_draft_order;
+
+	switch (*function_type) {
+	case 1:
+		function_type_patch = SDCA_FUNCTION_TYPE_SMART_AMP;
+		break;
+	case 2:
+		function_type_patch = SDCA_FUNCTION_TYPE_SMART_MIC;
+		break;
+	case 3:
+		function_type_patch = SDCA_FUNCTION_TYPE_SPEAKER_MIC;
+		break;
+	case 4:
+		function_type_patch = SDCA_FUNCTION_TYPE_UAJ;
+		break;
+	case 5:
+		function_type_patch = SDCA_FUNCTION_TYPE_RJ;
+		break;
+	case 6:
+		function_type_patch = SDCA_FUNCTION_TYPE_HID;
+		break;
+	default:
+		dev_warn(dev, "%s: SDCA version %#x unsupported function type %d, skipped\n",
+			 __func__, interface_revision, *function_type);
+		return -EINVAL;
+	}
+
+skip_early_draft_order:
+	if (function_type_patch)
+		*function_type = function_type_patch;
+
+	/* now double-check the values */
+	switch (*function_type) {
+	case SDCA_FUNCTION_TYPE_SMART_AMP:
+		*function_name = SDCA_FUNCTION_TYPE_SMART_AMP_NAME;
+		break;
+	case SDCA_FUNCTION_TYPE_SMART_MIC:
+		*function_name = SDCA_FUNCTION_TYPE_SMART_MIC_NAME;
+		break;
+	case SDCA_FUNCTION_TYPE_UAJ:
+		*function_name = SDCA_FUNCTION_TYPE_UAJ_NAME;
+		break;
+	case SDCA_FUNCTION_TYPE_HID:
+		*function_name = SDCA_FUNCTION_TYPE_HID_NAME;
+		break;
+	case SDCA_FUNCTION_TYPE_SIMPLE_AMP:
+	case SDCA_FUNCTION_TYPE_SIMPLE_MIC:
+	case SDCA_FUNCTION_TYPE_SPEAKER_MIC:
+	case SDCA_FUNCTION_TYPE_RJ:
+	case SDCA_FUNCTION_TYPE_IMP_DEF:
+		dev_warn(dev, "%s: found unsupported SDCA function type %d, skipped\n",
+			 __func__, *function_type);
+		return -EINVAL;
+	default:
+		dev_err(dev, "%s: found invalid SDCA function type %d, skipped\n",
+			__func__, *function_type);
+		return -EINVAL;
+	}
+
+	dev_info(dev, "%s: found SDCA function %s (type %d)\n",
+		 __func__, *function_name, *function_type);
+
+	return 0;
+}
+
+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, "%s: maximum number of functions exceeded\n", __func__);
+		return -EINVAL;
+	}
+
+	/*
+	 * The number of functions cannot exceed 8, we could use
+	 * acpi_get_local_address() but the value is stored as u64 so
+	 * we might as well avoid casts and intermediate levels
+	 */
+	ret = acpi_get_local_u64_address(adev->handle, &addr);
+	if (ret < 0)
+		return ret;
+
+	if (!addr) {
+		dev_err(dev, "%s: no addr\n", __func__);
+		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, "%s: the function type can only be determined from ACPI information\n",
+			__func__);
+		return ret;
+	}
+
+	ret = patch_sdca_function_type(dev, sdca_data->interface_revision,
+				       &function_type, &function_name);
+	if (ret < 0)
+		return ret;
+
+	/* 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);
+
+	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");