[<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 = ¶ms->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