lists.openwall.net   lists  /  announce  owl-users  owl-dev  john-users  john-dev  passwdqc-users  yescrypt  popa3d-users  /  oss-security  kernel-hardening  musl  sabotage  tlsify  passwords  /  crypt-dev  xvendor  /  Bugtraq  Full-Disclosure  linux-kernel  linux-netdev  linux-ext4  linux-hardening  linux-cve-announce  PHC 
Open Source and information security mailing list archives
 
Hash Suite: Windows password security audit tool. GUI, reports in PDF.
[<prev] [next>] [<thread-prev] [thread-next>] [day] [month] [year] [list]
Message-Id: <20250721023052.3586000-8-ew.kim@samsung.com>
Date: Mon, 21 Jul 2025 11:30:50 +0900
From: ew kim <ew.kim@...sung.com>
To: broonie@...nel.org, s.nawrocki@...sung.com, robh@...nel.org,
	krzk+dt@...nel.org
Cc: lgirdwood@...il.com, tiwai@...e.com, perex@...ex.cz,
	conor+dt@...nel.org, alim.akhtar@...sung.com, linux-sound@...r.kernel.org,
	devicetree@...r.kernel.org, linux-arm-kernel@...ts.infradead.org,
	linux-samsung-soc@...r.kernel.org, linux-kernel@...r.kernel.org, ew kim
	<ew.kim@...sung.com>
Subject: [PATCH 7/9] ASoC: samsung: Add PCM driver with solution support

Add abox_pcm_dev as an ALSA SoC frontend driver for PCM playback and
capture on the Exynos ABOX audio subsystem.

This driver registers per-PCM frontend devices and connects them to
the SoC-specific DMA engines and I2S dummy backends. It also supports
dynamic routing through DAPM and ALSA kcontrols, enabling flexible
audio path configuration.

Key features:
- Implements compress ops (.open, .set_params, .trigger, etc.)
- Dynamically generates DAPM widgets, routes, and kcontrols
- Integrates post-processing (PP) path support via mux control
- Provides runtime attach interface for solution management

The solution manager (`abox_solution_mgr`) is introduced as an optional
component that can be registered at runtime. When available, it enables
per-chain software audio solution selection via enum-type ALSA kcontrols.

Additionally, a new utility module (`abox_util_generic`) is added to
support reusable helpers for DMA management and device tree parsing.

Signed-off-by: ew kim <ew.kim@...sung.com>
---
 sound/soc/samsung/auto_abox/generic/Kbuild    |    5 +-
 .../samsung/auto_abox/generic/abox_generic.c  |  223 +-
 .../auto_abox/generic/abox_ipc_generic.c      |   23 +
 .../samsung/auto_abox/generic/abox_pcm_dev.c  | 2366 +++++++++++++++++
 .../auto_abox/generic/abox_solution_mgr.c     |  456 ++++
 .../auto_abox/generic/abox_util_generic.c     |   48 +
 .../auto_abox/generic/include/abox_generic.h  |   45 +-
 .../generic/include/abox_ipc_generic.h        |   44 +-
 .../auto_abox/generic/include/abox_pcm.h      |  135 +
 .../generic/include/abox_solution_mgr.h       |   98 +
 .../generic/include/abox_util_generic.h       |  110 +
 11 files changed, 3504 insertions(+), 49 deletions(-)
 create mode 100644 sound/soc/samsung/auto_abox/generic/abox_pcm_dev.c
 create mode 100644 sound/soc/samsung/auto_abox/generic/abox_solution_mgr.c
 create mode 100644 sound/soc/samsung/auto_abox/generic/abox_util_generic.c
 create mode 100644 sound/soc/samsung/auto_abox/generic/include/abox_pcm.h
 create mode 100644 sound/soc/samsung/auto_abox/generic/include/abox_solution_mgr.h
 create mode 100644 sound/soc/samsung/auto_abox/generic/include/abox_util_generic.h

diff --git a/sound/soc/samsung/auto_abox/generic/Kbuild b/sound/soc/samsung/auto_abox/generic/Kbuild
index 6a63d0609930..996bc814661a 100644
--- a/sound/soc/samsung/auto_abox/generic/Kbuild
+++ b/sound/soc/samsung/auto_abox/generic/Kbuild
@@ -3,7 +3,10 @@
 
 snd-soc-samsung-abox-generic-$(CONFIG_SND_SOC_SAMSUNG_AUTO_ABOX) := \
 	abox_generic.o \
-	abox_ipc_generic.o
+	abox_ipc_generic.o \
+	abox_util_generic.o \
+	abox_solution_mgr.o \
+	abox_pcm_dev.o
 
 ccflags-y += -I./include
 
diff --git a/sound/soc/samsung/auto_abox/generic/abox_generic.c b/sound/soc/samsung/auto_abox/generic/abox_generic.c
index 2c3f5ea910a2..e00dc07c0109 100644
--- a/sound/soc/samsung/auto_abox/generic/abox_generic.c
+++ b/sound/soc/samsung/auto_abox/generic/abox_generic.c
@@ -15,7 +15,6 @@
 #include "include/abox_generic.h"
 #include "include/abox_ipc_generic.h"
 
-extern struct platform_driver samsung_abox_ipc_generic_driver;
 /**
  * abox_generic_data_global - Shared state for ABOX generic driver.
  *
@@ -57,6 +56,60 @@ static struct abox_generic_data *abox_generic_get_generic_data_from_child(struct
 	return generic_data;
 }
 
+/**
+ * abox_generic_init_soc_route - Initialize SoC-specific PCM DMA routing
+ * @soc_dev: device pointer for the variable part (SoC-level controller)
+ *
+ * This function is called by the variable part (e.g., SoC-specific driver) to request
+ * initialization of PCM-to-DMA routing controls. It stores the given SoC device pointer
+ * and invokes dynamic DMA route kcontrol creation for each registered PCM frontend
+ * (playback and capture) device.
+ *
+ * It uses abox_pcm_dev_register_dma_route_kcontrol() internally to expose the routing
+ * controls as ALSA kcontrols, allowing user-space or driver components to configure
+ * how PCM streams are connected to backend DMA channels.
+ *
+ * Return: 0 on success or a negative error code on failure
+ */
+int abox_generic_init_soc_route(struct device *soc_dev)
+{
+	struct abox_generic_data *generic_data = abox_generic_get_abox_generic_data();
+	struct platform_device **pdev_pcm;
+	unsigned int stream_type;
+	unsigned int num_of_pcm;
+	unsigned int num_of_dma;
+	unsigned int pcm_id;
+	int ret = 0;
+
+	if (!generic_data)
+		return -ENODATA;
+	generic_data->soc_dev = soc_dev;
+
+	for (stream_type = SNDRV_PCM_STREAM_PLAYBACK; stream_type <= SNDRV_PCM_STREAM_CAPTURE;
+		stream_type++) {
+		if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) {
+			pdev_pcm = generic_data->pdev_pcm_playback;
+			num_of_pcm = generic_data->num_pcm_playback;
+			num_of_dma = generic_data->num_rdma;
+		} else {
+			pdev_pcm = generic_data->pdev_pcm_capture;
+			num_of_pcm = generic_data->num_pcm_capture;
+			num_of_dma = generic_data->num_wdma;
+		}
+
+		for (pcm_id = 0; pcm_id < num_of_pcm; pcm_id++) {
+			if (!pdev_pcm[pcm_id])
+				continue;
+			ret = abox_pcm_dev_register_dma_route_kcontrol(&pdev_pcm[pcm_id]->dev,
+				num_of_dma);
+			if (ret < 0)
+				dev_warn(soc_dev, "PCM Failed to attach dma : %d\n", ret);
+		}
+	}
+	return ret;
+}
+EXPORT_SYMBOL(abox_generic_init_soc_route);
+
 int abox_generic_set_dma_buffer(struct device *pcm_dev)
 {
 	struct abox_generic_data *generic_data = abox_generic_get_generic_data_from_child(pcm_dev);
@@ -85,6 +138,21 @@ int abox_generic_set_pp_pointer(struct device *pcm_dev)
 		pcm_dev);
 }
 
+/**
+ * abox_generic_attach_soc_callback - Register SoC callback and retrieve resource info
+ * @soc_dev: device pointer for the variable (SoC-specific) part
+ * @soc_ioctl: function pointer used to query SoC-specific resource info
+ *
+ * This function registers the variable part's device and ioctl callback function
+ * into the ABOX generic context. It is typically called by the SoC-specific
+ * driver during initialization.
+ *
+ * After registration, it queries the number of SoC-specific audio resources,
+ * such as RDMA, WDMA, and UAIF interfaces, and stores them in the generic context.
+ * These values are used later for dynamic kcontrol generation and routing setup.
+ *
+ * Return: 0 on success or -ENODATA if the ABOX generic context is not available
+ */
 int abox_generic_attach_soc_callback(struct device *soc_dev,
 				     soc_ioctl_fn soc_ioctl)
 {
@@ -96,11 +164,11 @@ int abox_generic_attach_soc_callback(struct device *soc_dev,
 	generic_data->soc_dev = soc_dev;
 	generic_data->soc_ioctl = soc_ioctl;
 
-	generic_data->num_of_rdma = generic_data->soc_ioctl(generic_data->soc_dev,
+	generic_data->num_rdma = generic_data->soc_ioctl(generic_data->soc_dev,
 		ABOX_SOC_IOCTL_GET_NUM_OF_RDMA, NULL);
-	generic_data->num_of_wdma = generic_data->soc_ioctl(generic_data->soc_dev,
+	generic_data->num_wdma = generic_data->soc_ioctl(generic_data->soc_dev,
 		ABOX_SOC_IOCTL_GET_NUM_OF_WDMA, NULL);
-	generic_data->num_of_uaif = generic_data->soc_ioctl(generic_data->soc_dev,
+	generic_data->num_uaif = generic_data->soc_ioctl(generic_data->soc_dev,
 		ABOX_SOC_IOCTL_GET_NUM_OF_UAIF, NULL);
 
 	return 0;
@@ -134,7 +202,7 @@ struct platform_device *abox_generic_get_pcm_platform_dev(int pcm_id, int stream
 EXPORT_SYMBOL(abox_generic_get_pcm_platform_dev);
 
 /**
- * abox_generic_get_num_of_pcm - Get number of supported PCM devices
+ * abox_generic_get_num_pcm - Get number of supported PCM devices
  * @stream_type: Stream direction (e.g. playback or capture)
  *
  * Returns the total number of PCM platform devices registered for
@@ -144,7 +212,7 @@ EXPORT_SYMBOL(abox_generic_get_pcm_platform_dev);
  *
  * Return: Number of PCM devices.
  */
-int abox_generic_get_num_of_pcm(int stream_type)
+int abox_generic_get_num_pcm(int stream_type)
 {
 	struct abox_generic_data *generic_data = abox_generic_get_abox_generic_data();
 
@@ -154,9 +222,9 @@ int abox_generic_get_num_of_pcm(int stream_type)
 	return (stream_type == SNDRV_PCM_STREAM_PLAYBACK) ? generic_data->num_pcm_playback :
 		generic_data->num_pcm_capture;
 }
-EXPORT_SYMBOL(abox_generic_get_num_of_pcm);
+EXPORT_SYMBOL(abox_generic_get_num_pcm);
 
-int abox_generic_get_num_of_i2s_dummy(void)
+int abox_generic_get_num_i2s_dummy(void)
 {
 	struct abox_generic_data *generic_data = abox_generic_get_abox_generic_data();
 
@@ -166,14 +234,71 @@ int abox_generic_get_num_of_i2s_dummy(void)
 	return generic_data->num_i2s_dummy;
 }
 
-int abox_generic_get_num_of_dma(struct device *pcm_dev, int stream_type)
+int abox_generic_get_num_dma(struct device *pcm_dev, int stream_type)
 {
 	struct abox_generic_data *generic_data = abox_generic_get_generic_data_from_child(pcm_dev);
 
 	return (stream_type == SNDRV_PCM_STREAM_PLAYBACK) ?
-		generic_data->num_of_rdma : generic_data->num_of_wdma;
+		generic_data->num_rdma : generic_data->num_wdma;
 }
 
+/**
+ * abox_generic_attach_dma - Attach a DMA controller to all PCM frontend devices
+ *                            registered under abox_generic.
+ * @dma_dev: DMA controller device provided by SoC-specific driver.
+ * @dma_id: DMA ID used to identify the specific DMA channel.
+ * @stream_type: PCM stream direction (SNDRV_PCM_STREAM_PLAYBACK or _CAPTURE).
+ * @dma_ioctl: Function pointer to the DMA ioctl interface provided by the DMA driver.
+ *
+ * This function is called by SoC-specific DMA drivers during their probe stage.
+ * It registers the given DMA controller with all PCM frontend devices of the
+ * matching stream type (playback or capture) under the abox_generic framework.
+ *
+ * It does so by iterating through the PCM device list managed by abox_generic
+ * and calling abox_pcm_dev_attach_dma() for each of them.
+ *
+ * Return: 0 on success or the last non-zero error code encountered.
+ */
+int abox_generic_attach_dma(struct device *dma_dev, int dma_id,
+				int stream_type, dma_ioctl_fn dma_ioctl)
+{
+	struct platform_device **pdev_pcm;
+	struct abox_generic_data *data = abox_generic_get_abox_generic_data();
+	struct device *generic_dev;
+	unsigned int num_of_pcm;
+	unsigned int pcm_id;
+	int ret = 0;
+
+	if (!data) {
+		pr_err("%s Failed to get abox_generic_data\n", __func__);
+		return -ENODATA;
+	}
+	generic_dev = &data->pdev->dev;
+
+	dev_info(generic_dev, "%s DMA%d Stream_type:%d\n", __func__, dma_id, stream_type);
+	if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) {
+		pdev_pcm = data->pdev_pcm_playback;
+		num_of_pcm = data->num_pcm_playback;
+	} else if (stream_type == SNDRV_PCM_STREAM_CAPTURE) {
+		pdev_pcm = data->pdev_pcm_capture;
+		num_of_pcm = data->num_pcm_capture;
+	} else {
+		dev_err(generic_dev, "%s Invalied stream type : %d\n", __func__, stream_type);
+		return -EINVAL;
+	}
+
+	for (pcm_id = 0; pcm_id < num_of_pcm; pcm_id++) {
+		if (!pdev_pcm[pcm_id])
+			continue;
+		dev_dbg(generic_dev, "%s DMA%d Register to PCM%d\n", __func__, dma_id, pcm_id);
+		ret = abox_pcm_dev_attach_dma(&pdev_pcm[pcm_id]->dev, dma_dev, dma_id, dma_ioctl);
+		if (ret < 0)
+			dev_err(generic_dev, "%s PCM Failed to attach dma : %d\n", __func__, ret);
+	}
+	return ret;
+}
+EXPORT_SYMBOL(abox_generic_attach_dma);
+
 int abox_generic_register_pcm_dev(struct platform_device *pdev_pcm,
 				   unsigned int id, int stream_type)
 {
@@ -197,6 +322,83 @@ int abox_generic_register_pcm_dev(struct platform_device *pdev_pcm,
 	return 0;
 }
 
+/**
+ * abox_generic_release_active_resource - Request to release pcm device resources
+ *
+ * This function is called by SoC driver during their releasing .
+ * It calls all abox_pcm_dev_release of PCM frontend devices.
+ *
+ */
+void abox_generic_release_active_resource(void)
+{
+	struct abox_generic_data *data = abox_generic_get_abox_generic_data();
+	struct device *generic_dev = &data->pdev->dev;
+	int index;
+
+	if (!data) {
+		dev_err(generic_dev, "%s: Invalid abox data\n", __func__);
+		return;
+	}
+
+	for (index = 0; index < data->num_pcm_playback; index++) {
+		if (!data->pdev_pcm_playback[index])
+			continue;
+		abox_pcm_dev_release(data->pdev_pcm_playback[index]);
+	}
+	for (index = 0; index < data->num_pcm_capture; index++) {
+		if (!data->pdev_pcm_capture[index])
+			continue;
+		abox_pcm_dev_release(data->pdev_pcm_capture[index]);
+	}
+}
+EXPORT_SYMBOL(abox_generic_release_active_resource);
+
+/**
+ * abox_generic_primary_dev_get - Get the primary PCM device number for a stream type
+ * @stream_type: Stream direction (SNDRV_PCM_STREAM_PLAYBACK or _CAPTURE)
+ *
+ * This function returns the ALSA PCM device number of the first available
+ * frontend PCM device for the given stream type (playback or capture).
+ *
+ * It is typically used by user space audio frameworks (e.g., Android Audio HAL)
+ * to identify the primary PCM device to use for audio routing or policy decisions.
+ *
+ * Since SoC-level drivers lack detailed knowledge of ALSA PCM configuration,
+ * this API provides a way for upper layers to query the correct frontend PCM
+ * device managed by the abox_generic framework.
+ *
+ * Return: The PCM device number on success, or a negative error code on failure.
+ */
+int abox_generic_primary_dev_get(int stream_type)
+{
+	struct abox_generic_data *data = abox_generic_get_abox_generic_data();
+	struct device *dev = &data->pdev->dev;
+	struct abox_platform_data *pcm_device_data;
+	struct platform_device **pdev_pcm;
+	unsigned int num_of_pcm;
+	int id;
+
+	if (!data) {
+		dev_err(dev, "%s: Invalid abox data\n", __func__);
+		return -ENODATA;
+	}
+	if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) {
+		pdev_pcm = data->pdev_pcm_playback;
+		num_of_pcm = data->num_pcm_playback;
+	} else {
+		pdev_pcm = data->pdev_pcm_capture;
+		num_of_pcm = data->num_pcm_capture;
+	}
+	for (id = 0; id < num_of_pcm; ++id) {
+		if (pdev_pcm[id]) {
+			pcm_device_data = platform_get_drvdata(pdev_pcm[id]);
+			return pcm_device_data->pcm_dev_num;
+		}
+	}
+	return -EINVAL;
+}
+EXPORT_SYMBOL(abox_generic_primary_dev_get);
+
 /**
  * abox_generic_attach_soc_callback - Register SoC-specific ioctl callback
  * @soc_dev: Device pointer for the SoC-specific driver
@@ -309,6 +511,7 @@ static int abox_generic_allocate_memory(struct device *dev, struct abox_generic_
 
 static struct platform_driver *abox_generic_sub_drivers[] = {
 	&samsung_abox_ipc_generic_driver,
+	&samsung_abox_pcm_dev_driver,
 };
 
 static int samsung_abox_generic_probe(struct platform_device *pdev)
diff --git a/sound/soc/samsung/auto_abox/generic/abox_ipc_generic.c b/sound/soc/samsung/auto_abox/generic/abox_ipc_generic.c
index 58d765cd5bfa..74a8b17c008e 100644
--- a/sound/soc/samsung/auto_abox/generic/abox_ipc_generic.c
+++ b/sound/soc/samsung/auto_abox/generic/abox_ipc_generic.c
@@ -154,6 +154,29 @@ int abox_ipc_generic_request_xfer(enum INTER_IPC_ID ipc_id, struct _abox_inter_i
 	return data->request_xfer(ipc_id, pmsg, sync, ipc_ret, adsp);
 }
 
+int abox_ipc_generic_register_pcm_dev_handler(struct device *pcm_dev, unsigned int irq_id,
+	ipc_generic_irq_handler_t handler)
+{
+	struct abox_ipc_generic_data *data;
+
+	data = abox_ipc_generic_get_data();
+	if (!data) {
+		pr_err("%s: There is no abox_ipc_generic_data\n", __func__);
+		return -EINVAL;
+	}
+	if (irq_id >= data->num_irq) {
+		dev_err(pcm_dev, "%s Invalid pcm irq id %d\n", __func__, irq_id);
+		return -EINVAL;
+	}
+
+	dev_dbg(pcm_dev, "%s[%d] pcm_irq_id(%d) handler is registered\n",
+		__func__, __LINE__, irq_id);
+	data->pcm_dev_irq_handler[irq_id].handler = handler;
+	data->pcm_dev_irq_handler[irq_id].dev = pcm_dev;
+
+	return 0;
+}
+
 static int samsung_abox_ipc_generic_probe(struct platform_device *pdev)
 {
 	struct device *dev = &pdev->dev;
diff --git a/sound/soc/samsung/auto_abox/generic/abox_pcm_dev.c b/sound/soc/samsung/auto_abox/generic/abox_pcm_dev.c
new file mode 100644
index 000000000000..613908f7e581
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/generic/abox_pcm_dev.c
@@ -0,0 +1,2366 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ * Author: Eunwoo Kim <ew.kim@...sung.com>
+ *
+ * EXYNOS Automotive Abox Generic Driver - abox_pcm_dev.c
+ *
+ * ALSA SoC frontend driver for PCM playback and capture.
+ *
+ * This driver registers PCM frontend devices for the ABOX audio subsystem
+ * in Exynos Automotive SoCs. It connects to SoC-specific DMA engines,
+ * communicates with the DSP via abox_ipc_generic, and supports post-processing
+ * (PP) features through abox_solution_mgr.
+ *
+ * This driver operates as a child device of abox_generic.
+ */
+
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/of_device.h>
+#include <linux/pm_runtime.h>
+#include <linux/dma-mapping.h>
+#include <linux/of_reserved_mem.h>
+#include <linux/ktime.h>
+
+#include <sound/soc.h>
+#include <sound/pcm_params.h>
+#include <sound/tlv.h>
+
+#include "include/abox_generic.h"
+#include "include/abox_pcm.h"
+#include "include/abox_ipc_generic.h"
+#include "include/abox_solution_mgr.h"
+#include "include/abox_util_generic.h"
+
+#define PCM_STREAM_STR(stream) stream ? "Capture":"Playback"
+#define PCM_KCTL_DUMMY_MUX	"DUMMY MUX"
+#define PCM_KCTL_DMA_MUX	"DMA MUX"
+#define DMA_NO_CONNECT		-1
+
+/* "%d" is replaced with PCM device ID (from DT "samsung,id" property) */
+static struct snd_soc_dai_driver abox_pcm_playback_dai_drv[] = {
+	{
+		.name = "PCM%dp",
+		.playback = {
+			.stream_name = "PCM%d Playback",
+			.channels_min = 1,
+			.channels_max = 32,
+			.rates = ABOX_SAMPLING_RATES,
+			.rate_min = 8000,
+			.rate_max = 384000,
+			.formats = ABOX_SAMPLE_FORMATS,
+		},
+	},
+};
+
+/* "%d" is replaced with PCM device ID (from DT "samsung,id" property) */
+static struct snd_soc_dai_driver abox_pcm_capture_dai_drv[] = {
+	{
+		.name = "PCM%dc",
+		.capture = {
+			.stream_name = "PCM%d Capture",
+			.channels_min = 1,
+			.channels_max = 32,
+			.rates = ABOX_SAMPLING_RATES,
+			.rate_min = 8000,
+			.rate_max = 384000,
+			.formats = ABOX_SAMPLE_FORMATS,
+		},
+	},
+};
+
+static const struct snd_compr_caps abox_pcm_dev_compr_caps = {
+	/* Temporary value:  Need to be checked */
+	.direction		= SND_COMPRESS_PLAYBACK,
+	.min_fragment_size	= SZ_4K,
+	.max_fragment_size	= SZ_32K,
+	.min_fragments		= 1,
+	.max_fragments		= 5,
+	.num_codecs		= 1,
+	.codecs			= {
+		SND_AUDIOCODEC_MP3,
+	},
+};
+
+const struct snd_soc_dai_ops abox_compr_dai_ops = {
+	.compress_new = snd_soc_new_compress,
+};
+
+/**
+ * abox_pcm_dev_request_ipc - Send IPC to SoC via abox_ipc_generic
+ * @data: ABOX PCM platform data
+ * @msg: IPC message structure
+ * @sync: Whether IPC is synchronous
+ * @ipc_ret: Return payload
+ *
+ * Thin wrapper around abox_ipc_generic_request_xfer() for message passing to ADSP.
+ */
+static int abox_pcm_dev_request_ipc(struct abox_platform_data *data,
+				struct _abox_inter_ipc_msg *msg,
+				bool sync,
+				struct __abox_inter_ipc_ret *ipc_ret)
+{
+	return abox_ipc_generic_request_xfer(msg->ipcid, msg, sync, ipc_ret, data->adsp);
+}
+
+static int abox_pcm_dev_request_soc_ioctl(struct device *pcm_dev,
+					enum abox_soc_ioctl_cmd cmd, void *data)
+{
+	struct device *generic_dev = pcm_dev->parent;
+
+	return abox_generic_request_soc_ioctl(generic_dev, cmd, data);
+}
+
+static void abox_pcm_dev_update_name(struct device *pcm_dev)
+{
+	struct abox_platform_data *pcm_data = dev_get_drvdata(pcm_dev);
+	int ret;
+
+	ret = snprintf(pcm_data->name, DEFAULT_STR_SIZE - 1, "PCM%d %s", pcm_data->id,
+		PCM_STREAM_STR(pcm_data->stream_type));
+	if (ret >= (DEFAULT_STR_SIZE - 1))
+		dev_warn(pcm_dev, "%s(%d) Buffer truncated (needed %d bytes)\n", __func__,
+			__LINE__, ret);
+}
+
+/*
+ * This function returns formatted SoC timer string.
+ * Though it uses a static buffer, this is only used for debug logs
+ * and called sequentially, so there's no practical concurrency issue.
+ */
+static char *abox_pcm_dev_get_soc_time(struct device *pcm_dev)
+{
+	static char soc_time[DEFAULT_STR_SIZE];
+
+	if (!pcm_dev)
+		return soc_time;
+
+	memset(soc_time, 0, DEFAULT_STR_SIZE);
+	abox_pcm_dev_request_soc_ioctl(pcm_dev, ABOX_SOC_IOCTL_GET_SOC_TIMER, soc_time);
+
+	return soc_time;
+}
+
+static struct abox_platform_dma_info *abox_pcm_dev_find_dma_info(struct device *pcm_dev,
+								int dma_id)
+{
+	struct abox_platform_data *data;
+	struct abox_platform_dma_info *dma_info;
+
+	data = dev_get_drvdata(pcm_dev);
+	if (!data)
+		return NULL;
+
+	list_for_each_entry(dma_info, &data->dma_list_head, list) {
+		if (dma_info->dma_id == dma_id)
+			return dma_info;
+	}
+	return NULL;
+}
+
+static struct snd_soc_dapm_widget *abox_pcm_dev_find_widget(struct device *pcm_dev,
+							char *widget_name)
+{
+	struct abox_platform_data *data = dev_get_drvdata(pcm_dev);
+	int i;
+
+	for (i = 0; i < data->cmpnt_drv->num_dapm_widgets; i++) {
+		const struct snd_soc_dapm_widget *widget = &data->cmpnt_drv->dapm_widgets[i];
+
+		if (!strcmp(widget->name, widget_name))
+			return (struct snd_soc_dapm_widget *)widget;
+	}
+	return NULL;
+}
+
+static struct snd_kcontrol_new *abox_pcm_dev_find_kcontrol(struct device *pcm_dev,
+							struct snd_soc_dapm_widget *widget,
+							char *kcontrol_name)
+{
+	int i;
+
+	for (i = 0; i < widget->num_kcontrols; i++) {
+		const struct snd_kcontrol_new *kctl = &widget->kcontrol_news[i];
+
+		if (!kctl)
+			continue;
+		if (!strcmp(kctl->name, kcontrol_name))
+			return (struct snd_kcontrol_new *)kctl;
+	}
+	return NULL;
+}
+
+/* Compose full control name with PCM ID prefix. */
+static int abox_pcm_dev_strcat_with_prefix_name(struct device *pcm_dev,
+						char *dst, const char *src)
+{
+	struct abox_platform_data *data = dev_get_drvdata(pcm_dev);
+
+	if (data->stream_type == SNDRV_PCM_STREAM_PLAYBACK)
+		return snprintf(dst, DEFAULT_STR_SIZE - 1, "PCM%dp %s", data->id, src);
+	else
+		return snprintf(dst, DEFAULT_STR_SIZE - 1, "PCM%dc %s", data->id, src);
+}
+
+/*
+ * DMA status read from IP SFR via ioctl
+ *
+ * Return DMA IP status read via ioctl.
+ * Values are IP-specific and typically:
+ * 0: idle, 1: running, others: error state
+ */
+unsigned int abox_pcm_dev_status(struct device *pcm_dev)
+{
+	struct abox_platform_data *data;
+	struct abox_platform_dma_info *dma_info;
+	unsigned int dma_status;
+	int dma_id;
+
+	data = dev_get_drvdata(pcm_dev);
+	if (!data) {
+		dev_err(pcm_dev, "%s(%d) PCM Drv Data is not ready\n", __func__, __LINE__);
+		return 0;
+	}
+	dma_id = data->dma_id;
+	if (dma_id < 0) {
+		dev_err(pcm_dev, "%s(%d) DMA is not connected\n", __func__, __LINE__);
+		return 0;
+	}
+	dma_info = abox_pcm_dev_find_dma_info(pcm_dev, dma_id);
+	if (!dma_info) {
+		dev_err(pcm_dev, "%s(%d) Can't find DMA%d Info.\n", __func__, __LINE__, dma_id);
+		return 0;
+	}
+	if (!dma_info->dma_ioctl) {
+		dev_err(pcm_dev, "%s(%d) DMA%d Ioctl is not ready\n", __func__, __LINE__, dma_id);
+		return 0;
+	}
+	dma_status = dma_info->dma_ioctl(dma_info->dma_dev, ABOX_DMA_IOCTL_GET_DMA_STATUS, NULL);
+
+	return dma_status;
+}
+
+static int abox_pcm_dev_call_solution_ops(struct device *dev,
+	enum abox_solution_ops_type ops_type, int cmd, void *data)
+{
+	struct abox_platform_data *pcm_data = dev_get_drvdata(dev);
+	int ret;
+
+	ret = abox_solution_mgr_ops(dev, ABOX_SOL_SW, ops_type, cmd, data);
+	if (ret < 0)
+		return ret;
+	if (pcm_data->dma_id >= 0) {
+		struct abox_platform_dma_info *dma_info =
+			abox_pcm_dev_find_dma_info(dev, pcm_data->dma_id);
+
+		if (!dma_info)
+			return -EINVAL;
+
+		ret = abox_solution_mgr_ops(dma_info->dma_dev, ABOX_SOL_HW, ops_type, cmd, data);
+		if (ret < 0)
+			return ret;
+	}
+	return ret;
+}
+
+/*
+ * Sends IPC messages to the ADSP (SoC-side) to notify state transitions.
+ * The ADSP performs DMA initialization, configuration, start, or stop
+ * depending on the current ALSA PCM state.
+ *
+ * This enables a clear separation between the host (control plane)
+ * and the SoC (data plane) via a platform-agnostic IPC interface.
+ */
+
+/*
+ * Sends IPC messages to the ADSP to configure DMA buffer and audio format.
+ * This function corresponds to the ALSA PCM hw_params() stage.
+ */
+static int abox_pcm_dev_hw_params(struct snd_soc_component *component,
+				struct snd_pcm_substream *substream,
+				struct snd_pcm_hw_params *params)
+{
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+	struct _abox_inter_ipc_msg msg = {0, };
+	struct inter_ipc_pcmtask_msg *pcmtask_msg = &msg.msg.pcmtask;
+	int id = data->id;
+	int ret;
+	int stream_type = substream->stream;
+	long long period_time;
+	unsigned int sample_rate;
+	unsigned int bit_depth;
+	unsigned int channels;
+	unsigned int period_bytes;
+	unsigned int period_cnt;
+
+	abox_pcm_dev_request_soc_ioctl(dev, ABOX_SOC_IOCTL_CHECK_TIME_MUTEX, (void *)__func__);
+	if (data->pcm_dbg_log.set_kernel_pcm_log & FUNC_LOG)
+		dev_info(dev, "%s %s%s : DMA %d ADSP:%u\n", __func__,
+			abox_pcm_dev_get_soc_time(dev), data->name, data->dma_id, data->adsp);
+
+	ret = snd_pcm_lib_malloc_pages(substream, params_buffer_bytes(params));
+	if (ret < 0) {
+		dev_err(dev, "Memory allocation failed(size:%u)\n", params_buffer_bytes(params));
+		return ret;
+	}
+
+	sample_rate = params_rate(params);
+	bit_depth = params_width(params);
+	channels = params_channels(params);
+	period_bytes = params_period_bytes(params);
+	period_cnt = params_periods(params);
+
+	msg.ipcid = (stream_type == SNDRV_PCM_STREAM_PLAYBACK) ?
+		INTER_IPC_PCMPLAYBACK : INTER_IPC_PCMCAPTURE;
+	msg.task_id = pcmtask_msg->pcm_device_id = id;
+	pcmtask_msg->msgtype = INTER_PCM_SET_BUFFER;
+
+	pcmtask_msg->param.setbuff.phyaddr = substream->dma_buffer.addr;
+	pcmtask_msg->param.setbuff.size = period_bytes;
+	pcmtask_msg->param.setbuff.count = period_cnt;
+
+	ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+	if (ret < 0)
+		return ret;
+
+	pcmtask_msg->msgtype = INTER_PCM_PLTDAI_HW_PARAMS;
+	pcmtask_msg->param.hw_params.sample_rate = sample_rate;
+	pcmtask_msg->param.hw_params.bit_depth = bit_depth;
+	pcmtask_msg->param.hw_params.channels = channels;
+
+	ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+	if (ret < 0)
+		return ret;
+
+	ret = abox_pcm_dev_call_solution_ops(dev, ABOX_SOL_HW_PARAM, 0, substream);
+	if (ret < 0) {
+		dev_err(dev, "Solution HW PARAM Failed ret:%d\n", ret);
+		return ret;
+	}
+
+	period_time = GET_PERIOD_TIME(bit_depth, channels, sample_rate, period_bytes);
+	abox_pcm_dev_request_soc_ioctl(dev, ABOX_SOC_IOCTL_SET_PERF_PERIOD, params);
+
+	return 0;
+}
+
+/*
+ * Sends IPC messages to the ADSP (SoC-side) to notify state transitions.
+ * The ADSP performs DMA initialization, configuration, start, or stop
+ * depending on the current ALSA PCM state.
+ *
+ * This enables a clear separation between the host (control plane)
+ * and the SoC (data plane) via a platform-agnostic IPC interface.
+ */
+
+/*
+ * Informs the ADSP to release any DMA or internal resources allocated
+ * during the hw_params stage.
+ *
+ * This function corresponds to the ALSA PCM hw_free() stage.
+ */
+static int abox_pcm_dev_hw_free(struct snd_soc_component *component,
+				struct snd_pcm_substream *substream)
+{
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+	struct _abox_inter_ipc_msg msg = {0, };
+	struct inter_ipc_pcmtask_msg *pcmtask_msg = &msg.msg.pcmtask;
+	int id = data->id;
+	int stream_type = substream->stream;
+	int ret;
+
+	if (data->pcm_dbg_log.set_kernel_pcm_log & FUNC_LOG)
+		dev_info(dev, "%s %s%s : DMA %d ADSP:%u\n", __func__,
+			abox_pcm_dev_get_soc_time(dev),
+			data->name, data->dma_id, data->adsp);
+
+	if (substream->runtime->state == SNDRV_PCM_STATE_DISCONNECTED) {
+		dev_err(dev, "%s[%d] DMA is disconnected\n", __func__, id);
+		return -EBADFD;
+	}
+
+	ret = abox_pcm_dev_call_solution_ops(dev, ABOX_SOL_HW_FREE, 0, substream);
+	if (ret < 0) {
+		dev_err(dev, "Solution HW FREE Failed ret:%d\n", ret);
+		return ret;
+	}
+
+	msg.ipcid = (stream_type == SNDRV_PCM_STREAM_PLAYBACK) ?
+		INTER_IPC_PCMPLAYBACK : INTER_IPC_PCMCAPTURE;
+	pcmtask_msg->msgtype = INTER_PCM_PLTDAI_HW_FREE;
+	msg.task_id = pcmtask_msg->pcm_device_id = id;
+	ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+	if (ret < 0) {
+		dev_err(dev, "abox_pcm_dev_request_ipc failed : %d\n", ret);
+		return ret;
+	}
+
+	return snd_pcm_lib_free_pages(substream);
+}
+
+/*
+ * Sends IPC messages to the ADSP (SoC-side) to notify state transitions.
+ * The ADSP performs DMA initialization, configuration, start, or stop
+ * depending on the current ALSA PCM state.
+ *
+ * This enables a clear separation between the host (control plane)
+ * and the SoC (data plane) via a platform-agnostic IPC interface.
+ */
+
+/*
+ * Notifies the ADSP that the PCM stream is ready for playback or capture.
+ * This allows the ADSP to prepare its internal state and initialize
+ * runtime structures before actual DMA trigger.
+ *
+ * This function corresponds to the ALSA PCM prepare() stage.
+ */
+static int abox_pcm_dev_prepare(struct snd_soc_component *component,
+	struct snd_pcm_substream *substream)
+{
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+	struct _abox_inter_ipc_msg msg = {0, };
+	struct inter_ipc_pcmtask_msg *pcmtask_msg = &msg.msg.pcmtask;
+	int id = data->id;
+	int stream_type = substream->stream;
+	int ret;
+
+	data->pointer = substream->dma_buffer.addr;
+
+	if (data->pcm_dbg_log.set_kernel_pcm_log & FUNC_LOG)
+		dev_info(dev, "%s %s%s : DMA %d ADSP:%u data->pointer=%u\n",
+			__func__, abox_pcm_dev_get_soc_time(dev), data->name, data->dma_id,
+			data->adsp, data->pointer);
+
+	ret = abox_pcm_dev_call_solution_ops(dev, ABOX_SOL_PREPARE, 0, substream);
+	if (ret < 0) {
+		dev_err(dev, "Solution Prepare Failed ret:%d\n", ret);
+		return ret;
+	}
+
+	msg.ipcid = (stream_type == SNDRV_PCM_STREAM_PLAYBACK) ?
+		INTER_IPC_PCMPLAYBACK : INTER_IPC_PCMCAPTURE;
+	pcmtask_msg->msgtype = INTER_PCM_PLTDAI_PREPARE;
+	msg.task_id = pcmtask_msg->pcm_device_id = id;
+	ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+
+	return ret;
+}
+
+/*
+ * Sends IPC messages to the ADSP (SoC-side) to notify state transitions.
+ * The ADSP performs DMA initialization, configuration, start, or stop
+ * depending on the current ALSA PCM state.
+ *
+ * This enables a clear separation between the host (control plane)
+ * and the SoC (data plane) via a platform-agnostic IPC interface.
+ */
+
+/*
+ * Sends a trigger command (START, STOP, etc.) to the ADSP over IPC.
+ * The ADSP starts or stops the DMA engine and related audio processing blocks
+ * based on the trigger type.
+ *
+ * This function corresponds to the ALSA PCM trigger() stage.
+ */
+static int abox_pcm_dev_trigger(struct snd_soc_component *component,
+	struct snd_pcm_substream *substream, int cmd)
+{
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	struct _abox_inter_ipc_msg msg = {0, };
+	struct inter_ipc_pcmtask_msg *pcmtask_msg = &msg.msg.pcmtask;
+	int id = data->id;
+	int stream_type = substream->stream;
+	int ret;
+
+	abox_pcm_dev_request_soc_ioctl(dev, ABOX_SOC_IOCTL_CHECK_TIME_NO_MUTEX, (void *)__func__);
+	if (data->pcm_dbg_log.set_kernel_pcm_log & FUNC_LOG)
+		dev_info(dev, "%s %s%s : DMA %d ADSP:%u CMD : %d\n",
+			__func__, abox_pcm_dev_get_soc_time(dev), data->name, data->dma_id,
+			data->adsp, cmd);
+
+	msg.ipcid = (stream_type == SNDRV_PCM_STREAM_PLAYBACK) ?
+		INTER_IPC_PCMPLAYBACK : INTER_IPC_PCMCAPTURE;
+	pcmtask_msg->msgtype = INTER_PCM_PLTDAI_TRIGGER;
+	msg.task_id = pcmtask_msg->pcm_device_id = id;
+	pcmtask_msg->param.dma_trigger.is_real_dma = !data->solution_mgr_data->pp_enabled;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SNDRV_PCM_TRIGGER_RESUME:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		data->pcm_dbg_log.lastTime = ktime_get();
+
+		ret = abox_pcm_dev_call_solution_ops(dev, ABOX_SOL_TRIGGER, cmd, substream);
+		if (ret < 0) {
+			dev_err(dev, "Solution Trigger Failed ret:%d\n", ret);
+			return ret;
+		}
+		pcmtask_msg->param.dma_trigger.trigger = 1;
+		pcmtask_msg->start_threshold = runtime->start_threshold;
+		ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		if (substream->runtime->state == SNDRV_PCM_STATE_DISCONNECTED) {
+			dev_err(dev, "%s[%d] DMA is disconnected\n", __func__, id);
+			return -EBADFD;
+		}
+
+		pcmtask_msg->param.dma_trigger.trigger = 0;
+		ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+		break;
+	default:
+		ret = -EINVAL;
+		break;
+	}
+
+	return ret;
+}
+
+/*
+ * Sends IPC messages to the ADSP (SoC-side) to notify state transitions.
+ * The ADSP performs DMA initialization, configuration, start, or stop
+ * depending on the current ALSA PCM state.
+ *
+ * This enables a clear separation between the host (control plane)
+ * and the SoC (data plane) via a platform-agnostic IPC interface.
+ */
+
+/*
+ * Get current DMA pointer for PCM stream.
+ *
+ * Depending on whether the solution manager's post-processing mode
+ * is enabled, the function either reads from the post-process pointer
+ * register or queries the DMA via ioctl interface.
+ *
+ * Returns the number of PCM frames elapsed in the current buffer.
+ */
+static snd_pcm_uframes_t abox_pcm_dev_pointer(struct snd_soc_component *component,
+	struct snd_pcm_substream *substream)
+{
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int id;
+	ssize_t pointer;
+	unsigned int dma_status;
+	unsigned int buf_status;
+	snd_pcm_sframes_t frames;
+
+	if (!data)
+		return 0;
+	id = data->id;
+
+	if (data->solution_mgr_data->pp_enabled) {
+		pointer = readl(data->pp_pointer_base);
+	} else {
+		struct abox_platform_dma_info *dma_info;
+		int dma_id;
+
+		dma_id = data->dma_id;
+		if (dma_id < 0) {
+			dev_err(dev, "%s(%d) DMA is not connected\n", __func__, __LINE__);
+			return 0;
+		}
+		dma_info = abox_pcm_dev_find_dma_info(dev, dma_id);
+		if (!dma_info) {
+			dev_err(dev, "%s(%d) Can't find DMA%d Info.\n", __func__, __LINE__,
+				dma_id);
+			return 0;
+		}
+		if (!dma_info->dma_ioctl) {
+			dev_err(dev, "%s(%d) DMA%d Ioctl is not ready\n", __func__, __LINE__,
+				dma_id);
+			return 0;
+		}
+		dma_status = dma_info->dma_ioctl(dma_info->dma_dev, ABOX_DMA_IOCTL_GET_DMA_STATUS,
+						NULL);
+		buf_status = dma_info->dma_ioctl(dma_info->dma_dev, ABOX_DMA_IOCTL_GET_BUF_STATUS,
+						NULL);
+		if (dma_status) {
+			ssize_t offset;
+			ssize_t count;
+			ssize_t buffer_bytes;
+			ssize_t period_bytes;
+
+			buffer_bytes = snd_pcm_lib_buffer_bytes(substream);
+			period_bytes = snd_pcm_lib_period_bytes(substream);
+
+			offset = (ssize_t)dma_info->dma_ioctl(dma_info->dma_dev,
+				ABOX_DMA_IOCTL_GET_BUF_OFFSET, &buf_status);
+			count = (ssize_t)dma_info->dma_ioctl(dma_info->dma_dev,
+				ABOX_DMA_IOCTL_GET_BUF_COUNT, &buf_status);
+
+			while ((offset % period_bytes) && (buffer_bytes >= 0)) {
+				buffer_bytes -= period_bytes;
+				if ((buffer_bytes & offset) == offset)
+					offset = buffer_bytes;
+			}
+			pointer = offset + count;
+		} else {
+			pointer = 0;
+		}
+	}
+
+	frames = bytes_to_frames(runtime, pointer);
+
+	if (data->pcm_dbg_log.set_kernel_pcm_log & POINTER_LOG)
+		dev_info(dev, "%s %s: pointer:%zd frames:%ld\n", __func__,
+			data->name, pointer, frames);
+
+	return frames;
+}
+
+/*
+ * Sends IPC messages to the ADSP (SoC-side) to notify state transitions.
+ * The ADSP performs DMA initialization, configuration, start, or stop
+ * depending on the current ALSA PCM state.
+ *
+ * This enables a clear separation between the host (control plane)
+ * and the SoC (data plane) via a platform-agnostic IPC interface.
+ */
+
+/*
+ * ALSA PCM open callback.
+ *
+ * Sets up runtime hardware constraints, associates substream with driver
+ * data, and notifies the ADSP of stream open via IPC message.
+ * Also invokes platform-specific solution manager hooks.
+ */
+static int abox_pcm_dev_open(struct snd_soc_component *component,
+	struct snd_pcm_substream *substream)
+{
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+	struct _abox_inter_ipc_msg msg = {0, };
+	struct inter_ipc_pcmtask_msg *pcmtask_msg = &msg.msg.pcmtask;
+	int id = data->id;
+	int stream_type = substream->stream;
+	int ret;
+
+	abox_pcm_dev_request_soc_ioctl(dev, ABOX_SOC_IOCTL_CHECK_TIME_MUTEX, (void *)__func__);
+
+	if (data->pcm_dbg_log.set_kernel_pcm_log & FUNC_LOG)
+		dev_info(dev, "%s %s%s : DMA %d ADSP:%u\n",
+			__func__, abox_pcm_dev_get_soc_time(dev), data->name, data->dma_id,
+			data->adsp);
+
+	snd_soc_set_runtime_hwparams(substream, data->abox_dma_hardware);
+	ret = snd_pcm_hw_constraint_integer(substream->runtime, SNDRV_PCM_HW_PARAM_PERIODS);
+	if (ret < 0) {
+		dev_err(dev, "%s[%d] Can't set hw_constraint: %d\n", __func__, id, ret);
+		return ret;
+	}
+
+	/*
+	 * `substream` is stored to allow checking the current ALSA stream state
+	 * when the request originates from the SoC (e.g., via IPC), not ALSA core.
+	 * This ensures proper resource handling even outside standard ALSA callbacks.
+	 */
+	data->substream = substream;
+
+	ret = abox_pcm_dev_call_solution_ops(dev, ABOX_SOL_OPEN, 0, substream);
+	if (ret < 0) {
+		dev_err(dev, "Solution Open Failed ret:%d\n", ret);
+		return ret;
+	}
+	msg.ipcid = (stream_type == SNDRV_PCM_STREAM_PLAYBACK) ?
+		INTER_IPC_PCMPLAYBACK : INTER_IPC_PCMCAPTURE;
+	msg.task_id = pcmtask_msg->pcm_device_id = id;
+	pcmtask_msg->msgtype = INTER_PCM_PLTDAI_OPEN;
+	pcmtask_msg->hw_dma_id = data->dma_id;
+	pcmtask_msg->irq_id = data->irq_id;
+	pcmtask_msg->pcm_alsa_id = data->pcm_dev_num;
+	pcmtask_msg->adsp = data->adsp;
+
+	ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+
+	return ret;
+}
+
+
+/*
+ * Sends IPC messages to the ADSP (SoC-side) to notify state transitions.
+ * The ADSP performs DMA initialization, configuration, start, or stop
+ * depending on the current ALSA PCM state.
+ *
+ * This enables a clear separation between the host (control plane)
+ * and the SoC (data plane) via a platform-agnostic IPC interface.
+ */
+
+/*
+ * ALSA PCM close callback.
+ *
+ * Releases all resources previously allocated for the PCM stream.
+ * This includes invoking solution manager shutdown hooks (software and hardware),
+ * clearing the substream reference, and notifying the ADSP (via IPC) to close the stream.
+ *
+ * If the stream has been disconnected (e.g., due to ADSP failure or hotplug),
+ * the function returns with an error to prevent invalid IPC transmission.
+ */
+static int abox_pcm_dev_close(struct snd_soc_component *component,
+	struct snd_pcm_substream *substream)
+{
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+	struct _abox_inter_ipc_msg msg = {0, };
+	struct inter_ipc_pcmtask_msg *pcmtask_msg = &msg.msg.pcmtask;
+	int id = data->id;
+	int stream_type = substream->stream;
+	int ret;
+
+	if (data->pcm_dbg_log.set_kernel_pcm_log & FUNC_LOG)
+		dev_info(dev, "%s %s%s : DMA %d ADSP:%u\n",
+			__func__, abox_pcm_dev_get_soc_time(dev), data->name, data->dma_id,
+			data->adsp);
+
+	/*
+	 * Clear the substream reference.
+	 * This is necessary because some control paths originate from the SoC
+	 * (not ALSA), and this allows state validation and cleanup later.
+	 */
+	data->substream = NULL;
+	if (substream->runtime->state == SNDRV_PCM_STATE_DISCONNECTED) {
+		dev_err(dev, "%s[%d] DMA is disconnected\n", __func__, id);
+		return -EBADFD;
+	}
+
+	ret = abox_pcm_dev_call_solution_ops(dev, ABOX_SOL_CLOSE, 0, substream);
+	if (ret < 0) {
+		dev_err(dev, "Solution Close Failed ret:%d\n", ret);
+		return ret;
+	}
+	msg.ipcid = (stream_type == SNDRV_PCM_STREAM_PLAYBACK) ?
+		INTER_IPC_PCMPLAYBACK : INTER_IPC_PCMCAPTURE;
+	pcmtask_msg->msgtype = INTER_PCM_PLTDAI_CLOSE;
+	msg.task_id = pcmtask_msg->pcm_device_id = id;
+	ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+	if (ret < 0) {
+		dev_err(dev, "%s: Failed to request ipc=%d\n", __func__, ret);
+		return ret;
+	}
+
+	return ret;
+}
+
+static int abox_pcm_dev_ioctl(struct snd_soc_component *component,
+	struct snd_pcm_substream *substream, unsigned int cmd, void *arg)
+{
+	return snd_pcm_lib_ioctl(substream, cmd, arg);
+}
+
+static int abox_pcm_dev_mmap(struct snd_soc_component *component,
+	struct snd_pcm_substream *substream,
+	struct vm_area_struct *vma)
+{
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+	struct snd_pcm_runtime *runtime = substream->runtime;
+	int id = data->id;
+	int stream_type = substream->stream;
+
+	dev_dbg(dev, "%s PCM%d %s : DMA %d\n", __func__, id,
+		PCM_STREAM_STR(stream_type), data->dma_id);
+
+	return dma_mmap_wc(dev, vma, runtime->dma_area, runtime->dma_addr, runtime->dma_bytes);
+}
+
+static void abox_pcm_dev_set_ops(struct snd_soc_component_driver *drv)
+{
+	drv->open		= abox_pcm_dev_open;
+	drv->close		= abox_pcm_dev_close;
+	drv->ioctl		= abox_pcm_dev_ioctl;
+	drv->hw_params		= abox_pcm_dev_hw_params;
+	drv->hw_free		= abox_pcm_dev_hw_free;
+	drv->prepare		= abox_pcm_dev_prepare;
+	drv->trigger		= abox_pcm_dev_trigger;
+	drv->pointer		= abox_pcm_dev_pointer;
+	drv->mmap		= abox_pcm_dev_mmap;
+}
+
+/*
+ * DMA buffer is not allocated here. It is obtained from the SoC side
+ * (runtime allocated or from reserved memory). This driver merely sets it
+ * into the ALSA framework after retrieving it via abox_generic_set_dma_buffer().
+ */
+static int abox_pcm_dev_allocate_dma_buf(struct device *dev, struct snd_pcm_substream *substream)
+{
+	struct abox_platform_data *platform_data = dev_get_drvdata(dev);
+	struct snd_dma_buffer *dma_buf = &substream->dma_buffer;
+	int ret;
+
+	ret = abox_generic_set_dma_buffer(dev);
+	if (ret < 0)
+		return ret;
+
+	memcpy(dma_buf, platform_data->dma_buffer, sizeof(struct snd_dma_buffer));
+	dma_buf->dev.dev = substream->pcm->card->dev;
+
+	return ret;
+}
+
+static int abox_pcm_dev_construct(struct snd_soc_component *component,
+	struct snd_soc_pcm_runtime *runtime)
+{
+	struct snd_pcm *pcm = runtime->pcm;
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+	int ret = 0;
+	int stream_type = data->stream_type;
+	struct snd_pcm_str *stream = &pcm->streams[stream_type];
+	struct snd_pcm_substream *substream = stream->substream;
+
+	ret = abox_pcm_dev_allocate_dma_buf(dev, substream);
+	if (ret < 0) {
+		dev_warn(dev, "Can't get reserved memory (size:%zd)\n",
+			data->abox_dma_hardware->buffer_bytes_max);
+		return -ENOMEM;
+	}
+	/*
+	 * Store runtime->id as `pcm_dev_num`, which uniquely identifies
+	 * the PCM device number under the ALSA card.
+	 * This value is later passed to the SoC (via IPC) for routing decisions.
+	 */
+	data->pcm_dev_num = runtime->id;
+	dev_dbg(dev, "%s:%s ADSP%u dma_buffer.addr=%llx dma_buffer.bytes=%zd buffer_bytes: %zd\n",
+		__func__, data->name, data->adsp, substream->dma_buffer.addr,
+		substream->dma_buffer.bytes, data->abox_dma_hardware->buffer_bytes_max);
+
+	return ret;
+}
+
+static void abox_pcm_dev_destruct(struct snd_soc_component *component,
+	struct snd_pcm *pcm)
+{
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+	struct snd_pcm_str *stream = &pcm->streams[data->stream_type];
+	struct snd_pcm_substream *substream = stream->substream;
+
+	deallocate_dma_memory(dev, substream);
+}
+
+/*
+ * Device Tree match table for abox PCM playback and capture devices.
+ *
+ * This table binds the compatible strings from the Device Tree to
+ * platform-specific driver data used by the abox_pcm_dev driver.
+ * Each entry provides the base DAI driver pointer and the number
+ * of DAIs for either playback or capture.
+ */
+static const struct of_device_id samsung_abox_pcm_dev_match[] = {
+	{
+		.compatible = "samsung,abox-pcm-playback",
+		.data = (void *)&(struct abox_platform_of_data)
+		{
+			.base_dai_drv = abox_pcm_playback_dai_drv,
+			.num_of_dai_drv = ARRAY_SIZE(abox_pcm_playback_dai_drv),
+		},
+	},
+	{
+		.compatible = "samsung,abox-pcm-capture",
+		.data = (void *)&(struct abox_platform_of_data)
+		{
+			.base_dai_drv = abox_pcm_capture_dai_drv,
+			.num_of_dai_drv = ARRAY_SIZE(abox_pcm_capture_dai_drv),
+		},
+	},
+	{},
+};
+MODULE_DEVICE_TABLE(of, samsung_abox_pcm_dev_match);
+
+static int abox_pcm_dev_cmpnt_probe(struct snd_soc_component *component)
+{
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+	int ret;
+
+	data->cmpnt = component;
+
+	ret = abox_generic_set_pp_pointer(dev);
+	if (ret < 0) {
+		dev_err(dev, "%s Can't Set PP Pointer. ret : %d\n", __func__, ret);
+		return ret;
+	}
+	if (data->pp_pointer_base)
+		dev_info(dev, "%s pp_pointer:%p pp_pointer_phys:%llx\n", __func__,
+			data->pp_pointer_base, data->pp_pointer_phys);
+
+	/* Register component-specific controls (widgets, kcontrols, etc.) */
+	abox_solution_mgr_add_controls(dev, component);
+
+	return 0;
+}
+
+/*
+ * These memory regions were allocated using devm_kzalloc() during PCM device probe.
+ * They must be explicitly freed here to ensure proper resource cleanup,
+ * especially in rebind or module-unload scenarios.
+ */
+static void abox_pcm_dev_cmpnt_remove(struct snd_soc_component *component)
+{
+	struct device *dev = component->dev;
+	struct abox_platform_data *data = dev_get_drvdata(dev);
+
+	if (data->plat_dapm_widgets)
+		devm_kfree(dev, data->plat_dapm_widgets);
+
+	if (data->plat_kcontrol) {
+		if (data->plat_kcontrol->private_value)
+			devm_kfree(dev,
+				(void *)data->plat_kcontrol->private_value);
+		devm_kfree(dev, data->plat_kcontrol);
+	}
+}
+
+static int abox_pcm_dev_compr_dma_set(struct snd_soc_component *component,
+	struct snd_compr_stream *stream)
+{
+	struct device *dev;
+	struct abox_platform_data *data;
+	struct snd_dma_buffer *dma_buf;
+	struct snd_soc_pcm_runtime *runtime;
+	int ret;
+
+	if (!component || !(component->dev))
+		return -EINVAL;
+
+	dev = component->dev;
+	data = dev_get_drvdata(dev);
+
+	if (!data || !stream) {
+		dev_err(dev, "%s: No data/stream\n", __func__);
+		return -EINVAL;
+	}
+
+	dma_buf = &stream->dma_buffer;
+	memcpy(dma_buf, data->dma_buffer, sizeof(struct snd_dma_buffer));
+	dma_buf->dev.dev = stream->device->card->dev;
+
+	runtime = stream->private_data;
+	data->pcm_dev_num = runtime->id;
+	dev_dbg(dev, "%s:[%d] dma_buffer.addr=%llx dma_buffer.bytes=%zd buffer_bytes: %zd\n",
+		__func__, data->id, stream->dma_buffer.addr, stream->dma_buffer.bytes,
+		data->abox_dma_hardware->buffer_bytes_max);
+
+	return ret;
+}
+
+/*
+ * ALSA compressed stream open callback.
+ *
+ * Initializes the compressed stream by setting up DMA context,
+ * resetting byte counters, and invoking solution manager hooks
+ * for software and hardware setup. Also sends an IPC to the ADSP
+ * to notify the stream open event and relevant configuration.
+ *
+ * All necessary metadata (DMA ID, IRQ ID, ALSA ID, stream direction)
+ * is included in the IPC payload.
+ */
+static int abox_pcm_dev_compr_open(struct snd_soc_component *component,
+			struct snd_compr_stream *cstream)
+{
+	struct device *dev;
+	struct abox_platform_data *data;
+	struct _abox_inter_ipc_msg msg = {0, };
+	struct inter_ipc_offloadtask_msg *offloadtask_msg = &msg.msg.offload_task;
+	int ret;
+
+	if (!component || !(component->dev))
+		return -EINVAL;
+
+	dev = component->dev;
+	data = dev_get_drvdata(dev);
+	if (!data) {
+		dev_err(dev, "%s: No data\n", __func__);
+		return -EINVAL;
+	}
+	abox_pcm_dev_request_soc_ioctl(dev, ABOX_SOC_IOCTL_CHECK_TIME_MUTEX, (void *)__func__);
+
+	data->compr_data.cstream = cstream;
+	data->compr_data.encoded_total_bytes  = 0;
+	data->compr_data.decoded_total_bytes  = 0;
+
+	abox_pcm_dev_compr_dma_set(component, cstream);
+
+	ret = abox_pcm_dev_call_solution_ops(dev, ABOX_SOL_OPEN, 0, NULL);
+	if (ret < 0) {
+		dev_err(dev, "Solution Open Failed ret:%d\n", ret);
+		return ret;
+	}
+
+	msg.ipcid = INTER_IPC_OFFLOAD;
+	msg.task_id = offloadtask_msg->pcm_device_id = data->id;
+	offloadtask_msg->msgtype = OFFLOAD_OPEN;
+	offloadtask_msg->hw_dma_id = data->dma_id;
+	offloadtask_msg->irq_id = data->irq_id;
+	offloadtask_msg->pcm_alsa_id = data->pcm_dev_num;
+	offloadtask_msg->direction = cstream->direction;
+
+	ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+
+	return ret;
+}
+
+/*
+ * ALSA compressed stream free callback.
+ *
+ * Cleans up the compressed stream by releasing any resources allocated
+ * during open or parameter setup. This includes invoking software and
+ * hardware shutdown routines via solution manager, as well as notifying
+ * the ADSP via IPC to terminate the stream.
+ *
+ * Sets `runtime->buffer = NULL` to ensure no stale buffer is referenced.
+ */
+static int abox_pcm_dev_compr_free(struct snd_soc_component *component,
+			struct snd_compr_stream *stream)
+{
+	struct device *dev;
+	struct abox_platform_data *data;
+	struct _abox_inter_ipc_msg msg = {0, };
+	struct inter_ipc_offloadtask_msg *offloadtask_msg = &msg.msg.offload_task;
+	int ret;
+
+	if (!component || !(component->dev))
+		return -EINVAL;
+
+	dev = component->dev;
+	data = dev_get_drvdata(dev);
+
+	if (!data) {
+		dev_err(dev, "%s: No data\n", __func__);
+		return -EINVAL;
+	}
+
+	if (!stream->runtime)
+		return -EINVAL;
+	stream->runtime->buffer = NULL;
+
+	ret = abox_pcm_dev_call_solution_ops(dev, ABOX_SOL_HW_FREE, 0, NULL);
+	if (ret < 0) {
+		dev_err(dev, "Solution Close Failed ret:%d\n", ret);
+		return ret;
+	}
+
+	msg.ipcid = INTER_IPC_OFFLOAD;
+	msg.task_id = offloadtask_msg->pcm_device_id = data->id;
+	offloadtask_msg->msgtype = OFFLOAD_CLOSE;
+	offloadtask_msg->hw_dma_id = data->dma_id;
+	offloadtask_msg->irq_id = data->irq_id;
+	offloadtask_msg->pcm_alsa_id = data->pcm_dev_num;
+	offloadtask_msg->direction = stream->direction;
+
+	ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+
+	return ret;
+}
+
+/*
+ * ALSA compressed stream set_params callback.
+ *
+ * Receives codec configuration parameters from user space and prepares
+ * the internal state for offload playback. It performs the following steps:
+ *
+ * 1. Parses codec-specific fields such as sample rate, bit rate,
+ *    input/output channels, and stores them into `compr_data.codec`.
+ *
+ * 2. Assigns the preallocated DMA buffer (from SoC/reserved memory)
+ *    to the runtime structure for playback.
+ *
+ * 3. Validates that the assigned buffer is large enough for the
+ *    requested buffer size. If not, it returns -ENOMEM.
+ *
+ * 4. Sends an IPC message to the ADSP to inform it of the parameters
+ *    via the `OFFLOAD_SETPARAM` message.
+ *
+ * 5. Invokes software and hardware solution manager hooks to complete
+ *    codec configuration and DMA hardware setup.
+ *
+ * Note:
+ *  - This function is mandatory before issuing any trigger commands.
+ *  - ALSA does not validate the codec type; unknown types must be handled here.
+ *  - The buffer address is passed to ADSP via IPC for direct access.
+ */
+static int abox_pcm_dev_compr_set_params(struct snd_soc_component *component,
+				struct snd_compr_stream *cstream, struct snd_compr_params *params)
+{
+	struct snd_soc_pcm_runtime *rtd;
+	struct snd_compr_runtime *runtime;
+	struct device *dev;
+	struct abox_platform_data *data;
+	struct snd_pcm_str *stream;
+	struct snd_pcm *pcm;
+	struct snd_pcm_substream *substream;
+	struct _abox_inter_ipc_msg msg = {0, };
+	struct inter_ipc_offloadtask_msg *offload_task_msg = &msg.msg.offload_task;
+	int ret;
+
+	if (!component || !(component->dev) || !cstream || !params)
+		return -EINVAL;
+
+	rtd = cstream->private_data;
+	runtime = cstream->runtime;
+	dev = component->dev;
+	data = dev_get_drvdata(dev);
+
+	if (!rtd) {
+		dev_err(dev, "%s: No rtd\n", __func__);
+		return -EINVAL;
+	}
+	pcm = rtd->pcm;
+	stream = &pcm->streams[SNDRV_PCM_STREAM_PLAYBACK];
+	substream = stream->substream;
+
+	if (!data || !runtime) {
+		dev_err(dev, "%s: No data/runtime\n", __func__);
+		return -EINVAL;
+	}
+	data->compr_data.codec = &params->codec;
+
+	switch (data->compr_data.codec->id) {
+	case SND_AUDIOCODEC_MP3:
+	case SND_AUDIOCODEC_AAC:
+	case SND_AUDIOCODEC_FLAC:
+		dev_dbg(dev, "%s: codec id: %d\n", __func__, data->compr_data.codec->id);
+		break;
+	default:
+		dev_err(dev, "%s: unknown codec id: %d\n", __func__, data->compr_data.codec->id);
+		break;
+	}
+	/* Set runtime buffer to pre-allocated DMA area from SoC */
+	runtime->buffer = (void *)data->dma_buffer->area;
+	if (runtime->buffer_size > data->dma_buffer->bytes) {
+		dev_err(dev, "allocated buffer size is smaller than requested(%llu > %zu)\n",
+				runtime->buffer_size, data->dma_buffer->bytes);
+		return -ENOMEM;
+	}
+	msg.ipcid = INTER_IPC_OFFLOAD;
+	msg.task_id = offload_task_msg->pcm_device_id = data->id;
+	offload_task_msg->msgtype = OFFLOAD_SETPARAM;
+	offload_task_msg->hw_dma_id = data->dma_id;
+	offload_task_msg->irq_id = data->irq_id;
+	offload_task_msg->codec_id = data->compr_data.codec->id;
+	offload_task_msg->direction = cstream->direction;
+	offload_task_msg->param.setparam.sample_rate = data->compr_data.codec->sample_rate;
+	offload_task_msg->param.setparam.channels = data->compr_data.codec->ch_out;
+	offload_task_msg->param.setparam.chunk_size = runtime->buffer_size;
+	offload_task_msg->param.setparam.phyaddr = cstream->dma_buffer.addr;
+
+	ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+	if (ret < 0)
+		return ret;
+
+	ret = abox_pcm_dev_call_solution_ops(dev, ABOX_SOL_HW_PARAM, 0, substream);
+	if (ret < 0) {
+		dev_err(dev, "Solution HW PARAM Failed ret:%d\n", ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static int abox_pcm_dev_compr_set_metadata(struct snd_soc_component *component,
+			struct snd_compr_stream *stream, struct snd_compr_metadata *metadata)
+{
+	return 0;
+}
+
+static int abox_pcm_dev_compr_get_caps(struct snd_soc_component *component,
+			struct snd_compr_stream *stream, struct snd_compr_caps *caps)
+{
+	/* Returns the list of audio formats supported */
+	struct device *dev;
+	struct abox_platform_data *data;
+
+	if (!component || !(component->dev))
+		return -EINVAL;
+
+	dev = component->dev;
+	data = dev_get_drvdata(dev);
+
+	if (!data) {
+		dev_err(dev, "%s: No data\n", __func__);
+		return -EINVAL;
+	}
+
+	memcpy(caps, &abox_pcm_dev_compr_caps, sizeof(*caps));
+
+	return 0;
+}
+
+/*
+ * ALSA compressed stream trigger callback.
+ *
+ * Handles runtime control commands such as START, STOP, PAUSE, etc.
+ * Based on the trigger type, it performs:
+ *  - Solution manager callbacks for SW/HW trigger execution
+ *  - IPC notifications to ADSP to reflect state changes
+ *
+ * For START-type triggers, it includes full stream configuration
+ * such as DMA and IRQ IDs, and signals the start of decoding.
+ *
+ * For STOP-type triggers, it resets internal counters and issues a STOP
+ * IPC message to the DSP.
+ *
+ * Note: Only specific commands result in action; others (e.g., RESUME)
+ * are handled as no-ops unless explicitly required.
+ */
+static int abox_pcm_dev_compr_trigger(struct snd_soc_component *component,
+			struct snd_compr_stream *stream, int cmd)
+{
+	struct device *dev;
+	struct abox_platform_data *data;
+	int id;
+	struct _abox_inter_ipc_msg msg = {0, };
+	struct inter_ipc_offloadtask_msg *offloadtask_msg = &msg.msg.offload_task;
+	int ret;
+
+	if (!component || !(component->dev))
+		return -EINVAL;
+
+	dev = component->dev;
+	data = dev_get_drvdata(dev);
+	if (!data) {
+		dev_err(dev, "%s: No data\n", __func__);
+		return -EINVAL;
+	}
+
+	id = data->id;
+	msg.ipcid = INTER_IPC_OFFLOAD;
+	msg.task_id = offloadtask_msg->pcm_device_id = id;
+	offloadtask_msg->param.dma_trigger.is_real_dma = !data->solution_mgr_data->pp_enabled;
+
+	switch (cmd) {
+	case SNDRV_PCM_TRIGGER_START:
+	case SND_COMPR_TRIGGER_NEXT_TRACK:
+	case SNDRV_PCM_TRIGGER_PAUSE_RELEASE:
+		ret = abox_pcm_dev_call_solution_ops(dev, ABOX_SOL_TRIGGER, cmd, NULL);
+		if (ret < 0) {
+			dev_err(dev, "Solution Trigger Failed ret:%d\n", ret);
+			return ret;
+		}
+		offloadtask_msg->msgtype = offload_start;
+		offloadtask_msg->hw_dma_id = data->dma_id;
+		offloadtask_msg->irq_id = data->irq_id;
+		offloadtask_msg->direction = stream->direction;
+		offloadtask_msg->param.dma_trigger.trigger = 1;
+		ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+		break;
+	case SNDRV_PCM_TRIGGER_STOP:
+		data->compr_data.encoded_total_bytes  = 0;
+		data->compr_data.decoded_total_bytes  = 0;
+		offloadtask_msg->msgtype = OFFLOAD_STOP;
+		offloadtask_msg->hw_dma_id = data->dma_id;
+		offloadtask_msg->irq_id = data->irq_id;
+		offloadtask_msg->direction = stream->direction;
+		offloadtask_msg->param.dma_trigger.trigger = 0;
+		ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+		break;
+	case SNDRV_PCM_TRIGGER_SUSPEND:
+	case SNDRV_PCM_TRIGGER_PAUSE_PUSH:
+		break;
+	case SNDRV_PCM_TRIGGER_RESUME:
+		break;
+	case SND_COMPR_TRIGGER_DRAIN:
+	case SND_COMPR_TRIGGER_PARTIAL_DRAIN:
+		break;
+	default:
+		return -EINVAL;
+	}
+
+	return 0;
+}
+
+static int abox_pcm_dev_compr_pointer(struct snd_soc_component *component,
+		struct snd_compr_stream *stream, struct snd_compr_tstamp *tstamp)
+{
+	struct device *dev;
+	struct abox_platform_data *data;
+
+	if (!component || !(component->dev))
+		return -EINVAL;
+
+	dev = component->dev;
+	data = dev_get_drvdata(dev);
+
+	if (!data) {
+		dev_err(dev, "%s: No data\n", __func__);
+		return -EINVAL;
+	}
+
+	//copied_total: Total number of bytes copied from/to ring buffer to/by DSP
+	tstamp->copied_total = data->compr_data.decoded_total_bytes;
+	tstamp->sampling_rate = data->compr_data.codec->sample_rate;
+	tstamp->pcm_io_frames = data->compr_data.pcm_io_frames;
+
+	return 0;
+}
+
+static int abox_pcm_dev_compr_ack(struct snd_soc_component *component,
+			struct snd_compr_stream *stream, size_t bytes)
+{
+	struct device *dev;
+	struct abox_platform_data *data;
+	struct snd_compr_runtime *runtime;
+	u64 app_pointer;
+	int ret;
+	struct _abox_inter_ipc_msg msg = {0, };
+	struct inter_ipc_offloadtask_msg *offloadtask_msg = &msg.msg.offload_task;
+
+	if (!component || !(component->dev))
+		return -EINVAL;
+
+	dev = component->dev;
+	data = dev_get_drvdata(dev);
+	if (!data) {
+		dev_err(dev, "%s: No data\n", __func__);
+		return -EINVAL;
+	}
+
+	runtime = stream->runtime;
+
+	// Send offset and copied size
+	msg.ipcid = INTER_IPC_OFFLOAD;
+	msg.task_id = offloadtask_msg->pcm_device_id = data->id;
+
+	// offset
+	app_pointer = div64_u64(runtime->total_bytes_available, runtime->buffer_size);
+	app_pointer = runtime->total_bytes_available - (app_pointer * runtime->buffer_size);
+	offloadtask_msg->param.write.buff = app_pointer;
+	// size
+	offloadtask_msg->param.write.size = bytes;
+	offloadtask_msg->msgtype = offload_write;
+	offloadtask_msg->hw_dma_id = data->dma_id;
+	offloadtask_msg->irq_id = data->irq_id;
+	offloadtask_msg->pcm_alsa_id = data->pcm_dev_num;
+	offloadtask_msg->direction = stream->direction;
+
+	ret = abox_pcm_dev_request_ipc(data, &msg, true, NULL);
+
+	return ret;
+}
+
+static int abox_pcm_dev_compr_mmap(struct snd_soc_component *component,
+		    struct snd_compr_stream *stream, struct vm_area_struct *vma)
+{
+	return 0;
+}
+
+static int abox_pcm_dev_compr_get_codec_caps(struct snd_soc_component *component,
+				struct snd_compr_stream *stream, struct snd_compr_codec_caps *codec)
+{
+	return 0;
+}
+
+static irqreturn_t abox_pcm_dev_compr_irq_handler(struct device *pcm_dev, int irq_id,
+	struct _abox_inter_ipc_msg *pmsg)
+{
+	struct abox_platform_data *data;
+	struct snd_compr_stream *cstream;
+	int consume_size;
+	struct _abox_inter_ipc_msg *ipc_msg;
+
+
+	data = dev_get_drvdata(pcm_dev);
+	if (!data)
+		return IRQ_HANDLED;
+
+	ipc_msg = (struct _abox_inter_ipc_msg *)pmsg;
+	if (ipc_msg->msg.offload_task.msgtype == offload_write) {
+		consume_size = ipc_msg->msg.offload_task.param.write.size;
+		data->compr_data.pcm_io_frames = ipc_msg->msg.offload_task.param.write.buff;
+	} else
+		return IRQ_HANDLED;
+
+	cstream = data->compr_data.cstream;
+	/*
+	 * If consume_size is 0, it means the DMA engine has stopped on the ADSP side.
+	 * This typically happens at the end of playback when the DSP finishes decoding
+	 * and no more data remains to be consumed.
+	 *
+	 * Since compressed streams are handled entirely by the DSP in offload mode,
+	 * the host must rely on such IRQ notifications to infer playback completion.
+	 * In this case, the ALSA state is explicitly transitioned to PAUSED to reflect
+	 * this condition, as ALSA core does not automatically update the state from
+	 * ADSP-side events.
+	 */
+	if (!consume_size && cstream->runtime->state == SNDRV_PCM_STATE_DRAINING)
+		cstream->runtime->state = SNDRV_PCM_STATE_PAUSED;
+	data->compr_data.decoded_total_bytes += consume_size;
+	snd_compr_fragment_elapsed(cstream);
+
+	return IRQ_HANDLED;
+}
+
+static const struct snd_compress_ops abox_pcm_dev_compress_ops = {
+	.open			= abox_pcm_dev_compr_open,
+	.free			= abox_pcm_dev_compr_free,
+	.set_params		= abox_pcm_dev_compr_set_params,
+	.set_metadata		= abox_pcm_dev_compr_set_metadata,
+	.get_caps		= abox_pcm_dev_compr_get_caps,
+	.trigger		= abox_pcm_dev_compr_trigger,
+	.pointer		= abox_pcm_dev_compr_pointer,
+	.copy			= NULL, // use alsa snd_compr_write_data()
+	.mmap			= abox_pcm_dev_compr_mmap,
+	.ack			= abox_pcm_dev_compr_ack,
+	.get_codec_caps		= abox_pcm_dev_compr_get_codec_caps,
+};
+
+static const struct snd_soc_component_driver abox_pcm_dev_base = {
+	.probe		= abox_pcm_dev_cmpnt_probe,
+	.remove		= abox_pcm_dev_cmpnt_remove,
+	.pcm_construct	= abox_pcm_dev_construct,
+	.pcm_destruct	= abox_pcm_dev_destruct,
+	.probe_order	= SND_SOC_COMP_ORDER_LAST,
+};
+
+static int abox_pcm_dev_get_pp_route(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
+	struct device *dev = w->dapm->dev;
+	struct abox_platform_data *pcm_data = dev_get_drvdata(dev);
+
+	ucontrol->value.enumerated.item[0] = pcm_data->solution_mgr_data->pp_enabled;
+
+	return 0;
+}
+
+static int abox_pcm_dev_put_pp_route(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	struct snd_soc_dapm_update *update = NULL;
+	struct device *dev = w->dapm->dev;
+	struct abox_platform_data *pcm_data = dev_get_drvdata(dev);
+	unsigned int item = ucontrol->value.enumerated.item[0];
+
+	if (item >= e->items) {
+		dev_err(dev, "%s: Invalid parameter\n", __func__);
+		return -EINVAL;
+	}
+
+	if (!pcm_data->solution_mgr_data) {
+		dev_info(dev, "%s: PP Solution is not ready\n", __func__);
+		return 0;
+	}
+	pcm_data->solution_mgr_data->pp_enabled = item ? true : false;
+	snd_soc_dapm_mux_update_power(w->dapm, kcontrol, item, e, update);
+
+	return 0;
+}
+
+static const char * const pcm_dev_route_texts[] = {
+	"Direct", "PostProcessing",
+};
+static SOC_ENUM_SINGLE_DECL(pcm_dev_route_enum, SND_SOC_NOPM,
+		0, pcm_dev_route_texts);
+static const struct snd_kcontrol_new pcm_dev_route_controls[] = {
+	SOC_DAPM_ENUM_EXT("DEMUX", pcm_dev_route_enum,
+		abox_pcm_dev_get_pp_route,
+		abox_pcm_dev_put_pp_route),
+	SOC_DAPM_ENUM_EXT("MUX", pcm_dev_route_enum,
+		abox_pcm_dev_get_pp_route,
+		abox_pcm_dev_put_pp_route)
+};
+
+static const char * const pcm_dev_backend_route_texts[] = {
+	"DMA", "I2S Dummy",
+};
+static SOC_ENUM_SINGLE_DECL(pcm_dev_backend_route_enum, SND_SOC_NOPM,
+		0, pcm_dev_backend_route_texts);
+static const struct snd_kcontrol_new pcm_dev_backend_route_controls[] = {
+	SOC_DAPM_ENUM("MUX", pcm_dev_backend_route_enum),
+};
+
+static int abox_pcm_dev_add_route_to_dapm(struct device *pcm_dev,
+	struct snd_soc_dapm_route *add_routes, int add_route_cnt)
+{
+	struct abox_platform_data *data = dev_get_drvdata(pcm_dev);
+	struct snd_soc_dapm_context *dapm;
+
+	dev_dbg(pcm_dev, "[%s] %s Add Route to DAPM\n", __func__, data->name);
+
+	dapm = snd_soc_component_get_dapm(data->cmpnt);
+	if (!dapm) {
+		dev_err(pcm_dev, "%s: can't get dapm from component\n", __func__);
+		return -EINVAL;
+	}
+
+	return snd_soc_dapm_add_routes(dapm, add_routes, add_route_cnt);
+}
+
+static int abox_pcm_dev_add_route_to_cmpnt_drv(struct device *pcm_dev,
+	struct snd_soc_dapm_route *add_routes, int add_route_cnt)
+{
+	struct abox_platform_data *data = dev_get_drvdata(pcm_dev);
+	struct snd_soc_dapm_route *cmpnt_routes;
+	const struct snd_soc_dapm_route *tmp_routes;
+	int current_route_cnt = 0;
+	int total_route_cnt = 0;
+
+	current_route_cnt = data->cmpnt_drv->num_dapm_routes;
+	total_route_cnt = current_route_cnt + add_route_cnt;
+
+	cmpnt_routes = devm_kcalloc(pcm_dev, total_route_cnt, sizeof(struct snd_soc_dapm_route),
+		GFP_KERNEL);
+	if (!cmpnt_routes)
+		return -ENOMEM;
+	tmp_routes = data->cmpnt_drv->dapm_routes;
+	memcpy(cmpnt_routes, data->cmpnt_drv->dapm_routes,
+		sizeof(struct snd_soc_dapm_route) * current_route_cnt);
+	memcpy(&cmpnt_routes[current_route_cnt], add_routes,
+		sizeof(struct snd_soc_dapm_route) * add_route_cnt);
+	data->cmpnt_drv->dapm_routes = cmpnt_routes;
+	data->cmpnt_drv->num_dapm_routes = total_route_cnt;
+
+	devm_kfree(pcm_dev, tmp_routes);
+
+	return 0;
+}
+
+static int abox_pcm_dev_add_route(struct device *pcm_dev,
+	struct snd_soc_dapm_route *add_routes, int add_route_cnt)
+{
+	struct abox_platform_data *data = dev_get_drvdata(pcm_dev);
+
+	if (data->cmpnt)
+		return abox_pcm_dev_add_route_to_dapm(pcm_dev, add_routes, add_route_cnt);
+	else
+		return abox_pcm_dev_add_route_to_cmpnt_drv(pcm_dev, add_routes, add_route_cnt);
+}
+
+static struct snd_soc_dapm_route *abox_pcm_dev_allocating_route(struct device *pcm_dev,
+	const struct snd_soc_dapm_route *routes_base, int num_of_routes,
+	int source_id, int control_id, int sink_id)
+{
+	struct snd_soc_dapm_route *routes;
+	int routes_idx;
+
+	routes = devm_kcalloc(pcm_dev, num_of_routes, sizeof(*routes), GFP_KERNEL);
+	if (!routes)
+		return NULL;
+
+	for (routes_idx = 0; routes_idx < num_of_routes; routes_idx++) {
+		routes[routes_idx].source = devm_kasprintf(pcm_dev, GFP_KERNEL,
+			routes_base[routes_idx].source, source_id);
+		routes[routes_idx].control = devm_kasprintf(pcm_dev, GFP_KERNEL,
+			routes_base[routes_idx].control, control_id);
+		routes[routes_idx].sink = devm_kasprintf(pcm_dev, GFP_KERNEL,
+			routes_base[routes_idx].sink, sink_id);
+		dev_dbg(pcm_dev, "%s(%d):[%d] sink[%s] <- control[%s] <- source[%s]\n",
+			__func__, __LINE__, routes_idx,
+			routes[routes_idx].sink,
+			routes[routes_idx].control,
+			routes[routes_idx].source);
+	}
+	return routes;
+}
+
+static int abox_pcm_dev_making_dma_routes(struct device *pcm_dev, int dma_id)
+{
+	struct abox_platform_data *data = dev_get_drvdata(pcm_dev);
+	int num_of_routes;
+	int source_id;
+	int sink_id;
+	int control_id;
+	int ret;
+	const struct snd_soc_dapm_route *dma_routes_base;
+	struct snd_soc_dapm_route *dma_routes;
+	static const struct snd_soc_dapm_route abox_pcm_playback_routes_base[] = {
+		/* sink, control, source */
+		{"RDMA%d",		"RDMA%d",	"PCM%dp RDMA Route"},
+	};
+	static const struct snd_soc_dapm_route abox_pcm_capture_routes_base[] = {
+		/* sink, control, source */
+		{"PCM%dc WDMA Route",	"WDMA%d",	"WDMA%d"},
+	};
+
+	if (data->stream_type == SNDRV_PCM_STREAM_PLAYBACK) {
+		num_of_routes = ARRAY_SIZE(abox_pcm_playback_routes_base);
+		dma_routes_base = abox_pcm_playback_routes_base;
+		source_id = data->id;
+		control_id = sink_id = dma_id;
+	} else {
+		num_of_routes = ARRAY_SIZE(abox_pcm_capture_routes_base);
+		dma_routes_base = abox_pcm_capture_routes_base;
+		sink_id = data->id;
+		control_id = source_id = dma_id;
+	}
+	dma_routes = abox_pcm_dev_allocating_route(pcm_dev, dma_routes_base, num_of_routes,
+		source_id, control_id, sink_id);
+	ret = abox_pcm_dev_add_route(pcm_dev, dma_routes, num_of_routes);
+	if (ret < 0) {
+		dev_err(pcm_dev, "%s: Failed to add dma routes. ret:%d\n", __func__, ret);
+		return ret;
+	}
+
+	return 0;
+}
+
+static void abox_pcm_dev_making_routes(struct device *dev,
+	struct abox_platform_data *data,
+	struct snd_soc_component_driver *cmpnt)
+{
+	int i;
+	int stream_type = data->stream_type;
+	int pcm_single_routes_size;
+	const struct snd_soc_dapm_route *abox_pcm_single_routes;
+	int num_of_single_routes;
+	static const struct snd_soc_dapm_route abox_pcm_playback_single_routes[] = {
+		/* sink, control, source */
+		/* Playback to PP route */
+		{"PCM%dp PP Route", NULL, "PCM%d Playback"},
+		/* PP Route to PostProcessing */
+		{"PCM%dp PostProcessing", "PostProcessing", "PCM%dp PP Route"},
+		/* PP Route to Backend Route */
+		{"PCM%dp Backend Route", "Direct", "PCM%dp PP Route"},
+		/* PostProcessing to Backend Route */
+		{"PCM%dp Backend Route", NULL, "PCM%dp PostProcessing"},
+		/* Backend Route to I2S Dummy Route */
+		{"PCM%dp I2S Dummy Route", "I2S Dummy", "PCM%dp Backend Route"},
+		/* Backend Route to RDMA Route */
+		{"PCM%dp RDMA Route", "DMA", "PCM%dp Backend Route"},
+	};
+	static const struct snd_soc_dapm_route abox_pcm_capture_single_routes[] = {
+		/* sink, control, source */
+		/* I2S Dummy Route to Backend Route */
+		{"PCM%dc Backend Route", "I2S Dummy", "PCM%dc I2S Dummy Route"},
+		/* WDMA Route to Backend Route */
+		{"PCM%dc Backend Route", "DMA", "PCM%dc WDMA Route"},
+		/* Backend Route to PP Route */
+		{"PCM%dc PP Route", NULL, "PCM%dc Backend Route"},
+		/* PP Route to PCM Capture by pcm_capture_route_texts */
+		{"PCM%d Capture", "Direct", "PCM%dc PP Route"},
+		/* PP Route to PCM PostProcessing */
+		{"PCM%dc PostProcessing", "PostProcessing", "PCM%dc PP Route"},
+		/* PostProcessing to PCM Capture */
+		{"PCM%d Capture", NULL, "PCM%dc PostProcessing"},
+	};
+
+	if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) {
+		num_of_single_routes = ARRAY_SIZE(abox_pcm_playback_single_routes);
+		pcm_single_routes_size = sizeof(abox_pcm_playback_single_routes);
+		abox_pcm_single_routes = abox_pcm_playback_single_routes;
+	} else {
+		num_of_single_routes = ARRAY_SIZE(abox_pcm_capture_single_routes);
+		pcm_single_routes_size = sizeof(abox_pcm_capture_single_routes);
+		abox_pcm_single_routes = abox_pcm_capture_single_routes;
+	}
+
+	data->plat_dapm_routes = devm_kzalloc(dev, pcm_single_routes_size, GFP_KERNEL);
+	if (!data->plat_dapm_routes)
+		return;
+
+	for (i = 0; i < num_of_single_routes; i++) {
+		if (abox_pcm_single_routes[i].sink)
+			data->plat_dapm_routes[i].sink = devm_kasprintf(dev, GFP_KERNEL,
+				abox_pcm_single_routes[i].sink, data->id);
+		if (abox_pcm_single_routes[i].control)
+			data->plat_dapm_routes[i].control = devm_kasprintf(dev, GFP_KERNEL,
+				abox_pcm_single_routes[i].control);
+		if (abox_pcm_single_routes[i].source)
+			data->plat_dapm_routes[i].source = devm_kasprintf(dev, GFP_KERNEL,
+				abox_pcm_single_routes[i].source, data->id);
+
+		dev_dbg(dev, "%s(%d):[%d] sink[%s] <- control[%s] <- source[%s]\n",
+			__func__, __LINE__, i,
+			data->plat_dapm_routes[i].sink,
+			data->plat_dapm_routes[i].control,
+			data->plat_dapm_routes[i].source);
+	}
+
+	cmpnt->dapm_routes = data->plat_dapm_routes;
+	cmpnt->num_dapm_routes = num_of_single_routes;
+}
+
+static int abox_pcm_dev_get_dma_route(struct snd_kcontrol *kcontrol,
+				 struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
+	struct device *dev = w->dapm->dev;
+	struct abox_platform_data *pcm_data = dev_get_drvdata(dev);
+	const int no_connect = 1;
+
+	ucontrol->value.enumerated.item[0] = pcm_data->dma_id + no_connect;
+
+	return 0;
+}
+
+static int abox_pcm_dev_put_dma_route(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_dapm_widget *w = snd_soc_dapm_kcontrol_widget(kcontrol);
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+	struct device *dev = w->dapm->dev;
+	struct abox_platform_data *pcm_data = dev_get_drvdata(dev);
+	struct abox_platform_dma_info *dma_info;
+	struct snd_soc_dapm_update *update;
+	unsigned int item = ucontrol->value.enumerated.item[0];
+	const int no_connect = 1;
+
+	if (item >= e->items) {
+		dev_err(dev, "%s: Invalid parameter\n", __func__);
+		return -EINVAL;
+	}
+
+	pcm_data->dma_id = item - no_connect;
+	if (pcm_data->dma_id >= 0) {
+		dma_info = abox_pcm_dev_find_dma_info(dev, pcm_data->dma_id);
+		dma_info->dma_ioctl(dma_info->dma_dev, ABOX_DMA_IOCTL_SET_ADSP_INFO,
+			&pcm_data->adsp);
+	}
+	snd_soc_dapm_mux_update_power(w->dapm, kcontrol, item, e, update);
+
+	return 0;
+}
+
+static const char * const *abox_pcm_dev_making_enum_str(struct device *pcm_dev,
+	int num_of_enum)
+{
+	struct abox_platform_data *pcm_data = dev_get_drvdata(pcm_dev);
+	const char *default_enum[] = {"PCM_OUT", "PCM_IN"};
+	char **enum_str;
+	int index;
+	int stream_type;
+	int ret;
+
+	stream_type = pcm_data->stream_type;
+	/* The default value of Route is NO_CONNECT, not Widgets.
+	 * Therefore, the size of the enum must be one greater than num_of_dma.
+	 */
+	enum_str = devm_kcalloc(pcm_dev, (num_of_enum + 1), sizeof(char *), GFP_KERNEL);
+	if (!enum_str)
+		return NULL;
+
+	for (index = 0; index < (num_of_enum + 1); index++) {
+		enum_str[index] = devm_kzalloc(pcm_dev, DEFAULT_STR_SIZE, GFP_KERNEL);
+		if (!enum_str[index])
+			return NULL;
+		ret = snprintf(enum_str[index], DEFAULT_STR_SIZE - 1, default_enum[stream_type]);
+		if (ret >= (DEFAULT_STR_SIZE - 1))
+			dev_warn(pcm_dev, "%s(%d) Buffer truncated (needed %d bytes)\n", __func__,
+				__LINE__, ret);
+	}
+
+	return (const char * const *)enum_str;
+}
+
+/**
+ * abox_generic_init_soc_route - Initialize route controls for PCM devices
+ * @soc_dev: Device pointer of the SoC master driver
+ *
+ * This function is called during the probe of the SoC master driver.
+ * It initializes ABOX frontend PCM devices by informing them of the
+ * number of DMA engines (RDMA or WDMA) available for each stream type
+ * (playback or capture). This allows each PCM device to pre-create
+ * the DAPM route kcontrols (e.g., DMA MUX) even before DMA devices
+ * are actually probed and attached.
+ *
+ * Later, when individual DMA devices are registered and attached,
+ * the corresponding mux controls will be dynamically updated to reflect
+ * the actually available routes.
+ *
+ * Return: 0 on success or a negative error code on failure.
+ */
+static struct snd_kcontrol_new *abox_pcm_dev_making_dma_kcontrol(struct device *pcm_dev,
+	unsigned int num_of_dma, int *num_of_kcontrols)
+{
+	const char * const *enum_dma_str = abox_pcm_dev_making_enum_str(pcm_dev, num_of_dma);
+	struct soc_enum dma_route_enum_base =
+		SOC_ENUM_DOUBLE(SND_SOC_NOPM, 0, 0, (num_of_dma + 1), enum_dma_str);
+	struct soc_enum *dma_route_enum = devm_kmemdup(pcm_dev, &dma_route_enum_base,
+		sizeof(struct soc_enum), GFP_KERNEL);
+	const struct snd_kcontrol_new dma_route_controls_base[] = {
+		SOC_DAPM_ENUM_EXT(PCM_KCTL_DMA_MUX, (*dma_route_enum),
+			abox_pcm_dev_get_dma_route, abox_pcm_dev_put_dma_route)
+	};
+	struct snd_kcontrol_new *dma_route_controls =
+		devm_kmemdup(pcm_dev, dma_route_controls_base,
+			sizeof(struct snd_kcontrol_new) * ARRAY_SIZE(dma_route_controls_base),
+			GFP_KERNEL);
+	*num_of_kcontrols = ARRAY_SIZE(dma_route_controls_base);
+
+	return dma_route_controls;
+}
+
+static struct snd_kcontrol_new *abox_pcm_dev_making_dummy_kcontrol(struct device *pcm_dev,
+	unsigned int num_of_i2s_dummy, int *num_of_kcontrols)
+{
+	const char * const *enum_dummy_str =
+		abox_pcm_dev_making_enum_str(pcm_dev, num_of_i2s_dummy);
+	struct soc_enum dummy_route_enum_base =
+		SOC_ENUM_DOUBLE(SND_SOC_NOPM, 0, 0, (num_of_i2s_dummy + 1), enum_dummy_str);
+	struct soc_enum *dummy_route_enum = devm_kmemdup(pcm_dev, &dummy_route_enum_base,
+		sizeof(struct soc_enum), GFP_KERNEL);
+	const struct snd_kcontrol_new dummy_route_controls_base[] = {
+		SOC_DAPM_ENUM(PCM_KCTL_DUMMY_MUX, (*dummy_route_enum))
+	};
+	struct snd_kcontrol_new *dummy_route_controls =
+		devm_kmemdup(pcm_dev, dummy_route_controls_base,
+			sizeof(struct snd_kcontrol_new) * ARRAY_SIZE(dummy_route_controls_base),
+			GFP_KERNEL);
+	*num_of_kcontrols = ARRAY_SIZE(dummy_route_controls_base);
+
+	return dummy_route_controls;
+}
+
+int abox_pcm_dev_register_dma_route_kcontrol(struct device *pcm_dev, int num_of_dma)
+{
+	struct abox_platform_data *data = dev_get_drvdata(pcm_dev);
+	struct snd_kcontrol_new *dma_route_controls;
+	struct snd_soc_dapm_widget *dapm_playback_widget;
+	char widget_name[DEFAULT_STR_SIZE] = {0, };
+	int ret;
+	int num_of_kcontrols;
+
+	ret = abox_pcm_dev_strcat_with_prefix_name(pcm_dev, widget_name,
+		data->stream_type == SNDRV_PCM_STREAM_PLAYBACK ? "RDMA Route" : "WDMA Route");
+	if (ret < 0) {
+		dev_err(pcm_dev, "%s(%d) Failed to make widget name\n", __func__, __LINE__);
+		return ret;
+	}
+	dapm_playback_widget = abox_pcm_dev_find_widget(pcm_dev, widget_name);
+	if (!dapm_playback_widget) {
+		dev_err(pcm_dev, "%s(%d) Failed to find widget. Name:%s\n",
+			__func__, __LINE__, widget_name);
+		return -EINVAL;
+	}
+	dma_route_controls =
+		abox_pcm_dev_making_dma_kcontrol(pcm_dev, num_of_dma, &num_of_kcontrols);
+	dapm_playback_widget->kcontrol_news = dma_route_controls;
+	dapm_playback_widget->num_kcontrols = num_of_kcontrols;
+
+	return 0;
+}
+
+static void abox_pcm_dev_register_dummy_route_kcontrol(struct device *pcm_dev)
+{
+	struct snd_kcontrol_new *dummy_route_controls = NULL;
+	struct snd_soc_dapm_widget *dapm_playback_widget = NULL;
+	char widget_name[DEFAULT_STR_SIZE] = {0, };
+	int ret;
+	int num_of_kcontrols;
+	int num_of_i2s_dummy = abox_generic_get_num_i2s_dummy();
+
+	ret = abox_pcm_dev_strcat_with_prefix_name(pcm_dev, widget_name, "I2S Dummy Route");
+	if (ret < 0) {
+		dev_err(pcm_dev, "%s(%d) Failed to make widget name\n", __func__, __LINE__);
+		return;
+	}
+	dapm_playback_widget = abox_pcm_dev_find_widget(pcm_dev, widget_name);
+	if (!dapm_playback_widget) {
+		dev_err(pcm_dev, "%s(%d) Failed to find widget. Name:%s\n",
+			__func__, __LINE__, widget_name);
+		return;
+	}
+	dummy_route_controls = abox_pcm_dev_making_dummy_kcontrol(pcm_dev, num_of_i2s_dummy,
+		&num_of_kcontrols);
+	dapm_playback_widget->kcontrol_news = dummy_route_controls;
+	dapm_playback_widget->num_kcontrols = num_of_kcontrols;
+}
+
+static struct snd_soc_dapm_widget *abox_pcm_dev_get_playback_widget(
+	struct device *dev, struct abox_platform_data *data, int *array_size)
+{
+	struct snd_soc_dapm_widget *dapm_playback_widgets;
+	const struct snd_soc_dapm_widget dapm_playback_widgets_base[] = {
+		SND_SOC_DAPM_DEMUX("PCM%dp PP Route", SND_SOC_NOPM, 0, 0, pcm_dev_route_controls),
+		ABOX_SND_SOC_DAPM_BUFFER("PCM%dp PostProcessing", SND_SOC_NOPM, NULL),
+		SND_SOC_DAPM_DEMUX("PCM%dp Backend Route", SND_SOC_NOPM, 0, 0,
+			pcm_dev_backend_route_controls),
+		SND_SOC_DAPM_DEMUX("PCM%dp I2S Dummy Route", SND_SOC_NOPM, 0, 0, NULL),
+		SND_SOC_DAPM_DEMUX("PCM%dp RDMA Route", SND_SOC_NOPM, 0, 0, NULL),
+	};
+
+	dapm_playback_widgets = devm_kmemdup(dev, dapm_playback_widgets_base,
+		sizeof(dapm_playback_widgets_base), GFP_KERNEL);
+	if (!dapm_playback_widgets)
+		return NULL;
+	*array_size = ARRAY_SIZE(dapm_playback_widgets_base);
+
+	return dapm_playback_widgets;
+}
+
+static struct snd_soc_dapm_widget *abox_pcm_dev_get_capture_widget(
+	struct device *dev, struct abox_platform_data *data, int *array_size)
+{
+	struct snd_soc_dapm_widget *dapm_capture_widgets;
+	const struct snd_soc_dapm_widget dapm_capture_widgets_base[] = {
+		SND_SOC_DAPM_MUX("PCM%dc WDMA Route", SND_SOC_NOPM, 0, 0, NULL),
+		SND_SOC_DAPM_MUX("PCM%dc I2S Dummy Route", SND_SOC_NOPM, 0, 0, NULL),
+		SND_SOC_DAPM_MUX("PCM%dc Backend Route", SND_SOC_NOPM, 0, 0,
+			pcm_dev_backend_route_controls),
+		ABOX_SND_SOC_DAPM_BUFFER("PCM%dc PostProcessing", SND_SOC_NOPM, NULL),
+		SND_SOC_DAPM_DEMUX("PCM%dc PP Route", SND_SOC_NOPM, 0, 0,
+			pcm_dev_route_controls),
+	};
+
+	dapm_capture_widgets = devm_kmemdup(dev, dapm_capture_widgets_base,
+		sizeof(dapm_capture_widgets_base), GFP_KERNEL);
+	if (!dapm_capture_widgets)
+		return NULL;
+	*array_size = ARRAY_SIZE(dapm_capture_widgets_base);
+
+	return dapm_capture_widgets;
+}
+
+static void abox_pcm_dev_making_widget(struct device *dev,
+	struct abox_platform_data *data,
+	struct snd_soc_component_driver *cmpnt)
+{
+	int i;
+	int array_size;
+
+	if (data->stream_type == SNDRV_PCM_STREAM_PLAYBACK)
+		data->plat_dapm_widgets =
+			abox_pcm_dev_get_playback_widget(dev, data, &array_size);
+	else
+		data->plat_dapm_widgets =
+			abox_pcm_dev_get_capture_widget(dev, data, &array_size);
+
+	for (i = 0; i < array_size; i++) {
+		data->plat_dapm_widgets[i].name = devm_kasprintf(dev,
+			GFP_KERNEL, data->plat_dapm_widgets[i].name, data->dai_drv->id);
+
+		dev_dbg(dev, "%s: dapm_widget:%s kcontrol_news:%p\n", __func__,
+			data->plat_dapm_widgets[i].name, data->plat_dapm_widgets[i].kcontrol_news);
+	}
+	cmpnt->dapm_widgets = data->plat_dapm_widgets;
+	cmpnt->num_dapm_widgets = array_size;
+}
+
+static int abox_pcm_dev_register_component(struct device *dev,
+	struct abox_platform_data *data)
+{
+	const struct of_device_id *of_node_id;
+	int ret;
+	int i;
+
+	if (!dev->of_node)
+		return -ENODEV;
+	of_node_id = of_match_node(&samsung_abox_pcm_dev_match[data->stream_type], dev->of_node);
+	if (!of_node_id) {
+		dev_err(dev, "%s(%d) of_node_id is not ready\n", __func__, __LINE__);
+		return -ENODEV;
+	}
+	data->of_data = of_node_id->data;
+
+	data->dai_drv = devm_kcalloc(dev, data->of_data->num_of_dai_drv,
+				sizeof(struct snd_soc_dai_driver), GFP_KERNEL);
+	if (!data->dai_drv)
+		return -ENOMEM;
+
+	memcpy(data->dai_drv, data->of_data->base_dai_drv,
+		sizeof(struct snd_soc_dai_driver) * data->of_data->num_of_dai_drv);
+	for (i = 0; i < data->of_data->num_of_dai_drv; i++) {
+		data->dai_drv[i].id = data->id;
+		data->dai_drv[i].name = devm_kasprintf(dev,
+			GFP_KERNEL, data->dai_drv[i].name, data->id);
+		if (data->stream_type == SNDRV_PCM_STREAM_PLAYBACK) {
+			data->dai_drv[i].playback.stream_name = devm_kasprintf(dev,
+				GFP_KERNEL, data->dai_drv[i].playback.stream_name, data->id);
+			if (data->category == PLATFORM_COMPRESS)
+				data->dai_drv[i].ops = &abox_compr_dai_ops;
+		} else {
+			data->dai_drv[i].capture.stream_name = devm_kasprintf(dev,
+				GFP_KERNEL, data->dai_drv[i].capture.stream_name, data->id);
+		}
+	}
+
+	data->cmpnt_drv = devm_kmemdup(dev, &abox_pcm_dev_base,
+		sizeof(abox_pcm_dev_base), GFP_KERNEL);
+	if (!data->cmpnt_drv)
+		return -ENOMEM;
+
+	abox_pcm_dev_making_widget(dev, data, data->cmpnt_drv);
+	abox_pcm_dev_making_routes(dev, data, data->cmpnt_drv);
+	abox_pcm_dev_register_dummy_route_kcontrol(dev);
+
+	abox_pcm_dev_set_ops(data->cmpnt_drv);
+
+	if (data->category == PLATFORM_COMPRESS) {
+		data->cmpnt_drv->compress_ops = &abox_pcm_dev_compress_ops;
+		ret = abox_generic_set_dma_buffer(dev);
+		if (ret < 0)
+			return ret;
+	}
+
+	ret = devm_snd_soc_register_component(dev, data->cmpnt_drv,
+			data->dai_drv, data->of_data->num_of_dai_drv);
+	if (ret < 0)
+		dev_warn(dev, "%s: component register failed:%d\n", __func__,  ret);
+
+	return ret;
+}
+
+static int abox_pcm_dev_update_dma_route_kcontrol_enum(struct device *pcm_dev,
+	int dma_id)
+{
+	struct abox_platform_data *data = dev_get_drvdata(pcm_dev);
+	struct snd_soc_dapm_widget *widget;
+	struct snd_kcontrol_new *kctl;
+	struct soc_enum *dma_enum;
+	const char * const *enum_dma_str;
+	char widget_name[DEFAULT_STR_SIZE] = {0, };
+	int ret;
+
+	if (!data)
+		return -ENODATA;
+
+	ret = abox_pcm_dev_strcat_with_prefix_name(pcm_dev, widget_name,
+		data->stream_type == SNDRV_PCM_STREAM_PLAYBACK ? "RDMA Route" : "WDMA Route");
+	if (ret < 0)
+		return ret;
+
+	widget = abox_pcm_dev_find_widget(pcm_dev, widget_name);
+	if (!widget)
+		return -ENODATA;
+	kctl = abox_pcm_dev_find_kcontrol(pcm_dev, widget, PCM_KCTL_DMA_MUX);
+	if (!kctl)
+		return -ENODATA;
+
+	dma_enum = (struct soc_enum *)kctl->private_value;
+	enum_dma_str = dma_enum->texts;
+	/*
+	 * 'enum_dma_str' is declared as 'const char * const *' to comply with
+	 * ALSA's soc_enum interface, but each entry was originally allocated
+	 * as writable memory via devm_kzalloc(). Therefore, writing to these
+	 * entries is safe and intentional.
+	 *
+	 * The default value of Route is NO_CONNECT, not DMA0.
+	 * Therefore, the size of the enum must be one greater than num_of_dma.
+	 */
+	if (data->stream_type == SNDRV_PCM_STREAM_PLAYBACK)
+		ret = snprintf((char *)enum_dma_str[dma_id + 1], DEFAULT_STR_SIZE - 1,
+			"RDMA%d", dma_id);
+	else
+		ret = snprintf((char *)enum_dma_str[dma_id + 1], DEFAULT_STR_SIZE - 1,
+			"WDMA%d", dma_id);
+	if (ret < 0)
+		return ret;
+
+	return 0;
+}
+
+/*
+ * Add the given DMA information to the list of DMA devices
+ * associated with this PCM device.
+ *
+ * This is typically called from the solution manager or device
+ * registration path when the DMA resource is discovered dynamically.
+ */
+static void abox_pcm_dev_input_list(struct device *pcm_dev,
+	struct abox_platform_dma_info *dma_info)
+{
+	struct abox_platform_data *data;
+
+	data = dev_get_drvdata(pcm_dev);
+	if (!data) {
+		dev_err(pcm_dev, "%s PCM data is not ready\n", __func__);
+		return;
+	}
+	list_add(&dma_info->list, &data->dma_list_head);
+}
+
+void abox_pcm_dev_attach_solution_mgr(struct device *pcm_dev,
+	struct abox_solution_mgr_data *solution_mgr_data)
+{
+	struct abox_platform_data *pcm_data = dev_get_drvdata(pcm_dev);
+
+	if (!pcm_data) {
+		dev_err(pcm_dev, "%s(%d) Failed to get PCM DATA\n", __func__, __LINE__);
+		return;
+	}
+	pcm_data->solution_mgr_data = solution_mgr_data;
+}
+
+int abox_pcm_dev_attach_dma(struct device *pcm_dev, struct device *dma_dev,
+				int dma_id, dma_ioctl_fn dma_ioctl)
+{
+	struct abox_platform_dma_info *dma_info;
+	struct abox_platform_data *data;
+	int ret;
+	int num_of_dma;
+
+	data = dev_get_drvdata(pcm_dev);
+	if (!data)
+		return -ENODATA;
+
+	num_of_dma = abox_generic_get_num_dma(pcm_dev, data->stream_type);
+	if (dma_id >= num_of_dma)
+		return -EINVAL;
+
+	ret = abox_pcm_dev_update_dma_route_kcontrol_enum(pcm_dev, dma_id);
+	if (ret < 0)
+		return ret;
+	ret = abox_pcm_dev_making_dma_routes(pcm_dev, dma_id);
+	if (ret < 0)
+		return ret;
+
+	dma_info = devm_kzalloc(pcm_dev, sizeof(*dma_info), GFP_KERNEL);
+	if (!dma_info)
+		return -ENOMEM;
+	dma_info->dma_dev = dma_dev;
+	dma_info->dma_id = dma_id;
+	dma_info->dma_ioctl = dma_ioctl;
+	abox_pcm_dev_input_list(pcm_dev, dma_info);
+
+	return 0;
+}
+
+static void abox_pcm_dev_print_isr_log(struct device *pcm_dev)
+{
+	struct abox_platform_data *data = dev_get_drvdata(pcm_dev);
+	ktime_t currentTime, intr_gap_time;
+
+	currentTime = ktime_get();
+	intr_gap_time = ktime_to_us(ktime_sub(currentTime, data->pcm_dbg_log.lastTime));
+	data->pcm_dbg_log.lastTime = currentTime;
+
+	if (data->solution_mgr_data->pp_enabled) {
+		unsigned int buf_off = readl(data->pp_pointer_base);
+
+		dev_info(pcm_dev, "%s %s DMA_%d isr_gap %lld usec, buf_off 0x%08x\n",
+			abox_pcm_dev_get_soc_time(pcm_dev), data->name,
+			data->dma_id, intr_gap_time, buf_off);
+	} else if (data->dma_id >= 0) {
+		unsigned int buf_status = 0xDEADC0DE;
+		struct abox_platform_dma_info *dma_info =
+			abox_pcm_dev_find_dma_info(pcm_dev, data->dma_id);
+
+		if (!dma_info) {
+			dev_err(pcm_dev, "%s(%d) Can't find DMA%d Info.\n", __func__, __LINE__,
+				data->dma_id);
+			return;
+		}
+
+		if (!dma_info->dma_ioctl) {
+			dev_err(pcm_dev, "%s(%d) DMA%d Ioctl is not ready\n", __func__, __LINE__,
+				data->dma_id);
+			return;
+		}
+
+		buf_status = dma_info->dma_ioctl(dma_info->dma_dev,
+						ABOX_DMA_IOCTL_GET_BUF_STATUS, NULL);
+		if (buf_status != 0xDEADC0DE) {
+			unsigned int buf_off = (ssize_t)dma_info->dma_ioctl(dma_info->dma_dev,
+				ABOX_DMA_IOCTL_GET_BUF_OFFSET, &buf_status);
+			unsigned int buf_cnt = (ssize_t)dma_info->dma_ioctl(dma_info->dma_dev,
+				ABOX_DMA_IOCTL_GET_BUF_COUNT, &buf_status);
+
+			dev_info(pcm_dev,
+				"%s %s DMA_%d isr_gap %lld usec, POS 0x%08X +0x%04X\n",
+				abox_pcm_dev_get_soc_time(pcm_dev),
+				data->name, data->dma_id, intr_gap_time, buf_off, buf_cnt);
+		}
+	}
+}
+
+/*
+ * IRQ handler for PCM DMA interrupts.
+ *
+ * If post-processing (PP) is enabled, logs PP buffer offset.
+ * Otherwise, queries the DMA driver via ioctl for current buffer status.
+ *
+ * The handler updates the kernel debug timestamp log if enabled,
+ * and signals period elapsed to the ALSA core.
+ *
+ * This function is primarily used to track buffer movement and
+ * trigger audio period completion events in the playback or capture path.
+ */
+static irqreturn_t abox_pcm_dev_irq_handler(struct device *pcm_dev, int irq_id,
+	struct _abox_inter_ipc_msg *pmsg)
+{
+	struct abox_platform_data *data;
+
+	data = dev_get_drvdata(pcm_dev);
+	if (!data || !data->substream)
+		return IRQ_HANDLED;
+
+	if (data->pcm_dbg_log.set_kernel_pcm_log & ISR_LOG)
+		abox_pcm_dev_print_isr_log(pcm_dev);
+
+	data->pointer = 0;
+	snd_pcm_period_elapsed(data->substream);
+
+	return IRQ_HANDLED;
+}
+
+int abox_pcm_dev_release(struct platform_device *pdev)
+{
+	struct abox_platform_data *pcm_data = platform_get_drvdata(pdev);
+
+	if (!pcm_data)
+		return -ENODATA;
+
+	if (!pcm_data->cmpnt)
+		return -ENODATA;
+
+	if (pcm_data->substream && pcm_data->substream->runtime)
+		pcm_data->substream->runtime->state = SNDRV_PCM_STATE_DISCONNECTED;
+
+	return 0;
+}
+
+static int abox_pcm_dev_read_property_from_dt(struct platform_device *pdev,
+	struct abox_platform_data *data)
+{
+	int ret;
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	const char *category;
+
+	/* Reads the PCM playback/capture id from the device tree and stores in data->id */
+	ret = of_property_read_u32_index(np, "samsung,id", 0, &data->id);
+	if (ret < 0)
+		return ret;
+
+	ret = of_property_read_u32_index(np, "samsung,irq_id", 0, &data->irq_id);
+	if (ret < 0)
+		return ret;
+
+	ret = of_property_read_u32_index(np, "samsung,allocate-adsp", 0, &data->adsp);
+	if (ret < 0)
+		return ret;
+
+	data->category = PLATFORM_DEEPBUFFER;
+	ret = of_property_read_string(np, "samsung,category", &category);
+	if (ret < 0)
+		dev_err(dev, "samsung,category property reading fail\n");
+	else
+		if (!strncmp(category, "compress", strlen("compress")))
+			data->category = PLATFORM_COMPRESS;
+
+	return ret;
+}
+
+static int samsung_abox_pcm_dev_probe(struct platform_device *pdev)
+{
+	struct device *dev = &pdev->dev;
+	struct device_node *np = dev->of_node;
+	struct abox_platform_data *data;
+	int ret;
+	const struct of_device_id *match;
+
+	match = of_match_device(samsung_abox_pcm_dev_match, dev);
+	if (!match) {
+		dev_err(dev, "%s Failed to get match device info\n", __func__);
+		return -EINVAL;
+	}
+	dev_dbg(dev, "%s: Start Compatible:%s\n", __func__, match->compatible);
+
+	data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	platform_set_drvdata(pdev, data);
+	data->pdev = pdev;
+
+	data->pcm_dbg_log.set_fw_intr_gap = 0;
+	data->pcm_dbg_log.set_kernel_pcm_log = 0;
+
+	if (!strncmp(match->compatible, "samsung,abox-pcm-playback", strlen(match->compatible)))
+		data->stream_type = SNDRV_PCM_STREAM_PLAYBACK;
+	else
+		data->stream_type = SNDRV_PCM_STREAM_CAPTURE;
+
+	data->name = devm_kcalloc(dev, DEFAULT_STR_SIZE, sizeof(char), GFP_KERNEL);
+	if (!data->name)
+		return -ENOMEM;
+	abox_pcm_dev_update_name(dev);
+
+	data->dma_id = DMA_NO_CONNECT;
+	data->dma_list_head.next = &data->dma_list_head;
+	data->dma_list_head.prev = &data->dma_list_head;
+
+	ret = abox_pcm_dev_read_property_from_dt(pdev, data);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				"%s Failed to read property. ret:%d\n", __func__, ret);
+
+	/*
+	 * pp_pointer_res is an optional memory resource used when post-processing
+	 * is enabled on the ADSP. In this case, the DMA buffer offset is not managed
+	 * by the hardware DMA, but updated by the ADSP.
+	 *
+	 * The resource address is used to map a virtual pointer to track
+	 * ADSP-side write progress into the audio buffer.
+	 *
+	 * This property must be declared in the device tree when post-processing
+	 * is enabled for this PCM stream.
+	 */
+	data->pp_pointer_res = platform_get_resource_byname(pdev, IORESOURCE_MEM,
+		"pp_pointer_offset");
+	if (IS_ERR_OR_NULL(data->pp_pointer_res))
+		return dev_err_probe(&pdev->dev, -EPROBE_DEFER,
+				"Failed to get %s\n", "pp_pointer_offset");
+
+	data->abox_dma_hardware = devm_kzalloc(dev, sizeof(*data->abox_dma_hardware), GFP_KERNEL);
+	if (!data->abox_dma_hardware)
+		return dev_err_probe(dev, -ENOMEM,
+				"%s(%d) Failed to allocate memory for dma_hardware\n",
+				__func__, __LINE__);
+	set_hw_params(np, data->abox_dma_hardware);
+
+	data->dma_buffer = devm_kzalloc(dev, sizeof(*data->dma_buffer), GFP_KERNEL);
+	if (!data->dma_buffer)
+		return dev_err_probe(dev, -ENOMEM,
+					"%s(%d) Failed to allocate memory for dma_buffer\n",
+					__func__, __LINE__);
+
+	ret = abox_generic_register_pcm_dev(pdev, data->id, data->stream_type);
+	if (ret < 0)
+		return dev_err_probe(dev, ret,
+				"%s Can't register %s pcm_dev pdev (%d)\n", __func__,
+				PCM_STREAM_STR(data->stream_type), ret);
+	if (data->category == PLATFORM_DEEPBUFFER)
+		ret = abox_ipc_generic_register_pcm_dev_handler(dev, data->irq_id,
+			abox_pcm_dev_irq_handler);
+	else
+		ret = abox_ipc_generic_register_pcm_dev_handler(dev, data->irq_id,
+			abox_pcm_dev_compr_irq_handler);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "%s Category:%d Can't register IRQ %d\n",
+				__func__, data->category, ret);
+
+	pm_runtime_no_callbacks(dev);
+	pm_runtime_enable(dev);
+
+	ret = abox_pcm_dev_register_component(dev, data);
+	if (ret < 0)
+		return dev_err_probe(dev, ret, "abox %s failed to register component:%d\n",
+				PCM_STREAM_STR(data->stream_type), ret);
+
+	return ret;
+}
+
+static void samsung_abox_pcm_dev_remove(struct platform_device *pdev)
+{
+	pm_runtime_disable(&pdev->dev);
+}
+
+/*
+ * - The samsung_abox_pcm_dev_probe() and samsung_abox_pcm_dev_remove() functions
+ * will be called by the kernel when the driver is bound to a device.
+ * - The probe() function is called when the kernel finds the driver in the device tree
+ * and matches the compatible string in the device tree to the compatible string in the
+ * driver.
+ */
+struct platform_driver samsung_abox_pcm_dev_driver = {
+	.probe  = samsung_abox_pcm_dev_probe,
+	.remove = samsung_abox_pcm_dev_remove,
+	.driver = {
+		.name = "samsung-abox-pcm-dev",
+		.owner = THIS_MODULE,
+		.of_match_table = of_match_ptr(samsung_abox_pcm_dev_match),
+	},
+};
diff --git a/sound/soc/samsung/auto_abox/generic/abox_solution_mgr.c b/sound/soc/samsung/auto_abox/generic/abox_solution_mgr.c
new file mode 100644
index 000000000000..11f6a8f6b0a4
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/generic/abox_solution_mgr.c
@@ -0,0 +1,456 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ * Author: Eunwoo Kim <ew.kim@...sung.com>
+ *
+ * EXYNOS Automotive Abox Solution Manager - abox_solution_mgr.c
+ *
+ * Software solution management layer for ALSA SoC ABOX audio subsystem.
+ *
+ * This module registers software-based audio processing "solutions" from
+ * solution_proxy and maps them dynamically to PCM frontend devices.
+ * It creates dynamic ALSA kcontrols for selecting solution chains per PCM device
+ * and manages the lifecycle and runtime callbacks for each solution.
+ *
+ * This manager does not directly interface with hardware, but works in coordination
+ * with abox_pcm_dev and abox_ipc_generic under the abox_generic parent.
+ */
+
+#include <linux/platform_device.h>
+#include <sound/pcm.h>
+#include <linux/of.h>
+#include <sound/soc.h>
+
+#include "include/abox_generic.h"
+#include "include/abox_pcm.h"
+#include "include/abox_ipc_generic.h"
+#include "include/abox_solution_mgr.h"
+#include "include/abox_util_generic.h"
+
+#define ABOX_SOLUTION_KCONTROL_FMT(type) \
+	((type) == SNDRV_PCM_STREAM_CAPTURE ? \
+	"PCM%dc SOLUTION CHAIN%d" : "PCM%dp SOLUTION CHAIN%d")
+
+/*
+ * Shared context for abox_solution_mgr.
+ *
+ * This context stores the IPC function pointers registered during init
+ * and is accessed by all solution-related functions.
+ * It replaces static global function pointers to improve structure and maintainability.
+ */
+static struct abox_solution_mgr_context {
+	sol_proxy_ioctl_fn ioctl;
+	sol_proxy_ops_callback_fn callback;
+} solution_mgr_ctx;
+
+int abox_solution_mgr_add_controls(struct device *pcm_dev, struct snd_soc_component *component)
+{
+	struct abox_platform_data *pcm_data = dev_get_drvdata(pcm_dev);
+	struct abox_solution_mgr_data *solution_mgr_data = pcm_data->solution_mgr_data;
+	struct abox_solution_controls *control_list;
+	int ret;
+
+	if (!solution_mgr_data)
+		return -ENODATA;
+
+	if (!solution_mgr_ctx.ioctl)
+		return -ENOLINK;
+
+	control_list = &solution_mgr_data->control_list;
+	ret = snd_soc_add_component_controls(component, control_list->controls,
+		control_list->num_of_controls);
+	if (ret < 0)
+		return ret;
+	ret = solution_mgr_ctx.ioctl(pcm_dev, ABOX_SOL_IOCTL_ADD_SOLUTION_CONTROLS,
+		component, NULL);
+
+	return ret;
+}
+
+int abox_solution_mgr_ops(struct device *pcm_dev, enum abox_solution_type type,
+	enum abox_solution_ops_type ops_type, int cmd, void *data)
+{
+	int ret;
+
+	if (!solution_mgr_ctx.callback) {
+		dev_info(pcm_dev, "%s(%d) Solution is not ready\n", __func__, __LINE__);
+		return -ENOLINK;
+	}
+	ret = solution_mgr_ctx.callback(pcm_dev, type, ops_type, cmd, data);
+
+	return ret;
+}
+
+
+static int abox_solution_get_enum_id(struct soc_enum *solution_enums, char *enum_str)
+{
+	int index;
+
+	for (index = 0; index < solution_enums->items; index++) {
+		if (!strncmp(solution_enums->texts[index], enum_str, ABOX_SOLUTION_NAME_LEN))
+			return index;
+	}
+	return 0;
+}
+
+static void *abox_solution_update_solution_chain(struct device *pcm_dev, int chain_index,
+	void *solution_data)
+{
+	struct abox_platform_data *pcm_data = dev_get_drvdata(pcm_dev);
+	struct abox_solution_mgr_data *solution_mgr_data;
+	void *previous_solution_data;
+
+	solution_mgr_data = pcm_data->solution_mgr_data;
+	previous_solution_data = solution_mgr_data->solution_chain[chain_index];
+	solution_mgr_data->solution_chain[chain_index] = solution_data;
+
+	return previous_solution_data;
+}
+
+static int abox_solution_chain_info(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_info *uinfo)
+{
+	struct soc_enum *e = (struct soc_enum *)kcontrol->private_value;
+
+	return snd_ctl_enum_info(uinfo, 1, e->items, e->texts);
+}
+
+static int abox_solution_get_chain_index(const char *control_name, int stream_type)
+{
+	int pcm_id;
+	int chain_index;
+	int ret;
+	char parse_fmt[64];
+
+	/* Generate parsing format with "ABOX " prefix */
+	ret = snprintf(parse_fmt, sizeof(parse_fmt), "ABOX %s",
+		       ABOX_SOLUTION_KCONTROL_FMT(stream_type));
+
+	/* Check for truncation or encoding error */
+	if (ret < 0 || ret >= sizeof(parse_fmt))
+		return -EINVAL;
+
+	/* Parse the control name using the composed format */
+	ret = sscanf(control_name, parse_fmt, &pcm_id, &chain_index);
+	if (ret != 2)
+		return -EINVAL;
+
+	return chain_index;
+}
+
+static int abox_solution_get_solution_chain(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+	struct device *pcm_dev = cmpnt->dev;
+	struct abox_platform_data *pcm_data = dev_get_drvdata(pcm_dev);
+	struct abox_solution_mgr_data *solution_mgr_data;
+	int chain_index;
+	char solution_name[ABOX_SOLUTION_NAME_LEN] = {0, };
+
+	if (!pcm_data) {
+		dev_err(pcm_dev, "%s pcm data is NULL\n", __func__);
+		return 0;
+	}
+
+	ucontrol->value.enumerated.item[0] = 0;
+	solution_mgr_data = pcm_data->solution_mgr_data;
+	if (!solution_mgr_data) {
+		dev_dbg(pcm_dev, "%s No solution data\n", __func__);
+		ucontrol->value.enumerated.item[0] = 0;
+		return 0;
+	}
+
+	chain_index = abox_solution_get_chain_index(kcontrol->id.name, pcm_data->stream_type);
+	if (chain_index < 0) {
+		dev_warn(pcm_dev, "Failed to get chain index from control name: %s\n",
+			kcontrol->id.name);
+		return 0;
+	}
+	if (!solution_mgr_data->solution_chain[chain_index]) {
+		ucontrol->value.enumerated.item[0] = 0;
+		return 0;
+	}
+	solution_mgr_ctx.ioctl(pcm_dev, ABOX_SOL_IOCTL_GET_SOLUTION_NAME, (void *)solution_name,
+		&solution_mgr_data->solution_chain[chain_index]);
+	ucontrol->value.enumerated.item[0] =
+		abox_solution_get_enum_id((struct soc_enum *)kcontrol->private_value,
+		solution_name);
+
+	return 0;
+}
+
+static int abox_solution_set_solution_chain(struct snd_kcontrol *kcontrol,
+	struct snd_ctl_elem_value *ucontrol)
+{
+	struct snd_soc_component *cmpnt = snd_soc_kcontrol_component(kcontrol);
+	struct soc_enum *solution_enum = (struct soc_enum *)kcontrol->private_value;
+	struct device *pcm_dev = cmpnt->dev;
+	struct abox_platform_data *pcm_data = dev_get_drvdata(pcm_dev);
+	void *solution_data;
+	void *previous_solution_data;
+	unsigned int enum_id;
+	int chain_index;
+	bool connected;
+
+	if (ucontrol->value.enumerated.item[0] >= solution_enum->items) {
+		dev_err(pcm_dev, "%s Wrong enum id:%u\n", __func__,
+			ucontrol->value.enumerated.item[0]);
+		return -EINVAL;
+	}
+
+	chain_index = abox_solution_get_chain_index(kcontrol->id.name, pcm_data->stream_type);
+	if (chain_index < 0) {
+		dev_warn(pcm_dev, "Failed to get chain index from control name: %s\n",
+			kcontrol->id.name);
+		return 0;
+	}
+	enum_id = ucontrol->value.enumerated.item[0];
+	if (enum_id > 0) {
+		solution_mgr_ctx.ioctl(pcm_dev, ABOX_SOL_IOCTL_GET_SOLUTION,
+			(void *)solution_enum->texts[enum_id], &solution_data);
+		if (!solution_data) {
+			dev_err(pcm_dev, "%s(%d) Failed to find solution. Name:%s\n",
+				__func__, __LINE__, solution_enum->texts[enum_id]);
+			return -EINVAL;
+		}
+		connected = true;
+		solution_mgr_ctx.ioctl(pcm_dev, ABOX_SOL_IOCTL_SET_SOLUTION_CONNECT,
+			(void *)&connected, &solution_data);
+	}
+	previous_solution_data = abox_solution_update_solution_chain(pcm_dev, chain_index,
+		solution_data);
+	if (previous_solution_data && previous_solution_data != solution_data) {
+		connected = false;
+		solution_mgr_ctx.ioctl(pcm_dev, ABOX_SOL_IOCTL_SET_SOLUTION_CONNECT,
+			(void *)&connected, &previous_solution_data);
+	}
+
+	return 0;
+}
+
+static int abox_solution_mgr_attach_solutions(struct device *pcm_dev,
+	struct abox_solution_mgr_data *solution_mgr_data, char **enum_str, int num_of_solution)
+{
+	char *solution_name;
+	int enum_id;
+	int solution_id;
+	int ret;
+
+	solution_mgr_data->solution_list = devm_kcalloc(pcm_dev, num_of_solution, sizeof(void *),
+		GFP_KERNEL);
+	if (!solution_mgr_data->solution_list)
+		return -ENOMEM;
+	solution_mgr_data->solution_chain = devm_kcalloc(pcm_dev, num_of_solution, sizeof(void *),
+		GFP_KERNEL);
+	if (!solution_mgr_data->solution_chain)
+		return -ENOMEM;
+	/* enum str = {"NO_USED", "SOL1", "SOL2",,,}
+	 * So solution name starts from '1'.
+	 * And num_of_solution is not length of enum_str, it is num of solution.
+	 */
+	for (enum_id = 0, solution_id = 0; enum_id < num_of_solution; enum_id++) {
+		void **solution_data = NULL;
+
+		solution_name = enum_str[enum_id + 1];
+		solution_data = &solution_mgr_data->solution_list[solution_id];
+
+		ret = solution_mgr_ctx.ioctl(pcm_dev, ABOX_SOL_IOCTL_REQUEST_TO_ADD_SOLUTION,
+			(void *)solution_name, solution_data);
+		if (ret < 0)
+			continue;
+		solution_id++;
+	}
+	solution_mgr_data->num_of_solution = solution_id;
+
+	return solution_id;
+}
+
+static void abox_solution_mgr_detach_solutions(struct device *pcm_dev,
+	struct abox_solution_mgr_data *solution_mgr_data)
+{
+	solution_mgr_ctx.ioctl(pcm_dev, ABOX_SOL_IOCTL_REQUEST_TO_REMOVE_SOLUTION, NULL, NULL);
+
+	solution_mgr_data->solution_list = NULL;
+	solution_mgr_data->solution_chain = NULL;
+}
+
+static int abox_solution_mgr_create_kcontrol(struct device *pcm_dev,
+	struct abox_solution_controls *solution_controls, char **enum_str, int num_of_solution)
+{
+	struct soc_enum solution_enum_base = SOC_ENUM_DOUBLE(SND_SOC_NOPM, 0, 0,
+			num_of_solution + 1, (const char * const *)enum_str);
+	struct soc_enum *solution_enum = devm_kmemdup(pcm_dev, &solution_enum_base,
+		sizeof(struct soc_enum), GFP_KERNEL);
+	const struct snd_kcontrol_new solution_chain_base =
+		ABOX_SOC_DAPM_ENUM_EXT_MULTI(NULL, (*solution_enum),
+			abox_solution_chain_info,
+			abox_solution_get_solution_chain,
+			abox_solution_set_solution_chain);
+	struct snd_kcontrol_new *solution_chain = devm_kcalloc(pcm_dev, num_of_solution,
+		sizeof(struct snd_kcontrol_new), GFP_KERNEL);
+	struct abox_platform_data *pcm_data = dev_get_drvdata(pcm_dev);
+	int solution_id;
+
+	if (!pcm_data)
+		return -ENODATA;
+
+	for (solution_id = 0; solution_id < num_of_solution; solution_id++) {
+		memcpy(&solution_chain[solution_id], &solution_chain_base,
+			sizeof(struct snd_kcontrol_new));
+		solution_chain[solution_id].name = devm_kasprintf(pcm_dev, GFP_KERNEL,
+			ABOX_SOLUTION_KCONTROL_FMT(pcm_data->stream_type),
+			pcm_data->id, solution_id);
+		if (!solution_chain[solution_id].name)
+			return -ENOMEM;
+	}
+	solution_controls->controls = solution_chain;
+	solution_controls->num_of_controls = solution_id;
+
+	return solution_id;
+}
+
+static struct abox_solution_mgr_data *abox_solution_mgr_create_solution(struct device *pcm_dev,
+	char **enum_str, int num_of_solution)
+{
+	struct abox_solution_mgr_data *solution_mgr_data;
+	int num_of_kcontrols;
+
+	solution_mgr_data = devm_kzalloc(pcm_dev, sizeof(struct abox_solution_mgr_data),
+		GFP_KERNEL);
+	if (!solution_mgr_data)
+		return NULL;
+
+	num_of_kcontrols = abox_solution_mgr_create_kcontrol(pcm_dev,
+		&solution_mgr_data->control_list, enum_str, num_of_solution);
+	if (num_of_kcontrols < 0) {
+		dev_err(pcm_dev, "%s(%d) Failed to allocate memory\n", __func__, __LINE__);
+		return NULL;
+	}
+
+	return solution_mgr_data;
+}
+
+/**
+ * abox_solution_mgr_register_ops_callback - Register operation callback from solution_proxy
+ * @callback: callback function to handle solution operations
+ *
+ * This function is called from the solution_proxy (in the variable part) to register
+ * a callback function that handles per-solution operations.
+ *
+ * The registered callback will be invoked by the solution manager during ALSA call steps
+ * (e.g., open, hw_params, trigger, etc.) to execute the corresponding operation
+ * for each solution in the chain.
+ */
+void abox_solution_mgr_register_ops_callback(sol_proxy_ops_callback_fn callback)
+{
+	solution_mgr_ctx.callback = callback;
+}
+EXPORT_SYMBOL(abox_solution_mgr_register_ops_callback);
+
+/**
+ * abox_solution_mgr_init - Initialize and register software solutions for each PCM device
+ * @proxy_dev: device pointer for the solution_proxy
+ * @num_of_solution: total number of software solutions supported by the variable part
+ * @enum_str: array of enum strings used for kcontrol display; contains num_of_solution + 1 strings
+ * @proxy_ioctl: function pointer used to send configuration requests to the solution_proxy
+ *
+ * This function is invoked by the solution_proxy after all solutions have been loaded.
+ * It passes the relevant solution data to the solution manager in the fixed part.
+ *
+ * The solution manager uses this data to attach solution information to each PCM device
+ * and to register the corresponding kcontrols needed to configure solutions per device.
+ *
+ * Return: 0 on success, or a negative error code on failure
+ */
+int abox_solution_mgr_init(struct device *proxy_dev, int num_of_solution, char **enum_str,
+	sol_proxy_ioctl_fn proxy_ioctl)
+{
+	struct platform_device *pdev_pcm;
+	int num_of_pcm;
+	int pcm_id;
+	int stream_type;
+	int ret;
+
+	solution_mgr_ctx.ioctl = proxy_ioctl;
+	if (!num_of_solution)
+		return 0;
+
+	for (stream_type = SNDRV_PCM_STREAM_PLAYBACK; stream_type <= SNDRV_PCM_STREAM_LAST;
+		stream_type++) {
+		struct abox_solution_mgr_data *solution_mgr_data;
+
+		num_of_pcm = abox_generic_get_num_pcm(stream_type);
+		for (pcm_id = 0; pcm_id < num_of_pcm; pcm_id++) {
+			pdev_pcm = abox_generic_get_pcm_platform_dev(pcm_id, stream_type);
+			if (!pdev_pcm)
+				continue;
+			solution_mgr_data = abox_solution_mgr_create_solution(&pdev_pcm->dev,
+				enum_str, num_of_solution);
+			if (!solution_mgr_data) {
+				dev_err(proxy_dev, "%s(%d) Failed to make solution mgr\n",
+					__func__, __LINE__);
+				return -EINVAL;
+			}
+			ret = abox_solution_mgr_attach_solutions(&pdev_pcm->dev, solution_mgr_data,
+				enum_str, num_of_solution);
+			if (ret < 0) {
+				dev_err(proxy_dev, "%s(%d) Failed to attach solution. ret:%d\n",
+					__func__, __LINE__, ret);
+				return ret;
+			}
+			abox_pcm_dev_attach_solution_mgr(&pdev_pcm->dev, solution_mgr_data);
+		}
+	}
+
+	return ret;
+}
+EXPORT_SYMBOL(abox_solution_mgr_init);
+
+/**
+ * abox_solution_mgr_deinit - Deinitialize and detach registered software solutions
+ * @proxy_dev: device pointer for the solution_proxy
+ *
+ * This function detaches all previously registered solutions from each PCM device,
+ * deallocates internal solution structures, and resets the shared solution context.
+ *
+ * It is typically called during shutdown or module removal to clean up solution-related state.
+ *
+ * Return: 0 on success
+ */
+int abox_solution_mgr_deinit(struct device *proxy_dev)
+{
+	struct platform_device *pdev_pcm;
+	int num_of_pcm;
+	int pcm_id;
+	int stream_type;
+
+	for (stream_type = SNDRV_PCM_STREAM_PLAYBACK; stream_type <= SNDRV_PCM_STREAM_LAST;
+		stream_type++) {
+		struct abox_solution_mgr_data *solution_mgr_data;
+
+		num_of_pcm = abox_generic_get_num_pcm(stream_type);
+		for (pcm_id = 0; pcm_id < num_of_pcm; pcm_id++) {
+			struct abox_platform_data *pcm_data;
+
+			pdev_pcm = abox_generic_get_pcm_platform_dev(pcm_id, stream_type);
+			if (!pdev_pcm)
+				continue;
+			pcm_data = platform_get_drvdata(pdev_pcm);
+			solution_mgr_data = pcm_data->solution_mgr_data;
+			if (!solution_mgr_data)
+				continue;
+
+			abox_solution_mgr_detach_solutions(&pdev_pcm->dev, solution_mgr_data);
+			devm_kfree(&pdev_pcm->dev, solution_mgr_data->control_list.controls);
+			devm_kfree(&pdev_pcm->dev, solution_mgr_data);
+			pcm_data->solution_mgr_data = NULL;
+		}
+	}
+
+	solution_mgr_ctx.ioctl = NULL;
+	solution_mgr_ctx.callback = NULL;
+
+	return 0;
+}
+EXPORT_SYMBOL(abox_solution_mgr_deinit);
diff --git a/sound/soc/samsung/auto_abox/generic/abox_util_generic.c b/sound/soc/samsung/auto_abox/generic/abox_util_generic.c
new file mode 100644
index 000000000000..d0dead1786a7
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/generic/abox_util_generic.c
@@ -0,0 +1,48 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ *
+ * EXYNOS Automotive Abox Utility - abox_util_generic.c
+ *
+ * Common utility functions for ABOX audio drivers.
+ */
+
+#include <linux/platform_device.h>
+#include <linux/of.h>
+
+#include "include/abox_pcm.h"
+#include "include/abox_util_generic.h"
+
+void deallocate_dma_memory(struct device *dev, struct snd_pcm_substream *substream)
+{
+	struct abox_platform_data *platform_data = dev_get_drvdata(dev);
+	struct snd_dma_buffer *dma_buf = &substream->dma_buffer;
+
+	memset(dma_buf, 0, sizeof(struct snd_dma_buffer));
+	memset(platform_data->dma_buffer, 0, sizeof(struct snd_dma_buffer));
+}
+
+void set_hw_params(const struct device_node *np, struct snd_pcm_hardware *hw)
+{
+	int ret = 0;
+	unsigned int buffer_bytes_max = DEFAULT_BUFFER_BYTES_MAX;
+
+	ret = of_property_read_u32(np, "samsung,buffer_bytes_max", &buffer_bytes_max);
+	if (ret < 0)
+		hw->buffer_bytes_max = DEFAULT_BUFFER_BYTES_MAX;
+	else
+		hw->buffer_bytes_max = buffer_bytes_max;
+
+	hw->info = SNDRV_PCM_INFO_INTERLEAVED
+		| SNDRV_PCM_INFO_BLOCK_TRANSFER
+		| SNDRV_PCM_INFO_MMAP
+		| SNDRV_PCM_INFO_MMAP_VALID;
+	hw->formats	= ABOX_SAMPLE_FORMATS;
+	hw->channels_min = 1;
+	hw->channels_max = 32; /* Maximum channel count for multichannel playback */
+	hw->period_bytes_min = PERIOD_BYTES_MIN,
+	hw->period_bytes_max = hw->buffer_bytes_max / 2;
+	hw->periods_min = hw->buffer_bytes_max / hw->period_bytes_max;
+	hw->periods_max = hw->buffer_bytes_max / hw->period_bytes_min;
+}
+
diff --git a/sound/soc/samsung/auto_abox/generic/include/abox_generic.h b/sound/soc/samsung/auto_abox/generic/include/abox_generic.h
index b1e6d9b9345d..bd52caab97f9 100644
--- a/sound/soc/samsung/auto_abox/generic/include/abox_generic.h
+++ b/sound/soc/samsung/auto_abox/generic/include/abox_generic.h
@@ -1,17 +1,22 @@
 /* SPDX-License-Identifier: GPL-2.0 */
 /*
- * ALSA SoC - Samsung ABOX Share Function and Data structure
- * for Exynos specific extensions
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ * Author: Eunwoo Kim <ew.kim@...sung.com>
  *
- * Copyright (C) 2013-2020 Samsung Electronics Co., Ltd.
+ * EXYNOS Automotive Abox Generic Driver - abox_generic.h
  *
- * EXYNOS - sound/soc/samsung/abox/include/abox_generic.h
+ * Header file for common functions and shared data structures
+ * used by Exynos Automotive ABOX audio subsystem.
+ *
+ * Provides platform-level interfaces and helpers shared across
+ * ABOX child drivers (e.g., PCM, IPC, I2S dummy backend) and
+ * interacts with core ALSA SoC and solution manager components.
  */
 
 #ifndef __SND_SOC_ABOX_GENERIC_BASE_H
 #define __SND_SOC_ABOX_GENERIC_BASE_H
 
-struct snd_soc_pcm_runtime;
+#include "abox_pcm.h"
 
 enum abox_soc_ioctl_cmd {
 	ABOX_SOC_IOCTL_GET_NUM_OF_RDMA,
@@ -23,9 +28,6 @@ enum abox_soc_ioctl_cmd {
 	ABOX_SOC_IOCTL_SET_PERF_PERIOD,
 	ABOX_SOC_IOCTL_CHECK_TIME_MUTEX,
 	ABOX_SOC_IOCTL_CHECK_TIME_NO_MUTEX,
-	ABOX_SOC_IOCTL_PCM_DUMP_INTR,
-	ABOX_SOC_IOCTL_PCM_DUMP_CLOSE,
-	ABOX_SOC_IOCTL_PCM_DUMP_ADD_CONTROL,
 	ABOX_SOC_IOCTL_MAX
 };
 
@@ -49,9 +51,9 @@ struct abox_generic_data {
 	unsigned int num_pcm_playback;
 	unsigned int num_pcm_capture;
 	unsigned int num_i2s_dummy;
-	unsigned int num_of_rdma;
-	unsigned int num_of_wdma;
-	unsigned int num_of_uaif;
+	unsigned int num_rdma;
+	unsigned int num_wdma;
+	unsigned int num_uaif;
 	struct device *soc_dev;
 	soc_ioctl_fn soc_ioctl;
 };
@@ -65,20 +67,31 @@ int abox_generic_request_soc_ioctl(struct device *generic_dev, enum abox_soc_ioc
 
 int abox_generic_set_pp_pointer(struct device *pcm_dev);
 
-int abox_generic_get_num_of_dma(struct device *pcm_dev, int stream_type);
+int abox_generic_get_num_dma(struct device *pcm_dev, int stream_type);
 
-int abox_generic_get_num_of_i2s_dummy(void);
+int abox_generic_get_num_i2s_dummy(void);
 
 int abox_generic_register_pcm_dev(struct platform_device *pdev_pcm_dev,
 				   unsigned int id, int stream_type);
 
 struct device *abox_generic_find_fe_dev_from_rtd(struct snd_soc_pcm_runtime *be);
 
-struct platform_device *abox_generic_get_pcm_platform_dev(int pcm_id, int stream_type);
-
-int abox_generic_get_num_of_pcm(int stream_type);
+int abox_generic_init_soc_route(struct device *soc_dev);
 
 int abox_generic_attach_soc_callback(struct device *soc_dev, soc_ioctl_fn soc_ioctl);
 
+struct platform_device *abox_generic_get_pcm_platform_dev(int pcm_id,
+	int stream_type);
+
+int abox_generic_get_num_pcm(int stream_type);
+
+int abox_generic_attach_dma(struct device *dma_dev, int dma_id,
+	int stream_type, dma_ioctl_fn dma_ioctl);
+
+void abox_generic_release_active_resource(void);
+
+int abox_generic_primary_dev_get(int stream_type);
+
+
 #endif //__SND_SOC_ABOX_GENERIC_BASE_H
 
diff --git a/sound/soc/samsung/auto_abox/generic/include/abox_ipc_generic.h b/sound/soc/samsung/auto_abox/generic/include/abox_ipc_generic.h
index c28a72306340..f5bca72706c9 100644
--- a/sound/soc/samsung/auto_abox/generic/include/abox_ipc_generic.h
+++ b/sound/soc/samsung/auto_abox/generic/include/abox_ipc_generic.h
@@ -58,19 +58,19 @@ enum INTER_PCMMSG {
 	INTER_PCMMSG_MAX	= 52,
 };
 
-struct PCMTASK_HW_PARAMS {
+struct pcmtask_hw_params {
 	int sample_rate;
 	int bit_depth;
 	int channels;
 };
 
-struct PCMTASK_SET_BUFFER {
+struct pcmtask_set_buffer {
 	int phyaddr;
 	int size;
 	int count;
 };
 
-struct PCMTASK_DMA_TRIGGER {
+struct pcmtask_dma_trigger {
 	int trigger;
 	int rbuf_offset;
 	int rbuf_cnt;
@@ -78,7 +78,7 @@ struct PCMTASK_DMA_TRIGGER {
 };
 
 /* Parameter of the PCMTASK command */
-struct INTER_IPC_PCMTASK_MSG {
+struct inter_ipc_pcmtask_msg {
 	enum INTER_PCMMSG msgtype;
 	int pcm_alsa_id;
 	int pcm_device_id;
@@ -88,14 +88,14 @@ struct INTER_IPC_PCMTASK_MSG {
 	unsigned int adsp;
 	unsigned long start_threshold;
 	union {
-		struct PCMTASK_HW_PARAMS hw_params;
-		struct PCMTASK_SET_BUFFER setbuff;
-		struct PCMTASK_DMA_TRIGGER dma_trigger;
+		struct pcmtask_hw_params hw_params;
+		struct pcmtask_set_buffer setbuff;
+		struct pcmtask_dma_trigger dma_trigger;
 	} param;
 };
 
 /* The parameter of the set_param */
-struct OFFLOAD_SET_PARAM {
+struct offload_set_param {
 	int sample_rate;
 	int bit_depth;
 	int channels;
@@ -104,12 +104,12 @@ struct OFFLOAD_SET_PARAM {
 };
 
 /* The parameter of the start */
-struct OFFLOAD_START {
+struct offload_start {
 	int id;
 };
 
 /* The parameter of the write */
-struct OFFLOAD_WRITE {
+struct offload_write {
 	int id;
 	int buff;
 	int size;
@@ -120,14 +120,14 @@ enum OFFLOADMSG {
 	OFFLOAD_OPEN = 1,
 	OFFLOAD_CLOSE,
 	OFFLOAD_SETPARAM,
-	OFFLOAD_START,
-	OFFLOAD_WRITE,
+	offload_start,
+	offload_write,
 	OFFLOAD_PAUSE,
 	OFFLOAD_STOP,
 };
 
 /* Parameter of the OFFLOADTASK command */
-struct INTER_IPC_OFFLOADTASK_MSG {
+struct inter_ipc_offloadtask_msg {
 	enum OFFLOADMSG msgtype;
 	int codec_id;
 	int pcm_device_id;
@@ -137,19 +137,19 @@ struct INTER_IPC_OFFLOADTASK_MSG {
 	int direction;
 	int domain_id;
 	union {
-		struct OFFLOAD_SET_PARAM setparam;
-		struct OFFLOAD_START start;
-		struct OFFLOAD_WRITE write;
-		struct PCMTASK_DMA_TRIGGER dma_trigger;
+		struct offload_set_param setparam;
+		struct offload_start start;
+		struct offload_write write;
+		struct pcmtask_dma_trigger dma_trigger;
 	} param;
 };
 
 struct _abox_inter_ipc_msg {
 	enum INTER_IPC_ID ipcid;
 	int task_id;
-	union INTER_IPC_MSG {
-		struct INTER_IPC_PCMTASK_MSG pcmtask;
-		struct INTER_IPC_OFFLOADTASK_MSG offload_task;
+	union inter_ipc_msg {
+		struct inter_ipc_pcmtask_msg pcmtask;
+		struct inter_ipc_offloadtask_msg offload_task;
 	} msg;
 };
 
@@ -159,8 +159,6 @@ typedef irqreturn_t (*ipc_generic_irq_handler_t)(struct device *dev, int irq_id,
 typedef int (*ipc_gen_request_xfer_t)(enum INTER_IPC_ID ipc_id, struct _abox_inter_ipc_msg *pmsg,
 	bool sync, struct __abox_inter_ipc_ret *ipc_ret, unsigned int adsp);
 
-struct abox_ipc_generic_irq_handler_t;
-
 struct abox_ipc_generic_data {
 	struct platform_device *pdev;
 	unsigned int num_irq;
@@ -168,6 +166,8 @@ struct abox_ipc_generic_data {
 	ipc_gen_request_xfer_t request_xfer;
 };
 
+extern struct platform_driver samsung_abox_ipc_generic_driver;
+
 int abox_ipc_generic_get_pcm_dev_handler_callback(struct device **dev_ipc_generic,
 	ipc_generic_irq_handler_t *handler);
 int abox_ipc_generic_register_xfer_callback(ipc_gen_request_xfer_t xfer);
diff --git a/sound/soc/samsung/auto_abox/generic/include/abox_pcm.h b/sound/soc/samsung/auto_abox/generic/include/abox_pcm.h
new file mode 100644
index 000000000000..30aebced6581
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/generic/include/abox_pcm.h
@@ -0,0 +1,135 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA SoC - Interface definitions for Samsung Exynos ABOX PCM frontend driver.
+ *
+ * This header provides structure definitions, constants, and function
+ * declarations used by the abox_pcm_dev frontend audio driver.
+ *
+ * Intended for use by ABOX child drivers and solution manager modules.
+ */
+
+#ifndef __SND_SOC_ABOX_PCM_H
+#define __SND_SOC_ABOX_PCM_H
+
+struct snd_compr_stream;
+struct snd_codec;
+struct snd_pcm_hardware;
+struct snd_dma_buffer;
+struct snd_soc_pcm_runtime;
+
+struct abox_solution_mgr_data;
+
+#define ABOX_SAMPLING_RATES			(SNDRV_PCM_RATE_KNOT)
+#define ABOX_SAMPLE_FORMATS \
+	(SNDRV_PCM_FMTBIT_S16 | SNDRV_PCM_FMTBIT_S24 | SNDRV_PCM_FMTBIT_S32)
+
+#define DEFAULT_BUFFER_BYTES_MAX		(SZ_32K)
+#define PERIOD_BYTES_MIN			(SZ_128)
+
+#define GET_PERIOD_TIME(bit, ch, rate, period_bytes) \
+		(((long long)period_bytes * 1000000) / (long long)((bit * ch / 8) * rate))
+
+#define ABOX_PCM_NO_CONNECT		-1
+#define MAX_PCM_IRQ_ID			63
+
+enum abox_dma_ioctl_cmd {
+	ABOX_DMA_IOCTL_GET_DMA_STATUS,
+	ABOX_DMA_IOCTL_GET_BUF_STATUS,
+	ABOX_DMA_IOCTL_GET_BUF_OFFSET,
+	ABOX_DMA_IOCTL_GET_BUF_COUNT,
+	ABOX_DMA_IOCTL_SET_ADSP_INFO,
+	ABOX_DMA_IOCTL_MAX
+};
+
+enum abox_platform_category {
+	PLATFORM_DEEPBUFFER,
+	PLATFORM_COMPRESS,
+};
+
+enum abox_kernel_intr_log {
+	FUNC_LOG = BIT(0),
+	ISR_LOG = BIT(1),
+	POINTER_LOG = BIT(2),
+};
+
+typedef unsigned int (*dma_ioctl_fn)(struct device *dma_dev, enum abox_dma_ioctl_cmd cmd,
+	void *data);
+
+struct abox_platform_of_data {
+	struct snd_soc_dai_driver *base_dai_drv;
+	unsigned int num_of_dai_drv;
+};
+
+struct abox_compr_data {
+	/* compress offload */
+	struct snd_compr_stream *cstream;
+	struct snd_codec *codec;
+	uint32_t encoded_total_bytes;
+	uint32_t decoded_total_bytes;
+	uint32_t pcm_io_frames;
+};
+
+struct abox_platform_debug_log {
+	unsigned int set_fw_intr_gap;
+	unsigned int set_kernel_pcm_log;
+	ktime_t lastTime;
+};
+
+struct abox_platform_dma_info {
+	struct list_head list;
+	struct device *dma_dev;
+	int dma_id;
+	dma_ioctl_fn dma_ioctl;
+};
+
+struct abox_platform_data {
+	char *name;                       /* Device name (e.g., "PCM0 Playback") */
+	unsigned int id;                 /* PCM device index (0~31) */
+	unsigned int irq_id;            /* IRQ line index assigned to this PCM */
+	unsigned int pointer;           /* DMA or runtime pointer position */
+	unsigned int pcm_dev_num;       /* Device number from ALSA runtime */
+	unsigned int stream_type;       /* SNDRV_PCM_STREAM_{PLAYBACK,CAPTURE} */
+	int dma_id;                     /* Connected DMA ID, -1 if not connected */
+	unsigned int adsp;              /* ADSP usage flag or ID */
+	struct platform_device *pdev;   /* Owning platform device */
+	struct abox_solution_mgr_data *solution_mgr_data; /* Pointer to PP solution manager */
+
+	struct resource *pp_pointer_res;     /* Resource from DT for PP pointer */
+	void __iomem *pp_pointer_base;      /* Mapped base for PP pointer */
+	phys_addr_t pp_pointer_phys;        /* Physical address for PP pointer */
+	size_t pp_pointer_size;             /* Size of the PP pointer memory */
+
+	const struct abox_platform_of_data *of_data;  /* OF data for dai drivers */
+
+	struct snd_soc_component_driver *cmpnt_drv;   /* Component driver ops */
+	struct snd_soc_component *cmpnt;              /* Component pointer */
+	struct snd_pcm_substream *substream;          /* Active PCM substream */
+	struct snd_pcm_hardware *abox_dma_hardware;   /* PCM hw capabilities */
+	struct snd_pcm_hw_params *params;             /* Current hw_params */
+	struct snd_soc_dai_driver *dai_drv;           /* Associated DAI driver */
+	struct snd_soc_dapm_widget *plat_dapm_widgets; /* DAPM widgets */
+	struct snd_soc_dapm_route *plat_dapm_routes;   /* Primary DAPM routes */
+	struct snd_soc_dapm_route *plat_dapm_post_routes; /* Post routes */
+	struct snd_kcontrol_new *plat_kcontrol;       /* Kcontrols for routes */
+	struct snd_dma_buffer *dma_buffer;            /* DMA buffer (from ADSP) */
+	enum abox_platform_category category;         /* Deepbuffer or Compress */
+	struct abox_compr_data compr_data;            /* Compress stream info */
+	struct abox_platform_debug_log pcm_dbg_log;   /* Debug logging */
+	struct list_head dma_list_head;               /* Connected DMA list */
+};
+
+extern struct platform_driver samsung_abox_pcm_dev_driver;
+
+void abox_pcm_dev_attach_solution_mgr(struct device *pcm_dev,
+	struct abox_solution_mgr_data *solution_mgr_data);
+
+int abox_pcm_dev_attach_dma(struct device *pcm_dev, struct device *dma_dev, int dma_id,
+	dma_ioctl_fn dma_ioctl);
+
+int abox_pcm_dev_register_dma_route_kcontrol(struct device *pcm_dev, int num_of_dma);
+
+unsigned int abox_pcm_dev_status(struct device *pcm_dev);
+
+int abox_pcm_dev_release(struct platform_device *pdev);
+
+#endif /* __SND_SOC_ABOX_PCM_H */
diff --git a/sound/soc/samsung/auto_abox/generic/include/abox_solution_mgr.h b/sound/soc/samsung/auto_abox/generic/include/abox_solution_mgr.h
new file mode 100644
index 000000000000..daef1d5556cf
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/generic/include/abox_solution_mgr.h
@@ -0,0 +1,98 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ * Author: Eunwoo Kim <ew.kim@...sung.com>
+ *
+ * EXYNOS Automotive Abox Solution Manager - abox_solution_mgr.h
+ *
+ * Header for managing software-based audio processing solutions for ABOX.
+ * Defines structures, enums, and function prototypes for solution control,
+ * used by both the fixed and variable parts of the ABOX audio driver stack.
+ */
+
+#ifndef __SND_SOC_SAMSUNG_ABOX_SOLUTION_MGR_H
+#define __SND_SOC_SAMSUNG_ABOX_SOLUTION_MGR_H
+
+#define ABOX_SOLUTION_NAME_LEN	64
+
+struct abox_solution_mgr_data;
+struct abox_solution_data;
+
+enum abox_solution_ops_type {
+	ABOX_SOL_CONSTRUCT,
+	ABOX_SOL_DESTRUCT,
+	ABOX_SOL_OPEN,
+	ABOX_SOL_CLOSE,
+	ABOX_SOL_HW_PARAM,
+	ABOX_SOL_HW_FREE,
+	ABOX_SOL_PREPARE,
+	ABOX_SOL_TRIGGER
+};
+
+enum abox_solution_type {
+	ABOX_SOL_SW,
+	ABOX_SOL_HW,
+	ABOX_SOL_MAX
+};
+
+enum abox_solution_hw_info {
+	ABOX_SOL_RATE,
+	ABOX_SOL_BIT_DEPTH,
+	ABOX_SOL_CHANNEL,
+	ABOX_SOL_HW_INFO_MAX
+};
+
+/* Keep in sync with solution_mgr implementation */
+enum abox_solution_ioctl_cmd {
+	ABOX_SOL_IOCTL_REQUEST_TO_ADD_SOLUTION,
+	ABOX_SOL_IOCTL_REQUEST_TO_REMOVE_SOLUTION,
+	ABOX_SOL_IOCTL_GET_SOLUTION,
+	ABOX_SOL_IOCTL_GET_SOLUTION_NAME,
+	ABOX_SOL_IOCTL_SET_SOLUTION_CONNECT,
+	ABOX_SOL_IOCTL_ADD_SOLUTION_CONTROLS,
+};
+
+typedef int (*sol_proxy_ioctl_fn)(struct device *proxy_dev, enum abox_solution_ioctl_cmd cmd,
+	void *p_data, void **pp_data);
+typedef int (*sol_proxy_ops_callback_fn)(struct device *dev, enum abox_solution_type type,
+	enum abox_solution_ops_type ops_type, int cmd, void *data);
+
+struct abox_solution_ops {
+	int (*construct)(struct device *dev, struct abox_solution_data *solution_data);
+	int (*destruct)(struct device *dev, struct abox_solution_data *solution_data);
+	int (*open)(struct device *dev, struct abox_solution_data *solution_data, void *data);
+	int (*close)(struct device *dev, struct abox_solution_data *solution_data);
+	int (*hw_params)(struct device *dev, struct abox_solution_data *solution_data, void *data);
+	int (*hw_free)(struct device *dev, struct abox_solution_data *solution_data);
+	int (*prepare)(struct device *dev, struct abox_solution_data *solution_data);
+	int (*trigger)(struct device *dev, struct abox_solution_data *solution_data, int cmd);
+};
+
+struct abox_solution_controls {
+	struct snd_kcontrol_new *controls;
+	int num_of_controls;
+};
+
+/**
+ * struct abox_solution_mgr_data - Solution manager state for one PCM
+ */
+struct abox_solution_mgr_data {
+	bool pp_enabled;
+	void **solution_list;
+	int num_of_solution;
+	void **solution_chain;
+	struct abox_solution_controls control_list;
+};
+
+int abox_solution_mgr_ops(struct device *dev, enum abox_solution_type type,
+	enum abox_solution_ops_type ops_type, int cmd, void *data);
+int abox_solution_mgr_add_controls(struct device *pcm_dev, struct snd_soc_component *component);
+
+void abox_solution_mgr_register_ops_callback(sol_proxy_ops_callback_fn callback);
+
+int abox_solution_mgr_init(struct device *proxy_dev, int num_of_solution, char **enum_str,
+	sol_proxy_ioctl_fn proxy_ioctl);
+
+int abox_solution_mgr_deinit(struct device *proxy_dev);
+
+#endif /* __SND_SOC_SAMSUNG_ABOX_SOLUTION_MGR_H */
diff --git a/sound/soc/samsung/auto_abox/generic/include/abox_util_generic.h b/sound/soc/samsung/auto_abox/generic/include/abox_util_generic.h
new file mode 100644
index 000000000000..15b5087353e6
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/generic/include/abox_util_generic.h
@@ -0,0 +1,110 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ * Author: Eunwoo Kim <ew.kim@...sung.com>
+ *
+ * EXYNOS Automotive Abox Utility - abox_util_generic.h
+ *
+ * Common macros and utility functions used by ABOX frontend and backend drivers.
+ */
+
+#ifndef __SND_SOC_ABOX_UTIL_GENERIC_H
+#define __SND_SOC_ABOX_UTIL_GENERIC_H
+
+#include <sound/pcm.h>
+
+#define DEFAULT_STR_SIZE	32
+#define ENUM_CTRL_STR_SIZE	32
+
+/*
+ * Define a DAPM buffer widget without event.
+ *
+ * @wname:  Widget name (string)
+ * @wreg:   Register index (used for routing control)
+ * @wcontrols: Pointer to associated kcontrols (can be NULL)
+ */
+#define ABOX_SND_SOC_DAPM_BUFFER(wname, wreg, wcontrols) \
+{	.id = snd_soc_dapm_buffer, .name = wname, .reg = wreg, \
+	.kcontrol_news = wcontrols, .num_kcontrols = 0}
+
+/*
+ * Define a DAPM buffer widget with event callback.
+ *
+ * @wname:  Widget name
+ * @wreg:   Register index
+ * @wcontrols: Pointer to kcontrols
+ * @wevent: Widget event handler
+ * @wflags: Event trigger condition flags (e.g., SND_SOC_DAPM_POST_PMU)
+ */
+#define ABOX_SND_SOC_DAPM_BUFFER_E(wname, wreg, wcontrols, \
+	wevent, wflags) \
+{	.id = snd_soc_dapm_buffer, .name = wname, .reg = wreg, \
+	.kcontrol_news = wcontrols, .num_kcontrols = 0, \
+	.event = wevent, .event_flags = wflags}
+
+/*
+ * Define an ASRC (Asynchronous Sample Rate Converter) widget.
+ *
+ * @wname:     Widget name
+ * @wreg:      Register index
+ * @wshift:    Bit shift
+ * @winvert:   Bit inversion flag
+ * @wcontrols: Array of kcontrols
+ * @wncontrols: Number of controls
+ */
+#define ABOX_SND_SOC_DAPM_ASRC(wname, wreg, wshift, winvert,\
+	 wcontrols, wncontrols) \
+{	.id = snd_soc_dapm_asrc, .name = wname, \
+	SND_SOC_DAPM_INIT_REG_VAL(wreg, wshift, winvert), \
+	.kcontrol_news = wcontrols, .num_kcontrols = wncontrols}
+
+/*
+ * Define a mixer control using SOC_SINGLE_VALUE with external get/put handlers.
+ *
+ * @xname:        Control name
+ * @xinfo:        Info callback function
+ * @xreg:         Register address
+ * @xshift:       Bit shift for the control value
+ * @xmax:         Maximum value of the control
+ * @xinvert:      Bit inversion flag
+ * @xhandler_get: Getter function
+ * @xhandler_put: Setter function
+ */
+#define ABOX_SOC_SINGLE_EXT_MULTI(xname, xinfo, xreg, xshift, xmax, xinvert, \
+	 xhandler_get, xhandler_put) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = xinfo, \
+	.get = xhandler_get, .put = xhandler_put, \
+	.private_value = SOC_SINGLE_VALUE(xreg, xshift, xmax, xinvert, 0) }
+
+/*
+ * Define a mixer enum control with external get/put handlers.
+ *
+ * @xname:  Control name
+ * @xenum:  soc_enum pointer
+ * @xinfo:  Info callback function
+ * @xget:   Get callback function
+ * @xput:   Set callback function
+ */
+#define ABOX_SOC_DAPM_ENUM_EXT_MULTI(xname, xenum, xinfo, xget, xput) \
+{	.iface = SNDRV_CTL_ELEM_IFACE_MIXER, .name = xname, \
+	.info = xinfo, \
+	.get = xget, \
+	.put = xput, \
+	.private_value = (unsigned long)&xenum }
+
+/*
+ * deallocate_dma_memory - Free DMA memory allocated to a PCM substream
+ * @dev: device requesting deallocation
+ * @substream: PCM substream associated with the buffer
+ */
+void deallocate_dma_memory(struct device *dev, struct snd_pcm_substream *substream);
+
+/*
+ * set_hw_params - Configure PCM hardware parameters from device tree
+ * @np: device node containing hw parameter properties
+ * @hw: target snd_pcm_hardware struct to populate
+ */
+void set_hw_params(const struct device_node *np, struct snd_pcm_hardware *hw);
+
+#endif /* __SND_SOC_ABOX_UTIL_GENERIC_H */
-- 
2.25.1


Powered by blists - more mailing lists

Powered by Openwall GNU/*/Linux Powered by OpenVZ