Linux Sound subsystem development
 help / color / mirror / Atom feed
* [PATCH v2 00/13] Add SDCA class driver
@ 2025-11-03 15:07 Charles Keepax
  2025-11-03 15:07 ` [PATCH v2 01/13] ASoC: SDCA: Remove duplicated module macros Charles Keepax
                   ` (12 more replies)
  0 siblings, 13 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:07 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

This series adds an initial SDCA class driver, this consists of a
primary driver attached to the SoundWire device, and auxiliary drivers
representing each of the functions of the SDCA device. These drivers all
use the APIs added over the past series's to provide the class
functionality, as such these final drivers themselves are quite thin.

Also a few fix ups at the start of the series that have gathered up
whilst the last SDCA series was in review.

Thanks,
Charles

Changes since v2:
 - Add a bunch of bug fix patches
 - Change some prints to dbgs
 - Add some additional comments
 - Flip order on regcache operations in resume

Charles Keepax (12):
  ASoC: SDCA: Remove duplicated module macros
  ASoC: SDCA: Fix missing dash in HIDE DisCo property
  ASoC: SDCA: Add missing forward declaration in header
  ASoC: SDCA: Correct FDL locking with scoped_guard
  ASoC: SDCA: Add comment for function reset polling
  ASoC: SDCA: Move most of the messages from info to debug
  ASoC: SDCA: Use helper macros for control identification
  ASoC: SDCA: Factor out helper to process Control defaults
  ASoC: SDCA: Populate regmap cache for readable Controls
  ASoC: SDCA: Add helper to write initialization writes
  ASoC: SDCA: Add basic SDCA class driver
  ASoC: SDCA: Add basic SDCA function driver

Pierre-Louis Bossart (1):
  ASoC: SDCA: add function devices

 include/linux/soundwire/sdw_registers.h |   2 +
 include/sound/sdca.h                    |  14 +
 include/sound/sdca_regmap.h             |   2 +
 sound/soc/sdca/Kconfig                  |  18 +
 sound/soc/sdca/Makefile                 |  10 +-
 sound/soc/sdca/sdca_class.c             | 304 ++++++++++++++++
 sound/soc/sdca/sdca_class.h             |  37 ++
 sound/soc/sdca/sdca_class_function.c    | 460 ++++++++++++++++++++++++
 sound/soc/sdca/sdca_fdl.c               |  94 ++---
 sound/soc/sdca/sdca_function_device.c   | 117 ++++++
 sound/soc/sdca/sdca_function_device.h   |  15 +
 sound/soc/sdca/sdca_functions.c         |  52 ++-
 sound/soc/sdca/sdca_hid.c               |   3 -
 sound/soc/sdca/sdca_interrupts.c        |  39 +-
 sound/soc/sdca/sdca_regmap.c            |  89 +++--
 15 files changed, 1133 insertions(+), 123 deletions(-)
 create mode 100644 sound/soc/sdca/sdca_class.c
 create mode 100644 sound/soc/sdca/sdca_class.h
 create mode 100644 sound/soc/sdca/sdca_class_function.c
 create mode 100644 sound/soc/sdca/sdca_function_device.c
 create mode 100644 sound/soc/sdca/sdca_function_device.h

-- 
2.47.3


^ permalink raw reply	[flat|nested] 15+ messages in thread

* [PATCH v2 01/13] ASoC: SDCA: Remove duplicated module macros
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
@ 2025-11-03 15:07 ` Charles Keepax
  2025-11-03 15:07 ` [PATCH v2 02/13] ASoC: SDCA: Fix missing dash in HIDE DisCo property Charles Keepax
                   ` (11 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:07 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

Both HID and the IRQ are now build into the wider SDCA kernel module, so
their module macros are redundant, remove them.

Fixes: 5030abcb0aa3 ("ASoC: SDCA: Pull HID and IRQ into the primary SDCA module")
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

New since v1.

 sound/soc/sdca/sdca_hid.c        | 3 ---
 sound/soc/sdca/sdca_interrupts.c | 3 ---
 2 files changed, 6 deletions(-)

diff --git a/sound/soc/sdca/sdca_hid.c b/sound/soc/sdca/sdca_hid.c
index ad53207b0d62b..abbd56a3d2971 100644
--- a/sound/soc/sdca/sdca_hid.c
+++ b/sound/soc/sdca/sdca_hid.c
@@ -166,6 +166,3 @@ int sdca_hid_process_report(struct sdca_interrupt *interrupt)
 	return 0;
 }
 EXPORT_SYMBOL_NS(sdca_hid_process_report, "SND_SOC_SDCA");
-
-MODULE_LICENSE("Dual BSD/GPL");
-MODULE_DESCRIPTION("SDCA HID library");
diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c
index 5176460416bbe..a18ec9dd3398b 100644
--- a/sound/soc/sdca/sdca_interrupts.c
+++ b/sound/soc/sdca/sdca_interrupts.c
@@ -618,6 +618,3 @@ struct sdca_interrupt_info *sdca_irq_allocate(struct device *sdev,
 	return info;
 }
 EXPORT_SYMBOL_NS_GPL(sdca_irq_allocate, "SND_SOC_SDCA");
-
-MODULE_LICENSE("GPL");
-MODULE_DESCRIPTION("SDCA IRQ library");
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 02/13] ASoC: SDCA: Fix missing dash in HIDE DisCo property
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
  2025-11-03 15:07 ` [PATCH v2 01/13] ASoC: SDCA: Remove duplicated module macros Charles Keepax
@ 2025-11-03 15:07 ` Charles Keepax
  2025-11-03 15:07 ` [PATCH v2 03/13] ASoC: SDCA: Add missing forward declaration in header Charles Keepax
                   ` (10 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:07 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

The property name is "mipi-sdca-RxUMP-ownership-transition-max-delay",
with a dash between max and delay. Add the missing dash.

Fixes: 13ef21dffe76 ("ASoC: SDCA: add support for HIDE entity properties and HID descriptor/report")
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

New since v1.

 sound/soc/sdca/sdca_functions.c | 2 +-
 1 file changed, 1 insertion(+), 1 deletion(-)

diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c
index b2e3fab9bd959..8ebc5161c9f85 100644
--- a/sound/soc/sdca/sdca_functions.c
+++ b/sound/soc/sdca/sdca_functions.c
@@ -1321,7 +1321,7 @@ find_sdca_entity_hide(struct device *dev, struct sdw_slave *sdw,
 	unsigned char *report_desc = NULL;
 
 	ret = fwnode_property_read_u32(entity_node,
-				       "mipi-sdca-RxUMP-ownership-transition-maxdelay", &delay);
+				       "mipi-sdca-RxUMP-ownership-transition-max-delay", &delay);
 	if (!ret)
 		hide->max_delay = delay;
 
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 03/13] ASoC: SDCA: Add missing forward declaration in header
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
  2025-11-03 15:07 ` [PATCH v2 01/13] ASoC: SDCA: Remove duplicated module macros Charles Keepax
  2025-11-03 15:07 ` [PATCH v2 02/13] ASoC: SDCA: Fix missing dash in HIDE DisCo property Charles Keepax
@ 2025-11-03 15:07 ` Charles Keepax
  2025-11-03 15:07 ` [PATCH v2 04/13] ASoC: SDCA: Correct FDL locking with scoped_guard Charles Keepax
                   ` (9 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:07 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

The structure sdca_function_desc contains a fwnode_handle which is
undefined if the user doesn't pull in an appropriate header. Add a
forward declaration to avoid this.

Fixes: 996bf834d0b6 ("ASoC: SDCA: Add code to parse Function information")
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

New since v1.

 include/sound/sdca.h | 1 +
 1 file changed, 1 insertion(+)

diff --git a/include/sound/sdca.h b/include/sound/sdca.h
index d38cdbfeb35f5..d58d602212771 100644
--- a/include/sound/sdca.h
+++ b/include/sound/sdca.h
@@ -13,6 +13,7 @@
 #include <linux/kconfig.h>
 
 struct acpi_table_swft;
+struct fwnode_handle;
 struct sdw_slave;
 
 #define SDCA_MAX_FUNCTION_COUNT 8
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 04/13] ASoC: SDCA: Correct FDL locking with scoped_guard
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
                   ` (2 preceding siblings ...)
  2025-11-03 15:07 ` [PATCH v2 03/13] ASoC: SDCA: Add missing forward declaration in header Charles Keepax
@ 2025-11-03 15:07 ` Charles Keepax
  2025-11-05 15:07   ` Charles Keepax
  2025-11-03 15:08 ` [PATCH v2 05/13] ASoC: SDCA: Add comment for function reset polling Charles Keepax
                   ` (8 subsequent siblings)
  12 siblings, 1 reply; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:07 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

The current locking in sdca_fdl_process() locks over
sdca_ump_cancel_timeout() and the timeout work function takes the same
lock, this can lead to a deadlock if the work runs as part of the
cancel. To fix this use scoped_guard and move the cancel timeout to be
outside the lock.

Fixes: e92e25f77748 ("ASoC: SDCA: Add UMP timeout handling for FDL")
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

New since v1.

 sound/soc/sdca/sdca_fdl.c | 90 ++++++++++++++++++++-------------------
 1 file changed, 46 insertions(+), 44 deletions(-)

diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c
index cb79dc3131b82..fe0ef2df4d9eb 100644
--- a/sound/soc/sdca/sdca_fdl.c
+++ b/sound/soc/sdca/sdca_fdl.c
@@ -402,8 +402,6 @@ int sdca_fdl_process(struct sdca_interrupt *interrupt)
 	unsigned int reg, status;
 	int response, ret;
 
-	guard(mutex)(&fdl_state->lock);
-
 	ret = sdca_ump_get_owner_host(dev, interrupt->function_regmap,
 				      interrupt->function, interrupt->entity,
 				      interrupt->control);
@@ -412,56 +410,60 @@ int sdca_fdl_process(struct sdca_interrupt *interrupt)
 
 	sdca_ump_cancel_timeout(&fdl_state->timeout);
 
-	reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id,
-			   SDCA_CTL_XU_FDL_STATUS, 0);
-	ret = regmap_read(interrupt->function_regmap, reg, &status);
-	if (ret < 0) {
-		dev_err(dev, "failed to read FDL status: %d\n", ret);
-		return ret;
-	}
+	scoped_guard(mutex, &fdl_state->lock) {
+		reg = SDW_SDCA_CTL(interrupt->function->desc->adr,
+				   interrupt->entity->id, SDCA_CTL_XU_FDL_STATUS, 0);
+		ret = regmap_read(interrupt->function_regmap, reg, &status);
+		if (ret < 0) {
+			dev_err(dev, "failed to read FDL status: %d\n", ret);
+			return ret;
+		}
 
-	dev_dbg(dev, "FDL status: %#x\n", status);
+		dev_dbg(dev, "FDL status: %#x\n", status);
 
-	ret = fdl_status_process(interrupt, status);
-	if (ret < 0)
-		goto reset_function;
-
-	response = ret;
+		ret = fdl_status_process(interrupt, status);
+		if (ret < 0)
+			goto reset_function;
 
-	dev_dbg(dev, "FDL response: %#x\n", response);
+		response = ret;
 
-	ret = regmap_write(interrupt->function_regmap, reg,
-			   response | (status & ~SDCA_CTL_XU_FDLH_MASK));
-	if (ret < 0) {
-		dev_err(dev, "failed to set FDL status signal: %d\n", ret);
-		return ret;
-	}
+		dev_dbg(dev, "FDL response: %#x\n", response);
 
-	ret = sdca_ump_set_owner_device(dev, interrupt->function_regmap,
-					interrupt->function, interrupt->entity,
-					interrupt->control);
-	if (ret)
-		return ret;
-
-	switch (response) {
-	case SDCA_CTL_XU_FDLH_RESET_ACK:
-		dev_dbg(dev, "FDL request reset\n");
+		ret = regmap_write(interrupt->function_regmap, reg,
+				   response | (status & ~SDCA_CTL_XU_FDLH_MASK));
+		if (ret < 0) {
+			dev_err(dev, "failed to set FDL status signal: %d\n", ret);
+			return ret;
+		}
 
-		switch (xu->reset_mechanism) {
-		default:
-			dev_warn(dev, "Requested reset mechanism not implemented\n");
+		ret = sdca_ump_set_owner_device(dev, interrupt->function_regmap,
+						interrupt->function,
+						interrupt->entity,
+						interrupt->control);
+		if (ret)
+			return ret;
+
+		switch (response) {
+		case SDCA_CTL_XU_FDLH_RESET_ACK:
+			dev_dbg(dev, "FDL request reset\n");
+
+			switch (xu->reset_mechanism) {
+			default:
+				dev_warn(dev, "Requested reset mechanism not implemented\n");
+				fallthrough;
+			case SDCA_XU_RESET_FUNCTION:
+				goto reset_function;
+			}
+		case SDCA_CTL_XU_FDLH_COMPLETE:
+			if (status & SDCA_CTL_XU_FDLD_REQ_ABORT ||
+			    status == SDCA_CTL_XU_FDLD_COMPLETE)
+				return 0;
 			fallthrough;
-		case SDCA_XU_RESET_FUNCTION:
-			goto reset_function;
-		}
-	case SDCA_CTL_XU_FDLH_COMPLETE:
-		if (status & SDCA_CTL_XU_FDLD_REQ_ABORT ||
-		    status == SDCA_CTL_XU_FDLD_COMPLETE)
+		default:
+			sdca_ump_schedule_timeout(&fdl_state->timeout,
+						  xu->max_delay);
 			return 0;
-		fallthrough;
-	default:
-		sdca_ump_schedule_timeout(&fdl_state->timeout, xu->max_delay);
-		return 0;
+		}
 	}
 
 reset_function:
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 05/13] ASoC: SDCA: Add comment for function reset polling
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
                   ` (3 preceding siblings ...)
  2025-11-03 15:07 ` [PATCH v2 04/13] ASoC: SDCA: Correct FDL locking with scoped_guard Charles Keepax
@ 2025-11-03 15:08 ` Charles Keepax
  2025-11-03 15:08 ` [PATCH v2 06/13] ASoC: SDCA: Move most of the messages from info to debug Charles Keepax
                   ` (7 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:08 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

Add a comment to better explain the function reset polling rate.

Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

New since v1.

 sound/soc/sdca/sdca_fdl.c | 4 ++++
 1 file changed, 4 insertions(+)

diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c
index fe0ef2df4d9eb..83386b7b9b429 100644
--- a/sound/soc/sdca/sdca_fdl.c
+++ b/sound/soc/sdca/sdca_fdl.c
@@ -51,6 +51,10 @@ int sdca_reset_function(struct device *dev, struct sdca_function_data *function,
 		return -EINVAL;
 	}
 
+	/*
+	 * Poll up to 16 times but no more than once per mS, these are just
+	 * arbitrarily selected values, so maybe fine tuned in future.
+	 */
 	poll_us = umin(function->reset_max_delay >> 4, 1000);
 
 	ret = regmap_read_poll_timeout(regmap, reg, val, !val, poll_us,
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 06/13] ASoC: SDCA: Move most of the messages from info to debug
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
                   ` (4 preceding siblings ...)
  2025-11-03 15:08 ` [PATCH v2 05/13] ASoC: SDCA: Add comment for function reset polling Charles Keepax
@ 2025-11-03 15:08 ` Charles Keepax
  2025-11-03 15:08 ` [PATCH v2 07/13] ASoC: SDCA: Use helper macros for control identification Charles Keepax
                   ` (6 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:08 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

The SDCA code is very spammy on boot as it prints a lot of parsing
details using info prints. Now primary development is complete move
these to debug prints to reduce the spam.

Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

New since v1.

 sound/soc/sdca/sdca_functions.c | 50 ++++++++++++++++-----------------
 1 file changed, 24 insertions(+), 26 deletions(-)

diff --git a/sound/soc/sdca/sdca_functions.c b/sound/soc/sdca/sdca_functions.c
index 8ebc5161c9f85..417194909abd5 100644
--- a/sound/soc/sdca/sdca_functions.c
+++ b/sound/soc/sdca/sdca_functions.c
@@ -6,8 +6,6 @@
  * https://www.mipi.org/mipi-sdca-v1-0-download
  */
 
-#define dev_fmt(fmt) "%s: " fmt, __func__
-
 #include <linux/acpi.h>
 #include <linux/byteorder/generic.h>
 #include <linux/cleanup.h>
@@ -1010,10 +1008,10 @@ static int find_sdca_entity_control(struct device *dev, struct sdca_entity *enti
 	control->type = find_sdca_control_datatype(entity, control);
 	control->nbits = find_sdca_control_bits(entity, control);
 
-	dev_info(dev, "%s: %s: control %#x mode %#x layers %#x cn %#llx int %d %s\n",
-		 entity->label, control->label, control->sel,
-		 control->mode, control->layers, control->cn_list,
-		 control->interrupt_position, control->deferrable ? "deferrable" : "");
+	dev_dbg(dev, "%s: %s: control %#x mode %#x layers %#x cn %#llx int %d %s\n",
+		entity->label, control->label, control->sel,
+		control->mode, control->layers, control->cn_list,
+		control->interrupt_position, control->deferrable ? "deferrable" : "");
 
 	return 0;
 }
@@ -1134,9 +1132,9 @@ static int find_sdca_entity_iot(struct device *dev,
 	if (!ret)
 		terminal->num_transducer = tmp;
 
-	dev_info(dev, "%s: terminal type %#x ref %#x conn %#x count %d\n",
-		 entity->label, terminal->type, terminal->reference,
-		 terminal->connector, terminal->num_transducer);
+	dev_dbg(dev, "%s: terminal type %#x ref %#x conn %#x count %d\n",
+		entity->label, terminal->type, terminal->reference,
+		terminal->connector, terminal->num_transducer);
 
 	return 0;
 }
@@ -1162,8 +1160,8 @@ static int find_sdca_entity_cs(struct device *dev,
 	if (!ret)
 		clock->max_delay = tmp;
 
-	dev_info(dev, "%s: clock type %#x delay %d\n", entity->label,
-		 clock->type, clock->max_delay);
+	dev_dbg(dev, "%s: clock type %#x delay %d\n", entity->label,
+		clock->type, clock->max_delay);
 
 	return 0;
 }
@@ -1214,8 +1212,8 @@ static int find_sdca_entity_pde(struct device *dev,
 		delays[i].to_ps = delay_list[j++];
 		delays[i].us = delay_list[j++];
 
-		dev_info(dev, "%s: from %#x to %#x delay %dus\n", entity->label,
-			 delays[i].from_ps, delays[i].to_ps, delays[i].us);
+		dev_dbg(dev, "%s: from %#x to %#x delay %dus\n", entity->label,
+			delays[i].from_ps, delays[i].to_ps, delays[i].us);
 	}
 
 	power->num_max_delay = num_delays;
@@ -1444,8 +1442,8 @@ static int find_sdca_entity(struct device *dev, struct sdw_slave *sdw,
 
 	entity->type = tmp;
 
-	dev_info(dev, "%s: entity %#x type %#x\n",
-		 entity->label, entity->id, entity->type);
+	dev_dbg(dev, "%s: entity %#x type %#x\n",
+		entity->label, entity->id, entity->type);
 
 	switch (entity->type) {
 	case SDCA_ENTITY_TYPE_IT:
@@ -1620,7 +1618,7 @@ static int find_sdca_entity_connection_iot(struct device *dev,
 
 	terminal->clock = clock_entity;
 
-	dev_info(dev, "%s -> %s\n", clock_entity->label, entity->label);
+	dev_dbg(dev, "%s -> %s\n", clock_entity->label, entity->label);
 
 	fwnode_handle_put(clock_node);
 	return 0;
@@ -1670,7 +1668,7 @@ static int find_sdca_entity_connection_pde(struct device *dev,
 			return -EINVAL;
 		}
 
-		dev_info(dev, "%s -> %s\n", managed[i]->label, entity->label);
+		dev_dbg(dev, "%s -> %s\n", managed[i]->label, entity->label);
 	}
 
 	power->num_managed = num_managed;
@@ -1805,7 +1803,7 @@ static int find_sdca_entity_connection(struct device *dev,
 
 		pins[i] = connected_entity;
 
-		dev_info(dev, "%s -> %s\n", connected_entity->label, entity->label);
+		dev_dbg(dev, "%s -> %s\n", connected_entity->label, entity->label);
 
 		i++;
 		fwnode_handle_put(connected_node);
@@ -1890,8 +1888,8 @@ static int find_sdca_cluster_channel(struct device *dev,
 
 	channel->relationship = tmp;
 
-	dev_info(dev, "cluster %#x: channel id %#x purpose %#x relationship %#x\n",
-		 cluster->id, channel->id, channel->purpose, channel->relationship);
+	dev_dbg(dev, "cluster %#x: channel id %#x purpose %#x relationship %#x\n",
+		cluster->id, channel->id, channel->purpose, channel->relationship);
 
 	return 0;
 }
@@ -2062,7 +2060,7 @@ static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw,
 			return -EINVAL;
 		}
 
-		dev_info(dev, "fileset: %#x\n", filesets_list[i]);
+		dev_dbg(dev, "fileset: %#x\n", filesets_list[i]);
 
 		files = devm_kcalloc(dev, num_entries / mult_fileset,
 				     sizeof(struct sdca_fdl_file), GFP_KERNEL);
@@ -2083,8 +2081,8 @@ static int find_sdca_filesets(struct device *dev, struct sdw_slave *sdw,
 			file->file_id = fileset_entries[j++];
 			file->fdl_offset = fileset_entries[j++];
 
-			dev_info(dev, "file: %#x, vendor: %#x, offset: %#x\n",
-				 file->file_id, file->vendor_id, file->fdl_offset);
+			dev_dbg(dev, "file: %#x, vendor: %#x, offset: %#x\n",
+				file->file_id, file->vendor_id, file->fdl_offset);
 		}
 
 		set->id = filesets_list[i];
@@ -2127,9 +2125,9 @@ int sdca_parse_function(struct device *dev, struct sdw_slave *sdw,
 	if (!ret)
 		function->reset_max_delay = tmp;
 
-	dev_info(dev, "%pfwP: name %s busy delay %dus reset delay %dus\n",
-		 function->desc->node, function->desc->name,
-		 function->busy_max_delay, function->reset_max_delay);
+	dev_dbg(dev, "%pfwP: name %s busy delay %dus reset delay %dus\n",
+		function->desc->node, function->desc->name,
+		function->busy_max_delay, function->reset_max_delay);
 
 	ret = find_sdca_init_table(dev, function_desc->node, function);
 	if (ret)
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 07/13] ASoC: SDCA: Use helper macros for control identification
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
                   ` (5 preceding siblings ...)
  2025-11-03 15:08 ` [PATCH v2 06/13] ASoC: SDCA: Move most of the messages from info to debug Charles Keepax
@ 2025-11-03 15:08 ` Charles Keepax
  2025-11-03 15:08 ` [PATCH v2 08/13] ASoC: SDCA: Factor out helper to process Control defaults Charles Keepax
                   ` (5 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:08 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

We have the SDCA_CTL_TYPE helper macros, we should use them when
identifying specific controls to simplify the code a little.

Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

New since v1.

 sound/soc/sdca/sdca_interrupts.c | 36 +++++++++++++-------------------
 1 file changed, 14 insertions(+), 22 deletions(-)

diff --git a/sound/soc/sdca/sdca_interrupts.c b/sound/soc/sdca/sdca_interrupts.c
index a18ec9dd3398b..8f6a2adfb6fbe 100644
--- a/sound/soc/sdca/sdca_interrupts.c
+++ b/sound/soc/sdca/sdca_interrupts.c
@@ -456,11 +456,8 @@ int sdca_irq_populate_early(struct device *dev, struct regmap *regmap,
 			else if (!interrupt)
 				continue;
 
-			switch (entity->type) {
-			case SDCA_ENTITY_TYPE_XU:
-				if (control->sel != SDCA_CTL_XU_FDL_CURRENTOWNER)
-					break;
-
+			switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
+			case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER):
 				ret = sdca_irq_data_populate(dev, regmap, NULL,
 							     function, entity,
 							     control, interrupt);
@@ -534,27 +531,22 @@ int sdca_irq_populate(struct sdca_function_data *function,
 
 			handler = base_handler;
 
-			switch (entity->type) {
-			case SDCA_ENTITY_TYPE_ENTITY_0:
-				if (control->sel == SDCA_CTL_ENTITY_0_FUNCTION_STATUS)
-					handler = function_status_handler;
+			switch (SDCA_CTL_TYPE(entity->type, control->sel)) {
+			case SDCA_CTL_TYPE_S(ENTITY_0, FUNCTION_STATUS):
+				handler = function_status_handler;
 				break;
-			case SDCA_ENTITY_TYPE_GE:
-				if (control->sel == SDCA_CTL_GE_DETECTED_MODE)
-					handler = detected_mode_handler;
+			case SDCA_CTL_TYPE_S(GE, DETECTED_MODE):
+				handler = detected_mode_handler;
 				break;
-			case SDCA_ENTITY_TYPE_XU:
-				if (control->sel == SDCA_CTL_XU_FDL_CURRENTOWNER) {
-					ret = sdca_fdl_alloc_state(interrupt);
-					if (ret)
-						return ret;
+			case SDCA_CTL_TYPE_S(XU, FDL_CURRENTOWNER):
+				ret = sdca_fdl_alloc_state(interrupt);
+				if (ret)
+					return ret;
 
-					handler = fdl_owner_handler;
-				}
+				handler = fdl_owner_handler;
 				break;
-			case SDCA_ENTITY_TYPE_HIDE:
-				if (control->sel == SDCA_CTL_HIDE_HIDTX_CURRENTOWNER)
-					handler = hid_handler;
+			case SDCA_CTL_TYPE_S(HIDE, HIDTX_CURRENTOWNER):
+				handler = hid_handler;
 				break;
 			default:
 				break;
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 08/13] ASoC: SDCA: Factor out helper to process Control defaults
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
                   ` (6 preceding siblings ...)
  2025-11-03 15:08 ` [PATCH v2 07/13] ASoC: SDCA: Use helper macros for control identification Charles Keepax
@ 2025-11-03 15:08 ` Charles Keepax
  2025-11-03 15:08 ` [PATCH v2 09/13] ASoC: SDCA: Populate regmap cache for readable Controls Charles Keepax
                   ` (4 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:08 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

The indentation of the loop processing writing out SDCA Control default
values is getting a bit large. Reduce indentation and make adding more
functionality easier by factoring out the Control handling into a helper
function.

Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

New since v1.

 sound/soc/sdca/sdca_regmap.c | 61 +++++++++++++++++++++++-------------
 1 file changed, 39 insertions(+), 22 deletions(-)

diff --git a/sound/soc/sdca/sdca_regmap.c b/sound/soc/sdca/sdca_regmap.c
index 8fa138fca00ff..5104ae99df33a 100644
--- a/sound/soc/sdca/sdca_regmap.c
+++ b/sound/soc/sdca/sdca_regmap.c
@@ -275,6 +275,40 @@ int sdca_regmap_populate_constants(struct device *dev,
 }
 EXPORT_SYMBOL_NS(sdca_regmap_populate_constants, "SND_SOC_SDCA");
 
+static int populate_control_defaults(struct device *dev, struct regmap *regmap,
+				     struct sdca_function_data *function,
+				     struct sdca_entity *entity,
+				     struct sdca_control *control)
+{
+	int i, ret;
+	int cn;
+
+	if (control->mode == SDCA_ACCESS_MODE_DC)
+		return 0;
+
+	if (!control->has_default && !control->has_fixed)
+		return 0;
+
+	i = 0;
+	for_each_set_bit(cn, (unsigned long *)&control->cn_list,
+			 BITS_PER_TYPE(control->cn_list)) {
+		unsigned int reg;
+
+		reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, cn);
+
+		ret = regmap_write(regmap, reg, control->values[i]);
+		if (ret) {
+			dev_err(dev, "Failed to write default %#x: %d\n",
+				reg, ret);
+			return ret;
+		}
+
+		i++;
+	}
+
+	return 0;
+}
+
 /**
  * sdca_regmap_write_defaults - write out DisCo defaults to device
  * @dev: Pointer to the device.
@@ -290,7 +324,7 @@ EXPORT_SYMBOL_NS(sdca_regmap_populate_constants, "SND_SOC_SDCA");
 int sdca_regmap_write_defaults(struct device *dev, struct regmap *regmap,
 			       struct sdca_function_data *function)
 {
-	int i, j, k;
+	int i, j;
 	int ret;
 
 	for (i = 0; i < function->num_entities; i++) {
@@ -298,28 +332,11 @@ int sdca_regmap_write_defaults(struct device *dev, struct regmap *regmap,
 
 		for (j = 0; j < entity->num_controls; j++) {
 			struct sdca_control *control = &entity->controls[j];
-			int cn;
-
-			if (control->mode == SDCA_ACCESS_MODE_DC)
-				continue;
 
-			if (!control->has_default && !control->has_fixed)
-				continue;
-
-			k = 0;
-			for_each_set_bit(cn, (unsigned long *)&control->cn_list,
-					 BITS_PER_TYPE(control->cn_list)) {
-				unsigned int reg;
-
-				reg = SDW_SDCA_CTL(function->desc->adr, entity->id,
-						   control->sel, cn);
-
-				ret = regmap_write(regmap, reg, control->values[k]);
-				if (ret)
-					return ret;
-
-				k++;
-			}
+			ret = populate_control_defaults(dev, regmap, function,
+							entity, control);
+			if (ret)
+				return ret;
 		}
 	}
 
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 09/13] ASoC: SDCA: Populate regmap cache for readable Controls
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
                   ` (7 preceding siblings ...)
  2025-11-03 15:08 ` [PATCH v2 08/13] ASoC: SDCA: Factor out helper to process Control defaults Charles Keepax
@ 2025-11-03 15:08 ` Charles Keepax
  2025-11-03 15:08 ` [PATCH v2 10/13] ASoC: SDCA: Add helper to write initialization writes Charles Keepax
                   ` (3 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:08 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

It is not uncommon for an SDCA Control to have no specified default
value in the DisCo. Non-volatile registers with no defaults will not be
present in the cache until they are accessed. However, if the first
operation user-space performs is a read whilst the device is runtime
suspended this read will fail.

To avoid such problems we should populate values from the hardware into
the cache for all non-volatile readable registers with no defaults.
Update the defaults handling to do this cache population since it is
iterating over the Controls and happens at a time the hardware is
always powered up.

Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

New since v1.

 sound/soc/sdca/sdca_regmap.c | 32 ++++++++++++++++++++++----------
 1 file changed, 22 insertions(+), 10 deletions(-)

diff --git a/sound/soc/sdca/sdca_regmap.c b/sound/soc/sdca/sdca_regmap.c
index 5104ae99df33a..6fbb241d9d357 100644
--- a/sound/soc/sdca/sdca_regmap.c
+++ b/sound/soc/sdca/sdca_regmap.c
@@ -286,24 +286,33 @@ static int populate_control_defaults(struct device *dev, struct regmap *regmap,
 	if (control->mode == SDCA_ACCESS_MODE_DC)
 		return 0;
 
-	if (!control->has_default && !control->has_fixed)
+	if (control->layers & SDCA_ACCESS_LAYER_DEVICE)
 		return 0;
 
 	i = 0;
 	for_each_set_bit(cn, (unsigned long *)&control->cn_list,
 			 BITS_PER_TYPE(control->cn_list)) {
-		unsigned int reg;
+		unsigned int reg, val;
 
 		reg = SDW_SDCA_CTL(function->desc->adr, entity->id, control->sel, cn);
 
-		ret = regmap_write(regmap, reg, control->values[i]);
-		if (ret) {
-			dev_err(dev, "Failed to write default %#x: %d\n",
-				reg, ret);
-			return ret;
-		}
+		if (control->has_default || control->has_fixed) {
+			ret = regmap_write(regmap, reg, control->values[i]);
+			if (ret) {
+				dev_err(dev, "Failed to write default %#x: %d\n",
+					reg, ret);
+				return ret;
+			}
 
-		i++;
+			i++;
+		} else if (!control->is_volatile) {
+			ret = regmap_read(regmap, reg, &val);
+			if (ret) {
+				dev_err(dev, "Failed to read initial %#x: %d\n",
+					reg, ret);
+				return ret;
+			}
+		}
 	}
 
 	return 0;
@@ -317,7 +326,10 @@ static int populate_control_defaults(struct device *dev, struct regmap *regmap,
  *
  * This function will write out to the hardware all the DisCo default and
  * fixed value controls. This will cause them to be populated into the cache,
- * and subsequent handling can be done through a cache sync.
+ * and subsequent handling can be done through a cache sync. It will also
+ * read any non-volatile registers that don't have defaults/fixed values to
+ * populate those into the cache, this ensures they are available for reads
+ * even when the device is runtime suspended.
  *
  * Return: Returns zero on success, and a negative error code on failure.
  */
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 10/13] ASoC: SDCA: Add helper to write initialization writes
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
                   ` (8 preceding siblings ...)
  2025-11-03 15:08 ` [PATCH v2 09/13] ASoC: SDCA: Populate regmap cache for readable Controls Charles Keepax
@ 2025-11-03 15:08 ` Charles Keepax
  2025-11-03 15:08 ` [PATCH v2 11/13] ASoC: SDCA: add function devices Charles Keepax
                   ` (2 subsequent siblings)
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:08 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

Add a helper function to write out the SDCA blind initialization writes.

Acked-by: Vinod Koul <vkoul@kernel.org>
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

No changes since v1.

 include/sound/sdca_regmap.h  |  2 ++
 sound/soc/sdca/sdca_regmap.c | 16 ++++++++++++++++
 2 files changed, 18 insertions(+)

diff --git a/include/sound/sdca_regmap.h b/include/sound/sdca_regmap.h
index b2e3c2ad2bb88..792540a530fc4 100644
--- a/include/sound/sdca_regmap.h
+++ b/include/sound/sdca_regmap.h
@@ -27,5 +27,7 @@ int sdca_regmap_populate_constants(struct device *dev, struct sdca_function_data
 
 int sdca_regmap_write_defaults(struct device *dev, struct regmap *regmap,
 			       struct sdca_function_data *function);
+int sdca_regmap_write_init(struct device *dev, struct regmap *regmap,
+			   struct sdca_function_data *function);
 
 #endif // __SDCA_REGMAP_H__
diff --git a/sound/soc/sdca/sdca_regmap.c b/sound/soc/sdca/sdca_regmap.c
index 6fbb241d9d357..2cca9a9c71ea9 100644
--- a/sound/soc/sdca/sdca_regmap.c
+++ b/sound/soc/sdca/sdca_regmap.c
@@ -355,3 +355,19 @@ int sdca_regmap_write_defaults(struct device *dev, struct regmap *regmap,
 	return 0;
 }
 EXPORT_SYMBOL_NS(sdca_regmap_write_defaults, "SND_SOC_SDCA");
+
+int sdca_regmap_write_init(struct device *dev, struct regmap *regmap,
+			   struct sdca_function_data *function)
+{
+	struct sdca_init_write *init = function->init_table;
+	int ret, i;
+
+	for (i = 0; i < function->num_init_table; i++) {
+		ret = regmap_write(regmap, init[i].addr, init[i].val);
+		if (ret)
+			return ret;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_NS(sdca_regmap_write_init, "SND_SOC_SDCA");
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 11/13] ASoC: SDCA: add function devices
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
                   ` (9 preceding siblings ...)
  2025-11-03 15:08 ` [PATCH v2 10/13] ASoC: SDCA: Add helper to write initialization writes Charles Keepax
@ 2025-11-03 15:08 ` Charles Keepax
  2025-11-03 15:08 ` [PATCH v2 12/13] ASoC: SDCA: Add basic SDCA class driver Charles Keepax
  2025-11-03 15:08 ` [PATCH v2 13/13] ASoC: SDCA: Add basic SDCA function driver Charles Keepax
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:08 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

From: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>

Use the auxiliary bus to register/unregister subdevices for each
function. Each function will be handled with a separate driver,
matched using a name.

If a vendor wants to override a specific function driver, they could
use a custom name to match with a custom function driver.

Signed-off-by: Pierre-Louis Bossart <pierre-louis.bossart@linux.intel.com>
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

No changes since v1.

 include/sound/sdca.h                  |  13 +++
 sound/soc/sdca/Kconfig                |   1 +
 sound/soc/sdca/Makefile               |   4 +-
 sound/soc/sdca/sdca_function_device.c | 117 ++++++++++++++++++++++++++
 sound/soc/sdca/sdca_function_device.h |  15 ++++
 5 files changed, 148 insertions(+), 2 deletions(-)
 create mode 100644 sound/soc/sdca/sdca_function_device.c
 create mode 100644 sound/soc/sdca/sdca_function_device.h

diff --git a/include/sound/sdca.h b/include/sound/sdca.h
index d58d602212771..67ff3c88705d5 100644
--- a/include/sound/sdca.h
+++ b/include/sound/sdca.h
@@ -15,18 +15,21 @@
 struct acpi_table_swft;
 struct fwnode_handle;
 struct sdw_slave;
+struct sdca_dev;
 
 #define SDCA_MAX_FUNCTION_COUNT 8
 
 /**
  * struct sdca_function_desc - short descriptor for an SDCA Function
  * @node: firmware node for the Function.
+ * @func_dev: pointer to SDCA function device.
  * @name: Human-readable string.
  * @type: Function topology type.
  * @adr: ACPI address (used for SDCA register access).
  */
 struct sdca_function_desc {
 	struct fwnode_handle *node;
+	struct sdca_dev *func_dev;
 	const char *name;
 	u32 type;
 	u8 adr;
@@ -59,6 +62,8 @@ void sdca_lookup_functions(struct sdw_slave *slave);
 void sdca_lookup_swft(struct sdw_slave *slave);
 void sdca_lookup_interface_revision(struct sdw_slave *slave);
 bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_quirk quirk);
+int sdca_dev_register_functions(struct sdw_slave *slave);
+void sdca_dev_unregister_functions(struct sdw_slave *slave);
 
 #else
 
@@ -69,6 +74,14 @@ static inline bool sdca_device_quirk_match(struct sdw_slave *slave, enum sdca_qu
 {
 	return false;
 }
+
+static inline int sdca_dev_register_functions(struct sdw_slave *slave)
+{
+	return 0;
+}
+
+static inline void sdca_dev_unregister_functions(struct sdw_slave *slave) {}
+
 #endif
 
 #endif
diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig
index a73920d07073e..e7f36d668f159 100644
--- a/sound/soc/sdca/Kconfig
+++ b/sound/soc/sdca/Kconfig
@@ -4,6 +4,7 @@ menu "SoundWire (SDCA)"
 config SND_SOC_SDCA
 	tristate
 	depends on ACPI
+	select AUXILIARY_BUS
 	help
 	  This option enables support for the MIPI SoundWire Device
 	  Class for Audio (SDCA).
diff --git a/sound/soc/sdca/Makefile b/sound/soc/sdca/Makefile
index be911c399bbde..babe3fa2bb3ff 100644
--- a/sound/soc/sdca/Makefile
+++ b/sound/soc/sdca/Makefile
@@ -1,7 +1,7 @@
 # SPDX-License-Identifier: GPL-2.0-only
 
-snd-soc-sdca-y := sdca_functions.o sdca_device.o sdca_regmap.o sdca_asoc.o \
-		  sdca_ump.o
+snd-soc-sdca-y := sdca_functions.o sdca_device.o sdca_function_device.o \
+		  sdca_regmap.o sdca_asoc.o sdca_ump.o
 snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_HID) += sdca_hid.o
 snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_IRQ) += sdca_interrupts.o
 snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_FDL) += sdca_fdl.o
diff --git a/sound/soc/sdca/sdca_function_device.c b/sound/soc/sdca/sdca_function_device.c
new file mode 100644
index 0000000000000..91c49d7389db0
--- /dev/null
+++ b/sound/soc/sdca/sdca_function_device.c
@@ -0,0 +1,117 @@
+// SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause)
+// Copyright(c) 2024 Intel Corporation.
+
+/*
+ * SDCA Function Device management
+ */
+
+#include <linux/acpi.h>
+#include <linux/module.h>
+#include <linux/auxiliary_bus.h>
+#include <linux/soundwire/sdw.h>
+#include <sound/sdca.h>
+#include <sound/sdca_function.h>
+#include "sdca_function_device.h"
+
+/*
+ * A SoundWire device can have multiple SDCA functions identified by
+ * their type and ADR. there can be multiple SoundWire devices per
+ * link, or multiple devices spread across multiple links. An IDA is
+ * required to identify each instance.
+ */
+static DEFINE_IDA(sdca_function_ida);
+
+static void sdca_dev_release(struct device *dev)
+{
+	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+	struct sdca_dev *sdev = auxiliary_dev_to_sdca_dev(auxdev);
+
+	ida_free(&sdca_function_ida, auxdev->id);
+	kfree(sdev);
+}
+
+/* alloc, init and add link devices */
+static struct sdca_dev *sdca_dev_register(struct device *parent,
+					  struct sdca_function_desc *function_desc)
+{
+	struct sdca_dev *sdev;
+	struct auxiliary_device *auxdev;
+	int ret;
+	int rc;
+
+	sdev = kzalloc(sizeof(*sdev), GFP_KERNEL);
+	if (!sdev)
+		return ERR_PTR(-ENOMEM);
+
+	auxdev = &sdev->auxdev;
+	auxdev->name = function_desc->name;
+	auxdev->dev.parent = parent;
+	auxdev->dev.fwnode = function_desc->node;
+	auxdev->dev.release = sdca_dev_release;
+
+	sdev->function.desc = function_desc;
+
+	rc = ida_alloc(&sdca_function_ida, GFP_KERNEL);
+	if (rc < 0) {
+		kfree(sdev);
+		return ERR_PTR(rc);
+	}
+	auxdev->id = rc;
+
+	/* now follow the two-step init/add sequence */
+	ret = auxiliary_device_init(auxdev);
+	if (ret < 0) {
+		dev_err(parent, "failed to initialize SDCA function dev %s\n",
+			function_desc->name);
+		ida_free(&sdca_function_ida, auxdev->id);
+		kfree(sdev);
+		return ERR_PTR(ret);
+	}
+
+	ret = auxiliary_device_add(auxdev);
+	if (ret < 0) {
+		dev_err(parent, "failed to add SDCA function dev %s\n",
+			sdev->auxdev.name);
+		/* sdev will be freed with the put_device() and .release sequence */
+		auxiliary_device_uninit(&sdev->auxdev);
+		return ERR_PTR(ret);
+	}
+
+	return sdev;
+}
+
+static void sdca_dev_unregister(struct sdca_dev *sdev)
+{
+	auxiliary_device_delete(&sdev->auxdev);
+	auxiliary_device_uninit(&sdev->auxdev);
+}
+
+int sdca_dev_register_functions(struct sdw_slave *slave)
+{
+	struct sdca_device_data *sdca_data = &slave->sdca_data;
+	int i;
+
+	for (i = 0; i < sdca_data->num_functions; i++) {
+		struct sdca_dev *func_dev;
+
+		func_dev = sdca_dev_register(&slave->dev,
+					     &sdca_data->function[i]);
+		if (!func_dev)
+			return -ENODEV;
+
+		sdca_data->function[i].func_dev = func_dev;
+	}
+
+	return 0;
+}
+EXPORT_SYMBOL_NS(sdca_dev_register_functions, "SND_SOC_SDCA");
+
+void sdca_dev_unregister_functions(struct sdw_slave *slave)
+{
+	struct sdca_device_data *sdca_data = &slave->sdca_data;
+	int i;
+
+	for (i = 0; i < sdca_data->num_functions; i++)
+		sdca_dev_unregister(sdca_data->function[i].func_dev);
+}
+EXPORT_SYMBOL_NS(sdca_dev_unregister_functions, "SND_SOC_SDCA");
diff --git a/sound/soc/sdca/sdca_function_device.h b/sound/soc/sdca/sdca_function_device.h
new file mode 100644
index 0000000000000..5adf7551d3a44
--- /dev/null
+++ b/sound/soc/sdca/sdca_function_device.h
@@ -0,0 +1,15 @@
+/* SPDX-License-Identifier: (GPL-2.0 OR BSD-3-Clause) */
+/* Copyright(c) 2024 Intel Corporation. */
+
+#ifndef __SDCA_FUNCTION_DEVICE_H
+#define __SDCA_FUNCTION_DEVICE_H
+
+struct sdca_dev {
+	struct auxiliary_device auxdev;
+	struct sdca_function_data function;
+};
+
+#define auxiliary_dev_to_sdca_dev(auxiliary_dev)		\
+	container_of(auxiliary_dev, struct sdca_dev, auxdev)
+
+#endif
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 12/13] ASoC: SDCA: Add basic SDCA class driver
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
                   ` (10 preceding siblings ...)
  2025-11-03 15:08 ` [PATCH v2 11/13] ASoC: SDCA: add function devices Charles Keepax
@ 2025-11-03 15:08 ` Charles Keepax
  2025-11-03 15:08 ` [PATCH v2 13/13] ASoC: SDCA: Add basic SDCA function driver Charles Keepax
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:08 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

Add a device level driver as the entry point for the class driver.
Additional auxiliary drivers will be registered to support each function
within the device. This driver will register those function drivers and
provide the device level functionality, such as monitoring bus
attach/detach, the device level register map, and the root for the IRQ
handling.

Co-developed-by: Maciej Strozek <mstrozek@opensource.cirrus.com>
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

Changes since v1:
 - Update some info prints to dbg prints.

 include/linux/soundwire/sdw_registers.h |   2 +
 sound/soc/sdca/Kconfig                  |  10 +
 sound/soc/sdca/Makefile                 |   4 +
 sound/soc/sdca/sdca_class.c             | 304 ++++++++++++++++++++++++
 sound/soc/sdca/sdca_class.h             |  37 +++
 5 files changed, 357 insertions(+)
 create mode 100644 sound/soc/sdca/sdca_class.c
 create mode 100644 sound/soc/sdca/sdca_class.h

diff --git a/include/linux/soundwire/sdw_registers.h b/include/linux/soundwire/sdw_registers.h
index 0a5939285583b..cae8a0a5a9b04 100644
--- a/include/linux/soundwire/sdw_registers.h
+++ b/include/linux/soundwire/sdw_registers.h
@@ -355,4 +355,6 @@
 /* Check the reserved and fixed bits in address */
 #define SDW_SDCA_VALID_CTL(reg) (((reg) & (GENMASK(31, 25) | BIT(18) | BIT(13))) == BIT(30))
 
+#define SDW_SDCA_MAX_REGISTER			0x47FFFFFF
+
 #endif /* __SDW_REGISTERS_H */
diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig
index e7f36d668f159..56e3ff8448762 100644
--- a/sound/soc/sdca/Kconfig
+++ b/sound/soc/sdca/Kconfig
@@ -37,4 +37,14 @@ config SND_SOC_SDCA_FDL
 config SND_SOC_SDCA_OPTIONAL
 	def_tristate SND_SOC_SDCA || !SND_SOC_SDCA
 
+config SND_SOC_SDCA_CLASS
+	tristate "SDCA Class Driver"
+	select SND_SOC_SDCA
+	select SND_SOC_SDCA_FDL
+	select SND_SOC_SDCA_HID
+	select SND_SOC_SDCA_IRQ
+	help
+	  This option enables support for the SDCA Class driver which should
+	  support any class compliant SDCA part.
+
 endmenu
diff --git a/sound/soc/sdca/Makefile b/sound/soc/sdca/Makefile
index babe3fa2bb3ff..95db4cef34833 100644
--- a/sound/soc/sdca/Makefile
+++ b/sound/soc/sdca/Makefile
@@ -6,4 +6,8 @@ snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_HID) += sdca_hid.o
 snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_IRQ) += sdca_interrupts.o
 snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_FDL) += sdca_fdl.o
 
+snd-soc-sdca-class-y := sdca_class.o
+
 obj-$(CONFIG_SND_SOC_SDCA) += snd-soc-sdca.o
+
+obj-$(CONFIG_SND_SOC_SDCA_CLASS) += snd-soc-sdca-class.o
diff --git a/sound/soc/sdca/sdca_class.c b/sound/soc/sdca/sdca_class.c
new file mode 100644
index 0000000000000..349d32933ba85
--- /dev/null
+++ b/sound/soc/sdca/sdca_class.c
@@ -0,0 +1,304 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Cirrus Logic, Inc. and
+//                    Cirrus Logic International Semiconductor Ltd.
+
+/*
+ * The MIPI SDCA specification is available for public downloads at
+ * https://www.mipi.org/mipi-sdca-v1-0-download
+ */
+
+#include <linux/device.h>
+#include <linux/err.h>
+#include <linux/mod_devicetable.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/regmap.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <linux/soundwire/sdw_type.h>
+#include <sound/sdca.h>
+#include <sound/sdca_function.h>
+#include <sound/sdca_interrupts.h>
+#include <sound/sdca_regmap.h>
+#include "sdca_class.h"
+
+#define CLASS_SDW_ATTACH_TIMEOUT_MS	5000
+
+static int class_read_prop(struct sdw_slave *sdw)
+{
+	struct sdw_slave_prop *prop = &sdw->prop;
+
+	sdw_slave_read_prop(sdw);
+
+	prop->use_domain_irq = true;
+	prop->scp_int1_mask = SDW_SCP_INT1_BUS_CLASH | SDW_SCP_INT1_PARITY |
+			      SDW_SCP_INT1_IMPL_DEF;
+
+	return 0;
+}
+
+static int class_sdw_update_status(struct sdw_slave *sdw, enum sdw_slave_status status)
+{
+	struct sdca_class_drv *drv = dev_get_drvdata(&sdw->dev);
+
+	switch (status) {
+	case SDW_SLAVE_ATTACHED:
+		dev_dbg(drv->dev, "device attach\n");
+
+		drv->attached = true;
+
+		complete(&drv->device_attach);
+		break;
+	case SDW_SLAVE_UNATTACHED:
+		dev_dbg(drv->dev, "device detach\n");
+
+		drv->attached = false;
+
+		reinit_completion(&drv->device_attach);
+		break;
+	default:
+		break;
+	}
+
+	return 0;
+}
+
+static const struct sdw_slave_ops class_sdw_ops = {
+	.read_prop	= class_read_prop,
+	.update_status	= class_sdw_update_status,
+};
+
+static void class_regmap_lock(void *data)
+{
+	struct mutex *lock = data;
+
+	mutex_lock(lock);
+}
+
+static void class_regmap_unlock(void *data)
+{
+	struct mutex *lock = data;
+
+	mutex_unlock(lock);
+}
+
+static int class_wait_for_attach(struct sdca_class_drv *drv)
+{
+	if (!drv->attached) {
+		unsigned long timeout = msecs_to_jiffies(CLASS_SDW_ATTACH_TIMEOUT_MS);
+		unsigned long time;
+
+		time = wait_for_completion_timeout(&drv->device_attach, timeout);
+		if (!time) {
+			dev_err(drv->dev, "timed out waiting for device re-attach\n");
+			return -ETIMEDOUT;
+		}
+	}
+
+	regcache_cache_only(drv->dev_regmap, false);
+
+	return 0;
+}
+
+static bool class_dev_regmap_volatile(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SDW_SCP_SDCA_INTMASK1 ... SDW_SCP_SDCA_INTMASK4:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static bool class_dev_regmap_precious(struct device *dev, unsigned int reg)
+{
+	switch (reg) {
+	case SDW_SCP_SDCA_INT1 ... SDW_SCP_SDCA_INT4:
+	case SDW_SCP_SDCA_INTMASK1 ... SDW_SCP_SDCA_INTMASK4:
+		return false;
+	default:
+		return true;
+	}
+}
+
+static const struct regmap_config class_dev_regmap_config = {
+	.name			= "sdca-device",
+	.reg_bits		= 32,
+	.val_bits		= 8,
+
+	.max_register		= SDW_SDCA_MAX_REGISTER,
+	.volatile_reg		= class_dev_regmap_volatile,
+	.precious_reg		= class_dev_regmap_precious,
+
+	.cache_type		= REGCACHE_MAPLE,
+
+	.lock			= class_regmap_lock,
+	.unlock			= class_regmap_unlock,
+};
+
+static void class_boot_work(struct work_struct *work)
+{
+	struct sdca_class_drv *drv = container_of(work,
+						  struct sdca_class_drv,
+						  boot_work);
+	int ret;
+
+	ret = class_wait_for_attach(drv);
+	if (ret)
+		goto err;
+
+	drv->irq_info = sdca_irq_allocate(drv->dev, drv->dev_regmap,
+					  drv->sdw->irq);
+	if (IS_ERR(drv->irq_info))
+		goto err;
+
+	ret = sdca_dev_register_functions(drv->sdw);
+	if (ret)
+		goto err;
+
+	dev_dbg(drv->dev, "boot work complete\n");
+
+	pm_runtime_mark_last_busy(drv->dev);
+	pm_runtime_put_autosuspend(drv->dev);
+
+	return;
+
+err:
+	pm_runtime_put_sync(drv->dev);
+}
+
+static void class_dev_remove(void *data)
+{
+	struct sdca_class_drv *drv = data;
+
+	cancel_work_sync(&drv->boot_work);
+
+	sdca_dev_unregister_functions(drv->sdw);
+}
+
+static int class_sdw_probe(struct sdw_slave *sdw, const struct sdw_device_id *id)
+{
+	struct device *dev = &sdw->dev;
+	struct sdca_device_data *data = &sdw->sdca_data;
+	struct regmap_config *dev_config;
+	struct sdca_class_drv *drv;
+	int ret;
+
+	sdca_lookup_swft(sdw);
+
+	drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
+	if (!drv)
+		return -ENOMEM;
+
+	dev_config = devm_kmemdup(dev, &class_dev_regmap_config,
+				  sizeof(*dev_config), GFP_KERNEL);
+	if (!dev_config)
+		return -ENOMEM;
+
+	drv->functions = devm_kcalloc(dev, data->num_functions,
+				      sizeof(*drv->functions),
+				      GFP_KERNEL);
+	if (!drv->functions)
+		return -ENOMEM;
+
+	drv->dev = dev;
+	drv->sdw = sdw;
+	mutex_init(&drv->regmap_lock);
+
+	dev_set_drvdata(drv->dev, drv);
+
+	INIT_WORK(&drv->boot_work, class_boot_work);
+	init_completion(&drv->device_attach);
+
+	dev_config->lock_arg = &drv->regmap_lock;
+
+	drv->dev_regmap = devm_regmap_init_sdw(sdw, dev_config);
+	if (IS_ERR(drv->dev_regmap))
+		return dev_err_probe(drv->dev, PTR_ERR(drv->dev_regmap),
+				     "failed to create device regmap\n");
+
+	regcache_cache_only(drv->dev_regmap, true);
+
+	pm_runtime_set_autosuspend_delay(dev, 250);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_set_active(dev);
+	pm_runtime_get_noresume(dev);
+
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		return ret;
+
+	ret = devm_add_action_or_reset(dev, class_dev_remove, drv);
+	if (ret)
+		return ret;
+
+	queue_work(system_long_wq, &drv->boot_work);
+
+	return 0;
+}
+
+static int class_runtime_suspend(struct device *dev)
+{
+	struct sdca_class_drv *drv = dev_get_drvdata(dev);
+
+	/*
+	 * Whilst the driver doesn't power the chip down here, going into runtime
+	 * suspend lets the SoundWire bus power down, which means the driver
+	 * can't communicate with the device any more.
+	 */
+	regcache_cache_only(drv->dev_regmap, true);
+
+	return 0;
+}
+
+static int class_runtime_resume(struct device *dev)
+{
+	struct sdca_class_drv *drv = dev_get_drvdata(dev);
+	int ret;
+
+	ret = class_wait_for_attach(drv);
+	if (ret)
+		goto err;
+
+	regcache_mark_dirty(drv->dev_regmap);
+
+	ret = regcache_sync(drv->dev_regmap);
+	if (ret) {
+		dev_err(drv->dev, "failed to restore cache: %d\n", ret);
+		goto err;
+	}
+
+	return 0;
+
+err:
+	regcache_cache_only(drv->dev_regmap, true);
+
+	return ret;
+}
+
+static const struct dev_pm_ops class_pm_ops = {
+	RUNTIME_PM_OPS(class_runtime_suspend, class_runtime_resume, NULL)
+};
+
+static const struct sdw_device_id class_sdw_id[] = {
+	SDW_SLAVE_ENTRY(0x01FA, 0x4245, 0),
+	{}
+};
+MODULE_DEVICE_TABLE(sdw, class_sdw_id);
+
+static struct sdw_driver class_sdw_driver = {
+	.driver = {
+		.name		= "sdca_class",
+		.pm		= pm_ptr(&class_pm_ops),
+	},
+
+	.probe		= class_sdw_probe,
+	.id_table	= class_sdw_id,
+	.ops		= &class_sdw_ops,
+};
+module_sdw_driver(class_sdw_driver);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SDCA Class Driver");
+MODULE_IMPORT_NS("SND_SOC_SDCA");
diff --git a/sound/soc/sdca/sdca_class.h b/sound/soc/sdca/sdca_class.h
new file mode 100644
index 0000000000000..bb4c9dd124296
--- /dev/null
+++ b/sound/soc/sdca/sdca_class.h
@@ -0,0 +1,37 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * The MIPI SDCA specification is available for public downloads at
+ * https://www.mipi.org/mipi-sdca-v1-0-download
+ *
+ * Copyright (C) 2025 Cirrus Logic, Inc. and
+ *                    Cirrus Logic International Semiconductor Ltd.
+ */
+
+#ifndef __SDCA_CLASS_H__
+#define __SDCA_CLASS_H__
+
+#include <linux/completion.h>
+#include <linux/mutex.h>
+#include <linux/workqueue.h>
+
+struct device;
+struct regmap;
+struct sdw_slave;
+struct sdca_function_data;
+
+struct sdca_class_drv {
+	struct device *dev;
+	struct regmap *dev_regmap;
+	struct sdw_slave *sdw;
+
+	struct sdca_function_data *functions;
+	struct sdca_interrupt_info *irq_info;
+
+	struct mutex regmap_lock;
+	struct work_struct boot_work;
+	struct completion device_attach;
+
+	bool attached;
+};
+
+#endif /* __SDCA_CLASS_H__ */
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* [PATCH v2 13/13] ASoC: SDCA: Add basic SDCA function driver
  2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
                   ` (11 preceding siblings ...)
  2025-11-03 15:08 ` [PATCH v2 12/13] ASoC: SDCA: Add basic SDCA class driver Charles Keepax
@ 2025-11-03 15:08 ` Charles Keepax
  12 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-03 15:08 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

Add a driver to support the individual SDCA functions within the class
driver. Use the SDCA helpers to parse the DisCo information and register
a function driver based on those properties. Manage the boot of the
function, reset, FDL, defaults. Manage the function level register map.

Co-developed-by: Maciej Strozek <mstrozek@opensource.cirrus.com>
Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
---

Changes since v1:
 - Add some extra comments to clarify some code
 - Remove spurious error print
 - Flip order of recache mark dirty and cache only

 sound/soc/sdca/Kconfig               |   7 +
 sound/soc/sdca/Makefile              |   2 +
 sound/soc/sdca/sdca_class_function.c | 460 +++++++++++++++++++++++++++
 3 files changed, 469 insertions(+)
 create mode 100644 sound/soc/sdca/sdca_class_function.c

diff --git a/sound/soc/sdca/Kconfig b/sound/soc/sdca/Kconfig
index 56e3ff8448762..0a37e94a60302 100644
--- a/sound/soc/sdca/Kconfig
+++ b/sound/soc/sdca/Kconfig
@@ -39,6 +39,7 @@ config SND_SOC_SDCA_OPTIONAL
 
 config SND_SOC_SDCA_CLASS
 	tristate "SDCA Class Driver"
+	select SND_SOC_SDCA_CLASS_FUNCTION
 	select SND_SOC_SDCA
 	select SND_SOC_SDCA_FDL
 	select SND_SOC_SDCA_HID
@@ -47,4 +48,10 @@ config SND_SOC_SDCA_CLASS
 	  This option enables support for the SDCA Class driver which should
 	  support any class compliant SDCA part.
 
+config SND_SOC_SDCA_CLASS_FUNCTION
+	tristate
+	help
+	  This option enables support for the SDCA Class Function drivers,
+	  these implement the individual functions of the SDCA Class driver.
+
 endmenu
diff --git a/sound/soc/sdca/Makefile b/sound/soc/sdca/Makefile
index 95db4cef34833..f6b73275d9649 100644
--- a/sound/soc/sdca/Makefile
+++ b/sound/soc/sdca/Makefile
@@ -7,7 +7,9 @@ snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_IRQ) += sdca_interrupts.o
 snd-soc-sdca-$(CONFIG_SND_SOC_SDCA_FDL) += sdca_fdl.o
 
 snd-soc-sdca-class-y := sdca_class.o
+snd-soc-sdca-class-function-y := sdca_class_function.o
 
 obj-$(CONFIG_SND_SOC_SDCA) += snd-soc-sdca.o
 
 obj-$(CONFIG_SND_SOC_SDCA_CLASS) += snd-soc-sdca-class.o
+obj-$(CONFIG_SND_SOC_SDCA_CLASS_FUNCTION) += snd-soc-sdca-class-function.o
diff --git a/sound/soc/sdca/sdca_class_function.c b/sound/soc/sdca/sdca_class_function.c
new file mode 100644
index 0000000000000..526b30e0e365d
--- /dev/null
+++ b/sound/soc/sdca/sdca_class_function.c
@@ -0,0 +1,460 @@
+// SPDX-License-Identifier: GPL-2.0
+// Copyright (C) 2025 Cirrus Logic, Inc. and
+//                    Cirrus Logic International Semiconductor Ltd.
+
+/*
+ * The MIPI SDCA specification is available for public downloads at
+ * https://www.mipi.org/mipi-sdca-v1-0-download
+ */
+
+#include <linux/auxiliary_bus.h>
+#include <linux/minmax.h>
+#include <linux/module.h>
+#include <linux/pm.h>
+#include <linux/pm_runtime.h>
+#include <linux/soundwire/sdw.h>
+#include <linux/soundwire/sdw_registers.h>
+#include <sound/pcm.h>
+#include <sound/sdca_asoc.h>
+#include <sound/sdca_fdl.h>
+#include <sound/sdca_function.h>
+#include <sound/sdca_interrupts.h>
+#include <sound/sdca_regmap.h>
+#include <sound/sdw.h>
+#include <sound/soc-component.h>
+#include <sound/soc-dai.h>
+#include <sound/soc.h>
+#include "sdca_class.h"
+
+struct class_function_drv {
+	struct device *dev;
+	struct regmap *regmap;
+	struct sdca_class_drv *core;
+
+	struct sdca_function_data *function;
+};
+
+static void class_function_regmap_lock(void *data)
+{
+	struct mutex *lock = data;
+
+	mutex_lock(lock);
+}
+
+static void class_function_regmap_unlock(void *data)
+{
+	struct mutex *lock = data;
+
+	mutex_unlock(lock);
+}
+
+static bool class_function_regmap_writeable(struct device *dev, unsigned int reg)
+{
+	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+	return sdca_regmap_writeable(drv->function, reg);
+}
+
+static bool class_function_regmap_readable(struct device *dev, unsigned int reg)
+{
+	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+	return sdca_regmap_readable(drv->function, reg);
+}
+
+static bool class_function_regmap_volatile(struct device *dev, unsigned int reg)
+{
+	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+	return sdca_regmap_volatile(drv->function, reg);
+}
+
+static const struct regmap_config class_function_regmap_config = {
+	.name			= "sdca",
+	.reg_bits		= 32,
+	.val_bits		= 32,
+	.reg_format_endian	= REGMAP_ENDIAN_LITTLE,
+	.val_format_endian	= REGMAP_ENDIAN_LITTLE,
+
+	.max_register		= SDW_SDCA_MAX_REGISTER,
+	.readable_reg		= class_function_regmap_readable,
+	.writeable_reg		= class_function_regmap_writeable,
+	.volatile_reg		= class_function_regmap_volatile,
+
+	.cache_type		= REGCACHE_MAPLE,
+
+	.lock			= class_function_regmap_lock,
+	.unlock			= class_function_regmap_unlock,
+};
+
+static int class_function_regmap_mbq_size(struct device *dev, unsigned int reg)
+{
+	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+	return sdca_regmap_mbq_size(drv->function, reg);
+}
+
+static bool class_function_regmap_deferrable(struct device *dev, unsigned int reg)
+{
+	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+	return sdca_regmap_deferrable(drv->function, reg);
+}
+
+static const struct regmap_sdw_mbq_cfg class_function_mbq_config = {
+	.mbq_size		= class_function_regmap_mbq_size,
+	.deferrable		= class_function_regmap_deferrable,
+	.retry_us		= 1000,
+	.timeout_us		= 10000,
+};
+
+static int class_function_startup(struct snd_pcm_substream *substream,
+				  struct snd_soc_dai *dai)
+{
+	struct class_function_drv *drv = snd_soc_component_get_drvdata(dai->component);
+
+	return sdca_asoc_set_constraints(drv->dev, drv->regmap, drv->function,
+					 substream, dai);
+}
+
+static int class_function_sdw_add_peripheral(struct snd_pcm_substream *substream,
+					     struct snd_pcm_hw_params *params,
+					     struct snd_soc_dai *dai)
+{
+	struct class_function_drv *drv = snd_soc_component_get_drvdata(dai->component);
+	struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
+	struct sdw_slave *sdw = dev_to_sdw_dev(drv->dev->parent);
+	struct sdw_stream_config sconfig = {0};
+	struct sdw_port_config pconfig = {0};
+	int ret;
+
+	if (!sdw_stream)
+		return -EINVAL;
+
+	snd_sdw_params_to_config(substream, params, &sconfig, &pconfig);
+
+	/*
+	 * FIXME: As also noted in sdca_asoc_get_port(), currently only
+	 * a single unshared port is supported for each DAI.
+	 */
+	ret = sdca_asoc_get_port(drv->dev, drv->regmap, drv->function, dai);
+	if (ret < 0)
+		return ret;
+
+	pconfig.num = ret;
+
+	ret = sdw_stream_add_slave(sdw, &sconfig, &pconfig, 1, sdw_stream);
+	if (ret) {
+		dev_err(drv->dev, "failed to add sdw stream: %d\n", ret);
+		return ret;
+	}
+
+	return sdca_asoc_hw_params(drv->dev, drv->regmap, drv->function,
+				   substream, params, dai);
+}
+
+static int class_function_sdw_remove_peripheral(struct snd_pcm_substream *substream,
+						struct snd_soc_dai *dai)
+{
+	struct class_function_drv *drv = snd_soc_component_get_drvdata(dai->component);
+	struct sdw_stream_runtime *sdw_stream = snd_soc_dai_get_dma_data(dai, substream);
+	struct sdw_slave *sdw = dev_to_sdw_dev(drv->dev->parent);
+
+	if (!sdw_stream)
+		return -EINVAL;
+
+	return sdw_stream_remove_slave(sdw, sdw_stream);
+}
+
+static int class_function_sdw_set_stream(struct snd_soc_dai *dai, void *sdw_stream,
+					 int direction)
+{
+	snd_soc_dai_dma_data_set(dai, direction, sdw_stream);
+
+	return 0;
+}
+
+static const struct snd_soc_dai_ops class_function_sdw_ops = {
+	.startup	= class_function_startup,
+	.shutdown	= sdca_asoc_free_constraints,
+	.set_stream	= class_function_sdw_set_stream,
+	.hw_params	= class_function_sdw_add_peripheral,
+	.hw_free	= class_function_sdw_remove_peripheral,
+};
+
+static int class_function_component_probe(struct snd_soc_component *component)
+{
+	struct class_function_drv *drv = snd_soc_component_get_drvdata(component);
+	struct sdca_class_drv *core = drv->core;
+
+	return sdca_irq_populate(drv->function, component, core->irq_info);
+}
+
+static const struct snd_soc_component_driver class_function_component_drv = {
+	.probe			= class_function_component_probe,
+	.endianness		= 1,
+};
+
+static int class_function_boot(struct class_function_drv *drv)
+{
+	unsigned int reg = SDW_SDCA_CTL(drv->function->desc->adr,
+					SDCA_ENTITY_TYPE_ENTITY_0,
+					SDCA_CTL_ENTITY_0_FUNCTION_STATUS, 0);
+	unsigned int val;
+	int ret;
+
+	ret = regmap_read(drv->regmap, reg, &val);
+	if (ret < 0) {
+		dev_err(drv->dev, "failed to read function status: %d\n", ret);
+		return ret;
+	}
+
+	if (!(val & SDCA_CTL_ENTITY_0_FUNCTION_HAS_BEEN_RESET)) {
+		dev_dbg(drv->dev, "reset function device\n");
+
+		ret = sdca_reset_function(drv->dev, drv->function, drv->regmap);
+		if (ret)
+			return ret;
+	}
+
+	if (val & SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION) {
+		dev_dbg(drv->dev, "write initialisation\n");
+
+		ret = sdca_regmap_write_init(drv->dev, drv->core->dev_regmap,
+					     drv->function);
+		if (ret)
+			return ret;
+
+		ret = regmap_write(drv->regmap, reg,
+				   SDCA_CTL_ENTITY_0_FUNCTION_NEEDS_INITIALIZATION);
+		if (ret < 0) {
+			dev_err(drv->dev,
+				"failed to clear function init status: %d\n",
+				ret);
+			return ret;
+		}
+	}
+
+	/* Start FDL process */
+	ret = sdca_irq_populate_early(drv->dev, drv->regmap, drv->function,
+				      drv->core->irq_info);
+	if (ret)
+		return ret;
+
+	ret = sdca_fdl_sync(drv->dev, drv->function, drv->core->irq_info);
+	if (ret)
+		return ret;
+
+	ret = sdca_regmap_write_defaults(drv->dev, drv->regmap, drv->function);
+	if (ret)
+		return ret;
+
+	ret = regmap_write(drv->regmap, reg, 0xFF);
+	if (ret < 0) {
+		dev_err(drv->dev, "failed to clear function status: %d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int class_function_probe(struct auxiliary_device *auxdev,
+				const struct auxiliary_device_id *aux_dev_id)
+{
+	struct device *dev = &auxdev->dev;
+	struct sdca_class_drv *core = dev_get_drvdata(dev->parent);
+	struct sdca_device_data *data = &core->sdw->sdca_data;
+	struct sdca_function_desc *desc;
+	struct snd_soc_component_driver *cmp_drv;
+	struct snd_soc_dai_driver *dais;
+	struct class_function_drv *drv;
+	struct regmap_sdw_mbq_cfg *mbq_config;
+	struct regmap_config *config;
+	struct reg_default *defaults;
+	int ndefaults;
+	int num_dais;
+	int ret;
+	int i;
+
+	drv = devm_kzalloc(dev, sizeof(*drv), GFP_KERNEL);
+	if (!drv)
+		return -ENOMEM;
+
+	cmp_drv = devm_kmemdup(dev, &class_function_component_drv, sizeof(*cmp_drv),
+			       GFP_KERNEL);
+	if (!cmp_drv)
+		return -ENOMEM;
+
+	config = devm_kmemdup(dev, &class_function_regmap_config, sizeof(*config),
+			      GFP_KERNEL);
+	if (!config)
+		return -ENOMEM;
+
+	mbq_config = devm_kmemdup(dev, &class_function_mbq_config, sizeof(*mbq_config),
+				  GFP_KERNEL);
+	if (!mbq_config)
+		return -ENOMEM;
+
+	drv->dev = dev;
+	drv->core = core;
+
+	for (i = 0; i < data->num_functions; i++) {
+		desc = &data->function[i];
+
+		if (desc->type == aux_dev_id->driver_data)
+			break;
+	}
+	if (i == core->sdw->sdca_data.num_functions) {
+		dev_err(dev, "failed to locate function\n");
+		return -EINVAL;
+	}
+
+	drv->function = &core->functions[i];
+
+	ret = sdca_parse_function(dev, core->sdw, desc, drv->function);
+	if (ret)
+		return ret;
+
+	ndefaults = sdca_regmap_count_constants(dev, drv->function);
+	if (ndefaults < 0)
+		return ndefaults;
+
+	defaults = devm_kcalloc(dev, ndefaults, sizeof(*defaults), GFP_KERNEL);
+	if (!defaults)
+		return -ENOMEM;
+
+	ret = sdca_regmap_populate_constants(dev, drv->function, defaults);
+	if (ret < 0)
+		return ret;
+
+	regcache_sort_defaults(defaults, ndefaults);
+
+	auxiliary_set_drvdata(auxdev, drv);
+
+	config->reg_defaults = defaults;
+	config->num_reg_defaults = ndefaults;
+	config->lock_arg = &core->regmap_lock;
+
+	if (drv->function->busy_max_delay) {
+		mbq_config->timeout_us = drv->function->busy_max_delay;
+		mbq_config->retry_us = umax(drv->function->busy_max_delay / 10,
+					    mbq_config->retry_us);
+	}
+
+	drv->regmap = devm_regmap_init_sdw_mbq_cfg(dev, core->sdw, config, mbq_config);
+	if (IS_ERR(drv->regmap))
+		return dev_err_probe(dev, PTR_ERR(drv->regmap),
+				     "failed to create regmap");
+
+	ret = sdca_asoc_populate_component(dev, drv->function, cmp_drv,
+					   &dais, &num_dais,
+					   &class_function_sdw_ops);
+	if (ret)
+		return ret;
+
+	pm_runtime_set_autosuspend_delay(dev, 200);
+	pm_runtime_use_autosuspend(dev);
+	pm_runtime_set_active(dev);
+	pm_runtime_get_noresume(dev);
+
+	ret = devm_pm_runtime_enable(dev);
+	if (ret)
+		return ret;
+
+	ret = class_function_boot(drv);
+	if (ret)
+		return ret;
+
+	ret = devm_snd_soc_register_component(dev, cmp_drv, dais, num_dais);
+	if (ret)
+		return dev_err_probe(dev, ret, "failed to register component\n");
+
+	pm_runtime_mark_last_busy(dev);
+	pm_runtime_put_autosuspend(dev);
+
+	return 0;
+}
+
+static int class_function_codec_runtime_suspend(struct device *dev)
+{
+	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+
+	/*
+	 * Whilst the driver doesn't power the chip down here, going into
+	 * runtime suspend means the driver can't be sure the bus won't
+	 * power down which would prevent communication with the device.
+	 */
+	regcache_cache_only(drv->regmap, true);
+
+	return 0;
+}
+
+static int class_function_codec_runtime_resume(struct device *dev)
+{
+	struct auxiliary_device *auxdev = to_auxiliary_dev(dev);
+	struct class_function_drv *drv = auxiliary_get_drvdata(auxdev);
+	int ret;
+
+	regcache_mark_dirty(drv->regmap);
+	regcache_cache_only(drv->regmap, false);
+
+	ret = regcache_sync(drv->regmap);
+	if (ret) {
+		dev_err(drv->dev, "failed to restore register cache: %d\n", ret);
+		goto err;
+	}
+
+	return 0;
+
+err:
+	regcache_cache_only(drv->regmap, true);
+
+	return ret;
+}
+
+static const struct dev_pm_ops class_function_pm_ops = {
+	RUNTIME_PM_OPS(class_function_codec_runtime_suspend,
+		       class_function_codec_runtime_resume, NULL)
+};
+
+static const struct auxiliary_device_id class_function_id_table[] = {
+	{
+		.name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_SMART_AMP_NAME,
+		.driver_data = SDCA_FUNCTION_TYPE_SMART_AMP,
+	},
+	{
+		.name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_SMART_MIC_NAME,
+		.driver_data = SDCA_FUNCTION_TYPE_SMART_MIC,
+	},
+	{
+		.name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_UAJ_NAME,
+		.driver_data = SDCA_FUNCTION_TYPE_UAJ,
+	},
+	{
+		.name = "snd_soc_sdca." SDCA_FUNCTION_TYPE_HID_NAME,
+		.driver_data = SDCA_FUNCTION_TYPE_HID,
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(auxiliary, class_function_id_table);
+
+static struct auxiliary_driver class_function_drv = {
+	.driver = {
+		.name		= "sdca_function",
+		.pm		= pm_ptr(&class_function_pm_ops),
+	},
+
+	.probe		= class_function_probe,
+	.id_table	= class_function_id_table
+};
+module_auxiliary_driver(class_function_drv);
+
+MODULE_LICENSE("GPL");
+MODULE_DESCRIPTION("SDCA Class Function Driver");
+MODULE_IMPORT_NS("SND_SOC_SDCA");
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 15+ messages in thread

* Re: [PATCH v2 04/13] ASoC: SDCA: Correct FDL locking with scoped_guard
  2025-11-03 15:07 ` [PATCH v2 04/13] ASoC: SDCA: Correct FDL locking with scoped_guard Charles Keepax
@ 2025-11-05 15:07   ` Charles Keepax
  0 siblings, 0 replies; 15+ messages in thread
From: Charles Keepax @ 2025-11-05 15:07 UTC (permalink / raw)
  To: broonie
  Cc: vkoul, yung-chuan.liao, pierre-louis.bossart, peter.ujfalusi,
	shumingf, lgirdwood, linux-sound, patches

On Mon, Nov 03, 2025 at 03:07:59PM +0000, Charles Keepax wrote:
> The current locking in sdca_fdl_process() locks over
> sdca_ump_cancel_timeout() and the timeout work function takes the same
> lock, this can lead to a deadlock if the work runs as part of the
> cancel. To fix this use scoped_guard and move the cancel timeout to be
> outside the lock.
> 
> Fixes: e92e25f77748 ("ASoC: SDCA: Add UMP timeout handling for FDL")
> Signed-off-by: Charles Keepax <ckeepax@opensource.cirrus.com>
> ---
> 
> New since v1.
> 
>  sound/soc/sdca/sdca_fdl.c | 90 ++++++++++++++++++++-------------------
>  1 file changed, 46 insertions(+), 44 deletions(-)
> 
> diff --git a/sound/soc/sdca/sdca_fdl.c b/sound/soc/sdca/sdca_fdl.c
> index cb79dc3131b82..fe0ef2df4d9eb 100644
> --- a/sound/soc/sdca/sdca_fdl.c
> +++ b/sound/soc/sdca/sdca_fdl.c
> @@ -402,8 +402,6 @@ int sdca_fdl_process(struct sdca_interrupt *interrupt)
>  	unsigned int reg, status;
>  	int response, ret;
>  
> -	guard(mutex)(&fdl_state->lock);
> -
>  	ret = sdca_ump_get_owner_host(dev, interrupt->function_regmap,
>  				      interrupt->function, interrupt->entity,
>  				      interrupt->control);
> @@ -412,56 +410,60 @@ int sdca_fdl_process(struct sdca_interrupt *interrupt)
>  
>  	sdca_ump_cancel_timeout(&fdl_state->timeout);
>  
> -	reg = SDW_SDCA_CTL(interrupt->function->desc->adr, interrupt->entity->id,
> -			   SDCA_CTL_XU_FDL_STATUS, 0);
> -	ret = regmap_read(interrupt->function_regmap, reg, &status);
> -	if (ret < 0) {
> -		dev_err(dev, "failed to read FDL status: %d\n", ret);
> -		return ret;
> -	}
> +	scoped_guard(mutex, &fdl_state->lock) {

My esteemed colleague Maciej has pointed out (off list) we can
just move the guard down rather than switching to scoped_guard
since the whole reset of the function is covered. This makes the
patch much nicer so I will respin for that soon.

Thanks,
Charles

^ permalink raw reply	[flat|nested] 15+ messages in thread

end of thread, other threads:[~2025-11-05 15:07 UTC | newest]

Thread overview: 15+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2025-11-03 15:07 [PATCH v2 00/13] Add SDCA class driver Charles Keepax
2025-11-03 15:07 ` [PATCH v2 01/13] ASoC: SDCA: Remove duplicated module macros Charles Keepax
2025-11-03 15:07 ` [PATCH v2 02/13] ASoC: SDCA: Fix missing dash in HIDE DisCo property Charles Keepax
2025-11-03 15:07 ` [PATCH v2 03/13] ASoC: SDCA: Add missing forward declaration in header Charles Keepax
2025-11-03 15:07 ` [PATCH v2 04/13] ASoC: SDCA: Correct FDL locking with scoped_guard Charles Keepax
2025-11-05 15:07   ` Charles Keepax
2025-11-03 15:08 ` [PATCH v2 05/13] ASoC: SDCA: Add comment for function reset polling Charles Keepax
2025-11-03 15:08 ` [PATCH v2 06/13] ASoC: SDCA: Move most of the messages from info to debug Charles Keepax
2025-11-03 15:08 ` [PATCH v2 07/13] ASoC: SDCA: Use helper macros for control identification Charles Keepax
2025-11-03 15:08 ` [PATCH v2 08/13] ASoC: SDCA: Factor out helper to process Control defaults Charles Keepax
2025-11-03 15:08 ` [PATCH v2 09/13] ASoC: SDCA: Populate regmap cache for readable Controls Charles Keepax
2025-11-03 15:08 ` [PATCH v2 10/13] ASoC: SDCA: Add helper to write initialization writes Charles Keepax
2025-11-03 15:08 ` [PATCH v2 11/13] ASoC: SDCA: add function devices Charles Keepax
2025-11-03 15:08 ` [PATCH v2 12/13] ASoC: SDCA: Add basic SDCA class driver Charles Keepax
2025-11-03 15:08 ` [PATCH v2 13/13] ASoC: SDCA: Add basic SDCA function driver Charles Keepax

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox