* Add ExynosAuto ABOX generic platform and PCM support
[not found] <CGME20250721024611epcas2p43099e043aaa6f48c05eb0237065d31c7@epcas2p4.samsung.com>
@ 2025-07-21 2:30 ` ew kim
[not found] ` <CGME20250721024611epcas2p45ddc52c1644f5779c7da822573f03246@epcas2p4.samsung.com>
` (9 more replies)
0 siblings, 10 replies; 18+ messages in thread
From: ew kim @ 2025-07-21 2:30 UTC (permalink / raw)
To: broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel
This patch series adds the ABOX Generic audio management driver for
Samsung ExynosAuto SoCs, along with IPC messaging and PCM frontend
drivers, including their corresponding device tree bindings.
### ABOX Architecture Design: Fixed and Variable Structure
The ABOX audio framework is designed with a clear split between:
- **Fixed part** (common framework): reusable components across SoCs
- Generic ABOX platform driver
- IPC messaging handler
- PCM frontend with ALSA compressed audio support
- Solution manager for dynamic control
- **Variable part** (SoC-specific): defined via device tree
- IRQ numbers, stream IDs, buffer sizes, and ADSP allocation
- Defined per PCM/IPC device node in DTS
This separation allows the fixed part to be upstreamed and maintained
as a reusable framework, while only the DTS and minimal changes are
needed to adapt to each SoC generation.
### Major Components in This Series
- `abox_generic`: Root platform driver coordinating subcomponents
- `abox_ipc_generic`: Handles ADSP messaging via IRQ-based IPC
- `abox_pcm_dev`: PCM frontend with support for solution chain selection
- `abox_solution_mgr`: Manages software-based audio chains via dynamic
kcontrols
### Testing
Tested on ExynosAuto v920 board:
- Verified ALSA compressed audio playback via DSP
- Confirmed solution switching via kcontrol
- Verified DT node parsing and IRQ routing from device tree
^ permalink raw reply [flat|nested] 18+ messages in thread
* [PATCH 1/9] ASoC: samsung: Add generic ABOX management driver
[not found] ` <CGME20250721024611epcas2p45ddc52c1644f5779c7da822573f03246@epcas2p4.samsung.com>
@ 2025-07-21 2:30 ` ew kim
2025-07-30 12:04 ` Mark Brown
0 siblings, 1 reply; 18+ messages in thread
From: ew kim @ 2025-07-21 2:30 UTC (permalink / raw)
To: broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
ew kim
This driver implements the *generic* (fixed) part of the ABOX management
stack for Samsung Automotive SoCs. It does not directly control hardware
but provides common interfaces and state needed by SoC-specific
(variable) drivers.
The abox generic driver manages child drivers and provides an interface
that bridges the fixed and variable parts, connecting the two modules.
Design Note:
- The abox generic driver has child drivers, but they are built into a
single kernel module.
- The generic driver and the SoC-specific (variable) driver do not have
a parent-child device relationship.
- Instead, they are independent, peer modules.
- To bridge the gap, the generic driver exposes shared state
via a global pointer and EXPORT_SYMBOL accessor.
- This allows multiple SoC-specific modules to attach to
common generic functionality without tight coupling.
- Only one instance of the generic driver is supported.
Future work will focus on integrating this driver tightly with
ALSA SoC components and improving user-space interfaces.
Code style and interface improvements will be made in subsequent patches
based on upstream review feedback.
Tested on Exynos Auto platform for basic initialization and state sharing.
Note:
The ABOX (Audio Box) is an audio DSP hardware block integrated into
Exynos Automotive SoCs. It includes components such as DMA controllers,
I2S interfaces, GIC (interrupt controller), and the ADSP (audio DSP core).
Signed-off-by: ew kim <ew.kim@samsung.com>
---
sound/soc/samsung/Kconfig | 2 +
sound/soc/samsung/Makefile | 1 +
sound/soc/samsung/auto_abox/Kconfig | 22 +
sound/soc/samsung/auto_abox/generic/Kbuild | 12 +
.../samsung/auto_abox/generic/abox_generic.c | 384 ++++++++++++++++++
.../auto_abox/generic/include/abox_generic.h | 84 ++++
6 files changed, 505 insertions(+)
create mode 100644 sound/soc/samsung/auto_abox/Kconfig
create mode 100644 sound/soc/samsung/auto_abox/generic/Kbuild
create mode 100644 sound/soc/samsung/auto_abox/generic/abox_generic.c
create mode 100644 sound/soc/samsung/auto_abox/generic/include/abox_generic.h
diff --git a/sound/soc/samsung/Kconfig b/sound/soc/samsung/Kconfig
index 60b4b7b75215..359aa67f49db 100644
--- a/sound/soc/samsung/Kconfig
+++ b/sound/soc/samsung/Kconfig
@@ -148,4 +148,6 @@ config SND_SOC_SAMSUNG_MIDAS_WM1811
help
Say Y if you want to add support for SoC audio on the Midas boards.
+source "sound/soc/samsung/auto_abox/Kconfig"
+
endif #SND_SOC_SAMSUNG
diff --git a/sound/soc/samsung/Makefile b/sound/soc/samsung/Makefile
index 8d5f09147900..5d99cfbfa71c 100644
--- a/sound/soc/samsung/Makefile
+++ b/sound/soc/samsung/Makefile
@@ -42,3 +42,4 @@ obj-$(CONFIG_SND_SOC_ARNDALE) += snd-soc-arndale.o
obj-$(CONFIG_SND_SOC_SAMSUNG_TM2_WM5110) += snd-soc-tm2-wm5110.o
obj-$(CONFIG_SND_SOC_SAMSUNG_ARIES_WM8994) += snd-soc-aries-wm8994.o
obj-$(CONFIG_SND_SOC_SAMSUNG_MIDAS_WM1811) += snd-soc-midas-wm1811.o
+obj-$(CONFIG_SND_SOC_SAMSUNG_AUTO_ABOX) += auto_abox/generic/
\ No newline at end of file
diff --git a/sound/soc/samsung/auto_abox/Kconfig b/sound/soc/samsung/auto_abox/Kconfig
new file mode 100644
index 000000000000..d22b54fb785f
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/Kconfig
@@ -0,0 +1,22 @@
+#: SPDX-License-Identifier: GPL-2.0-only
+
+menu "Exynosauto Automotive Abox Modules Options"
+
+config SND_SOC_SAMSUNG_AUTO_ABOX
+ tristate "ASoC support for Samsung Exynosauto Automotive ABOX Audio"
+ select SND_SOC_COMPRESS
+ help
+ This driver provides a generic management layer for the ABOX
+ audio block on Samsung SoCs.
+
+ The design splits the ABOX support into:
+ - Fixed generic driver
+ - SoC-specific hardware drivers
+
+ These parts are independent modules without parent-child
+ binding. The generic driver therefore exposes a global
+ accessor via EXPORT_SYMBOL so that variable parts can share
+ state and callbacks safely.
+
+endmenu
+
diff --git a/sound/soc/samsung/auto_abox/generic/Kbuild b/sound/soc/samsung/auto_abox/generic/Kbuild
new file mode 100644
index 000000000000..fa6ba7091730
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/generic/Kbuild
@@ -0,0 +1,12 @@
+# SPDX-License-Identifier: GPL-2.0
+# Exynosauto Automotive Abox Driver Support
+
+snd-soc-samsung-abox-generic-$(CONFIG_SND_SOC_SAMSUNG_AUTO_ABOX) := \
+ abox_generic.o
+
+ccflags-y += -I./include
+
+obj-$(CONFIG_SND_SOC_SAMSUNG_AUTO_ABOX) += \
+ snd-soc-samsung-abox-generic.o
+
+
diff --git a/sound/soc/samsung/auto_abox/generic/abox_generic.c b/sound/soc/samsung/auto_abox/generic/abox_generic.c
new file mode 100644
index 000000000000..e1e14750ac8d
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/generic/abox_generic.c
@@ -0,0 +1,384 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ * Author: Eunwoo Kim <ew.kim@samsung.com>
+ *
+ * EXYNOS Automotive Abox Generic Driver - abox_generic.c
+ */
+
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <sound/soc.h>
+#include <sound/soc-dapm.h>
+
+#include "include/abox_generic.h"
+
+/**
+ * abox_generic_data_global - Shared state for ABOX generic driver.
+ *
+ * Architecture note:
+ * -------------------------------------------
+ * Our driver stack is split into:
+ * - Fixed part (Generic): does not directly control HW
+ * - Variable part (SoC-specific): directly controls HW
+ *
+ * The fixed part (this driver) must expose internal state to
+ * multiple independent variable drivers which are not parent-child related.
+ *
+ * Since there is no device hierarchy or explicit binding between
+ * generic and SoC-specific parts, we use this single global
+ * pointer plus EXPORT_SYMBOL accessors to share the state safely.
+ *
+ * Caution: Only one instance of the generic driver is supported.
+ * Future work may replace this with a dedicated bus or component binding.
+ */
+static struct abox_generic_data *g_abox_generic_data;
+
+struct abox_generic_data *abox_generic_get_abox_generic_data(void)
+{
+ return g_abox_generic_data;
+}
+
+static struct abox_generic_data *abox_generic_get_generic_data_from_child(struct device *child_dev)
+{
+ struct device *generic_dev = child_dev->parent;
+ struct abox_generic_data *generic_data = NULL;
+
+ if (!generic_dev)
+ return NULL;
+
+ generic_data = dev_get_drvdata(generic_dev);
+ if (!generic_data)
+ return NULL;
+
+ return generic_data;
+}
+
+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);
+
+ if (!generic_data)
+ return -ENODATA;
+
+ if (!generic_data->soc_ioctl)
+ return -ENODATA;
+
+ return generic_data->soc_ioctl(generic_data->soc_dev, ABOX_SOC_IOCTL_SET_DMA_BUFFER,
+ pcm_dev);
+}
+
+int abox_generic_set_pp_pointer(struct device *pcm_dev)
+{
+ struct abox_generic_data *generic_data = abox_generic_get_generic_data_from_child(pcm_dev);
+
+ if (!generic_data)
+ return 0;
+
+ if (!generic_data->soc_ioctl)
+ return 0;
+
+ return generic_data->soc_ioctl(generic_data->soc_dev, ABOX_SOC_IOCTL_SET_PP_POINTER,
+ pcm_dev);
+}
+
+int abox_generic_attach_soc_callback(struct device *soc_dev,
+ soc_ioctl_fn soc_ioctl)
+{
+ struct abox_generic_data *generic_data = abox_generic_get_abox_generic_data();
+
+ if (!generic_data)
+ return -ENODATA;
+
+ 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,
+ ABOX_SOC_IOCTL_GET_NUM_OF_RDMA, NULL);
+ generic_data->num_of_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,
+ ABOX_SOC_IOCTL_GET_NUM_OF_UAIF, NULL);
+
+ return 0;
+}
+EXPORT_SYMBOL(abox_generic_attach_soc_callback);
+
+/**
+ * abox_generic_get_pcm_platform_dev - Find PCM platform device by ID
+ * @pcm_id: PCM index to find
+ * @stream_type: Stream direction (e.g. playback or capture)
+ *
+ * This API returns the registered PCM platform device that matches
+ * the given PCM ID and stream type.
+ * It is exported for SoC-specific drivers to look up a specific PCM
+ * device instance managed by the generic ABOX layer.
+ *
+ * Return: Pointer to matching platform device, or NULL if not found.
+ */
+struct platform_device *abox_generic_get_pcm_platform_dev(int pcm_id, int stream_type)
+{
+ struct abox_generic_data *generic_data = abox_generic_get_abox_generic_data();
+ struct platform_device **pdev_pcm = NULL;
+
+ if (stream_type == SNDRV_PCM_STREAM_PLAYBACK)
+ pdev_pcm = generic_data->pdev_pcm_playback;
+ else
+ pdev_pcm = generic_data->pdev_pcm_capture;
+
+ return pdev_pcm[pcm_id];
+}
+EXPORT_SYMBOL(abox_generic_get_pcm_platform_dev);
+
+/**
+ * abox_generic_get_num_of_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
+ * the given stream direction.
+ * This API is exported for SoC-specific drivers that need to manage
+ * or iterate over all available PCM instances.
+ *
+ * Return: Number of PCM devices.
+ */
+int abox_generic_get_num_of_pcm(int stream_type)
+{
+ struct abox_generic_data *generic_data = abox_generic_get_abox_generic_data();
+
+ if (!generic_data)
+ return -ENODATA;
+
+ 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);
+
+int abox_generic_get_num_of_i2s_dummy(void)
+{
+ struct abox_generic_data *generic_data = abox_generic_get_abox_generic_data();
+
+ if (!generic_data)
+ return -ENODATA;
+
+ return generic_data->num_i2s_dummy;
+}
+
+int abox_generic_get_num_of_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;
+}
+
+int abox_generic_register_pcm_dev(struct platform_device *pdev_pcm,
+ unsigned int id, int stream_type)
+{
+ struct device *pcm_dev = &pdev_pcm->dev;
+ struct abox_generic_data *generic_data = abox_generic_get_generic_data_from_child(pcm_dev);
+ int num_of_pcm_dev = 0;
+
+ num_of_pcm_dev = (stream_type == SNDRV_PCM_STREAM_PLAYBACK) ?
+ generic_data->num_pcm_playback : generic_data->num_pcm_capture;
+ if (id >= num_of_pcm_dev) {
+ dev_err(pcm_dev, "%s: invalid id(%u) : Stream Type:%d\n", __func__, id,
+ stream_type);
+ return -EINVAL;
+ }
+
+ if (stream_type == SNDRV_PCM_STREAM_PLAYBACK)
+ generic_data->pdev_pcm_playback[id] = pdev_pcm;
+ else
+ generic_data->pdev_pcm_capture[id] = pdev_pcm;
+
+ return 0;
+}
+
+/**
+ * abox_generic_attach_soc_callback - Register SoC-specific ioctl callback
+ * @soc_dev: Device pointer for the SoC-specific driver
+ * @soc_ioctl: SoC-specific ioctl handler to attach
+ *
+ * This API lets a SoC-specific driver register its ioctl handler with
+ * the generic ABOX driver.
+ * Used to connect variable drivers that directly control hardware
+ * with the generic layer that provides shared infrastructure.
+ *
+ * Return: 0 on success or a negative error code.
+ */
+struct device *abox_generic_find_fe_dev_from_rtd(struct snd_soc_pcm_runtime *be)
+{
+ struct abox_generic_data *generic_data = abox_generic_get_abox_generic_data();
+ struct snd_soc_dpcm *dpcm;
+ struct snd_soc_pcm_runtime *fe;
+ int stream_type;
+
+ if (!generic_data)
+ return NULL;
+
+ for (stream_type = 0; stream_type <= SNDRV_PCM_STREAM_LAST; stream_type++) {
+ int cmpnt_index = 0;
+ struct snd_soc_component *component = NULL;
+
+ for_each_dpcm_fe(be, stream_type, dpcm) {
+ fe = dpcm->fe;
+ if (fe)
+ break;
+ }
+ if (!fe)
+ continue;
+
+ for_each_rtd_components(fe, cmpnt_index, component) {
+ struct platform_device **pdev = NULL;
+ int num_of_pcm_dev = 0;
+ int i;
+
+ if (stream_type == SNDRV_PCM_STREAM_PLAYBACK) {
+ num_of_pcm_dev = generic_data->num_pcm_playback;
+ pdev = generic_data->pdev_pcm_playback;
+ } else {
+ num_of_pcm_dev = generic_data->num_pcm_capture;
+ pdev = generic_data->pdev_pcm_capture;
+ }
+ for (i = 0; i < num_of_pcm_dev; i++)
+ if (pdev[i] && component->dev == &pdev[i]->dev)
+ return component->dev;
+ }
+ }
+
+ return NULL;
+}
+EXPORT_SYMBOL_GPL(abox_generic_find_fe_dev_from_rtd);
+
+int abox_generic_request_soc_ioctl(struct device *generic_dev, enum abox_soc_ioctl_cmd cmd,
+ void *data)
+{
+ struct abox_generic_data *generic_data = dev_get_drvdata(generic_dev);
+ struct device *soc_dev = generic_data->soc_dev;
+
+ if (IS_ERR_OR_NULL(soc_dev)) {
+ dev_err(generic_dev, "%s SoC Device is not ready\n", __func__);
+ return -ENODATA;
+ }
+ return generic_data->soc_ioctl(soc_dev, cmd, data);
+}
+
+static struct platform_driver *abox_generic_sub_drivers[] = {
+};
+
+static int abox_generic_read_property_from_dt(struct device *dev, struct abox_generic_data *data)
+{
+ struct device_node *np = dev->of_node;
+ int ret = 0;
+
+ ret = of_property_read_u32(np, "samsung,num-of-pcm_playback", &data->num_pcm_playback);
+ if (ret < 0) {
+ dev_err(dev, "%s property reading fail\n", "samsung,num-of-pcm_playback");
+ return ret;
+ }
+ ret = of_property_read_u32(np, "samsung,num-of-pcm_capture", &data->num_pcm_capture);
+ if (ret < 0) {
+ dev_err(dev, "%s property reading fail\n", "samsung,num-of-pcm_capture");
+ return ret;
+ }
+ ret = of_property_read_u32(np, "samsung,num-of-i2s-dummy-backend", &data->num_i2s_dummy);
+ if (ret < 0) {
+ dev_err(dev, "%s property reading fail\n", "samsung,num-of-i2s-dummy-backend");
+ return ret;
+ }
+
+ return ret;
+}
+
+static int abox_generic_allocate_memory(struct device *dev, struct abox_generic_data *data)
+{
+ data->pdev_pcm_playback = devm_kcalloc(dev, data->num_pcm_playback,
+ sizeof(struct platform_device *),
+ GFP_KERNEL);
+ if (!data->pdev_pcm_playback)
+ return -ENOMEM;
+
+ data->pdev_pcm_capture = devm_kcalloc(dev, data->num_pcm_capture,
+ sizeof(struct platform_device *),
+ GFP_KERNEL);
+ if (!data->pdev_pcm_capture)
+ return -ENOMEM;
+
+ return 0;
+}
+
+static int samsung_abox_generic_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+ struct abox_generic_data *data;
+ int ret = 0;
+
+ data = devm_kzalloc(dev, sizeof(*data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->pdev = pdev;
+ ret = abox_generic_read_property_from_dt(dev, data);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%s Failed to read property. ret:%d\n", __func__,
+ ret);
+
+ ret = abox_generic_allocate_memory(dev, data);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "%s Failed to allocate memory. ret:%d\n", __func__,
+ ret);
+
+ g_abox_generic_data = data;
+ platform_set_drvdata(pdev, data);
+
+ platform_register_drivers(abox_generic_sub_drivers, ARRAY_SIZE(abox_generic_sub_drivers));
+ ret = of_platform_populate(np, NULL, NULL, dev);
+ if (ret < 0)
+ return dev_err_probe(dev, ret, "Failed to populate sub-platform_devices. ret:%d\n",
+ ret);
+
+ return ret;
+}
+
+static void samsung_abox_generic_remove(struct platform_device *pdev)
+{
+ struct abox_generic_data *data = platform_get_drvdata(pdev);
+
+ platform_unregister_drivers(abox_generic_sub_drivers,
+ ARRAY_SIZE(abox_generic_sub_drivers));
+
+ g_abox_generic_data = NULL;
+
+ return 0;
+}
+
+static void samsung_abox_generic_shutdown(struct platform_device *pdev)
+{
+}
+
+static const struct of_device_id samsung_abox_generic_match[] = {
+ {
+ .compatible = "samsung,abox_generic",
+ },
+ {}
+};
+MODULE_DEVICE_TABLE(of, samsung_abox_generic_match);
+
+struct platform_driver samsung_abox_generic_driver = {
+ .probe = samsung_abox_generic_probe,
+ .remove = samsung_abox_generic_remove,
+ .shutdown = samsung_abox_generic_shutdown,
+ .driver = {
+ .name = "samsung-abox-generic",
+ .of_match_table = of_match_ptr(samsung_abox_generic_match),
+ },
+};
+
+module_platform_driver(samsung_abox_generic_driver);
+/* Module information */
+MODULE_AUTHOR("Eunwoo Kim, <ew.kim@samsung.com>");
+MODULE_DESCRIPTION("Samsung ASoC A-Box Generic Driver");
+MODULE_LICENSE("GPL");
+
diff --git a/sound/soc/samsung/auto_abox/generic/include/abox_generic.h b/sound/soc/samsung/auto_abox/generic/include/abox_generic.h
new file mode 100644
index 000000000000..b2a3f32ac577
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/generic/include/abox_generic.h
@@ -0,0 +1,84 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * ALSA SoC - Samsung ABOX Share Function and Data structure
+ * for Exynos specific extensions
+ *
+ * Copyright (C) 2013-2020 Samsung Electronics Co., Ltd.
+ *
+ * EXYNOS - sound/soc/samsung/abox/include/abox_generic.h
+ */
+
+#ifndef __SND_SOC_ABOX_GENERIC_BASE_H
+#define __SND_SOC_ABOX_GENERIC_BASE_H
+
+struct snd_soc_pcm_runtime;
+
+enum abox_soc_ioctl_cmd {
+ ABOX_SOC_IOCTL_GET_NUM_OF_RDMA,
+ ABOX_SOC_IOCTL_GET_NUM_OF_WDMA,
+ ABOX_SOC_IOCTL_GET_NUM_OF_UAIF,
+ ABOX_SOC_IOCTL_GET_SOC_TIMER,
+ ABOX_SOC_IOCTL_SET_DMA_BUFFER,
+ ABOX_SOC_IOCTL_SET_PP_POINTER,
+ 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
+};
+
+/**
+ * SOC_IOCTL - SoC-specific callback prototype
+ * @soc_dev: SoC device pointer
+ * @cmd: Command to handle (enum abox_soc_ioctl_cmd)
+ * @data: Additional argument, type depends on command
+ *
+ * This is the callback type which the SoC-specific driver must provide.
+ * The generic driver calls this to communicate with the hardware layer.
+ *
+ * Return: 0 on success or negative error code.
+ */
+typedef int (*soc_ioctl_fn)(struct device *soc_dev, enum abox_soc_ioctl_cmd cmd, void *data);
+
+struct abox_generic_data {
+ struct platform_device *pdev;
+ struct platform_device **pdev_pcm_playback;
+ struct platform_device **pdev_pcm_capture;
+ 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;
+ struct device *soc_dev;
+ soc_ioctl_fn soc_ioctl;
+};
+
+struct abox_generic_data *abox_generic_get_abox_generic_data(void);
+
+int abox_generic_set_dma_buffer(struct device *pcm_dev);
+
+int abox_generic_request_soc_ioctl(struct device *generic_dev, enum abox_soc_ioctl_cmd cmd,
+ void *data);
+
+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_of_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_attach_soc_callback(struct device *soc_dev, soc_ioctl_fn soc_ioctl);
+
+#endif //__SND_SOC_ABOX_GENERIC_BASE_H
+
--
2.25.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 2/9] arm64: dts: exynosautov920: add abox_generic dt node
[not found] ` <CGME20250721024611epcas2p37ecbc204ea695d97f6477c04712a9974@epcas2p3.samsung.com>
@ 2025-07-21 2:30 ` ew kim
2025-07-21 6:41 ` Krzysztof Kozlowski
0 siblings, 1 reply; 18+ messages in thread
From: ew kim @ 2025-07-21 2:30 UTC (permalink / raw)
To: broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
ew kim
Add device tree node for the abox_generic platform driver to enable
its registration as a platform device. This node does not represent
direct hardware resources but is necessary for driver initialization
and platform device binding.
Properties added in the device tree node:
- samsung,num-pcm-playback (uint32):
Maximum number of supported PCM playback devices.
Here, PCM playback devices refer to ALSA PCM devices.
- samsung,num-pcm-capture (uint32):
Maximum number of supported PCM capture devices.
Here, PCM capture devices refer to ALSA PCM devices.
- samsung,num-i2s-dummy-backend (uint32):
Maximum number of supported I2S dummy backend devices.
The node is declared disabled by default in the main device tree source,
and enabled via board-specific DTS overlays by setting status = "okay".
This device tree binding document will be added under
Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
to describe the node properties and usage.
Signed-off-by: ew kim <ew.kim@samsung.com>
---
arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts | 4 ++++
arch/arm64/boot/dts/exynos/exynosautov920.dtsi | 10 ++++++++++
2 files changed, 14 insertions(+)
diff --git a/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts b/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
index a397f068ed53..a870c0b6847f 100644
--- a/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
+++ b/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
@@ -86,3 +86,7 @@ &usi_0 {
&xtcxo {
clock-frequency = <38400000>;
};
+
+&abox_generic {
+ status = "okay";
+};
\ No newline at end of file
diff --git a/arch/arm64/boot/dts/exynos/exynosautov920.dtsi b/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
index 2cb8041c8a9f..4f086a7a79c8 100644
--- a/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
+++ b/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
@@ -1126,6 +1126,16 @@ timer {
<GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>,
<GIC_PPI 12 IRQ_TYPE_LEVEL_LOW>;
};
+
+ abox_generic: abox_generic {
+ compatible = "samsung,abox_generic";
+ samsung,num-pcm-playback = <32>;
+ samsung,num-pcm-capture = <32>;
+ samsung,num-i2s-dummy-backend = <5>;
+ status = "disabled";
+ #address-cells = <2>;
+ #size-cells = <1>;
+ };
};
#include "exynosautov920-pinctrl.dtsi"
--
2.25.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 3/9] ASoC: dt-bindings: sound: Add Samsung ExynosAuto ABOX binding
[not found] ` <CGME20250721024611epcas2p47ebaf8cb494fc2bf71a83b00ba47f2b3@epcas2p4.samsung.com>
@ 2025-07-21 2:30 ` ew kim
2025-07-21 6:44 ` Krzysztof Kozlowski
0 siblings, 1 reply; 18+ messages in thread
From: ew kim @ 2025-07-21 2:30 UTC (permalink / raw)
To: broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
ew kim
Add the device tree binding documentation for the Samsung Exynos Automotive
ABOX generic audio management core. This binding describes how to configure
the maximum number of PCM playback, PCM capture, and dummy I2S backend
instances for the ABOX core. Actual hardware functionality is provided
by child audio sub-drivers.
Signed-off-by: ew kim <ew.kim@samsung.com>
---
.../bindings/sound/samsung,exynosauto.yaml | 69 +++++++++++++++++++
1 file changed, 69 insertions(+)
create mode 100644 Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
diff --git a/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
new file mode 100644
index 000000000000..b1e49f38ffe9
--- /dev/null
+++ b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
@@ -0,0 +1,69 @@
+# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
+%YAML 1.2
+---
+$id: http://devicetree.org/schemas/sound/samsung,exynosauto.yaml#
+$schema: http://devicetree.org/meta-schemas/core.yaml#
+
+title: Samsung Exynos Automotive Abox Generic
+
+maintainers:
+ - Eunwoo Kim <ew.kim@samsung.com>
+
+description: |
+ The Samsung Exynos Automotive Abox Generic node represents a
+ generic audio management platform device inside Exynos Automotive SoCs.
+ It does not directly control hardware resources itself, but acts as
+ a common interface to manage child audio sub-drivers for PCM playback,
+ PCM capture, and I2S dummy backends.
+
+ Typically, this node provides configuration for the maximum number of
+ PCM playback and capture devices (ALSA PCM) and the maximum number
+ of dummy I2S backend devices. The actual hardware control is handled
+ by child drivers attached to this generic core.
+
+ This node must exist for the platform driver to probe,
+ even though it does not map any physical hardware address.
+
+properties:
+ compatible:
+ const: samsung,abox_generic
+
+ samsung,num-pcm-playback:
+ description: Maximum number of PCM playback instances (ALSA PCM devices).
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ samsung,num-pcm-capture:
+ description: Maximum number of PCM capture instances (ALSA PCM devices).
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ samsung,num-i2s-dummy-backend:
+ description: Maximum number of dummy I2S backend instances.
+ $ref: /schemas/types.yaml#/definitions/uint32
+
+ '#address-cells':
+ description: Required for child nodes that may declare address space.
+ const: 2
+
+ '#size-cells':
+ description: Required for child nodes that may declare address space.
+ const: 1
+
+required:
+ - compatible
+ - samsung,num-pcm-playback
+ - samsung,num-pcm-capture
+ - samsung,num-i2s-dummy-backend
+
+additionalProperties: false
+
+examples:
+ - |
+ abox_generic {
+ compatible = "samsung,abox_generic";
+ samsung,num-pcm-playback = <32>;
+ samsung,num-pcm-capture = <32>;
+ samsung,num-i2s-dummy-backend = <5>;
+ status = "disabled";
+ #address-cells = <2>;
+ #size-cells = <1>;
+ };
--
2.25.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 4/9] ASoC: samsung: abox: Add IPC generic support for message forwarding
[not found] ` <CGME20250721024611epcas2p4baca500b3b1f185dcdc35552b2abe8d9@epcas2p4.samsung.com>
@ 2025-07-21 2:30 ` ew kim
2025-07-30 12:06 ` Mark Brown
0 siblings, 1 reply; 18+ messages in thread
From: ew kim @ 2025-07-21 2:30 UTC (permalink / raw)
To: broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
ew kim
This adds the IPC generic driver as a child module of abox_generic
to handle bidirectional IPC message and IRQ routing between the
generic fixed layer and the SoC-specific audio side.
The driver:
- Registers a shared IRQ handler to allow SoC (variable part) to notify
the generic layer of IPC messages.
- Exports a registration interface to allow the generic layer to send
IPC messages to the SoC side.
- Uses a global singleton (non-DT) to coordinate state and callback
registration between the layers, due to lack of direct parent-child
linkage.
The driver is not a standalone module, and is built as part of the
snd-soc-samsung-abox-generic.ko module.
Signed-off-by: ew kim <ew.kim@samsung.com>
---
sound/soc/samsung/auto_abox/Kconfig | 19 +-
sound/soc/samsung/auto_abox/generic/Kbuild | 3 +-
.../samsung/auto_abox/generic/abox_generic.c | 13 +-
.../auto_abox/generic/abox_ipc_generic.c | 218 ++++++++++++++++++
.../auto_abox/generic/include/abox_generic.h | 2 +-
.../generic/include/abox_ipc_generic.h | 181 +++++++++++++++
6 files changed, 421 insertions(+), 15 deletions(-)
create mode 100644 sound/soc/samsung/auto_abox/generic/abox_ipc_generic.c
create mode 100644 sound/soc/samsung/auto_abox/generic/include/abox_ipc_generic.h
diff --git a/sound/soc/samsung/auto_abox/Kconfig b/sound/soc/samsung/auto_abox/Kconfig
index d22b54fb785f..55755b4166b8 100644
--- a/sound/soc/samsung/auto_abox/Kconfig
+++ b/sound/soc/samsung/auto_abox/Kconfig
@@ -10,13 +10,20 @@ config SND_SOC_SAMSUNG_AUTO_ABOX
audio block on Samsung SoCs.
The design splits the ABOX support into:
- - Fixed generic driver
- - SoC-specific hardware drivers
+ - A fixed generic control layer (ABOX Generic)
+ - SoC-specific hardware drivers (e.g., IPC, PCM, Backend)
- These parts are independent modules without parent-child
- binding. The generic driver therefore exposes a global
- accessor via EXPORT_SYMBOL so that variable parts can share
- state and callbacks safely.
+ These components are tightly coupled and share internal data
+ and callbacks. To ensure correct symbol resolution and
+ initialization ordering, all drivers are built together into
+ a single kernel object: snd-soc-samsung-abox-generic.ko
+
+ The sub-drivers are registered internally from the generic
+ driver's probe using platform_register_drivers(), rather than
+ being standalone platform drivers with independent module init.
+
+ This approach ensures integration consistency for fixed and
+ variable components across different automotive SoCs.
endmenu
diff --git a/sound/soc/samsung/auto_abox/generic/Kbuild b/sound/soc/samsung/auto_abox/generic/Kbuild
index fa6ba7091730..6a63d0609930 100644
--- a/sound/soc/samsung/auto_abox/generic/Kbuild
+++ b/sound/soc/samsung/auto_abox/generic/Kbuild
@@ -2,7 +2,8 @@
# Exynosauto Automotive Abox Driver Support
snd-soc-samsung-abox-generic-$(CONFIG_SND_SOC_SAMSUNG_AUTO_ABOX) := \
- abox_generic.o
+ abox_generic.o \
+ abox_ipc_generic.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 e1e14750ac8d..2c3f5ea910a2 100644
--- a/sound/soc/samsung/auto_abox/generic/abox_generic.c
+++ b/sound/soc/samsung/auto_abox/generic/abox_generic.c
@@ -13,7 +13,9 @@
#include <sound/soc-dapm.h>
#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.
*
@@ -264,9 +266,6 @@ int abox_generic_request_soc_ioctl(struct device *generic_dev, enum abox_soc_ioc
return generic_data->soc_ioctl(soc_dev, cmd, data);
}
-static struct platform_driver *abox_generic_sub_drivers[] = {
-};
-
static int abox_generic_read_property_from_dt(struct device *dev, struct abox_generic_data *data)
{
struct device_node *np = dev->of_node;
@@ -308,6 +307,10 @@ static int abox_generic_allocate_memory(struct device *dev, struct abox_generic_
return 0;
}
+static struct platform_driver *abox_generic_sub_drivers[] = {
+ &samsung_abox_ipc_generic_driver,
+};
+
static int samsung_abox_generic_probe(struct platform_device *pdev)
{
struct device *dev = &pdev->dev;
@@ -344,14 +347,10 @@ static int samsung_abox_generic_probe(struct platform_device *pdev)
static void samsung_abox_generic_remove(struct platform_device *pdev)
{
- struct abox_generic_data *data = platform_get_drvdata(pdev);
-
platform_unregister_drivers(abox_generic_sub_drivers,
ARRAY_SIZE(abox_generic_sub_drivers));
g_abox_generic_data = NULL;
-
- return 0;
}
static void samsung_abox_generic_shutdown(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
new file mode 100644
index 000000000000..58d765cd5bfa
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/generic/abox_ipc_generic.c
@@ -0,0 +1,218 @@
+// SPDX-License-Identifier: GPL-2.0
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ * Author: Eunwoo Kim <ew.kim@samsung.com>
+ *
+ * EXYNOS Automotive Abox IPC Generic Driver - abox_ipc_generic.c
+ *
+ * This driver is part of the ABOX generic audio stack and provides
+ * IPC message/IRQ handling between the fixed (generic) layer and the
+ * SoC-specific variable layer.
+ *
+ * It is a child driver managed by abox_generic, and is built as part
+ * of a single kernel module (snd-soc-samsung-abox-generic.ko) along with
+ * other related drivers.
+ *
+ * The IPC generic driver is registered independently as a platform
+ * driver and uses a global singleton to expose callback interfaces.
+ */
+
+#include <linux/io.h>
+#include <linux/module.h>
+#include <linux/of.h>
+#include <linux/of_platform.h>
+#include <linux/device.h>
+#include <linux/platform_device.h>
+#include <linux/interrupt.h>
+
+#include "include/abox_ipc_generic.h"
+
+struct abox_ipc_generic_irq_handler_t {
+ ipc_generic_irq_handler_t handler;
+ struct device *dev;
+};
+
+/*
+ * Singleton instance for ABOX IPC Generic driver.
+ *
+ * The generic layer and SoC-specific IPC driver are independent modules,
+ * not in a parent-child device relationship. A global symbol is used to
+ * expose internal state and register callbacks.
+ *
+ * Only one instance is supported. Accessed via exported helper functions.
+ */
+static struct abox_ipc_generic_data *g_abox_ipc_generic_data;
+
+static struct abox_ipc_generic_data *abox_ipc_generic_get_data(void)
+{
+ return g_abox_ipc_generic_data;
+}
+
+static irqreturn_t abox_ipc_generic_pcm_dev_irq_handler(struct device *ipc_generic_dev, int irq_id,
+ struct _abox_inter_ipc_msg *pmsg)
+{
+ struct device *pcm_dev;
+ struct abox_ipc_generic_data *data;
+ struct abox_ipc_generic_irq_handler_t *pcm_dev_irq_handler;
+
+ if (!ipc_generic_dev)
+ return IRQ_NONE;
+
+ data = dev_get_drvdata(ipc_generic_dev);
+ if (!data || irq_id >= data->num_irq)
+ return IRQ_NONE;
+
+ pcm_dev_irq_handler = &data->pcm_dev_irq_handler[irq_id];
+ if (!pcm_dev_irq_handler)
+ return IRQ_NONE;
+
+ if (!pcm_dev_irq_handler->handler)
+ return IRQ_NONE;
+
+ pcm_dev = pcm_dev_irq_handler->dev;
+ if (!pcm_dev)
+ return IRQ_NONE;
+
+ return pcm_dev_irq_handler->handler(pcm_dev, irq_id, pmsg);
+}
+
+static int abox_ipc_generic_read_property_from_dt(struct platform_device *pdev,
+ struct abox_ipc_generic_data *data)
+{
+ int ret;
+ struct device *dev = &pdev->dev;
+ struct device_node *np = dev->of_node;
+
+ ret = of_property_read_u32(np, "samsung,num-of-irq", &data->num_irq);
+ if (ret < 0) {
+ dev_err(dev, "%s property reading fail\n", "samsung,num-of-irq");
+ return ret;
+ }
+
+ return ret;
+}
+
+/**
+ * abox_ipc_generic_get_pcm_dev_handler_callback() - Register IRQ handler to receive IPCs from SoC
+ * @dev_ipc_gen: [out] pointer to the generic device
+ * @handler: [out] function pointer to be called on IPC IRQ
+ *
+ * This function is used by SoC-specific drivers to register the IRQ
+ * handler provided by the generic layer, enabling upward message passing.
+ *
+ * Return: 0 on success, -EINVAL on failure
+ */
+int abox_ipc_generic_get_pcm_dev_handler_callback(struct device **dev_ipc_gen,
+ ipc_generic_irq_handler_t *handler)
+{
+ struct abox_ipc_generic_data *data = NULL;
+
+ data = abox_ipc_generic_get_data();
+ if (!data)
+ return -EINVAL;
+
+ *dev_ipc_gen = &data->pdev->dev;
+ *handler = abox_ipc_generic_pcm_dev_irq_handler;
+
+ return 0;
+}
+EXPORT_SYMBOL(abox_ipc_generic_get_pcm_dev_handler_callback);
+
+/**
+ * abox_ipc_generic_register_xfer_callback() - Register callback to send IPC to SoC
+ * @xfer: SoC-provided IPC transmission function
+ *
+ * Used by the generic layer to send IPC messages to the hardware
+ * through the SoC-specific xfer function.
+ *
+ * Return: 0 on success, -EINVAL on failure
+ */
+int abox_ipc_generic_register_xfer_callback(ipc_gen_request_xfer_t xfer)
+{
+ struct abox_ipc_generic_data *data = NULL;
+
+ data = abox_ipc_generic_get_data();
+ if (!data)
+ return -EINVAL;
+
+ data->request_xfer = xfer;
+
+ return 0;
+}
+EXPORT_SYMBOL(abox_ipc_generic_register_xfer_callback);
+
+int abox_ipc_generic_request_xfer(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_data *data = NULL;
+
+ data = abox_ipc_generic_get_data();
+ if (!data || !data->request_xfer)
+ return -EINVAL;
+
+ return data->request_xfer(ipc_id, pmsg, sync, ipc_ret, adsp);
+}
+
+static int samsung_abox_ipc_generic_probe(struct platform_device *pdev)
+{
+ struct device *dev = &pdev->dev;
+ struct abox_ipc_generic_data *data;
+ int ret = 0;
+
+ data = devm_kzalloc(dev, sizeof(struct abox_ipc_generic_data), GFP_KERNEL);
+ if (!data)
+ return -ENOMEM;
+
+ data->pdev = pdev;
+ platform_set_drvdata(pdev, data);
+ g_abox_ipc_generic_data = data;
+
+ ret = abox_ipc_generic_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);
+
+ data->pcm_dev_irq_handler = devm_kcalloc(dev, data->num_irq,
+ sizeof(struct abox_ipc_generic_irq_handler_t),
+ GFP_KERNEL);
+ if (!data->pcm_dev_irq_handler)
+ return dev_err_probe(dev, -ENOMEM,
+ "%s Failed to alloc memory for pcm_dev_irq_handler\n", __func__);
+
+ return ret;
+}
+
+static void samsung_abox_ipc_generic_remove(struct platform_device *pdev)
+{
+ struct abox_ipc_generic_data *data = platform_get_drvdata(pdev);
+ int i;
+
+ for (i = 0; i < data->num_irq; ++i) {
+ data->pcm_dev_irq_handler[i].handler = NULL;
+ data->pcm_dev_irq_handler[i].dev = NULL;
+ }
+ g_abox_ipc_generic_data = NULL;
+}
+
+static void samsung_abox_ipc_generic_shutdown(struct platform_device *pdev)
+{
+}
+
+static const struct of_device_id samsung_abox_ipc_generic_of_match[] = {
+ {
+ .compatible = "samsung,abox_ipc_generic",
+ },
+ {}
+};
+
+struct platform_driver samsung_abox_ipc_generic_driver = {
+ .probe = samsung_abox_ipc_generic_probe,
+ .remove = samsung_abox_ipc_generic_remove,
+ .shutdown = samsung_abox_ipc_generic_shutdown,
+ .driver = {
+ .name = "samsung-abox-ipc-generic",
+ .of_match_table = of_match_ptr(samsung_abox_ipc_generic_of_match),
+ },
+};
+
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 b2a3f32ac577..b1e6d9b9345d 100644
--- a/sound/soc/samsung/auto_abox/generic/include/abox_generic.h
+++ b/sound/soc/samsung/auto_abox/generic/include/abox_generic.h
@@ -30,7 +30,7 @@ enum abox_soc_ioctl_cmd {
};
/**
- * SOC_IOCTL - SoC-specific callback prototype
+ * soc_ioctl_fn - SoC-specific callback prototype
* @soc_dev: SoC device pointer
* @cmd: Command to handle (enum abox_soc_ioctl_cmd)
* @data: Additional argument, type depends on command
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
new file mode 100644
index 000000000000..c28a72306340
--- /dev/null
+++ b/sound/soc/samsung/auto_abox/generic/include/abox_ipc_generic.h
@@ -0,0 +1,181 @@
+/* SPDX-License-Identifier: GPL-2.0 */
+/*
+ * Copyright (c) 2025 Samsung Electronics Co., Ltd.
+ * Author: Eunwoo Kim <ew.kim@samsung.com>
+ *
+ * ALSA SoC - Samsung ABOX IPC Generic Interface Header
+ *
+ * This header defines IPC message types, data structures, and
+ * interfaces shared between the ABOX generic layer and
+ * SoC-specific IPC handlers.
+ */
+
+#ifndef __SND_SOC_ABOX_IPC_GENERIC_H
+#define __SND_SOC_ABOX_IPC_GENERIC_H
+
+#include <linux/interrupt.h>
+#include <linux/device.h>
+
+/************ IPC DEFINITION ***************/
+/* Categorized IPC, */
+enum INTER_IPC_ID {
+ INTER_NOT_USED0 = BIT(0),
+ INTER_NOT_USED1 = BIT(1),
+ INTER_IPC_PCMPLAYBACK = BIT(2),
+ INTER_IPC_PCMCAPTURE = BIT(3),
+ INTER_IPC_OFFLOAD = BIT(4),
+ INTER_IPC_DMA_INTR = BIT(5),
+ INTER_NOT_USED6 = BIT(6),
+ INTER_NOT_USED7 = BIT(7),
+ INTER_NOT_USED8 = BIT(8),
+ INTER_NOT_USED9 = BIT(9),
+ INTER_NOT_USED10 = BIT(10),
+ INTER_NOT_USED11 = BIT(11),
+ INTER_NOT_USED12 = BIT(12),
+ INTER_NOT_USED13 = BIT(13),
+ INTER_NOT_USED14 = BIT(14),
+ INTER_NOT_USED15 = BIT(15),
+ INTER_IPC_ID_COUNT = 16,
+ INTER_IPC_ID_COUNT_BIT = BIT(INTER_IPC_ID_COUNT),
+};
+
+struct __abox_inter_ipc_ret {
+ int param1;
+};
+
+/******** PCMTASK IPC ***********************/
+enum INTER_PCMMSG {
+ INTER_PCM_PLTDAI_OPEN = 11,
+ INTER_PCM_PLTDAI_CLOSE = 12,
+ INTER_PCM_PLTDAI_IOCTL = 13,
+ INTER_PCM_PLTDAI_HW_PARAMS = 14,
+ INTER_PCM_PLTDAI_HW_FREE = 15,
+ INTER_PCM_PLTDAI_PREPARE = 16,
+ INTER_PCM_PLTDAI_TRIGGER = 17,
+ INTER_PCM_PLTDAI_POINTER = 18,
+ INTER_PCM_SET_BUFFER = 20,
+ INTER_PCM_SET_FW_INTR_GAP_LOG = 51,
+ INTER_PCMMSG_MAX = 52,
+};
+
+struct PCMTASK_HW_PARAMS {
+ int sample_rate;
+ int bit_depth;
+ int channels;
+};
+
+struct PCMTASK_SET_BUFFER {
+ int phyaddr;
+ int size;
+ int count;
+};
+
+struct PCMTASK_DMA_TRIGGER {
+ int trigger;
+ int rbuf_offset;
+ int rbuf_cnt;
+ bool is_real_dma;
+};
+
+/* Parameter of the PCMTASK command */
+struct INTER_IPC_PCMTASK_MSG {
+ enum INTER_PCMMSG msgtype;
+ int pcm_alsa_id;
+ int pcm_device_id;
+ int hw_dma_id; // should know ??
+ int irq_id;
+ unsigned int domain_id; // SMH - should be removed
+ 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;
+ } param;
+};
+
+/* The parameter of the set_param */
+struct OFFLOAD_SET_PARAM {
+ int sample_rate;
+ int bit_depth;
+ int channels;
+ int phyaddr;
+ int chunk_size;
+};
+
+/* The parameter of the start */
+struct OFFLOAD_START {
+ int id;
+};
+
+/* The parameter of the write */
+struct OFFLOAD_WRITE {
+ int id;
+ int buff;
+ int size;
+};
+
+/******** OFFLOAD IPC ***********************/
+enum OFFLOADMSG {
+ OFFLOAD_OPEN = 1,
+ OFFLOAD_CLOSE,
+ OFFLOAD_SETPARAM,
+ OFFLOAD_START,
+ OFFLOAD_WRITE,
+ OFFLOAD_PAUSE,
+ OFFLOAD_STOP,
+};
+
+/* Parameter of the OFFLOADTASK command */
+struct INTER_IPC_OFFLOADTASK_MSG {
+ enum OFFLOADMSG msgtype;
+ int codec_id;
+ int pcm_device_id;
+ int hw_dma_id;
+ int irq_id;
+ int pcm_alsa_id;
+ 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;
+ } 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;
+ } msg;
+};
+
+typedef irqreturn_t (*ipc_generic_irq_handler_t)(struct device *dev, int irq_id,
+ struct _abox_inter_ipc_msg *pmsg);
+
+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;
+ struct abox_ipc_generic_irq_handler_t *pcm_dev_irq_handler;
+ ipc_gen_request_xfer_t request_xfer;
+};
+
+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);
+int abox_ipc_generic_register_pcm_dev_handler(struct device *dev_pcm, unsigned int irq_id,
+ ipc_generic_irq_handler_t handler);
+int abox_ipc_generic_request_xfer(enum INTER_IPC_ID ipc_id, struct _abox_inter_ipc_msg *pmsg,
+ bool sync, struct __abox_inter_ipc_ret *ipc_ret, unsigned int adsp);
+
+
+#endif /* __SND_SOC_ABOX_IPC_GENERIC_H */
+
--
2.25.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 5/9] arm64: dts: exynosautov920: Add ABOX IPC Generic device node
[not found] ` <CGME20250721024611epcas2p375cd5e4b53fcff3b69a39ef19c0825a4@epcas2p3.samsung.com>
@ 2025-07-21 2:30 ` ew kim
2025-07-21 6:45 ` Krzysztof Kozlowski
0 siblings, 1 reply; 18+ messages in thread
From: ew kim @ 2025-07-21 2:30 UTC (permalink / raw)
To: broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
ew kim
This patch adds a new child node `abox_ipc_generic` under the
`abox_generic` node for ExynosAuto v920. The ABOX IPC Generic
driver handles inter-processor communication (IPC) between
the ABOX DSP and host SoC using IRQs.
The node includes configuration for the number of IRQ channels
used for IPC routing. This allows SoC-specific subsystems to
send and receive messages through the ABOX generic audio stack.
Signed-off-by: ew kim <ew.kim@samsung.com>
---
arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts | 6 +++++-
arch/arm64/boot/dts/exynos/exynosautov920.dtsi | 10 ++++++++--
2 files changed, 13 insertions(+), 3 deletions(-)
diff --git a/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts b/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
index a870c0b6847f..2f4cf112675a 100644
--- a/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
+++ b/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
@@ -89,4 +89,8 @@ &xtcxo {
&abox_generic {
status = "okay";
-};
\ No newline at end of file
+};
+
+&abox_ipc_generic {
+ status = "okay";
+};
diff --git a/arch/arm64/boot/dts/exynos/exynosautov920.dtsi b/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
index 4f086a7a79c8..21bcbcf7e2b6 100644
--- a/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
+++ b/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
@@ -1133,8 +1133,14 @@ abox_generic: abox_generic {
samsung,num-pcm-capture = <32>;
samsung,num-i2s-dummy-backend = <5>;
status = "disabled";
- #address-cells = <2>;
- #size-cells = <1>;
+ /* #address-cells = <2>; */
+ /* #size-cells = <1>; */
+
+ abox_ipc_generic: abox_ipc_generic {
+ compatible = "samsung,abox_ipc_generic";
+ samsung,num-irq = <64>;
+ status = "disabled";
+ };
};
};
--
2.25.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 6/9] ASoC : dt-bindings: sound: Add binding for ABOX IPC Generic
[not found] ` <CGME20250721024611epcas2p382f3decd51152a5c89c673f222e22da1@epcas2p3.samsung.com>
@ 2025-07-21 2:30 ` ew kim
2025-07-21 6:46 ` Krzysztof Kozlowski
0 siblings, 1 reply; 18+ messages in thread
From: ew kim @ 2025-07-21 2:30 UTC (permalink / raw)
To: broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
ew kim
This patch updates the existing samsung,exynosauto.yaml schema to
describe the ABOX IPC Generic child node. This node represents
a virtual IPC interface used by the ABOX audio subsystem to
communicate with SoC-specific hardware using shared IRQ channels.
The schema describes the `samsung,num-irq` property and allows
integration of the IPC node under `abox_generic`.
Signed-off-by: ew kim <ew.kim@samsung.com>
---
.../bindings/sound/samsung,exynosauto.yaml | 23 +++++++++++++++++++
1 file changed, 23 insertions(+)
diff --git a/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
index b1e49f38ffe9..3a7b5be627ee 100644
--- a/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
+++ b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
@@ -48,6 +48,23 @@ properties:
description: Required for child nodes that may declare address space.
const: 1
+ abox_ipc_generic:
+ type: object
+ description: ABOX IPC Generic subnode for SoC-level message routing
+ properties:
+ compatible:
+ const: samsung,abox_ipc_generic
+
+ samsung,num-irq:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: Number of IRQ channels supported for IPC routing.
+
+ required:
+ - compatible
+ - samsung,num-irq
+
+ additionalProperties: false
+
required:
- compatible
- samsung,num-pcm-playback
@@ -66,4 +83,10 @@ examples:
status = "disabled";
#address-cells = <2>;
#size-cells = <1>;
+
+ abox_ipc_generic {
+ compatible = "samsung,abox_ipc_generic";
+ samsung,num-irq = <64>;
+ status = "disabled";
+ };
};
--
2.25.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 7/9] ASoC: samsung: Add PCM driver with solution support
[not found] ` <CGME20250721024611epcas2p423f2e6084264b08f43c6f86ce1ad0892@epcas2p4.samsung.com>
@ 2025-07-21 2:30 ` ew kim
0 siblings, 0 replies; 18+ messages in thread
From: ew kim @ 2025-07-21 2:30 UTC (permalink / raw)
To: broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
ew kim
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@samsung.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@samsung.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@samsung.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@samsung.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@samsung.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@samsung.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
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 8/9] arm64: dts: exynosautov920: add PCM playback/capture
[not found] ` <CGME20250721024611epcas2p3da8e99d27a57cf7ad4ed46729e86602f@epcas2p3.samsung.com>
@ 2025-07-21 2:30 ` ew kim
0 siblings, 0 replies; 18+ messages in thread
From: ew kim @ 2025-07-21 2:30 UTC (permalink / raw)
To: broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
ew kim
This patch adds the PCM playback and capture device nodes as children of
the abox_generic audio controller for ExynosAuto v920.
Each PCM device is defined with a unique ID and an associated IRQ SW number
used for communication with the ADSP. These nodes include information such
as buffer size, ALSA DAI name prefix, and category type(e.g., deep_buffer).
The nodes are initially marked as "disabled" and can be enabled per board
(e.g., in the SADK .dts) as needed.
Signed-off-by: ew kim <ew.kim@samsung.com>
---
.../boot/dts/exynos/exynosautov920-sadk.dts | 8 +++++
.../arm64/boot/dts/exynos/exynosautov920.dtsi | 32 +++++++++++++++++--
2 files changed, 38 insertions(+), 2 deletions(-)
diff --git a/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts b/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
index 2f4cf112675a..f9f717fa95d4 100644
--- a/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
+++ b/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
@@ -94,3 +94,11 @@ &abox_generic {
&abox_ipc_generic {
status = "okay";
};
+
+&abox_pcm_playback_0 {
+ status = "okay";
+};
+
+&abox_pcm_capture_0 {
+ status = "okay";
+};
diff --git a/arch/arm64/boot/dts/exynos/exynosautov920.dtsi b/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
index 21bcbcf7e2b6..094fdec2e6f5 100644
--- a/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
+++ b/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
@@ -1133,14 +1133,42 @@ abox_generic: abox_generic {
samsung,num-pcm-capture = <32>;
samsung,num-i2s-dummy-backend = <5>;
status = "disabled";
- /* #address-cells = <2>; */
- /* #size-cells = <1>; */
+ #address-cells = <1>;
+ #size-cells = <1>;
abox_ipc_generic: abox_ipc_generic {
compatible = "samsung,abox_ipc_generic";
samsung,num-irq = <64>;
status = "disabled";
};
+
+ abox_pcm_playback_0: abox_pcm_playback@3fd0000 {
+ compatible = "samsung,abox-pcm-playback";
+ samsung,id = <0>;
+ samsung,irq_id = <0>;
+ samsung,allocate-adsp = <0>;
+ reg = <0x3fd0000 0x10>;
+ reg-names = "pp_pointer_offset";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "ABOX";
+ samsung,category = "deep_buffer";
+ samsung,buffer_bytes_max = <0x24000>;
+ status = "disabled";
+ };
+
+ abox_pcm_capture_0: abox_pcm_capture@3fd0400 {
+ compatible = "samsung,abox-pcm-capture";
+ samsung,id = <0>;
+ samsung,irq_id = <32>;
+ samsung,allocate-adsp = <0>;
+ reg = <0x3fd0400 0x10>;
+ reg-names = "pp_pointer_offset";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "ABOX";
+ samsung,category = "deep_buffer";
+ samsung,buffer_bytes_max = <0x24000>;
+ status = "disabled";
+ };
};
};
--
2.25.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* [PATCH 9/9] ASoC: dt-bindings: sound: exynosauto: add PCM frontend nodes for ABOX generic
[not found] ` <CGME20250721024612epcas2p122d627cfb90eac508b6ed3667acd9b9b@epcas2p1.samsung.com>
@ 2025-07-21 2:30 ` ew kim
2025-07-21 6:47 ` Krzysztof Kozlowski
0 siblings, 1 reply; 18+ messages in thread
From: ew kim @ 2025-07-21 2:30 UTC (permalink / raw)
To: broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel,
ew kim
This patch extends the Exynos Automotive ABOX generic device tree bindings
to support PCM playback and capture frontend nodes.
Each PCM device node describes an audio stream interface handled by the
ABOX DSP. These nodes include properties for stream ID, IRQ, ADSP core
assignment, buffer limits, and stream category (deep_buffer or compress).
The bindings use patternProperties to match playback and capture nodes
as children of the abox_generic controller.
Signed-off-by: ew kim <ew.kim@samsung.com>
---
.../bindings/sound/samsung,exynosauto.yaml | 126 +++++++++++++++++-
1 file changed, 123 insertions(+), 3 deletions(-)
diff --git a/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
index 3a7b5be627ee..e477550afc7c 100644
--- a/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
+++ b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
@@ -28,6 +28,14 @@ properties:
compatible:
const: samsung,abox_generic
+ status:
+ enum: [ okay, disabled ]
+ description: DTS node enablement state
+
+ $nodename:
+ pattern: "^abox_generic$"
+ description: Node name must be 'abox_generic'
+
samsung,num-pcm-playback:
description: Maximum number of PCM playback instances (ALSA PCM devices).
$ref: /schemas/types.yaml#/definitions/uint32
@@ -42,7 +50,7 @@ properties:
'#address-cells':
description: Required for child nodes that may declare address space.
- const: 2
+ const: 1
'#size-cells':
description: Required for child nodes that may declare address space.
@@ -55,6 +63,10 @@ properties:
compatible:
const: samsung,abox_ipc_generic
+ status:
+ enum: [ okay, disabled ]
+ description: DTS node enablement state
+
samsung,num-irq:
$ref: /schemas/types.yaml#/definitions/uint32
description: Number of IRQ channels supported for IPC routing.
@@ -65,13 +77,92 @@ properties:
additionalProperties: false
+patternProperties:
+ "^abox_pcm_(playback|capture)@[0-9a-f]+$":
+ type: object
+ description: |
+ ABOX PCM playback or capture frontend device node. These nodes define
+ individual PCM streams used by the audio subsystem, and are children of
+ the abox_generic controller. Each node describes a PCM stream ID, IRQ line,
+ ADSP core allocation, and stream-specific parameters.
+
+ properties:
+ compatible:
+ enum:
+ - samsung,abox-pcm-playback
+ - samsung,abox-pcm-capture
+ description: Compatible string for PCM playback or capture device.
+
+ reg:
+ maxItems: 1
+ description: Offset for pointer register (pp_pointer_offset) used for communication with ADSP.
+
+ reg-names:
+ const: pp_pointer_offset
+
+ samsung,id:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ Unique ID per PCM Device, separated by stream type (playback or capture).
+ Must be less than samsung,num-of-pcm_playback or samsung,num-of-pcm_capture
+ defined in the abox_generic node.
+
+ samsung,irq_id:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ Software IRQ ID assigned in the control domain. Represents the software interrupt
+ line used by the ADSP to communicate with this PCM device.
+
+ samsung,allocate-adsp:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ Index of the ADSP core that this PCM device is allocated to. Indicates on which core
+ the stream is handled.
+
+ sound-name-prefix:
+ $ref: /schemas/types.yaml#/definitions/string
+ description: |
+ Prefix name for ALSA DAI interface. Helps to namespace controls and routing.
+
+ samsung,category:
+ enum: [deep_buffer, compress]
+ description: |
+ Type of the PCM stream. Can be either 'deep_buffer' for normal PCM playback/capture,
+ or 'compress' for compressed offload streams.
+
+ samsung,buffer_bytes_max:
+ $ref: /schemas/types.yaml#/definitions/uint32
+ description: |
+ Maximum size (in bytes) of the DMA buffer allocated for this PCM device.
+
+ '#sound-dai-cells':
+ const: 0
+ description: Must be 0. Required by ALSA SoC bindings.
+
+ status:
+ enum: [ okay, disabled ]
+ description: Device node enablement status. Set to "okay" to activate the node.
+
+ required:
+ - compatible
+ - reg
+ - reg-names
+ - samsung,id
+ - samsung,irq_id
+ - samsung,allocate-adsp
+ - samsung,category
+ - samsung,buffer_bytes_max
+ - '#sound-dai-cells'
+
+ additionalProperties: true
+
required:
- compatible
- samsung,num-pcm-playback
- samsung,num-pcm-capture
- samsung,num-i2s-dummy-backend
-additionalProperties: false
+unevaluatedProperties: false
examples:
- |
@@ -81,7 +172,7 @@ examples:
samsung,num-pcm-capture = <32>;
samsung,num-i2s-dummy-backend = <5>;
status = "disabled";
- #address-cells = <2>;
+ #address-cells = <1>;
#size-cells = <1>;
abox_ipc_generic {
@@ -89,4 +180,33 @@ examples:
samsung,num-irq = <64>;
status = "disabled";
};
+
+ abox_pcm_playback@3fd0000 {
+ compatible = "samsung,abox-pcm-playback";
+ samsung,id = <0>;
+ samsung,irq_id = <0>;
+ samsung,allocate-adsp = <0>;
+ reg = <0x3fd0000 0x10>;
+ reg-names = "pp_pointer_offset";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "ABOX";
+ samsung,category = "deep_buffer";
+ samsung,buffer_bytes_max = <0x24000>;
+ status = "disabled";
+ };
+
+ abox_pcm_capture@3fd0400 {
+ compatible = "samsung,abox-pcm-capture";
+ samsung,id = <0>;
+ samsung,irq_id = <32>;
+ samsung,allocate-adsp = <0>;
+ reg = <0x3fd0400 0x10>;
+ reg-names = "pp_pointer_offset";
+ #sound-dai-cells = <0>;
+ sound-name-prefix = "ABOX";
+ samsung,category = "deep_buffer";
+ samsung,buffer_bytes_max = <0x24000>;
+ status = "disabled";
+ };
};
+
--
2.25.1
^ permalink raw reply related [flat|nested] 18+ messages in thread
* Re: [PATCH 2/9] arm64: dts: exynosautov920: add abox_generic dt node
2025-07-21 2:30 ` [PATCH 2/9] arm64: dts: exynosautov920: add abox_generic dt node ew kim
@ 2025-07-21 6:41 ` Krzysztof Kozlowski
0 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-21 6:41 UTC (permalink / raw)
To: ew kim, broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel
On 21/07/2025 04:30, ew kim wrote:
> Add device tree node for the abox_generic platform driver to enable
> its registration as a platform device. This node does not represent
> direct hardware resources but is necessary for driver initialization
> and platform device binding.
>
> Properties added in the device tree node:
>
> - samsung,num-pcm-playback (uint32):
> Maximum number of supported PCM playback devices.
> Here, PCM playback devices refer to ALSA PCM devices.
>
> - samsung,num-pcm-capture (uint32):
> Maximum number of supported PCM capture devices.
> Here, PCM capture devices refer to ALSA PCM devices.
>
> - samsung,num-i2s-dummy-backend (uint32):
> Maximum number of supported I2S dummy backend devices.
>
> The node is declared disabled by default in the main device tree source,
> and enabled via board-specific DTS overlays by setting status = "okay".
>
> This device tree binding document will be added under
> Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
>
> to describe the node properties and usage.
>
> Signed-off-by: ew kim <ew.kim@samsung.com>
> ---
> arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts | 4 ++++
> arch/arm64/boot/dts/exynos/exynosautov920.dtsi | 10 ++++++++++
Entirely wrong order of patches.
> 2 files changed, 14 insertions(+)
>
> diff --git a/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts b/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
> index a397f068ed53..a870c0b6847f 100644
> --- a/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
> +++ b/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
> @@ -86,3 +86,7 @@ &usi_0 {
> &xtcxo {
> clock-frequency = <38400000>;
> };
> +
> +&abox_generic {
> + status = "okay";
> +};
> \ No newline at end of file
?
> diff --git a/arch/arm64/boot/dts/exynos/exynosautov920.dtsi b/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
> index 2cb8041c8a9f..4f086a7a79c8 100644
> --- a/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
> +++ b/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
> @@ -1126,6 +1126,16 @@ timer {
> <GIC_PPI 10 IRQ_TYPE_LEVEL_LOW>,
> <GIC_PPI 12 IRQ_TYPE_LEVEL_LOW>;
> };
> +
> + abox_generic: abox_generic {
And you did not resolve any of previous comments, just sent the same v1.
NAK.
Implement and respond to feedback. Then version properly your patches.
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 3/9] ASoC: dt-bindings: sound: Add Samsung ExynosAuto ABOX binding
2025-07-21 2:30 ` [PATCH 3/9] ASoC: dt-bindings: sound: Add Samsung ExynosAuto ABOX binding ew kim
@ 2025-07-21 6:44 ` Krzysztof Kozlowski
0 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-21 6:44 UTC (permalink / raw)
To: ew kim, broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel
On 21/07/2025 04:30, ew kim wrote:
> Add the device tree binding documentation for the Samsung Exynos Automotive
> ABOX generic audio management core. This binding describes how to configure
> the maximum number of PCM playback, PCM capture, and dummy I2S backend
> instances for the ABOX core. Actual hardware functionality is provided
> by child audio sub-drivers.
>
> Signed-off-by: ew kim <ew.kim@samsung.com>
> ---
> .../bindings/sound/samsung,exynosauto.yaml | 69 +++++++++++++++++++
> 1 file changed, 69 insertions(+)
> create mode 100644 Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
>
> diff --git a/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
> new file mode 100644
> index 000000000000..b1e49f38ffe9
> --- /dev/null
> +++ b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
> @@ -0,0 +1,69 @@
> +# SPDX-License-Identifier: (GPL-2.0-only OR BSD-2-Clause)
> +%YAML 1.2
> +---
> +$id: http://devicetree.org/schemas/sound/samsung,exynosauto.yaml#
> +$schema: http://devicetree.org/meta-schemas/core.yaml#
> +
> +title: Samsung Exynos Automotive Abox Generic
> +
> +maintainers:
> + - Eunwoo Kim <ew.kim@samsung.com>
> +
> +description: |
> + The Samsung Exynos Automotive Abox Generic node represents a
> + generic audio management platform device inside Exynos Automotive SoCs.
No, we do not add bindings for "generic" stuff.
Describe the SoC.
> + It does not directly control hardware resources itself, but acts as
Does not control hardware? so not suitable for bindings and DTS.
> + a common interface to manage child audio sub-drivers for PCM playback,
> + PCM capture, and I2S dummy backends.
Not relevant.
> +
> + Typically, this node provides configuration for the maximum number of
> + PCM playback and capture devices (ALSA PCM) and the maximum number
> + of dummy I2S backend devices. The actual hardware control is handled
> + by child drivers attached to this generic core.
Not relevant. Describe the hardware.
> +
> + This node must exist for the platform driver to probe,
> + even though it does not map any physical hardware address.
Drivers are not relevant, read writing bindings.
> +
> +properties:
> + compatible:
> + const: samsung,abox_generic
You did not implement previous feedback. This does not follow DTS coding
style and writing bindings.
I already asked you to read DTS coding style and nothing improved, no
issues were resolved.
> +
> + samsung,num-pcm-playback:
> + description: Maximum number of PCM playback instances (ALSA PCM devices).
> + $ref: /schemas/types.yaml#/definitions/uint32
> +
> + samsung,num-pcm-capture:
> + description: Maximum number of PCM capture instances (ALSA PCM devices).
> + $ref: /schemas/types.yaml#/definitions/uint32
> +
> + samsung,num-i2s-dummy-backend:
> + description: Maximum number of dummy I2S backend instances.
> + $ref: /schemas/types.yaml#/definitions/uint32
Nothing above describes hardware. Bindings are not for ALSA.
> +
> + '#address-cells':
> + description: Required for child nodes that may declare address space.
> + const: 2
> +
> + '#size-cells':
> + description: Required for child nodes that may declare address space.
> + const: 1
> +
> +required:
> + - compatible
> + - samsung,num-pcm-playback
> + - samsung,num-pcm-capture
> + - samsung,num-i2s-dummy-backend
> +
> +additionalProperties: false
> +
> +examples:
> + - |
> + abox_generic {
Did you read DTS coding style?
> + compatible = "samsung,abox_generic";
> + samsung,num-pcm-playback = <32>;
> + samsung,num-pcm-capture = <32>;
> + samsung,num-i2s-dummy-backend = <5>;
> + status = "disabled";
No.
> + #address-cells = <2>;
> + #size-cells = <1>;
> + };
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 5/9] arm64: dts: exynosautov920: Add ABOX IPC Generic device node
2025-07-21 2:30 ` [PATCH 5/9] arm64: dts: exynosautov920: Add ABOX IPC Generic device node ew kim
@ 2025-07-21 6:45 ` Krzysztof Kozlowski
0 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-21 6:45 UTC (permalink / raw)
To: ew kim, broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel
On 21/07/2025 04:30, ew kim wrote:
> This patch adds a new child node `abox_ipc_generic` under the
Please read kernel submitting patches and DT bindings submitting patches.
> `abox_generic` node for ExynosAuto v920. The ABOX IPC Generic
> driver handles inter-processor communication (IPC) between
> the ABOX DSP and host SoC using IRQs.
>
> The node includes configuration for the number of IRQ channels
> used for IPC routing. This allows SoC-specific subsystems to
> send and receive messages through the ABOX generic audio stack.
>
> Signed-off-by: ew kim <ew.kim@samsung.com>
> ---
> arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts | 6 +++++-
> arch/arm64/boot/dts/exynos/exynosautov920.dtsi | 10 ++++++++--
> 2 files changed, 13 insertions(+), 3 deletions(-)
>
> diff --git a/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts b/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
> index a870c0b6847f..2f4cf112675a 100644
> --- a/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
> +++ b/arch/arm64/boot/dts/exynos/exynosautov920-sadk.dts
> @@ -89,4 +89,8 @@ &xtcxo {
>
> &abox_generic {
> status = "okay";
> -};
> \ No newline at end of file
> +};
> +
> +&abox_ipc_generic {
> + status = "okay";
> +};
> diff --git a/arch/arm64/boot/dts/exynos/exynosautov920.dtsi b/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
> index 4f086a7a79c8..21bcbcf7e2b6 100644
> --- a/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
> +++ b/arch/arm64/boot/dts/exynos/exynosautov920.dtsi
> @@ -1133,8 +1133,14 @@ abox_generic: abox_generic {
> samsung,num-pcm-capture = <32>;
> samsung,num-i2s-dummy-backend = <5>;
> status = "disabled";
> - #address-cells = <2>;
> - #size-cells = <1>;
> + /* #address-cells = <2>; */
> + /* #size-cells = <1>; */
> +
> + abox_ipc_generic: abox_ipc_generic {
> + compatible = "samsung,abox_ipc_generic";
No bindings. Are you sure you follow standard patchset order?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 6/9] ASoC : dt-bindings: sound: Add binding for ABOX IPC Generic
2025-07-21 2:30 ` [PATCH 6/9] ASoC : dt-bindings: sound: Add binding for ABOX IPC Generic ew kim
@ 2025-07-21 6:46 ` Krzysztof Kozlowski
0 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-21 6:46 UTC (permalink / raw)
To: ew kim, broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel
On 21/07/2025 04:30, ew kim wrote:
> This patch updates the existing samsung,exynosauto.yaml schema to
> describe the ABOX IPC Generic child node. This node represents
> a virtual IPC interface used by the ABOX audio subsystem to
> communicate with SoC-specific hardware using shared IRQ channels.
>
> The schema describes the `samsung,num-irq` property and allows
> integration of the IPC node under `abox_generic`.
>
> Signed-off-by: ew kim <ew.kim@samsung.com>
> ---
> .../bindings/sound/samsung,exynosauto.yaml | 23 +++++++++++++++++++
> 1 file changed, 23 insertions(+)
>
> diff --git a/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
> index b1e49f38ffe9..3a7b5be627ee 100644
> --- a/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
> +++ b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
> @@ -48,6 +48,23 @@ properties:
> description: Required for child nodes that may declare address space.
> const: 1
>
> + abox_ipc_generic:
> + type: object
> + description: ABOX IPC Generic subnode for SoC-level message routing
> + properties:
> + compatible:
> + const: samsung,abox_ipc_generic
We cannot take generic IPC.
> +
> + samsung,num-irq:
> + $ref: /schemas/types.yaml#/definitions/uint32
> + description: Number of IRQ channels supported for IPC routing.
> +
> + required:
> + - compatible
> + - samsung,num-irq
> +
> + additionalProperties: false
> +
> required:
> - compatible
> - samsung,num-pcm-playback
> @@ -66,4 +83,10 @@ examples:
> status = "disabled";
> #address-cells = <2>;
> #size-cells = <1>;
> +
> + abox_ipc_generic {
> + compatible = "samsung,abox_ipc_generic";
> + samsung,num-irq = <64>;
> + status = "disabled";
So you never test it...
> + };
> };
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 9/9] ASoC: dt-bindings: sound: exynosauto: add PCM frontend nodes for ABOX generic
2025-07-21 2:30 ` [PATCH 9/9] ASoC: dt-bindings: sound: exynosauto: add PCM frontend nodes for ABOX generic ew kim
@ 2025-07-21 6:47 ` Krzysztof Kozlowski
0 siblings, 0 replies; 18+ messages in thread
From: Krzysztof Kozlowski @ 2025-07-21 6:47 UTC (permalink / raw)
To: ew kim, broonie, s.nawrocki, robh, krzk+dt
Cc: lgirdwood, tiwai, perex, conor+dt, alim.akhtar, linux-sound,
devicetree, linux-arm-kernel, linux-samsung-soc, linux-kernel
On 21/07/2025 04:30, ew kim wrote:
> This patch extends the Exynos Automotive ABOX generic device tree bindings
> to support PCM playback and capture frontend nodes.
>
> Each PCM device node describes an audio stream interface handled by the
> ABOX DSP. These nodes include properties for stream ID, IRQ, ADSP core
> assignment, buffer limits, and stream category (deep_buffer or compress).
>
> The bindings use patternProperties to match playback and capture nodes
> as children of the abox_generic controller.
This split of one device into three makes no sense. Adding new binding
is one commit.
>
> Signed-off-by: ew kim <ew.kim@samsung.com>
> ---
> .../bindings/sound/samsung,exynosauto.yaml | 126 +++++++++++++++++-
> 1 file changed, 123 insertions(+), 3 deletions(-)
>
> diff --git a/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
> index 3a7b5be627ee..e477550afc7c 100644
> --- a/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
> +++ b/Documentation/devicetree/bindings/sound/samsung,exynosauto.yaml
> @@ -28,6 +28,14 @@ properties:
> compatible:
> const: samsung,abox_generic
>
> + status:
> + enum: [ okay, disabled ]
> + description: DTS node enablement state
Sorry, but why are you writing something entirely different than every
other binding?
Best regards,
Krzysztof
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 1/9] ASoC: samsung: Add generic ABOX management driver
2025-07-21 2:30 ` [PATCH 1/9] ASoC: samsung: Add generic ABOX management driver ew kim
@ 2025-07-30 12:04 ` Mark Brown
0 siblings, 0 replies; 18+ messages in thread
From: Mark Brown @ 2025-07-30 12:04 UTC (permalink / raw)
To: ew kim
Cc: s.nawrocki, robh, krzk+dt, lgirdwood, tiwai, perex, conor+dt,
alim.akhtar, linux-sound, devicetree, linux-arm-kernel,
linux-samsung-soc, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 690 bytes --]
On Mon, Jul 21, 2025 at 11:30:44AM +0900, ew kim wrote:
> This driver implements the *generic* (fixed) part of the ABOX management
> stack for Samsung Automotive SoCs. It does not directly control hardware
> but provides common interfaces and state needed by SoC-specific
> (variable) drivers.
> The abox generic driver manages child drivers and provides an interface
> that bridges the fixed and variable parts, connecting the two modules.
I think for such an unusual design we need a much clearer description of
what this is trying to do and why it's not following normal kernel
patterns. I can't tell what the services this generic code is providing
are, nor why it's done this way.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: [PATCH 4/9] ASoC: samsung: abox: Add IPC generic support for message forwarding
2025-07-21 2:30 ` [PATCH 4/9] ASoC: samsung: abox: Add IPC generic support for message forwarding ew kim
@ 2025-07-30 12:06 ` Mark Brown
0 siblings, 0 replies; 18+ messages in thread
From: Mark Brown @ 2025-07-30 12:06 UTC (permalink / raw)
To: ew kim
Cc: s.nawrocki, robh, krzk+dt, lgirdwood, tiwai, perex, conor+dt,
alim.akhtar, linux-sound, devicetree, linux-arm-kernel,
linux-samsung-soc, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 226 bytes --]
On Mon, Jul 21, 2025 at 11:30:47AM +0900, ew kim wrote:
> +EXPORT_SYMBOL(abox_ipc_generic_get_pcm_dev_handler_callback);
ASoC is all EXPORT_SYMBOL_GPL() so if you're providing access to ASoC
things you should also use that.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 18+ messages in thread
* Re: Add ExynosAuto ABOX generic platform and PCM support
2025-07-21 2:30 ` Add ExynosAuto ABOX generic platform and PCM support ew kim
` (8 preceding siblings ...)
[not found] ` <CGME20250721024612epcas2p122d627cfb90eac508b6ed3667acd9b9b@epcas2p1.samsung.com>
@ 2025-07-30 12:46 ` Mark Brown
9 siblings, 0 replies; 18+ messages in thread
From: Mark Brown @ 2025-07-30 12:46 UTC (permalink / raw)
To: ew kim
Cc: s.nawrocki, robh, krzk+dt, lgirdwood, tiwai, perex, conor+dt,
alim.akhtar, linux-sound, devicetree, linux-arm-kernel,
linux-samsung-soc, linux-kernel
[-- Attachment #1: Type: text/plain, Size: 1302 bytes --]
On Mon, Jul 21, 2025 at 11:30:43AM +0900, ew kim wrote:
> This patch series adds the ABOX Generic audio management driver for
> Samsung ExynosAuto SoCs, along with IPC messaging and PCM frontend
> drivers, including their corresponding device tree bindings.
>
> ### ABOX Architecture Design: Fixed and Variable Structure
>
> The ABOX audio framework is designed with a clear split between:
> - **Fixed part** (common framework): reusable components across SoCs
> - Generic ABOX platform driver
> - IPC messaging handler
> - PCM frontend with ALSA compressed audio support
> - Solution manager for dynamic control
> - **Variable part** (SoC-specific): defined via device tree
> - IRQ numbers, stream IDs, buffer sizes, and ADSP allocation
> - Defined per PCM/IPC device node in DTS
This all sounds from a system integration point of view like a fairly
standard audio DSP. Usually something like that would be structured
with the generic bits of DSP support done as a library which the drivers
for specific bits of hardware link to, the SOF code is the most obvious
example of this we have upstream but there's a few other simpler ones
like the Cirrus CODECs. If there's a reason why this wouldn't work here
it's not clear to me from what you've posted thus far.
[-- Attachment #2: signature.asc --]
[-- Type: application/pgp-signature, Size: 488 bytes --]
^ permalink raw reply [flat|nested] 18+ messages in thread
end of thread, other threads:[~2025-07-30 12:46 UTC | newest]
Thread overview: 18+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
[not found] <CGME20250721024611epcas2p43099e043aaa6f48c05eb0237065d31c7@epcas2p4.samsung.com>
2025-07-21 2:30 ` Add ExynosAuto ABOX generic platform and PCM support ew kim
[not found] ` <CGME20250721024611epcas2p45ddc52c1644f5779c7da822573f03246@epcas2p4.samsung.com>
2025-07-21 2:30 ` [PATCH 1/9] ASoC: samsung: Add generic ABOX management driver ew kim
2025-07-30 12:04 ` Mark Brown
[not found] ` <CGME20250721024611epcas2p37ecbc204ea695d97f6477c04712a9974@epcas2p3.samsung.com>
2025-07-21 2:30 ` [PATCH 2/9] arm64: dts: exynosautov920: add abox_generic dt node ew kim
2025-07-21 6:41 ` Krzysztof Kozlowski
[not found] ` <CGME20250721024611epcas2p47ebaf8cb494fc2bf71a83b00ba47f2b3@epcas2p4.samsung.com>
2025-07-21 2:30 ` [PATCH 3/9] ASoC: dt-bindings: sound: Add Samsung ExynosAuto ABOX binding ew kim
2025-07-21 6:44 ` Krzysztof Kozlowski
[not found] ` <CGME20250721024611epcas2p4baca500b3b1f185dcdc35552b2abe8d9@epcas2p4.samsung.com>
2025-07-21 2:30 ` [PATCH 4/9] ASoC: samsung: abox: Add IPC generic support for message forwarding ew kim
2025-07-30 12:06 ` Mark Brown
[not found] ` <CGME20250721024611epcas2p375cd5e4b53fcff3b69a39ef19c0825a4@epcas2p3.samsung.com>
2025-07-21 2:30 ` [PATCH 5/9] arm64: dts: exynosautov920: Add ABOX IPC Generic device node ew kim
2025-07-21 6:45 ` Krzysztof Kozlowski
[not found] ` <CGME20250721024611epcas2p382f3decd51152a5c89c673f222e22da1@epcas2p3.samsung.com>
2025-07-21 2:30 ` [PATCH 6/9] ASoC : dt-bindings: sound: Add binding for ABOX IPC Generic ew kim
2025-07-21 6:46 ` Krzysztof Kozlowski
[not found] ` <CGME20250721024611epcas2p423f2e6084264b08f43c6f86ce1ad0892@epcas2p4.samsung.com>
2025-07-21 2:30 ` [PATCH 7/9] ASoC: samsung: Add PCM driver with solution support ew kim
[not found] ` <CGME20250721024611epcas2p3da8e99d27a57cf7ad4ed46729e86602f@epcas2p3.samsung.com>
2025-07-21 2:30 ` [PATCH 8/9] arm64: dts: exynosautov920: add PCM playback/capture ew kim
[not found] ` <CGME20250721024612epcas2p122d627cfb90eac508b6ed3667acd9b9b@epcas2p1.samsung.com>
2025-07-21 2:30 ` [PATCH 9/9] ASoC: dt-bindings: sound: exynosauto: add PCM frontend nodes for ABOX generic ew kim
2025-07-21 6:47 ` Krzysztof Kozlowski
2025-07-30 12:46 ` Add ExynosAuto ABOX generic platform and PCM support Mark Brown
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).