From bd8a9364b0e68678f6c682c408c776a1d6e62372 Mon Sep 17 00:00:00 2001 From: WrenIX Date: Fri, 1 May 2026 07:18:27 +0200 Subject: [PATCH 01/28] --- patches from sdm670 --- From ac6db3fa41320291e6e48e9b9fe3d95d50184a17 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Tue, 28 Apr 2020 16:08:46 +0200 Subject: [PATCH 02/28] ASoC: qdsp6: Add shared session management for q6voice services Signed-off-by: Stephan Gerhold (cherry picked from commit a7f55b13fcdd40101c65625550d02a0d3cf75db7) Link: https://github.com/msm8916-mainline/linux Signed-off-by: Richard Acayan --- sound/soc/qcom/Kconfig | 4 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6voice-common.c | 272 ++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6voice-common.h | 44 +++++ 4 files changed, 321 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6voice-common.c create mode 100644 sound/soc/qcom/qdsp6/q6voice-common.h diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index e6e24f3b99222d..f6d8998adac87b 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -156,6 +156,10 @@ config SND_SOC_QDSP6 audio drivers. This includes q6asm, q6adm, q6afe interfaces to DSP using apr. +config SND_SOC_QDSP6_Q6VOICE + tristate "QDSP6 Q6Voice audio driver" + depends on SND_SOC_QDSP6 + config SND_SOC_MSM8996 tristate "SoC Machine driver for MSM8996 and APQ8096 boards" depends on QCOM_APR diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 67267304e7e920..db31273026e312 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -11,6 +11,7 @@ obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o +obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE) += q6voice-common.o obj-$(CONFIG_SND_SOC_QDSP6_APM) += snd-q6apm.o obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o diff --git a/sound/soc/qcom/qdsp6/q6voice-common.c b/sound/soc/qcom/qdsp6/q6voice-common.c new file mode 100644 index 00000000000000..f821d619e16aaa --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6voice-common.c @@ -0,0 +1,272 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2020, Stephan Gerhold + +#include +#include +#include +#include +#include "q6voice-common.h" + +#define APRV2_IBASIC_CMD_DESTROY_SESSION 0x0001003C + +#define TIMEOUT_MS 300 + +struct q6voice_service { + struct apr_device *adev; + enum q6voice_service_type type; + + /* Protect sessions array */ + spinlock_t lock; + struct q6voice_session *sessions[Q6VOICE_PATH_COUNT]; +}; + +/* Protect q6voice_services */ +static DEFINE_SPINLOCK(q6voice_services_lock); +static struct q6voice_service *q6voice_services[Q6VOICE_SERVICE_COUNT] = {0}; + +int q6voice_common_probe(struct apr_device *adev, enum q6voice_service_type type) +{ + struct device *dev = &adev->dev; + struct q6voice_service *svc, *current_svc; + unsigned long flags; + + if (type >= Q6VOICE_SERVICE_COUNT) + return -EINVAL; + + svc = devm_kzalloc(dev, sizeof(*svc), GFP_KERNEL); + if (!svc) + return -ENOMEM; + + svc->adev = adev; + svc->type = type; + spin_lock_init(&svc->lock); + + dev_set_drvdata(dev, svc); + + spin_lock_irqsave(&q6voice_services_lock, flags); + current_svc = q6voice_services[type]; + if (!current_svc) + q6voice_services[type] = svc; + spin_unlock_irqrestore(&q6voice_services_lock, flags); + + return current_svc ? -EEXIST : 0; +} +EXPORT_SYMBOL_GPL(q6voice_common_probe); + +void q6voice_common_remove(struct apr_device *adev) +{ + struct q6voice_service *svc = dev_get_drvdata(&adev->dev); + enum q6voice_service_type type = svc->type; + unsigned long flags; + + spin_lock_irqsave(&q6voice_services_lock, flags); + if (q6voice_services[type] == svc) + q6voice_services[type] = NULL; + spin_unlock_irqrestore(&q6voice_services_lock, flags); + + /* TODO: Should probably free up sessions here??? */ +} +EXPORT_SYMBOL_GPL(q6voice_common_remove); + +static void q6voice_session_free(struct kref *ref) +{ + struct q6voice_session *s = container_of(ref, struct q6voice_session, + refcount); + + kfree(s); +} + +static int q6voice_session_destroy(struct q6voice_session *s) +{ + struct apr_pkt cmd; + + cmd.hdr.pkt_size = APR_HDR_SIZE; + cmd.hdr.opcode = APRV2_IBASIC_CMD_DESTROY_SESSION; + + return q6voice_common_send(s, &cmd.hdr); +} + +void q6voice_session_release(struct q6voice_session *s) +{ + struct q6voice_service *svc = s->svc; + unsigned long flags; + + if (s->handle) + q6voice_session_destroy(s); + + spin_lock_irqsave(&svc->lock, flags); + if (svc->sessions[s->port] == s) + svc->sessions[s->port] = NULL; + spin_unlock_irqrestore(&svc->lock, flags); + + kref_put(&s->refcount, q6voice_session_free); +} +EXPORT_SYMBOL_GPL(q6voice_session_release); + +struct q6voice_session * +q6voice_session_create(enum q6voice_service_type type, + enum q6voice_path_type path, struct apr_hdr *hdr) +{ + struct q6voice_service *svc; + struct q6voice_session *s; + unsigned long flags; + int ret; + + spin_lock_irqsave(&q6voice_services_lock, flags); + svc = q6voice_services[type]; + spin_unlock_irqrestore(&q6voice_services_lock, flags); + if (!svc) + return ERR_PTR(-ENODEV); + + s = kzalloc(sizeof(*s), GFP_KERNEL); + if (!s) + return ERR_PTR(-ENOMEM); + + s->dev = &svc->adev->dev; + s->svc = svc; + s->port = path; + + kref_init(&s->refcount); + spin_lock_init(&s->lock); + init_waitqueue_head(&s->wait); + + spin_lock_irqsave(&svc->lock, flags); + if (svc->sessions[path]) { + spin_unlock_irqrestore(&svc->lock, flags); + kfree(s); + return ERR_PTR(-EBUSY); + } + svc->sessions[path] = s; + spin_unlock_irqrestore(&svc->lock, flags); + + dev_dbg(s->dev, "create session\n"); + + ret = q6voice_common_send(s, hdr); + if (ret) + goto err; + + if (!s->handle) { + dev_warn(s->dev, "failed to receive handle\n"); + ret = -EIO; + goto err; + } + + dev_dbg(s->dev, "handle: %d\n", s->handle); + + return s; + +err: + q6voice_session_release(s); + return ERR_PTR(ret); +} +EXPORT_SYMBOL_GPL(q6voice_session_create); + +static void q6voice_session_callback(struct q6voice_session *s, + struct apr_resp_pkt *data) +{ + struct aprv2_ibasic_rsp_result_t *result = data->payload; + unsigned long flags; + + if (data->hdr.opcode != APR_BASIC_RSP_RESULT) + return; /* Not handled here */ + + dev_dbg(s->dev, "basic result: opcode %#x, status: %#x\n", + result->opcode, result->status); + + spin_lock_irqsave(&s->lock, flags); + if (result->opcode != s->expected_opcode) { + spin_unlock_irqrestore(&s->lock, flags); + dev_warn(s->dev, "unexpected reply for opcode %#x (status: %#x)\n", + result->opcode, result->status); + return; + } + + if (!s->handle) { + s->handle = data->hdr.src_port; + } else if (s->handle != data->hdr.src_port) { + spin_unlock_irqrestore(&s->lock, flags); + dev_warn(s->dev, "unexpected reply for session %#x (!= %#x)\n", + data->hdr.src_port, s->handle); + return; + } + + s->result = result->status; + s->expected_opcode = 0; + spin_unlock_irqrestore(&s->lock, flags); + + wake_up(&s->wait); +} + +int q6voice_common_callback(struct apr_device *adev, struct apr_resp_pkt *data) +{ + struct device *dev = &adev->dev; + struct q6voice_service *v = dev_get_drvdata(dev); + struct q6voice_session *s; + unsigned long flags; + + dev_dbg(dev, "callback: %#x\n", data->hdr.opcode); + + if (data->hdr.dest_port >= Q6VOICE_PATH_COUNT) { + dev_warn(dev, "callback() called for unhandled/invalid path: %d\n", + data->hdr.dest_port); + return 0; + } + + spin_lock_irqsave(&v->lock, flags); + s = v->sessions[data->hdr.dest_port]; + if (s) + kref_get(&s->refcount); + spin_unlock_irqrestore(&v->lock, flags); + + if (s) { + q6voice_session_callback(s, data); + kref_put(&s->refcount, q6voice_session_free); + } else { + dev_warn(dev, "callback() called for inactive path: %d\n", + data->hdr.dest_port); + } + + return 0; +} +EXPORT_SYMBOL_GPL(q6voice_common_callback); + +int q6voice_common_send(struct q6voice_session *s, struct apr_hdr *hdr) +{ + unsigned long flags; + int ret; + + hdr->hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + hdr->src_port = s->port; + hdr->dest_port = s->handle; + hdr->token = 0; + + spin_lock_irqsave(&s->lock, flags); + s->expected_opcode = hdr->opcode; + s->result = 0; + spin_unlock_irqrestore(&s->lock, flags); + + ret = apr_send_pkt(s->svc->adev, (struct apr_pkt *)hdr); + if (ret < 0) + return ret; + + ret = wait_event_timeout(s->wait, (s->expected_opcode == 0), + msecs_to_jiffies(TIMEOUT_MS)); + if (!ret) { + s->expected_opcode = 0; + return -ETIMEDOUT; + } + + if (s->result > 0) { + dev_err(s->dev, "command %#x failed with error %d\n", + hdr->opcode, s->result); + return -EIO; + } + + return 0; +} +EXPORT_SYMBOL_GPL(q6voice_common_send); + +MODULE_AUTHOR("Stephan Gerhold "); +MODULE_DESCRIPTION("Q6Voice common session management"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6voice-common.h b/sound/soc/qcom/qdsp6/q6voice-common.h new file mode 100644 index 00000000000000..6fb3c9560b2e25 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6voice-common.h @@ -0,0 +1,44 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _Q6_VOICE_COMMON_H +#define _Q6_VOICE_COMMON_H + +#include +#include "q6voice.h" + +enum q6voice_service_type { + Q6VOICE_SERVICE_MVM, + Q6VOICE_SERVICE_CVP, + Q6VOICE_SERVICE_CVS, + Q6VOICE_SERVICE_COUNT +}; + +struct q6voice_service; + +struct q6voice_session { + struct device *dev; + struct q6voice_service *svc; + struct kref refcount; + + u16 port; + u16 handle; + + wait_queue_head_t wait; + + /* Protect expected_opcode and result */ + spinlock_t lock; + u32 expected_opcode; + u32 result; +}; + +int q6voice_common_probe(struct apr_device *adev, enum q6voice_service_type type); +void q6voice_common_remove(struct apr_device *adev); + +int q6voice_common_callback(struct apr_device *adev, struct apr_resp_pkt *data); +int q6voice_common_send(struct q6voice_session *s, struct apr_hdr *hdr); + +struct q6voice_session *q6voice_session_create(enum q6voice_service_type type, + enum q6voice_path_type path, + struct apr_hdr *hdr); +void q6voice_session_release(struct q6voice_session *s); + +#endif /*_Q6_VOICE_COMMON_H */ From ff434d9e47bba23805ca2f9c90ddcb0188a86f95 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Tue, 28 Apr 2020 16:10:05 +0200 Subject: [PATCH 03/28] ASoC: qdsp6: Add driver for Q6 Multimode Voice Manager (q6mvm) v2: strlcpy -> strscpy Signed-off-by: Stephan Gerhold Signed-off-by: Nikita Travkin (cherry picked from commit eb82ba2c42299aa5f210ce84495660a1345c23d0) Link: https://github.com/msm8916-mainline/linux Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/Makefile | 2 +- sound/soc/qcom/qdsp6/q6mvm.c | 160 ++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6mvm.h | 15 ++++ 3 files changed, 176 insertions(+), 1 deletion(-) create mode 100644 sound/soc/qcom/qdsp6/q6mvm.c create mode 100644 sound/soc/qcom/qdsp6/q6mvm.h diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index db31273026e312..d704b727b8ddbc 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -11,7 +11,7 @@ obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o -obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE) += q6voice-common.o +obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE) += q6mvm.o q6voice-common.o obj-$(CONFIG_SND_SOC_QDSP6_APM) += snd-q6apm.o obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o diff --git a/sound/soc/qcom/qdsp6/q6mvm.c b/sound/soc/qcom/qdsp6/q6mvm.c new file mode 100644 index 00000000000000..f1f8d520ba0d9b --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6mvm.c @@ -0,0 +1,160 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2020, Stephan Gerhold + +#include +#include +#include +#include +#include "q6mvm.h" +#include "q6voice-common.h" + +#define VSS_IMVM_CMD_CREATE_PASSIVE_CONTROL_SESSION 0x000110FF + +struct vss_imvm_cmd_create_control_session_cmd { + struct apr_hdr hdr; + + /* A variable-sized stream name. */ + char name[20]; +} __packed; + +#define VSS_IMVM_CMD_SET_POLICY_DUAL_CONTROL 0x00011327 + +/* This command is required to let MVM know who is in control of session. */ +struct vss_imvm_cmd_set_policy_dual_control_cmd { + struct apr_hdr hdr; + + /* Set to TRUE to enable modem state machine control */ + bool enable; +} __packed; + +#define VSS_IMVM_CMD_ATTACH_VOCPROC 0x0001123E +#define VSS_IMVM_CMD_DETACH_VOCPROC 0x0001123F + +/* + * Attach/detach a vocproc to the MVM. + * The MVM will symmetrically connect/disconnect this vocproc + * to/from all the streams currently attached to it. + */ +struct vss_imvm_cmd_attach_vocproc_cmd { + struct apr_hdr hdr; + + /* Handle of vocproc being attached. */ + u16 handle; +} __packed; + +#define VSS_IMVM_CMD_START_VOICE 0x00011190 +#define VSS_IMVM_CMD_STOP_VOICE 0x00011192 + +static inline const char *q6mvm_session_name(enum q6voice_path_type path) +{ + switch (path) { + case Q6VOICE_PATH_VOICE: + return "default modem voice"; + default: + return NULL; + } +} + +static int q6mvm_set_dual_control(struct q6voice_session *mvm) +{ + struct vss_imvm_cmd_set_policy_dual_control_cmd cmd; + + cmd.hdr.pkt_size = sizeof(cmd); + cmd.hdr.opcode = VSS_IMVM_CMD_SET_POLICY_DUAL_CONTROL; + + cmd.enable = true; + + return q6voice_common_send(mvm, &cmd.hdr); +} + +struct q6voice_session *q6mvm_session_create(enum q6voice_path_type path) +{ + struct vss_imvm_cmd_create_control_session_cmd cmd; + struct q6voice_session *mvm; + const char *session_name; + int ret; + + cmd.hdr.pkt_size = sizeof(cmd); + cmd.hdr.opcode = VSS_IMVM_CMD_CREATE_PASSIVE_CONTROL_SESSION; + + session_name = q6mvm_session_name(path); + if (session_name) + strscpy(cmd.name, session_name, sizeof(cmd.name)); + + mvm = q6voice_session_create(Q6VOICE_SERVICE_MVM, path, &cmd.hdr); + if (IS_ERR(mvm)) + return mvm; + + ret = q6mvm_set_dual_control(mvm); + if (ret) { + dev_err(mvm->dev, "failed to set dual control: %d\n", ret); + q6voice_session_release(mvm); + return ERR_PTR(ret); + } + + return mvm; +} +EXPORT_SYMBOL_GPL(q6mvm_session_create); + +int q6mvm_attach(struct q6voice_session *mvm, struct q6voice_session *cvp, + bool state) +{ + struct vss_imvm_cmd_attach_vocproc_cmd cmd; + + cmd.hdr.pkt_size = sizeof(cmd); + cmd.hdr.opcode = state ? VSS_IMVM_CMD_ATTACH_VOCPROC : VSS_IMVM_CMD_DETACH_VOCPROC; + + cmd.handle = cvp->handle; + + return q6voice_common_send(mvm, &cmd.hdr); +} +EXPORT_SYMBOL_GPL(q6mvm_attach); + +int q6mvm_start(struct q6voice_session *mvm, bool state) +{ + struct apr_pkt cmd; + + cmd.hdr.pkt_size = APR_HDR_SIZE; + cmd.hdr.opcode = state ? VSS_IMVM_CMD_START_VOICE : VSS_IMVM_CMD_STOP_VOICE; + + return q6voice_common_send(mvm, &cmd.hdr); +} +EXPORT_SYMBOL_GPL(q6mvm_start); + +static int q6mvm_probe(struct apr_device *adev) +{ + int ret; + + ret = q6voice_common_probe(adev, Q6VOICE_SERVICE_MVM); + if (ret) + return ret; + + return of_platform_populate(adev->dev.of_node, NULL, NULL, &adev->dev); +} + +static void q6mvm_remove(struct apr_device *adev) +{ + of_platform_depopulate(&adev->dev); + q6voice_common_remove(adev); +} + +static const struct of_device_id q6mvm_device_id[] = { + { .compatible = "qcom,q6mvm" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6mvm_device_id); + +static struct apr_driver qcom_q6mvm_driver = { + .probe = q6mvm_probe, + .remove = q6mvm_remove, + .callback = q6voice_common_callback, + .driver = { + .name = "qcom-q6mvm", + .of_match_table = of_match_ptr(q6mvm_device_id), + }, +}; + +module_apr_driver(qcom_q6mvm_driver); +MODULE_DESCRIPTION("Q6 Multimode Voice Manager"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6mvm.h b/sound/soc/qcom/qdsp6/q6mvm.h new file mode 100644 index 00000000000000..51279eddb89b66 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6mvm.h @@ -0,0 +1,15 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _Q6_MVM_H +#define _Q6_MVM_H + +#include "q6voice.h" + +struct q6voice_session; + +struct q6voice_session *q6mvm_session_create(enum q6voice_path_type path); + +int q6mvm_attach(struct q6voice_session *mvm, struct q6voice_session *cvp, + bool state); +int q6mvm_start(struct q6voice_session *mvm, bool state); + +#endif /*_Q6_MVM_H */ From d0cd196d5a04000715eec3e8b205e76a83ab8b5c Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Tue, 28 Apr 2020 16:11:12 +0200 Subject: [PATCH 04/28] ASoC: qdsp6: Add driver for Q6 Core Voice Processor (q6cvp) Signed-off-by: Stephan Gerhold (cherry picked from commit d04fd7b3a87367c039034969004c34772ad19381) Link: https://github.com/msm8916-mainline/linux Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/Makefile | 2 +- sound/soc/qcom/qdsp6/q6cvp.c | 153 ++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6cvp.h | 13 +++ 3 files changed, 167 insertions(+), 1 deletion(-) create mode 100644 sound/soc/qcom/qdsp6/q6cvp.c create mode 100644 sound/soc/qcom/qdsp6/q6cvp.h diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index d704b727b8ddbc..07bac415f0d392 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -11,7 +11,7 @@ obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o -obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE) += q6mvm.o q6voice-common.o +obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE) += q6cvp.o q6mvm.o q6voice-common.o obj-$(CONFIG_SND_SOC_QDSP6_APM) += snd-q6apm.o obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o diff --git a/sound/soc/qcom/qdsp6/q6cvp.c b/sound/soc/qcom/qdsp6/q6cvp.c new file mode 100644 index 00000000000000..3549d3f14c914e --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6cvp.c @@ -0,0 +1,153 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2020, Stephan Gerhold + +#include +#include +#include +#include "q6cvp.h" +#include "q6voice-common.h" + +#define VSS_IVOCPROC_DIRECTION_RX 0 +#define VSS_IVOCPROC_DIRECTION_TX 1 +#define VSS_IVOCPROC_DIRECTION_RX_TX 2 + +#define VSS_IVOCPROC_PORT_ID_NONE 0xFFFF + +#define VSS_IVOCPROC_TOPOLOGY_ID_NONE 0x00010F70 +#define VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS 0x00010F71 +#define VSS_IVOCPROC_TOPOLOGY_ID_TX_DM_FLUENCE 0x00010F72 + +#define VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT 0x00010F77 + +#define VSS_IVOCPROC_VOCPROC_MODE_EC_INT_MIXING 0x00010F7C +#define VSS_IVOCPROC_VOCPROC_MODE_EC_EXT_MIXING 0x00010F7D + +#define VSS_ICOMMON_CAL_NETWORK_ID_NONE 0x0001135E + +#define VSS_IVOCPROC_CMD_ENABLE 0x000100C6 +#define VSS_IVOCPROC_CMD_DISABLE 0x000110E1 + +#define VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION_V2 0x000112BF + +struct vss_ivocproc_cmd_create_full_control_session_v2_cmd { + struct apr_hdr hdr; + + /* + * Vocproc direction. The supported values: + * VSS_IVOCPROC_DIRECTION_RX + * VSS_IVOCPROC_DIRECTION_TX + * VSS_IVOCPROC_DIRECTION_RX_TX + */ + u16 direction; + + /* + * Tx device port ID to which the vocproc connects. If a port ID is + * not being supplied, set this to #VSS_IVOCPROC_PORT_ID_NONE. + */ + u16 tx_port_id; + + /* + * Tx path topology ID. If a topology ID is not being supplied, set + * this to #VSS_IVOCPROC_TOPOLOGY_ID_NONE. + */ + u32 tx_topology_id; + + /* + * Rx device port ID to which the vocproc connects. If a port ID is + * not being supplied, set this to #VSS_IVOCPROC_PORT_ID_NONE. + */ + u16 rx_port_id; + + /* + * Rx path topology ID. If a topology ID is not being supplied, set + * this to #VSS_IVOCPROC_TOPOLOGY_ID_NONE. + */ + u32 rx_topology_id; + + /* Voice calibration profile ID. */ + u32 profile_id; + + /* + * Vocproc mode. The supported values: + * VSS_IVOCPROC_VOCPROC_MODE_EC_INT_MIXING + * VSS_IVOCPROC_VOCPROC_MODE_EC_EXT_MIXING + */ + u32 vocproc_mode; + + /* + * Port ID to which the vocproc connects for receiving echo + * cancellation reference signal. If a port ID is not being supplied, + * set this to #VSS_IVOCPROC_PORT_ID_NONE. This parameter value is + * ignored when the vocproc_mode parameter is set to + * VSS_IVOCPROC_VOCPROC_MODE_EC_INT_MIXING. + */ + u16 ec_ref_port_id; + + /* + * Session name string used to identify a session that can be shared + * with passive controllers (optional). + */ + char name[20]; +} __packed; + +struct q6voice_session *q6cvp_session_create(enum q6voice_path_type path, + u16 tx_port, u16 rx_port) +{ + struct vss_ivocproc_cmd_create_full_control_session_v2_cmd cmd; + + cmd.hdr.pkt_size = sizeof(cmd); + cmd.hdr.opcode = VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION_V2; + + /* TODO: Implement calibration */ + cmd.tx_topology_id = VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; + cmd.rx_topology_id = VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; + + cmd.direction = VSS_IVOCPROC_DIRECTION_RX_TX; + cmd.tx_port_id = tx_port; + cmd.rx_port_id = rx_port; + cmd.profile_id = VSS_ICOMMON_CAL_NETWORK_ID_NONE; + cmd.vocproc_mode = VSS_IVOCPROC_VOCPROC_MODE_EC_INT_MIXING; + cmd.ec_ref_port_id = VSS_IVOCPROC_PORT_ID_NONE; + + return q6voice_session_create(Q6VOICE_SERVICE_CVP, path, &cmd.hdr); +} +EXPORT_SYMBOL_GPL(q6cvp_session_create); + +int q6cvp_enable(struct q6voice_session *cvp, bool state) +{ + struct apr_pkt cmd; + + cmd.hdr.pkt_size = APR_HDR_SIZE; + cmd.hdr.opcode = state ? VSS_IVOCPROC_CMD_ENABLE : VSS_IVOCPROC_CMD_DISABLE; + + return q6voice_common_send(cvp, &cmd.hdr); +} +EXPORT_SYMBOL_GPL(q6cvp_enable); + +static int q6cvp_probe(struct apr_device *adev) +{ + return q6voice_common_probe(adev, Q6VOICE_SERVICE_CVP); +} + +static const struct of_device_id q6cvp_device_id[] = { + { .compatible = "qcom,q6cvp" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6cvp_device_id); + +static struct apr_driver qcom_q6cvp_driver = { + .probe = q6cvp_probe, + .remove = q6voice_common_remove, + .callback = q6voice_common_callback, + .driver = { + .name = "qcom-q6cvp", + .of_match_table = of_match_ptr(q6cvp_device_id), + }, +}; + +module_apr_driver(qcom_q6cvp_driver); + +MODULE_AUTHOR("Stephan Gerhold "); +MODULE_DESCRIPTION("Q6 Core Voice Processor"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6cvp.h b/sound/soc/qcom/qdsp6/q6cvp.h new file mode 100644 index 00000000000000..2381a3cfe0784d --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6cvp.h @@ -0,0 +1,13 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _Q6_CVP_H +#define _Q6_CVP_H + +#include "q6voice.h" + +struct q6voice_session; + +struct q6voice_session *q6cvp_session_create(enum q6voice_path_type path, + u16 tx_port, u16 rx_port); +int q6cvp_enable(struct q6voice_session *cvp, bool enable); + +#endif /*_Q6_CVP_H */ From 0cf4a444b2414a3966bcb9de30776c442f78d7a9 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Tue, 28 Apr 2020 16:11:53 +0200 Subject: [PATCH 05/28] ASoC: qdsp6: Add driver for Q6 Core Voice Stream (q6cvs) This is just a stub for now. Later this would be used for features like call recording. Signed-off-by: Stephan Gerhold (cherry picked from commit ba9cd1dac6c77c234b5956cc11981d5663cdc431) Link: https://github.com/msm8916-mainline/linux Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/Makefile | 2 +- sound/soc/qcom/qdsp6/q6cvs.c | 36 +++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6cvs.h | 9 +++++++++ 3 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 sound/soc/qcom/qdsp6/q6cvs.c create mode 100644 sound/soc/qcom/qdsp6/q6cvs.h diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 07bac415f0d392..4856fc094f304f 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -11,7 +11,7 @@ obj-$(CONFIG_SND_SOC_QDSP6_ADM) += q6adm.o obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o -obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE) += q6cvp.o q6mvm.o q6voice-common.o +obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE) += q6cvp.o q6cvs.o q6mvm.o q6voice-common.o obj-$(CONFIG_SND_SOC_QDSP6_APM) += snd-q6apm.o obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o diff --git a/sound/soc/qcom/qdsp6/q6cvs.c b/sound/soc/qcom/qdsp6/q6cvs.c new file mode 100644 index 00000000000000..440715870a04cf --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6cvs.c @@ -0,0 +1,36 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2020, Stephan Gerhold + +#include +#include +#include +#include "q6cvs.h" +#include "q6voice-common.h" + +static int q6cvs_probe(struct apr_device *adev) +{ + return q6voice_common_probe(adev, Q6VOICE_SERVICE_CVS); +} + +static const struct of_device_id q6cvs_device_id[] = { + { .compatible = "qcom,q6cvs" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6cvs_device_id); + +static struct apr_driver qcom_q6cvs_driver = { + .probe = q6cvs_probe, + .remove = q6voice_common_remove, + .callback = q6voice_common_callback, + .driver = { + .name = "qcom-q6cvs", + .of_match_table = of_match_ptr(q6cvs_device_id), + }, +}; + +module_apr_driver(qcom_q6cvs_driver); + +MODULE_AUTHOR("Stephan Gerhold "); +MODULE_DESCRIPTION("Q6 Core Voice Stream"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6cvs.h b/sound/soc/qcom/qdsp6/q6cvs.h new file mode 100644 index 00000000000000..5c204682c76496 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6cvs.h @@ -0,0 +1,9 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _Q6_CVS_H +#define _Q6_CVS_H + +#include "q6voice.h" + +struct q6voice_session; + +#endif /*_Q6_CVS_H */ From 35da5bcacd9b7052f4f9b2db1a00f8836c8961c7 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Tue, 28 Apr 2020 16:13:01 +0200 Subject: [PATCH 06/28] ASoC: qdsp6: Add voice call functionality in Q6 Voice driver q6voice combines the 3 q6voice-related services (q6mvm, q6cvp, q6cvs) and allows to start/end voice calls at the moment. Signed-off-by: Stephan Gerhold (cherry picked from commit e49b67185e83332b34c07f3d38424f30cacfd438) Link: https://github.com/msm8916-mainline/linux Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6voice.c | 249 +++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6voice.h | 23 +++ 3 files changed, 273 insertions(+) create mode 100644 sound/soc/qcom/qdsp6/q6voice.c create mode 100644 sound/soc/qcom/qdsp6/q6voice.h diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index 4856fc094f304f..a953bbed6d44f0 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -12,6 +12,7 @@ obj-$(CONFIG_SND_SOC_QDSP6_ROUTING) += q6routing.o obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE) += q6cvp.o q6cvs.o q6mvm.o q6voice-common.o +obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE) += q6voice.o obj-$(CONFIG_SND_SOC_QDSP6_APM) += snd-q6apm.o obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o diff --git a/sound/soc/qcom/qdsp6/q6voice.c b/sound/soc/qcom/qdsp6/q6voice.c new file mode 100644 index 00000000000000..2a6910d965156c --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6voice.c @@ -0,0 +1,249 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2020, Stephan Gerhold + +#include +#include +#include +#include +#include +#include "q6cvp.h" +#include "q6cvs.h" +#include "q6mvm.h" +#include "q6voice-common.h" + +/* FIXME: Remove */ +#define AFE_PORT_ID_PRIMARY_MI2S_RX 0x1000 +#define AFE_PORT_ID_PRIMARY_MI2S_TX 0x1001 +#define AFE_PORT_ID_SECONDARY_MI2S_RX 0x1002 +#define AFE_PORT_ID_SECONDARY_MI2S_TX 0x1003 +#define AFE_PORT_ID_TERTIARY_MI2S_RX 0x1004 +#define AFE_PORT_ID_TERTIARY_MI2S_TX 0x1005 +#define AFE_PORT_ID_QUATERNARY_MI2S_RX 0x1006 +#define AFE_PORT_ID_QUATERNARY_MI2S_TX 0x1007 + +struct q6voice_path_runtime { + struct q6voice_session *sessions[Q6VOICE_SERVICE_COUNT]; + unsigned int started; +}; + +struct q6voice_path { + struct q6voice *v; + + enum q6voice_path_type type; + /* Serialize access to voice path session */ + struct mutex lock; + struct q6voice_path_runtime *runtime; +}; + +struct q6voice { + struct device *dev; + struct q6voice_path paths[Q6VOICE_PATH_COUNT]; +}; + +static int q6voice_path_start(struct q6voice_path *p) +{ + struct device *dev = p->v->dev; + struct q6voice_session *mvm, *cvp; + int ret; + + dev_dbg(dev, "start path %d\n", p->type); + + mvm = p->runtime->sessions[Q6VOICE_SERVICE_MVM]; + if (!mvm) { + mvm = q6mvm_session_create(p->type); + if (IS_ERR(mvm)) + return PTR_ERR(mvm); + p->runtime->sessions[Q6VOICE_SERVICE_MVM] = mvm; + } + + cvp = p->runtime->sessions[Q6VOICE_SERVICE_CVP]; + if (!cvp) { + /* FIXME: Stop hardcoding */ + cvp = q6cvp_session_create(p->type, AFE_PORT_ID_TERTIARY_MI2S_TX, + AFE_PORT_ID_PRIMARY_MI2S_RX); + if (IS_ERR(cvp)) + return PTR_ERR(cvp); + p->runtime->sessions[Q6VOICE_SERVICE_CVP] = cvp; + } + + ret = q6cvp_enable(cvp, true); + if (ret) { + dev_err(dev, "failed to enable cvp: %d\n", ret); + goto cvp_err; + } + + ret = q6mvm_attach(mvm, cvp, true); + if (ret) { + dev_err(dev, "failed to attach cvp to mvm: %d\n", ret); + goto attach_err; + } + + ret = q6mvm_start(mvm, true); + if (ret) { + dev_err(dev, "failed to start voice: %d\n", ret); + goto start_err; + } + + return ret; + +start_err: + q6mvm_start(mvm, false); +attach_err: + q6mvm_attach(mvm, cvp, false); +cvp_err: + q6cvp_enable(cvp, false); + return ret; +} + +int q6voice_start(struct q6voice *v, enum q6voice_path_type path, bool capture) +{ + struct q6voice_path *p = &v->paths[path]; + int ret = 0; + + mutex_lock(&p->lock); + if (!p->runtime) { + p->runtime = kzalloc(sizeof(*p), GFP_KERNEL); + if (!p->runtime) { + ret = -ENOMEM; + goto out; + } + } + + if (p->runtime->started & BIT(capture)) { + ret = -EALREADY; + goto out; + } + + p->runtime->started |= BIT(capture); + + /* FIXME: For now we only start if both RX/TX are active */ + if (p->runtime->started != 3) + goto out; + + ret = q6voice_path_start(p); + if (ret) { + p->runtime->started &= ~BIT(capture); + dev_err(v->dev, "failed to start path %d: %d\n", path, ret); + goto out; + } + +out: + mutex_unlock(&p->lock); + return ret; +} +EXPORT_SYMBOL_GPL(q6voice_start); + +static void q6voice_path_stop(struct q6voice_path *p) +{ + struct device *dev = p->v->dev; + struct q6voice_session *mvm = p->runtime->sessions[Q6VOICE_SERVICE_MVM]; + struct q6voice_session *cvp = p->runtime->sessions[Q6VOICE_SERVICE_CVP]; + int ret; + + dev_dbg(dev, "stop path %d\n", p->type); + + ret = q6mvm_start(mvm, false); + if (ret) + dev_err(dev, "failed to stop voice: %d\n", ret); + + ret = q6mvm_attach(mvm, cvp, false); + if (ret) + dev_err(dev, "failed to detach cvp from mvm: %d\n", ret); + + ret = q6cvp_enable(cvp, false); + if (ret) + dev_err(dev, "failed to disable cvp: %d\n", ret); +} + +static void q6voice_path_destroy(struct q6voice_path *p) +{ + struct q6voice_path_runtime *runtime = p->runtime; + enum q6voice_service_type svc; + + for (svc = 0; svc < Q6VOICE_SERVICE_COUNT; ++svc) { + if (runtime->sessions[svc]) + q6voice_session_release(runtime->sessions[svc]); + } + + p->runtime = NULL; + kfree(runtime); +} + +int q6voice_stop(struct q6voice *v, enum q6voice_path_type path, bool capture) +{ + struct q6voice_path *p = &v->paths[path]; + int ret = 0; + + mutex_lock(&p->lock); + if (!p->runtime || !(p->runtime->started & BIT(capture))) + goto out; + + if (p->runtime->started == 3) + q6voice_path_stop(p); + + p->runtime->started &= ~BIT(capture); + + if (p->runtime->started == 0) + q6voice_path_destroy(p); + +out: + mutex_unlock(&p->lock); + return ret; +} +EXPORT_SYMBOL_GPL(q6voice_stop); + +static void q6voice_free(void *data) +{ + struct q6voice *v = data; + enum q6voice_path_type path; + + for (path = 0; path < Q6VOICE_PATH_COUNT; ++path) { + struct q6voice_path *p = &v->paths[path]; + + mutex_lock(&p->lock); + if (p->runtime) { + dev_warn(v->dev, + "q6voice_remove() called while path %d is active\n", + path); + + if (p->runtime->started == 3) + q6voice_path_stop(p); + q6voice_path_destroy(p); + } + mutex_unlock(&p->lock); + mutex_destroy(&p->lock); + } +} + +struct q6voice *q6voice_create(struct device *dev) +{ + struct q6voice *v; + enum q6voice_path_type path; + int ret; + + v = devm_kzalloc(dev, sizeof(*v), GFP_KERNEL); + if (!v) + return ERR_PTR(-ENOMEM); + + v->dev = dev; + + for (path = 0; path < Q6VOICE_PATH_COUNT; ++path) { + struct q6voice_path *p = &v->paths[path]; + + p->v = v; + p->type = path; + mutex_init(&p->lock); + } + + ret = devm_add_action(dev, q6voice_free, v); + if (ret) + return ERR_PTR(ret); + + return v; +} +EXPORT_SYMBOL_GPL(q6voice_create); + +MODULE_AUTHOR("Stephan Gerhold "); +MODULE_DESCRIPTION("Q6Voice driver"); +MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6voice.h b/sound/soc/qcom/qdsp6/q6voice.h new file mode 100644 index 00000000000000..b760b85ad37ec9 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6voice.h @@ -0,0 +1,23 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef _Q6_VOICE_H +#define _Q6_VOICE_H + +enum q6voice_path_type { + Q6VOICE_PATH_VOICE = 0, + /* TODO: Q6VOICE_PATH_VOIP = 1, */ + /* TODO: Q6VOICE_PATH_VOLTE = 2, */ + /* TODO: Q6VOICE_PATH_VOICE2 = 3, */ + /* TODO: Q6VOICE_PATH_QCHAT = 4, */ + /* TODO: Q6VOICE_PATH_VOWLAN = 5, */ + /* TODO: Q6VOICE_PATH_VOICEMMODE1 = 6, */ + /* TODO: Q6VOICE_PATH_VOICEMMODE2 = 7, */ + Q6VOICE_PATH_COUNT +}; + +struct q6voice; + +struct q6voice *q6voice_create(struct device *dev); +int q6voice_start(struct q6voice *v, enum q6voice_path_type path, bool capture); +int q6voice_stop(struct q6voice *v, enum q6voice_path_type path, bool capture); + +#endif /*_Q6_VOICE_H */ From b4b39da84b93cd624373df2a12fd1110e5bf58a9 Mon Sep 17 00:00:00 2001 From: Stephan Gerhold Date: Tue, 28 Apr 2020 16:14:50 +0200 Subject: [PATCH 07/28] ASoC: qdsp6: Add Q6 Voice DAI driver (v2.1) The Q6 Voice DAI driver exposes the Q6 Voice subsystem to ASoC. At the moment it provides a single CS-Voice DAI with a hostless FE. Eventually usage should be simplified using a codec2codec link. v2: Set AIFs SND_SOC_NOPM v2.1: Fix __DT_BINDINGS_Q6_VOCIE_H__ typo Signed-off-by: Stephan Gerhold (cherry picked from commit fe7841409608c17b02398ef8e592f5d6ba09b53e) Link: https://github.com/msm8916-mainline/linux Signed-off-by: Richard Acayan --- include/dt-bindings/sound/qcom,q6voice.h | 7 ++ sound/soc/qcom/Kconfig | 4 + sound/soc/qcom/qdsp6/Makefile | 1 + sound/soc/qcom/qdsp6/q6voice-dai.c | 136 +++++++++++++++++++++++ 4 files changed, 148 insertions(+) create mode 100644 include/dt-bindings/sound/qcom,q6voice.h create mode 100644 sound/soc/qcom/qdsp6/q6voice-dai.c diff --git a/include/dt-bindings/sound/qcom,q6voice.h b/include/dt-bindings/sound/qcom,q6voice.h new file mode 100644 index 00000000000000..fe5aa3a3fce49b --- /dev/null +++ b/include/dt-bindings/sound/qcom,q6voice.h @@ -0,0 +1,7 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +#ifndef __DT_BINDINGS_Q6_VOICE_H__ +#define __DT_BINDINGS_Q6_VOICE_H__ + +#define CS_VOICE 0 + +#endif /* __DT_BINDINGS_Q6_VOICE_H__ */ diff --git a/sound/soc/qcom/Kconfig b/sound/soc/qcom/Kconfig index f6d8998adac87b..63a7ef1887d93e 100644 --- a/sound/soc/qcom/Kconfig +++ b/sound/soc/qcom/Kconfig @@ -156,9 +156,13 @@ config SND_SOC_QDSP6 audio drivers. This includes q6asm, q6adm, q6afe interfaces to DSP using apr. +config SND_SOC_QDSP6_Q6VOICE_DAI + tristate + config SND_SOC_QDSP6_Q6VOICE tristate "QDSP6 Q6Voice audio driver" depends on SND_SOC_QDSP6 + select SND_SOC_QDSP6_Q6VOICE_DAI config SND_SOC_MSM8996 tristate "SoC Machine driver for MSM8996 and APQ8096 boards" diff --git a/sound/soc/qcom/qdsp6/Makefile b/sound/soc/qcom/qdsp6/Makefile index a953bbed6d44f0..940b9ec19694ef 100644 --- a/sound/soc/qcom/qdsp6/Makefile +++ b/sound/soc/qcom/qdsp6/Makefile @@ -13,6 +13,7 @@ obj-$(CONFIG_SND_SOC_QDSP6_ASM) += q6asm.o obj-$(CONFIG_SND_SOC_QDSP6_ASM_DAI) += q6asm-dai.o obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE) += q6cvp.o q6cvs.o q6mvm.o q6voice-common.o obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE) += q6voice.o +obj-$(CONFIG_SND_SOC_QDSP6_Q6VOICE_DAI) += q6voice-dai.o obj-$(CONFIG_SND_SOC_QDSP6_APM) += snd-q6apm.o obj-$(CONFIG_SND_SOC_QDSP6_APM_DAI) += q6apm-dai.o diff --git a/sound/soc/qcom/qdsp6/q6voice-dai.c b/sound/soc/qcom/qdsp6/q6voice-dai.c new file mode 100644 index 00000000000000..73401ed12b9414 --- /dev/null +++ b/sound/soc/qcom/qdsp6/q6voice-dai.c @@ -0,0 +1,136 @@ +// SPDX-License-Identifier: GPL-2.0 +// Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. +// Copyright (c) 2020, Stephan Gerhold + +#include +#include +#include +#include +#include "q6voice.h" + +#define DRV_NAME "q6voice-dai" + +static int q6voice_dai_startup(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6voice *v = snd_soc_dai_get_drvdata(dai); + + return q6voice_start(v, Q6VOICE_PATH_VOICE, substream->stream); +} + +static void q6voice_dai_shutdown(struct snd_pcm_substream *substream, + struct snd_soc_dai *dai) +{ + struct q6voice *v = snd_soc_dai_get_drvdata(dai); + + q6voice_stop(v, Q6VOICE_PATH_VOICE, substream->stream); +} + +static struct snd_soc_dai_ops q6voice_dai_ops = { + .startup = q6voice_dai_startup, + .shutdown = q6voice_dai_shutdown, +}; + +static struct snd_soc_dai_driver q6voice_dais[] = { + { + .id = CS_VOICE, + .name = "CS-VOICE", + /* The constraints here are not really meaningful... */ + .playback = { + .stream_name = "CS-VOICE Playback", + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + }, + .capture = { + .stream_name = "CS-VOICE Capture", + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &q6voice_dai_ops, + }, +}; + +/* FIXME: Use codec2codec instead */ +static struct snd_pcm_hardware q6voice_dai_hardware = { + .info = SNDRV_PCM_INFO_INTERLEAVED, + .buffer_bytes_max = 4096 * 2, + .period_bytes_min = 2048, + .period_bytes_max = 4096, + .periods_min = 2, + .periods_max = 4, + .fifo_size = 0, +}; + +static int q6voice_dai_open(struct snd_soc_component *component, + struct snd_pcm_substream *substream) +{ + substream->runtime->hw = q6voice_dai_hardware; + return 0; +} + +static const struct snd_soc_dapm_widget q6voice_dapm_widgets[] = { + SND_SOC_DAPM_AIF_IN("CS-VOICE_DL1", "CS-VOICE Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("CS-VOICE_UL1", "CS-VOICE Capture", 0, SND_SOC_NOPM, 0, 0), +}; + +static const struct snd_soc_dapm_route q6voice_dapm_routes[] = { + /* TODO: Make routing configurable */ + {"CS-VOICE_UL1", NULL, "TERT_MI2S_TX"}, + {"PRI_MI2S_RX", NULL, "CS-VOICE_DL1"}, +}; + +static const struct snd_soc_component_driver q6voice_dai_component = { + .name = DRV_NAME, + .open = q6voice_dai_open, + + .dapm_widgets = q6voice_dapm_widgets, + .num_dapm_widgets = ARRAY_SIZE(q6voice_dapm_widgets), + .dapm_routes = q6voice_dapm_routes, + .num_dapm_routes = ARRAY_SIZE(q6voice_dapm_routes), + + /* Needs to probe after q6afe */ + .probe_order = SND_SOC_COMP_ORDER_LATE, +}; + +static int q6voice_dai_probe(struct platform_device *pdev) +{ + struct device *dev = &pdev->dev; + struct q6voice *v; + + v = q6voice_create(dev); + if (IS_ERR(v)) + return PTR_ERR(v); + + dev_set_drvdata(dev, v); + + return devm_snd_soc_register_component(dev, &q6voice_dai_component, + q6voice_dais, + ARRAY_SIZE(q6voice_dais)); +} + +static const struct of_device_id q6voice_dai_device_id[] = { + { .compatible = "qcom,q6voice-dais" }, + {}, +}; +MODULE_DEVICE_TABLE(of, q6voice_dai_device_id); + +static struct platform_driver q6voice_dai_platform_driver = { + .driver = { + .name = "q6voice-dai", + .of_match_table = of_match_ptr(q6voice_dai_device_id), + }, + .probe = q6voice_dai_probe, +}; +module_platform_driver(q6voice_dai_platform_driver); + +MODULE_AUTHOR("Stephan Gerhold "); +MODULE_DESCRIPTION("Q6Voice DAI driver"); +MODULE_LICENSE("GPL v2"); From ac0e100ca4b670dead134e304a4fcc86d2e4467f Mon Sep 17 00:00:00 2001 From: Vincent Knecht Date: Tue, 28 Sep 2021 20:03:13 +0200 Subject: [PATCH 08/28] ASoC: qdsp6: q6voice: Add getter/setter for rx and tx ports Add function to get and set RX/TX ports and use those to un-hardcode the ports set in q6cvp_session_create() call. Signed-off-by: Vincent Knecht [use bool capture instead of enum similar to q6voice_start(), add path parameter] Signed-off-by: Stephan Gerhold (cherry picked from commit 6599fc6fa4acd85e66e3238a0a46ccf20cff00ff) Link: https://github.com/msm8916-mainline/linux Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6voice.c | 42 +++++++++++++++++++++++----------- sound/soc/qcom/qdsp6/q6voice.h | 5 ++++ 2 files changed, 34 insertions(+), 13 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6voice.c b/sound/soc/qcom/qdsp6/q6voice.c index 2a6910d965156c..d5751905a731ef 100644 --- a/sound/soc/qcom/qdsp6/q6voice.c +++ b/sound/soc/qcom/qdsp6/q6voice.c @@ -7,21 +7,12 @@ #include #include #include +#include "q6afe.h" #include "q6cvp.h" #include "q6cvs.h" #include "q6mvm.h" #include "q6voice-common.h" -/* FIXME: Remove */ -#define AFE_PORT_ID_PRIMARY_MI2S_RX 0x1000 -#define AFE_PORT_ID_PRIMARY_MI2S_TX 0x1001 -#define AFE_PORT_ID_SECONDARY_MI2S_RX 0x1002 -#define AFE_PORT_ID_SECONDARY_MI2S_TX 0x1003 -#define AFE_PORT_ID_TERTIARY_MI2S_RX 0x1004 -#define AFE_PORT_ID_TERTIARY_MI2S_TX 0x1005 -#define AFE_PORT_ID_QUATERNARY_MI2S_RX 0x1006 -#define AFE_PORT_ID_QUATERNARY_MI2S_TX 0x1007 - struct q6voice_path_runtime { struct q6voice_session *sessions[Q6VOICE_SERVICE_COUNT]; unsigned int started; @@ -31,6 +22,7 @@ struct q6voice_path { struct q6voice *v; enum q6voice_path_type type; + int tx_port, rx_port; /* Serialize access to voice path session */ struct mutex lock; struct q6voice_path_runtime *runtime; @@ -59,9 +51,9 @@ static int q6voice_path_start(struct q6voice_path *p) cvp = p->runtime->sessions[Q6VOICE_SERVICE_CVP]; if (!cvp) { - /* FIXME: Stop hardcoding */ - cvp = q6cvp_session_create(p->type, AFE_PORT_ID_TERTIARY_MI2S_TX, - AFE_PORT_ID_PRIMARY_MI2S_RX); + cvp = q6cvp_session_create(p->type, + q6afe_get_port_id(p->tx_port), + q6afe_get_port_id(p->rx_port)); if (IS_ERR(cvp)) return PTR_ERR(cvp); p->runtime->sessions[Q6VOICE_SERVICE_CVP] = cvp; @@ -244,6 +236,30 @@ struct q6voice *q6voice_create(struct device *dev) } EXPORT_SYMBOL_GPL(q6voice_create); +int q6voice_get_port(struct q6voice *v, enum q6voice_path_type path, + bool capture) +{ + struct q6voice_path *p = &v->paths[path]; + + if (capture) + return p->tx_port; + else + return p->rx_port; +} +EXPORT_SYMBOL_GPL(q6voice_get_port); + +void q6voice_set_port(struct q6voice *v, enum q6voice_path_type path, + bool capture, int index) +{ + struct q6voice_path *p = &v->paths[path]; + + if (capture) + p->tx_port = index; + else + p->rx_port = index; +} +EXPORT_SYMBOL_GPL(q6voice_set_port); + MODULE_AUTHOR("Stephan Gerhold "); MODULE_DESCRIPTION("Q6Voice driver"); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6voice.h b/sound/soc/qcom/qdsp6/q6voice.h index b760b85ad37ec9..5b2d2dd679b34d 100644 --- a/sound/soc/qcom/qdsp6/q6voice.h +++ b/sound/soc/qcom/qdsp6/q6voice.h @@ -20,4 +20,9 @@ struct q6voice *q6voice_create(struct device *dev); int q6voice_start(struct q6voice *v, enum q6voice_path_type path, bool capture); int q6voice_stop(struct q6voice *v, enum q6voice_path_type path, bool capture); +int q6voice_get_port(struct q6voice *v, enum q6voice_path_type path, + bool capture); +void q6voice_set_port(struct q6voice *v, enum q6voice_path_type path, + bool capture, int index); + #endif /*_Q6_VOICE_H */ From 419a2967e1ed111c90294b7ad23e4f5cb03b4ffa Mon Sep 17 00:00:00 2001 From: Vincent Knecht Date: Wed, 6 Oct 2021 18:21:21 +0200 Subject: [PATCH 09/28] ASoC: qdsp6: q6voice-dai: Add controls for voice rx and tx ports Add ALSA controls for voice RX and TX ports. Signed-off-by: Vincent Knecht [use mc->shift to mark TX mixers for now to simplify code] Signed-off-by: Stephan Gerhold (cherry picked from commit 0f3005393555a4ddd6261ff7413186be751b2ad6) Link: https://github.com/msm8916-mainline/linux Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6voice-dai.c | 126 ++++++++++++++++++++++++++++- 1 file changed, 123 insertions(+), 3 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6voice-dai.c b/sound/soc/qcom/qdsp6/q6voice-dai.c index 73401ed12b9414..f3ae9d75f82b7a 100644 --- a/sound/soc/qcom/qdsp6/q6voice-dai.c +++ b/sound/soc/qcom/qdsp6/q6voice-dai.c @@ -5,6 +5,7 @@ #include #include #include +#include #include #include "q6voice.h" @@ -76,17 +77,134 @@ static int q6voice_dai_open(struct snd_soc_component *component, return 0; } +static int q6voice_get_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct q6voice *v = snd_soc_component_get_drvdata(c); + bool capture = !!mc->shift; + + ucontrol->value.integer.value[0] = + q6voice_get_port(v, Q6VOICE_PATH_VOICE, capture) == mc->reg; + return 0; +} + +static int q6voice_put_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct q6voice *v = snd_soc_component_get_drvdata(c); + bool val = !!ucontrol->value.integer.value[0]; + bool capture = !!mc->shift; + + if (val) + q6voice_set_port(v, Q6VOICE_PATH_VOICE, capture, mc->reg); + else if (q6voice_get_port(v, Q6VOICE_PATH_VOICE, capture) == mc->reg) + q6voice_set_port(v, Q6VOICE_PATH_VOICE, capture, 0); + + snd_soc_dapm_mixer_update_power(dapm, kcontrol, val, NULL); + return 1; +} + +static const struct snd_kcontrol_new voice_tx_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, 1, 1, 0, + q6voice_get_mixer, q6voice_put_mixer), + SOC_SINGLE_EXT("SEC_MI2S_TX", SECONDARY_MI2S_TX, 1, 1, 0, + q6voice_get_mixer, q6voice_put_mixer), + SOC_SINGLE_EXT("TERT_MI2S_TX", TERTIARY_MI2S_TX, 1, 1, 0, + q6voice_get_mixer, q6voice_put_mixer), + SOC_SINGLE_EXT("QUAT_MI2S_TX", QUATERNARY_MI2S_TX, 1, 1, 0, + q6voice_get_mixer, q6voice_put_mixer), + SOC_SINGLE_EXT("QUIN_MI2S_TX", QUINARY_MI2S_TX, 1, 1, 0, + q6voice_get_mixer, q6voice_put_mixer), +}; + +static const struct snd_kcontrol_new primary_mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("CS-Voice", PRIMARY_MI2S_RX, 0, 1, 0, + q6voice_get_mixer, q6voice_put_mixer) +}; + +static const struct snd_kcontrol_new secondary_mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("CS-Voice", SECONDARY_MI2S_RX, 0, 1, 0, + q6voice_get_mixer, q6voice_put_mixer) +}; + +static const struct snd_kcontrol_new tertiary_mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("CS-Voice", TERTIARY_MI2S_RX, 0, 1, 0, + q6voice_get_mixer, q6voice_put_mixer) +}; + +static const struct snd_kcontrol_new quaternary_mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("CS-Voice", QUATERNARY_MI2S_RX, 0, 1, 0, + q6voice_get_mixer, q6voice_put_mixer) +}; + +static const struct snd_kcontrol_new quinary_mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("CS-Voice", QUINARY_MI2S_RX, 0, 1, 0, + q6voice_get_mixer, q6voice_put_mixer) +}; + static const struct snd_soc_dapm_widget q6voice_dapm_widgets[] = { SND_SOC_DAPM_AIF_IN("CS-VOICE_DL1", "CS-VOICE Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("CS-VOICE_UL1", "CS-VOICE Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MIXER("CS-Voice Capture Mixer", SND_SOC_NOPM, 0, 0, + voice_tx_mixer_controls, + ARRAY_SIZE(voice_tx_mixer_controls)), + SND_SOC_DAPM_MIXER("PRI_MI2S_RX Voice Mixer", SND_SOC_NOPM, 0, 0, + primary_mi2s_rx_mixer_controls, + ARRAY_SIZE(primary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SEC_MI2S_RX Voice Mixer", SND_SOC_NOPM, 0, 0, + secondary_mi2s_rx_mixer_controls, + ARRAY_SIZE(secondary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("TERT_MI2S_RX Voice Mixer", SND_SOC_NOPM, 0, 0, + tertiary_mi2s_rx_mixer_controls, + ARRAY_SIZE(tertiary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("QUAT_MI2S_RX Voice Mixer", SND_SOC_NOPM, 0, 0, + quaternary_mi2s_rx_mixer_controls, + ARRAY_SIZE(quaternary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("QUIN_MI2S_RX Voice Mixer", SND_SOC_NOPM, 0, 0, + quinary_mi2s_rx_mixer_controls, + ARRAY_SIZE(quinary_mi2s_rx_mixer_controls)), }; static const struct snd_soc_dapm_route q6voice_dapm_routes[] = { - /* TODO: Make routing configurable */ - {"CS-VOICE_UL1", NULL, "TERT_MI2S_TX"}, - {"PRI_MI2S_RX", NULL, "CS-VOICE_DL1"}, + { "CS-Voice Capture Mixer", "PRI_MI2S_TX", "PRI_MI2S_TX" }, + { "CS-Voice Capture Mixer", "SEC_MI2S_TX", "SEC_MI2S_TX" }, + { "CS-Voice Capture Mixer", "TERT_MI2S_TX", "TERT_MI2S_TX" }, + { "CS-Voice Capture Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX" }, + { "CS-Voice Capture Mixer", "QUIN_MI2S_TX", "QUIN_MI2S_TX" }, + { "CS-VOICE_UL1", NULL, "CS-Voice Capture Mixer" }, + + { "PRI_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, + { "SEC_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, + { "TERT_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, + { "QUAT_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, + { "QUIN_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, + { "PRI_MI2S_RX", NULL, "PRI_MI2S_RX Voice Mixer" }, + { "SEC_MI2S_RX", NULL, "SEC_MI2S_RX Voice Mixer" }, + { "TERT_MI2S_RX", NULL, "TERT_MI2S_RX Voice Mixer" }, + { "QUAT_MI2S_RX", NULL, "QUAT_MI2S_RX Voice Mixer" }, + { "QUIN_MI2S_RX", NULL, "QUIN_MI2S_RX Voice Mixer" }, }; +static unsigned int q6voice_reg_read(struct snd_soc_component *component, + unsigned int reg) +{ + /* default value */ + return 0; +} + +static int q6voice_reg_write(struct snd_soc_component *component, + unsigned int reg, unsigned int val) +{ + /* dummy */ + return 0; +} + static const struct snd_soc_component_driver q6voice_dai_component = { .name = DRV_NAME, .open = q6voice_dai_open, @@ -95,6 +213,8 @@ static const struct snd_soc_component_driver q6voice_dai_component = { .num_dapm_widgets = ARRAY_SIZE(q6voice_dapm_widgets), .dapm_routes = q6voice_dapm_routes, .num_dapm_routes = ARRAY_SIZE(q6voice_dapm_routes), + .read = q6voice_reg_read, + .write = q6voice_reg_write, /* Needs to probe after q6afe */ .probe_order = SND_SOC_COMP_ORDER_LATE, From 952e2cd09acae29c37b8fa8baf411e870f92e391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Otto=20Pfl=C3=BCger?= Date: Thu, 20 Apr 2023 18:45:03 +0200 Subject: [PATCH 10/28] ASoC: qdsp6: q6voice-dai: Add VoiceMMode1 DAI MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit VoiceMMode1 is used instead of CS-Voice on newer firmwares. Signed-off-by: Otto Pflüger (cherry picked from commit a3e2be31da5825407c7783f6a2ce752846dcd54b) Link: https://github.com/msm8953-mainline/linux Signed-off-by: Richard Acayan --- include/dt-bindings/sound/qcom,q6voice.h | 1 + sound/soc/qcom/qdsp6/q6mvm.c | 14 ++ sound/soc/qcom/qdsp6/q6voice-dai.c | 183 +++++++++++++++++++---- sound/soc/qcom/qdsp6/q6voice.h | 16 +- 4 files changed, 173 insertions(+), 41 deletions(-) diff --git a/include/dt-bindings/sound/qcom,q6voice.h b/include/dt-bindings/sound/qcom,q6voice.h index fe5aa3a3fce49b..825bf7d47fe316 100644 --- a/include/dt-bindings/sound/qcom,q6voice.h +++ b/include/dt-bindings/sound/qcom,q6voice.h @@ -3,5 +3,6 @@ #define __DT_BINDINGS_Q6_VOICE_H__ #define CS_VOICE 0 +#define VOICEMMODE1 1 #endif /* __DT_BINDINGS_Q6_VOICE_H__ */ diff --git a/sound/soc/qcom/qdsp6/q6mvm.c b/sound/soc/qcom/qdsp6/q6mvm.c index f1f8d520ba0d9b..ace0d9ca302572 100644 --- a/sound/soc/qcom/qdsp6/q6mvm.c +++ b/sound/soc/qcom/qdsp6/q6mvm.c @@ -51,6 +51,20 @@ static inline const char *q6mvm_session_name(enum q6voice_path_type path) switch (path) { case Q6VOICE_PATH_VOICE: return "default modem voice"; + case Q6VOICE_PATH_VOIP: + return "10004000"; + case Q6VOICE_PATH_VOLTE: + return "10C02000"; + case Q6VOICE_PATH_VOICE2: + return "10DC1000"; + case Q6VOICE_PATH_QCHAT: + return "10803000"; + case Q6VOICE_PATH_VOWLAN: + return "10002000"; + case Q6VOICE_PATH_VOICEMMODE1: + return "11C05000"; + case Q6VOICE_PATH_VOICEMMODE2: + return "11DC5000"; default: return NULL; } diff --git a/sound/soc/qcom/qdsp6/q6voice-dai.c b/sound/soc/qcom/qdsp6/q6voice-dai.c index f3ae9d75f82b7a..93318d10e14542 100644 --- a/sound/soc/qcom/qdsp6/q6voice-dai.c +++ b/sound/soc/qcom/qdsp6/q6voice-dai.c @@ -11,20 +11,44 @@ #define DRV_NAME "q6voice-dai" +static enum q6voice_path_type q6voice_get_path(unsigned int dai_id) +{ + switch (dai_id) { + case CS_VOICE: + return Q6VOICE_PATH_VOICE; + case VOICEMMODE1: + return Q6VOICE_PATH_VOICEMMODE1; + } + + return Q6VOICE_PATH_COUNT; +} + static int q6voice_dai_startup(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct q6voice *v = snd_soc_dai_get_drvdata(dai); + enum q6voice_path_type path = q6voice_get_path(dai->driver->id); - return q6voice_start(v, Q6VOICE_PATH_VOICE, substream->stream); + if (path == Q6VOICE_PATH_COUNT) { + dev_err(dai->dev, "Invalid DAI ID %u\n", dai->driver->id); + return -EINVAL; + } + + return q6voice_start(v, path, substream->stream); } static void q6voice_dai_shutdown(struct snd_pcm_substream *substream, struct snd_soc_dai *dai) { struct q6voice *v = snd_soc_dai_get_drvdata(dai); + enum q6voice_path_type path = q6voice_get_path(dai->driver->id); + + if (path == Q6VOICE_PATH_COUNT) { + dev_err(dai->dev, "Invalid DAI ID %u\n", dai->driver->id); + return; + } - q6voice_stop(v, Q6VOICE_PATH_VOICE, substream->stream); + q6voice_stop(v, path, substream->stream); } static struct snd_soc_dai_ops q6voice_dai_ops = { @@ -57,6 +81,29 @@ static struct snd_soc_dai_driver q6voice_dais[] = { }, .ops = &q6voice_dai_ops, }, + { + .id = VOICEMMODE1, + .name = "VOICEMMODE1", + .playback = { + .stream_name = "VOICEMMODE1 Playback", + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + }, + .capture = { + .stream_name = "VOICEMMODE1 Capture", + .formats = SNDRV_PCM_FMTBIT_S16_LE, + .rates = SNDRV_PCM_RATE_8000, + .rate_min = 8000, + .rate_max = 8000, + .channels_min = 1, + .channels_max = 1, + }, + .ops = &q6voice_dai_ops, + }, }; /* FIXME: Use codec2codec instead */ @@ -77,21 +124,26 @@ static int q6voice_dai_open(struct snd_soc_component *component, return 0; } -static int q6voice_get_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +static int q6voice_get_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, bool capture) { struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; struct q6voice *v = snd_soc_component_get_drvdata(c); - bool capture = !!mc->shift; + enum q6voice_path_type path = q6voice_get_path(mc->shift); + + if (path == Q6VOICE_PATH_COUNT) { + dev_err(c->dev, "Invalid DAI ID %u\n", mc->shift); + return -EINVAL; + } ucontrol->value.integer.value[0] = - q6voice_get_port(v, Q6VOICE_PATH_VOICE, capture) == mc->reg; + q6voice_get_port(v, path, capture) == mc->reg; return 0; } -static int q6voice_put_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +static int q6voice_put_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, bool capture) { struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); @@ -99,61 +151,114 @@ static int q6voice_put_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_ (struct soc_mixer_control *)kcontrol->private_value; struct q6voice *v = snd_soc_component_get_drvdata(c); bool val = !!ucontrol->value.integer.value[0]; - bool capture = !!mc->shift; + enum q6voice_path_type path = q6voice_get_path(mc->shift); + + if (path == Q6VOICE_PATH_COUNT) { + dev_err(c->dev, "Invalid DAI ID %u\n", mc->shift); + return -EINVAL; + } if (val) - q6voice_set_port(v, Q6VOICE_PATH_VOICE, capture, mc->reg); - else if (q6voice_get_port(v, Q6VOICE_PATH_VOICE, capture) == mc->reg) - q6voice_set_port(v, Q6VOICE_PATH_VOICE, capture, 0); + q6voice_set_port(v, path, capture, mc->reg); + else if (q6voice_get_port(v, path, capture) == mc->reg) + q6voice_set_port(v, path, capture, 0); snd_soc_dapm_mixer_update_power(dapm, kcontrol, val, NULL); return 1; } -static const struct snd_kcontrol_new voice_tx_mixer_controls[] = { - SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, 1, 1, 0, - q6voice_get_mixer, q6voice_put_mixer), - SOC_SINGLE_EXT("SEC_MI2S_TX", SECONDARY_MI2S_TX, 1, 1, 0, - q6voice_get_mixer, q6voice_put_mixer), - SOC_SINGLE_EXT("TERT_MI2S_TX", TERTIARY_MI2S_TX, 1, 1, 0, - q6voice_get_mixer, q6voice_put_mixer), - SOC_SINGLE_EXT("QUAT_MI2S_TX", QUATERNARY_MI2S_TX, 1, 1, 0, - q6voice_get_mixer, q6voice_put_mixer), - SOC_SINGLE_EXT("QUIN_MI2S_TX", QUINARY_MI2S_TX, 1, 1, 0, - q6voice_get_mixer, q6voice_put_mixer), +static int q6voice_get_mixer_capture(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + return q6voice_get_mixer(kcontrol, ucontrol, true); +} + +static int q6voice_get_mixer_playback(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + return q6voice_get_mixer(kcontrol, ucontrol, false); +} + +static int q6voice_put_mixer_capture(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + return q6voice_put_mixer(kcontrol, ucontrol, true); +} + +static int q6voice_put_mixer_playback(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + return q6voice_put_mixer(kcontrol, ucontrol, false); +} + +static const struct snd_kcontrol_new cs_voice_tx_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, CS_VOICE, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("SEC_MI2S_TX", SECONDARY_MI2S_TX, CS_VOICE, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("TERT_MI2S_TX", TERTIARY_MI2S_TX, CS_VOICE, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("QUAT_MI2S_TX", QUATERNARY_MI2S_TX, CS_VOICE, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("QUIN_MI2S_TX", QUINARY_MI2S_TX, CS_VOICE, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), +}; + +static const struct snd_kcontrol_new voicemmode1_tx_mixer_controls[] = { + SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("SEC_MI2S_TX", SECONDARY_MI2S_TX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("TERT_MI2S_TX", TERTIARY_MI2S_TX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("QUAT_MI2S_TX", QUATERNARY_MI2S_TX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("QUIN_MI2S_TX", QUINARY_MI2S_TX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), }; static const struct snd_kcontrol_new primary_mi2s_rx_mixer_controls[] = { - SOC_SINGLE_EXT("CS-Voice", PRIMARY_MI2S_RX, 0, 1, 0, - q6voice_get_mixer, q6voice_put_mixer) + SOC_SINGLE_EXT("CS-Voice", PRIMARY_MI2S_RX, CS_VOICE, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), + SOC_SINGLE_EXT("VoiceMMode1", PRIMARY_MI2S_RX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), }; static const struct snd_kcontrol_new secondary_mi2s_rx_mixer_controls[] = { - SOC_SINGLE_EXT("CS-Voice", SECONDARY_MI2S_RX, 0, 1, 0, - q6voice_get_mixer, q6voice_put_mixer) + SOC_SINGLE_EXT("CS-Voice", SECONDARY_MI2S_RX, CS_VOICE, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), + SOC_SINGLE_EXT("VoiceMMode1", SECONDARY_MI2S_RX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), }; static const struct snd_kcontrol_new tertiary_mi2s_rx_mixer_controls[] = { - SOC_SINGLE_EXT("CS-Voice", TERTIARY_MI2S_RX, 0, 1, 0, - q6voice_get_mixer, q6voice_put_mixer) + SOC_SINGLE_EXT("CS-Voice", TERTIARY_MI2S_RX, CS_VOICE, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), + SOC_SINGLE_EXT("VoiceMMode1", TERTIARY_MI2S_RX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), }; static const struct snd_kcontrol_new quaternary_mi2s_rx_mixer_controls[] = { - SOC_SINGLE_EXT("CS-Voice", QUATERNARY_MI2S_RX, 0, 1, 0, - q6voice_get_mixer, q6voice_put_mixer) + SOC_SINGLE_EXT("CS-Voice", QUATERNARY_MI2S_RX, CS_VOICE, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), + SOC_SINGLE_EXT("VoiceMMode1", QUATERNARY_MI2S_RX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), }; static const struct snd_kcontrol_new quinary_mi2s_rx_mixer_controls[] = { - SOC_SINGLE_EXT("CS-Voice", QUINARY_MI2S_RX, 0, 1, 0, - q6voice_get_mixer, q6voice_put_mixer) + SOC_SINGLE_EXT("CS-Voice", QUINARY_MI2S_RX, CS_VOICE, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), + SOC_SINGLE_EXT("VoiceMMode1", QUINARY_MI2S_RX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), }; static const struct snd_soc_dapm_widget q6voice_dapm_widgets[] = { SND_SOC_DAPM_AIF_IN("CS-VOICE_DL1", "CS-VOICE Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("CS-VOICE_UL1", "CS-VOICE Capture", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_MIXER("CS-Voice Capture Mixer", SND_SOC_NOPM, 0, 0, - voice_tx_mixer_controls, - ARRAY_SIZE(voice_tx_mixer_controls)), + cs_voice_tx_mixer_controls, + ARRAY_SIZE(cs_voice_tx_mixer_controls)), + SND_SOC_DAPM_AIF_IN("VOICEMMODE1_DL1", "VOICEMMODE1 Playback", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_AIF_OUT("VOICEMMODE1_UL1", "VOICEMMODE1 Capture", 0, SND_SOC_NOPM, 0, 0), + SND_SOC_DAPM_MIXER("VoiceMMode1 Capture Mixer", SND_SOC_NOPM, 0, 0, + voicemmode1_tx_mixer_controls, + ARRAY_SIZE(voicemmode1_tx_mixer_controls)), SND_SOC_DAPM_MIXER("PRI_MI2S_RX Voice Mixer", SND_SOC_NOPM, 0, 0, primary_mi2s_rx_mixer_controls, ARRAY_SIZE(primary_mi2s_rx_mixer_controls)), @@ -178,12 +283,24 @@ static const struct snd_soc_dapm_route q6voice_dapm_routes[] = { { "CS-Voice Capture Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX" }, { "CS-Voice Capture Mixer", "QUIN_MI2S_TX", "QUIN_MI2S_TX" }, { "CS-VOICE_UL1", NULL, "CS-Voice Capture Mixer" }, + { "VoiceMMode1 Capture Mixer", "PRI_MI2S_TX", "PRI_MI2S_TX" }, + { "VoiceMMode1 Capture Mixer", "SEC_MI2S_TX", "SEC_MI2S_TX" }, + { "VoiceMMode1 Capture Mixer", "TERT_MI2S_TX", "TERT_MI2S_TX" }, + { "VoiceMMode1 Capture Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX" }, + { "VoiceMMode1 Capture Mixer", "QUIN_MI2S_TX", "QUIN_MI2S_TX" }, + { "VOICEMMODE1_UL1", NULL, "VoiceMMode1 Capture Mixer" }, { "PRI_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, { "SEC_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, { "TERT_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, { "QUAT_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, { "QUIN_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, + { "PRI_MI2S_RX Voice Mixer", "VoiceMMode1", "VOICEMMODE1_DL1" }, + { "SEC_MI2S_RX Voice Mixer", "VoiceMMode1", "VOICEMMODE1_DL1" }, + { "TERT_MI2S_RX Voice Mixer", "VoiceMMode1", "VOICEMMODE1_DL1" }, + { "QUAT_MI2S_RX Voice Mixer", "VoiceMMode1", "VOICEMMODE1_DL1" }, + { "QUIN_MI2S_RX Voice Mixer", "VoiceMMode1", "VOICEMMODE1_DL1" }, + { "PRI_MI2S_RX", NULL, "PRI_MI2S_RX Voice Mixer" }, { "SEC_MI2S_RX", NULL, "SEC_MI2S_RX Voice Mixer" }, { "TERT_MI2S_RX", NULL, "TERT_MI2S_RX Voice Mixer" }, diff --git a/sound/soc/qcom/qdsp6/q6voice.h b/sound/soc/qcom/qdsp6/q6voice.h index 5b2d2dd679b34d..13b058c7303ac9 100644 --- a/sound/soc/qcom/qdsp6/q6voice.h +++ b/sound/soc/qcom/qdsp6/q6voice.h @@ -3,14 +3,14 @@ #define _Q6_VOICE_H enum q6voice_path_type { - Q6VOICE_PATH_VOICE = 0, - /* TODO: Q6VOICE_PATH_VOIP = 1, */ - /* TODO: Q6VOICE_PATH_VOLTE = 2, */ - /* TODO: Q6VOICE_PATH_VOICE2 = 3, */ - /* TODO: Q6VOICE_PATH_QCHAT = 4, */ - /* TODO: Q6VOICE_PATH_VOWLAN = 5, */ - /* TODO: Q6VOICE_PATH_VOICEMMODE1 = 6, */ - /* TODO: Q6VOICE_PATH_VOICEMMODE2 = 7, */ + Q6VOICE_PATH_VOICE = 0, + Q6VOICE_PATH_VOIP = 1, + Q6VOICE_PATH_VOLTE = 2, + Q6VOICE_PATH_VOICE2 = 3, + Q6VOICE_PATH_QCHAT = 4, + Q6VOICE_PATH_VOWLAN = 5, + Q6VOICE_PATH_VOICEMMODE1 = 6, + Q6VOICE_PATH_VOICEMMODE2 = 7, Q6VOICE_PATH_COUNT }; From 22bc2e2eebb89540540416423dd3c2bb26770b2e Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Tue, 25 Jun 2024 18:43:31 -0400 Subject: [PATCH 11/28] ASoC: qdsp6: q6afe: pass tdm ctrl_sync_data_delay from config The codec may expect to delay data by 0, 1 or 2 bit clock cycles. Keeping the TDM ctrl_sync_data_delay value zero-initialized could result in corruption of data being read at the AFE. Add a configuration value for the data delay and send it when preparing the TDM port. Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6afe.c | 1 + sound/soc/qcom/qdsp6/q6afe.h | 1 + 2 files changed, 2 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6afe.c b/sound/soc/qcom/qdsp6/q6afe.c index 28b5b6b91897f5..109b189d667551 100644 --- a/sound/soc/qcom/qdsp6/q6afe.c +++ b/sound/soc/qcom/qdsp6/q6afe.c @@ -1366,6 +1366,7 @@ void q6afe_tdm_port_prepare(struct q6afe_port *port, pcfg->tdm_cfg.sync_mode = cfg->sync_mode; pcfg->tdm_cfg.sync_src = cfg->sync_src; pcfg->tdm_cfg.nslots_per_frame = cfg->nslots_per_frame; + pcfg->tdm_cfg.ctrl_sync_data_delay = cfg->data_delay; pcfg->tdm_cfg.slot_width = cfg->slot_width; pcfg->tdm_cfg.slot_mask = cfg->slot_mask; diff --git a/sound/soc/qcom/qdsp6/q6afe.h b/sound/soc/qcom/qdsp6/q6afe.h index 0b8c3ec1315caf..657973ca0be6e7 100644 --- a/sound/soc/qcom/qdsp6/q6afe.h +++ b/sound/soc/qcom/qdsp6/q6afe.h @@ -190,6 +190,7 @@ struct q6afe_tdm_cfg { u16 sync_mode; u16 sync_src; u16 nslots_per_frame; + u16 data_delay; u16 slot_width; u16 slot_mask; u32 data_align_type; From 128633b04480ff4795556bde8865d463cc46bac8 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Tue, 25 Jun 2024 18:52:30 -0400 Subject: [PATCH 12/28] ASoC: qdsp6: q6afe-dai: configure tdm data delay from device tree The codec may expect to delay data by 0, 1 or 2 bit clock cycles. Keeping the TDM data delay zero-initialized by ignoring the device tree value could result in corruption of data being read at the AFE. Configure the data delay according to the value already supplied in device tree. Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6afe-dai.c | 1 + 1 file changed, 1 insertion(+) diff --git a/sound/soc/qcom/qdsp6/q6afe-dai.c b/sound/soc/qcom/qdsp6/q6afe-dai.c index a0d21034a6264d..354c1d9896244f 100644 --- a/sound/soc/qcom/qdsp6/q6afe-dai.c +++ b/sound/soc/qcom/qdsp6/q6afe-dai.c @@ -278,6 +278,7 @@ static int q6tdm_hw_params(struct snd_pcm_substream *substream, tdm->data_align_type = dai_data->priv[dai->id].data_align; tdm->sync_src = dai_data->priv[dai->id].sync_src; tdm->sync_mode = dai_data->priv[dai->id].sync_mode; + tdm->data_delay = dai_data->priv[dai->id].data_delay; return 0; } From 943bf9719191fcc6e070d745e0dbf232c7043544 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Tue, 8 Oct 2024 18:55:21 -0400 Subject: [PATCH 13/28] ASoC: qdsp6: q6core: add support for registering topologies To use a topology ID, the DSP must first have the topology registered. Add support for registering topologies from an ACDB (Audio Calibration DataBase). No-submit: topologies are useless upstream, unless support is added to q6afe; missing dt-bindings Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6core.c | 148 +++++++++++++++++++++++++++++++++- 1 file changed, 147 insertions(+), 1 deletion(-) diff --git a/sound/soc/qcom/qdsp6/q6core.c b/sound/soc/qcom/qdsp6/q6core.c index 48825756a7abb5..dd55a1f94894fb 100644 --- a/sound/soc/qcom/qdsp6/q6core.c +++ b/sound/soc/qcom/qdsp6/q6core.c @@ -10,6 +10,7 @@ #include #include #include +#include #include #include "q6core.h" #include "q6dsp-errno.h" @@ -20,9 +21,17 @@ #define AVCS_CMDRSP_ADSP_EVENT_GET_STATE 0x0001290D #define AVCS_GET_VERSIONS 0x00012905 #define AVCS_GET_VERSIONS_RSP 0x00012906 +#define AVCS_CMD_REGISTER_TOPOLOGIES 0x00012923 +#define AVCS_CMD_DEREGISTER_TOPOLOGIES 0x0001292a #define AVCS_CMD_GET_FWK_VERSION 0x001292c #define AVCS_CMDRSP_GET_FWK_VERSION 0x001292d +#define AVCS_MODE_DEREGISTER_ALL_CUSTOM_TOPOLOGIES 2 + +#define ACDB_SECTION_GPROPLUT 0x54554c504f525047 +#define ACDB_SECTION_DATAPOOL 0x4c4f4f5041544144 +#define ACDB_TOPOLOGIES_BLOB 0x000131a7 + struct avcs_svc_info { uint32_t service_id; uint32_t version; @@ -41,6 +50,13 @@ struct avcs_svc_api_info { uint32_t api_branch_version; } __packed; +struct avcs_cmd_register_topologies { + uint32_t payload_addr_lsw; + uint32_t payload_addr_msw; + uint32_t mem_map_handle; + uint32_t payload_size; +} __packed; + struct avcs_cmdrsp_get_fwk_version { uint32_t build_major_version; uint32_t build_minor_version; @@ -236,6 +252,38 @@ static bool __q6core_is_adsp_ready(struct q6core *core) return false; } +static int q6core_register_topologies(struct device *dev, u32 size, const void *data) +{ + struct q6core *core = dev_get_drvdata(dev); + struct apr_device *adev = core->adev; + struct avcs_cmd_register_topologies *cmd; + struct apr_pkt *pkt; + void *p, *dest; + int pkt_size; + + pkt_size = APR_HDR_SIZE + sizeof(*cmd) + size; + + p = kzalloc(pkt_size, GFP_KERNEL); + if (!p) + return -ENOMEM; + + pkt = p; + cmd = p + APR_HDR_SIZE; + dest = p + APR_HDR_SIZE + sizeof(*cmd); + if (!p) + return -ENOMEM; + + pkt->hdr.hdr_field = APR_HDR_FIELD(APR_MSG_TYPE_SEQ_CMD, + APR_HDR_LEN(APR_HDR_SIZE), APR_PKT_VER); + pkt->hdr.pkt_size = APR_HDR_SIZE + sizeof(*cmd) + size; + pkt->hdr.opcode = AVCS_CMD_REGISTER_TOPOLOGIES; + cmd->payload_size = size; + + memcpy(dest, data, size); + + return apr_send_pkt(adev, pkt); +} + /** * q6core_get_svc_api_info() - Get version number of a service. * @@ -325,6 +373,103 @@ bool q6core_is_adsp_ready(void) } EXPORT_SYMBOL_GPL(q6core_is_adsp_ready); +static int acdb_section_offset(const struct firmware *fw, + u64 section, u32 *off_out) +{ + const u8 *data = fw->data + 32; + u32 off = 32; + u32 sect_size; + u64 sect_id; + + while (off + 12 <= fw->size) { + sect_id = *(const u64 *) data; + sect_size = *(const u32 *) (data + 8); + + if (off + 12 + sect_size > fw->size) + return -EINVAL; + + if (sect_id == section) { + *off_out = off; + return 0; + } + + off += 12 + sect_size; + data += 12 + sect_size; + } + + return -ENOENT; +} + +static int acdb_get_global_property(const struct firmware *fw, + u32 prop_id, u32 *offset) +{ + u32 gproplut_off, datapool_off; + u32 lut_size, lut_nelts, i; + const u32 *data; + int ret; + + ret = acdb_section_offset(fw, ACDB_SECTION_GPROPLUT, &gproplut_off); + if (ret) + return ret; + + ret = acdb_section_offset(fw, ACDB_SECTION_DATAPOOL, &datapool_off); + if (ret) + return ret; + + lut_size = *(const u32 *) (fw->data + gproplut_off + 8); + if (lut_size < 4) + return -EINVAL; + + lut_nelts = *(const u32 *) (fw->data + gproplut_off + 12); + if (lut_size < 4 + 8 * lut_nelts) + return -EINVAL; + + data = (const u32 *) (fw->data + gproplut_off + 16); + for (i = 0; i < lut_nelts; i++) { + if (datapool_off + 12 + data[i * 2 + 1] + 4 > fw->size) + return -EINVAL; + + if (data[i * 2] == prop_id) { + *offset = datapool_off + 12 + data[i * 2 + 1]; + return 0; + } + } + + return -ENOENT; +} + +static int q6core_load_and_register_topologies(struct device *dev) +{ + const struct device_node *np = dev_of_node(dev); + const char *fw_name; + const struct firmware *fw; + u32 topo_off, size; + int ret; + + ret = of_property_read_string(np, "qcom,acdb-name", &fw_name); + if (ret) + return 0; + + ret = request_firmware(&fw, fw_name, dev); + if (ret) + return dev_err_probe(dev, ret, "failed to load Global_cal.acdb"); + + ret = acdb_get_global_property(fw, ACDB_TOPOLOGIES_BLOB, &topo_off); + if (ret) { + dev_err(dev, "ACDB parse error: %d\n", ret); + goto release_fw; + } + + size = *(const u32 *) (fw->data + topo_off); + ret = q6core_register_topologies(dev, size, fw->data + topo_off + 4); + if (ret < 0) + goto release_fw; + +release_fw: + release_firmware(fw); + return 0; +} + static int q6core_probe(struct apr_device *adev) { g_core = kzalloc_obj(*g_core); @@ -336,7 +481,8 @@ static int q6core_probe(struct apr_device *adev) mutex_init(&g_core->lock); g_core->adev = adev; init_waitqueue_head(&g_core->wait); - return 0; + + return q6core_load_and_register_topologies(&adev->dev); } static void q6core_exit(struct apr_device *adev) From 6f84fd754baa4def2f26e97d973366b6d5983a67 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Mon, 11 Sep 2023 21:48:47 -0400 Subject: [PATCH 14/28] ASoC: qdsp6: q6cvp: add commands in cvd 2.3 The CVD 2.3 revision expects a different initialization sequence. Implement the commands to make this possible. Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6cvp.c | 178 +++++++++++++++++++++++++++++++++++ sound/soc/qcom/qdsp6/q6cvp.h | 5 + 2 files changed, 183 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6cvp.c b/sound/soc/qcom/qdsp6/q6cvp.c index 3549d3f14c914e..deadf960ec1b64 100644 --- a/sound/soc/qcom/qdsp6/q6cvp.c +++ b/sound/soc/qcom/qdsp6/q6cvp.c @@ -6,6 +6,7 @@ #include #include #include "q6cvp.h" +#include "q6dsp-common.h" #include "q6voice-common.h" #define VSS_IVOCPROC_DIRECTION_RX 0 @@ -29,6 +30,22 @@ #define VSS_IVOCPROC_CMD_DISABLE 0x000110E1 #define VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION_V2 0x000112BF +#define VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION_V3 0x00013169 + +#define VSS_IVOCPROC_CMD_TOPOLOGY_COMMIT 0x00013198 + +#define VSS_ICOMMON_CMD_SET_PARAM_V2 0x0001133D + +#define VSS_MODULE_CVD_GENERIC 0x0001316E + +#define VSS_PARAM_VOCPROC_RX_CHANNEL_INFO 0x0001328F +#define VSS_PARAM_VOCPROC_TX_CHANNEL_INFO 0x0001328E +#define VSS_PARAM_RX_PORT_ENDPOINT_MEDIA_INFO 0x00013254 +#define VSS_PARAM_TX_PORT_ENDPOINT_MEDIA_INFO 0x00013253 + +#define VOC_SET_MEDIA_FORMAT_PARAM_TOKEN 2 + +#define VSS_NUM_CHANNELS_MAX 8 struct vss_ivocproc_cmd_create_full_control_session_v2_cmd { struct apr_hdr hdr; @@ -91,6 +108,62 @@ struct vss_ivocproc_cmd_create_full_control_session_v2_cmd { char name[20]; } __packed; +struct vss_param_endpoint_media_format_info { + u32 port_id; + u16 num_channels; + u16 bits_per_sample; + u32 sample_rate; + u8 channel_mapping[VSS_NUM_CHANNELS_MAX]; +} __packed; + +struct vss_param_vocproc_dev_channel_info { + u32 num_channels; + u32 bits_per_sample; + u8 channel_mapping[VSS_NUM_CHANNELS_MAX]; +} __packed; + +struct vss_icommon_param_data { + u32 module_id; + u32 param_id; + u16 param_size; + u16 reserved; + + union { + struct vss_param_endpoint_media_format_info media_format_info; + struct vss_param_vocproc_dev_channel_info channel_info; + }; +} __packed; + +struct vss_icommon_cmd_set_param_v2_cmd { + struct apr_hdr hdr; + + /* + * Pointer to the unique identifier for an address. What type of + * address? DMA? Device-specific memory? Please direct inquiries of + * this type to the SoC manufacturer. + * + * Anyway, the most important information for this parameter is that + * this field is set to zero if the data is in-band, i.e. in the same + * packet. + */ + u32 mem_handle; + + /* + * This field is ignored if you are only paying attention to this + * documentation of the fields. Otherwise, refer to your own + * documentation. I did not ask for this, and will not fully document + * it if its accuracy cannot be measured. + */ + u64 mem_address; + + /* + * Just set this to the size of the parameter data. + */ + u32 mem_size; + + struct vss_icommon_param_data param_data; +} __packed; + struct q6voice_session *q6cvp_session_create(enum q6voice_path_type path, u16 tx_port, u16 rx_port) { @@ -114,6 +187,111 @@ struct q6voice_session *q6cvp_session_create(enum q6voice_path_type path, } EXPORT_SYMBOL_GPL(q6cvp_session_create); +struct q6voice_session *q6cvp_session_create_v3(enum q6voice_path_type path, + u16 tx_port, u16 rx_port) +{ + /* + * According to downstream, the v3 parameters are the exact same, with + * the only difference being the opcode. + */ + struct vss_ivocproc_cmd_create_full_control_session_v2_cmd cmd; + + cmd.hdr.pkt_size = sizeof(cmd); + cmd.hdr.opcode = VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION_V3; + + /* TODO: Implement calibration */ + cmd.tx_topology_id = VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; + cmd.rx_topology_id = VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; + + cmd.direction = VSS_IVOCPROC_DIRECTION_RX_TX; + cmd.tx_port_id = tx_port; + cmd.rx_port_id = rx_port; + cmd.profile_id = VSS_ICOMMON_CAL_NETWORK_ID_NONE; + cmd.vocproc_mode = VSS_IVOCPROC_VOCPROC_MODE_EC_INT_MIXING; + cmd.ec_ref_port_id = VSS_IVOCPROC_PORT_ID_NONE; + + return q6voice_session_create(Q6VOICE_SERVICE_CVP, path, &cmd.hdr); +} +EXPORT_SYMBOL_GPL(q6cvp_session_create_v3); + +int q6cvp_send_channel_info(struct q6voice_session *cvp, bool is_tx) +{ + struct vss_icommon_cmd_set_param_v2_cmd cmd; + + cmd.hdr.pkt_size = offsetof(struct vss_icommon_cmd_set_param_v2_cmd, param_data.channel_info) + + sizeof(struct vss_param_vocproc_dev_channel_info); + cmd.hdr.opcode = VSS_ICOMMON_CMD_SET_PARAM_V2; + + cmd.mem_handle = 0; + cmd.mem_size = offsetof(struct vss_icommon_param_data, channel_info) + + sizeof(struct vss_param_vocproc_dev_channel_info); + + cmd.param_data.module_id = VSS_MODULE_CVD_GENERIC; + cmd.param_data.param_id = is_tx ? VSS_PARAM_VOCPROC_TX_CHANNEL_INFO + : VSS_PARAM_VOCPROC_RX_CHANNEL_INFO; + cmd.param_data.param_size = sizeof(struct vss_param_vocproc_dev_channel_info); + cmd.param_data.reserved = 0; + + if (is_tx) + cmd.param_data.channel_info.num_channels = 1; + else + cmd.param_data.channel_info.num_channels = 2; + + cmd.param_data.channel_info.bits_per_sample = 16; + + q6dsp_map_channels(cmd.param_data.channel_info.channel_mapping, + cmd.param_data.channel_info.num_channels); + + return q6voice_common_send(cvp, &cmd.hdr); +} +EXPORT_SYMBOL_GPL(q6cvp_send_channel_info); + +int q6cvp_send_media_format(struct q6voice_session *cvp, int port_id, bool is_tx) +{ + struct vss_icommon_cmd_set_param_v2_cmd cmd; + + cmd.hdr.pkt_size = offsetof(struct vss_icommon_cmd_set_param_v2_cmd, param_data.media_format_info) + + sizeof(struct vss_param_endpoint_media_format_info); + cmd.hdr.opcode = VSS_ICOMMON_CMD_SET_PARAM_V2; + + cmd.mem_handle = 0; + cmd.mem_size = offsetof(struct vss_icommon_param_data, media_format_info) + + sizeof(struct vss_param_endpoint_media_format_info); + + cmd.param_data.module_id = VSS_MODULE_CVD_GENERIC; + cmd.param_data.param_id = is_tx ? VSS_PARAM_TX_PORT_ENDPOINT_MEDIA_INFO + : VSS_PARAM_RX_PORT_ENDPOINT_MEDIA_INFO; + cmd.param_data.param_size = sizeof(struct vss_param_endpoint_media_format_info); + cmd.param_data.reserved = 0; + + cmd.param_data.media_format_info.port_id = port_id; + + if (is_tx) + cmd.param_data.media_format_info.num_channels = 1; + else + cmd.param_data.media_format_info.num_channels = 2; + + cmd.param_data.media_format_info.bits_per_sample = 16; + cmd.param_data.media_format_info.sample_rate = 48000; + + q6dsp_map_channels(cmd.param_data.channel_info.channel_mapping, + cmd.param_data.channel_info.num_channels); + + return q6voice_common_send(cvp, &cmd.hdr); +} +EXPORT_SYMBOL_GPL(q6cvp_send_media_format); + +int q6cvp_topology_commit(struct q6voice_session *cvp) +{ + struct apr_pkt cmd; + + cmd.hdr.pkt_size = APR_HDR_SIZE; + cmd.hdr.opcode = VSS_IVOCPROC_CMD_TOPOLOGY_COMMIT; + + return q6voice_common_send(cvp, &cmd.hdr); +} +EXPORT_SYMBOL_GPL(q6cvp_topology_commit); + int q6cvp_enable(struct q6voice_session *cvp, bool state) { struct apr_pkt cmd; diff --git a/sound/soc/qcom/qdsp6/q6cvp.h b/sound/soc/qcom/qdsp6/q6cvp.h index 2381a3cfe0784d..34940bf4dbd9e0 100644 --- a/sound/soc/qcom/qdsp6/q6cvp.h +++ b/sound/soc/qcom/qdsp6/q6cvp.h @@ -8,6 +8,11 @@ struct q6voice_session; struct q6voice_session *q6cvp_session_create(enum q6voice_path_type path, u16 tx_port, u16 rx_port); +struct q6voice_session *q6cvp_session_create_v3(enum q6voice_path_type path, + u16 tx_port, u16 rx_port); +int q6cvp_send_channel_info(struct q6voice_session *cvp, bool is_tx); +int q6cvp_send_media_format(struct q6voice_session *cvp, int port_id, bool is_tx); +int q6cvp_topology_commit(struct q6voice_session *cvp); int q6cvp_enable(struct q6voice_session *cvp, bool enable); #endif /*_Q6_CVP_H */ From 5317226a52fad94e7b0ce3a41f8598a14ff0d215 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Mon, 11 Sep 2023 21:50:54 -0400 Subject: [PATCH 15/28] ASoC: qdsp6: q6voice: add cvd 2.3 initialization sequence Some platforms such as SDM670 have CVD version 2.3. This version requires different commands to run on creation. Add the initialization sequence for this version, using a device tree property to determine which sequence to run. No-submit: detect the params (channels, bit width, sample rate) Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6voice-dai.c | 6 +++- sound/soc/qcom/qdsp6/q6voice.c | 47 +++++++++++++++++++++++++++--- sound/soc/qcom/qdsp6/q6voice.h | 2 +- 3 files changed, 49 insertions(+), 6 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6voice-dai.c b/sound/soc/qcom/qdsp6/q6voice-dai.c index 93318d10e14542..e8fb390b182570 100644 --- a/sound/soc/qcom/qdsp6/q6voice-dai.c +++ b/sound/soc/qcom/qdsp6/q6voice-dai.c @@ -341,8 +341,12 @@ static int q6voice_dai_probe(struct platform_device *pdev) { struct device *dev = &pdev->dev; struct q6voice *v; + struct device_node *np = dev_of_node(dev); + bool cvd_v2_3; - v = q6voice_create(dev); + cvd_v2_3 = of_property_read_bool(np, "qcom,cvd-v2.3"); + + v = q6voice_create(dev, cvd_v2_3); if (IS_ERR(v)) return PTR_ERR(v); diff --git a/sound/soc/qcom/qdsp6/q6voice.c b/sound/soc/qcom/qdsp6/q6voice.c index d5751905a731ef..b553da3134fff3 100644 --- a/sound/soc/qcom/qdsp6/q6voice.c +++ b/sound/soc/qcom/qdsp6/q6voice.c @@ -23,6 +23,7 @@ struct q6voice_path { enum q6voice_path_type type; int tx_port, rx_port; + u32 tx_topo, rx_topo; /* Serialize access to voice path session */ struct mutex lock; struct q6voice_path_runtime *runtime; @@ -30,9 +31,37 @@ struct q6voice_path { struct q6voice { struct device *dev; + bool cvd_v2_3; struct q6voice_path paths[Q6VOICE_PATH_COUNT]; }; +static inline struct q6voice_session *session_create(enum q6voice_path_type type, + int tx_port, int rx_port) +{ + return q6cvp_session_create(type, tx_port, rx_port); +} + +static struct q6voice_session *session_create_v3(enum q6voice_path_type type, + int tx_port, int rx_port, + u32 tx_topo, u32 rx_topo) +{ + struct q6voice_session *cvp; + + cvp = q6cvp_session_create_v3(type, tx_port, rx_port); + if (cvp == NULL) + return NULL; + + q6cvp_send_channel_info(cvp, false); + q6cvp_send_channel_info(cvp, true); + + q6cvp_send_media_format(cvp, rx_port, false); + q6cvp_send_media_format(cvp, tx_port, true); + + q6cvp_topology_commit(cvp); + + return cvp; +} + static int q6voice_path_start(struct q6voice_path *p) { struct device *dev = p->v->dev; @@ -51,9 +80,16 @@ static int q6voice_path_start(struct q6voice_path *p) cvp = p->runtime->sessions[Q6VOICE_SERVICE_CVP]; if (!cvp) { - cvp = q6cvp_session_create(p->type, - q6afe_get_port_id(p->tx_port), - q6afe_get_port_id(p->rx_port)); + if (p->v->cvd_v2_3) + cvp = session_create_v3(p->type, + q6afe_get_port_id(p->tx_port), + q6afe_get_port_id(p->rx_port), + p->tx_topo, p->rx_topo); + else + cvp = session_create(p->type, + q6afe_get_port_id(p->tx_port), + q6afe_get_port_id(p->rx_port)); + if (IS_ERR(cvp)) return PTR_ERR(cvp); p->runtime->sessions[Q6VOICE_SERVICE_CVP] = cvp; @@ -208,7 +244,7 @@ static void q6voice_free(void *data) } } -struct q6voice *q6voice_create(struct device *dev) +struct q6voice *q6voice_create(struct device *dev, bool cvd_v2_3) { struct q6voice *v; enum q6voice_path_type path; @@ -219,12 +255,15 @@ struct q6voice *q6voice_create(struct device *dev) return ERR_PTR(-ENOMEM); v->dev = dev; + v->cvd_v2_3 = cvd_v2_3; for (path = 0; path < Q6VOICE_PATH_COUNT; ++path) { struct q6voice_path *p = &v->paths[path]; p->v = v; p->type = path; + p->tx_topo = VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; + p->rx_topo = VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; mutex_init(&p->lock); } diff --git a/sound/soc/qcom/qdsp6/q6voice.h b/sound/soc/qcom/qdsp6/q6voice.h index 13b058c7303ac9..a1e3300ffbe9c0 100644 --- a/sound/soc/qcom/qdsp6/q6voice.h +++ b/sound/soc/qcom/qdsp6/q6voice.h @@ -16,7 +16,7 @@ enum q6voice_path_type { struct q6voice; -struct q6voice *q6voice_create(struct device *dev); +struct q6voice *q6voice_create(struct device *dev, bool cvd_v2_3); int q6voice_start(struct q6voice *v, enum q6voice_path_type path, bool capture); int q6voice_stop(struct q6voice *v, enum q6voice_path_type path, bool capture); From c173ca18f371ee632403673afca51e263acd5945 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Tue, 8 Oct 2024 19:13:30 -0400 Subject: [PATCH 16/28] ASoC: qdsp6: q6cvp: add topology ID parameters to session functions For some VoLTE calls between new phones, the topology IDs must be set to a value in the ACDB. Add RX and TX topology ID parameters to the functions that send the CREATE_FULL_CONTROL_SESSION command, so the correct topology IDs can be sent. Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6cvp.c | 16 ++++++++-------- sound/soc/qcom/qdsp6/q6cvp.h | 6 ++++-- 2 files changed, 12 insertions(+), 10 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6cvp.c b/sound/soc/qcom/qdsp6/q6cvp.c index deadf960ec1b64..d87849b6dc992f 100644 --- a/sound/soc/qcom/qdsp6/q6cvp.c +++ b/sound/soc/qcom/qdsp6/q6cvp.c @@ -165,16 +165,16 @@ struct vss_icommon_cmd_set_param_v2_cmd { } __packed; struct q6voice_session *q6cvp_session_create(enum q6voice_path_type path, - u16 tx_port, u16 rx_port) + u16 tx_port, u16 rx_port, + u32 tx_topo, u32 rx_topo) { struct vss_ivocproc_cmd_create_full_control_session_v2_cmd cmd; cmd.hdr.pkt_size = sizeof(cmd); cmd.hdr.opcode = VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION_V2; - /* TODO: Implement calibration */ - cmd.tx_topology_id = VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; - cmd.rx_topology_id = VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; + cmd.tx_topology_id = tx_topo; + cmd.rx_topology_id = rx_topo; cmd.direction = VSS_IVOCPROC_DIRECTION_RX_TX; cmd.tx_port_id = tx_port; @@ -188,7 +188,8 @@ struct q6voice_session *q6cvp_session_create(enum q6voice_path_type path, EXPORT_SYMBOL_GPL(q6cvp_session_create); struct q6voice_session *q6cvp_session_create_v3(enum q6voice_path_type path, - u16 tx_port, u16 rx_port) + u16 tx_port, u16 rx_port, + u32 tx_topo, u32 rx_topo) { /* * According to downstream, the v3 parameters are the exact same, with @@ -199,9 +200,8 @@ struct q6voice_session *q6cvp_session_create_v3(enum q6voice_path_type path, cmd.hdr.pkt_size = sizeof(cmd); cmd.hdr.opcode = VSS_IVOCPROC_CMD_CREATE_FULL_CONTROL_SESSION_V3; - /* TODO: Implement calibration */ - cmd.tx_topology_id = VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; - cmd.rx_topology_id = VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; + cmd.tx_topology_id = tx_topo; + cmd.rx_topology_id = rx_topo; cmd.direction = VSS_IVOCPROC_DIRECTION_RX_TX; cmd.tx_port_id = tx_port; diff --git a/sound/soc/qcom/qdsp6/q6cvp.h b/sound/soc/qcom/qdsp6/q6cvp.h index 34940bf4dbd9e0..aad99eb9e95def 100644 --- a/sound/soc/qcom/qdsp6/q6cvp.h +++ b/sound/soc/qcom/qdsp6/q6cvp.h @@ -7,9 +7,11 @@ struct q6voice_session; struct q6voice_session *q6cvp_session_create(enum q6voice_path_type path, - u16 tx_port, u16 rx_port); + u16 tx_port, u16 rx_port, + u32 tx_topo, u32 rx_topo); struct q6voice_session *q6cvp_session_create_v3(enum q6voice_path_type path, - u16 tx_port, u16 rx_port); + u16 tx_port, u16 rx_port, + u32 tx_topo, u32 rx_topo); int q6cvp_send_channel_info(struct q6voice_session *cvp, bool is_tx); int q6cvp_send_media_format(struct q6voice_session *cvp, int port_id, bool is_tx); int q6cvp_topology_commit(struct q6voice_session *cvp); From 0e05c33f838942099e07d8ca0db2cbf20e539c4c Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Tue, 8 Oct 2024 19:25:59 -0400 Subject: [PATCH 17/28] ASoC: qdsp6: q6voice: pass new topology property to q6cvp The topology ID is now expected by q6cvp. Add the topology ID properties to the paths with accessors, and pass them to the DSP. Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6voice.c | 37 ++++++++++++++++++++++++++++++---- sound/soc/qcom/qdsp6/q6voice.h | 5 +++++ 2 files changed, 38 insertions(+), 4 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6voice.c b/sound/soc/qcom/qdsp6/q6voice.c index b553da3134fff3..461a5eceb3cf87 100644 --- a/sound/soc/qcom/qdsp6/q6voice.c +++ b/sound/soc/qcom/qdsp6/q6voice.c @@ -13,6 +13,9 @@ #include "q6mvm.h" #include "q6voice-common.h" +#define VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS 0x00010F71 +#define VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT 0x00010F77 + struct q6voice_path_runtime { struct q6voice_session *sessions[Q6VOICE_SERVICE_COUNT]; unsigned int started; @@ -36,9 +39,10 @@ struct q6voice { }; static inline struct q6voice_session *session_create(enum q6voice_path_type type, - int tx_port, int rx_port) + int tx_port, int rx_port, + u32 tx_topo, u32 rx_topo) { - return q6cvp_session_create(type, tx_port, rx_port); + return q6cvp_session_create(type, tx_port, rx_port, tx_topo, rx_topo); } static struct q6voice_session *session_create_v3(enum q6voice_path_type type, @@ -47,7 +51,7 @@ static struct q6voice_session *session_create_v3(enum q6voice_path_type type, { struct q6voice_session *cvp; - cvp = q6cvp_session_create_v3(type, tx_port, rx_port); + cvp = q6cvp_session_create_v3(type, tx_port, rx_port, tx_topo, rx_topo); if (cvp == NULL) return NULL; @@ -88,7 +92,8 @@ static int q6voice_path_start(struct q6voice_path *p) else cvp = session_create(p->type, q6afe_get_port_id(p->tx_port), - q6afe_get_port_id(p->rx_port)); + q6afe_get_port_id(p->rx_port), + p->tx_topo, p->rx_topo); if (IS_ERR(cvp)) return PTR_ERR(cvp); @@ -299,6 +304,30 @@ void q6voice_set_port(struct q6voice *v, enum q6voice_path_type path, } EXPORT_SYMBOL_GPL(q6voice_set_port); +u32 q6voice_get_topology(struct q6voice *v, enum q6voice_path_type path, + bool capture) +{ + struct q6voice_path *p = &v->paths[path]; + + if (capture) + return p->tx_topo; + else + return p->rx_topo; +} +EXPORT_SYMBOL_GPL(q6voice_get_topology); + +void q6voice_set_topology(struct q6voice *v, enum q6voice_path_type path, + bool capture, u32 topo) +{ + struct q6voice_path *p = &v->paths[path]; + + if (capture) + p->tx_topo = topo; + else + p->rx_topo = topo; +} +EXPORT_SYMBOL_GPL(q6voice_set_topology); + MODULE_AUTHOR("Stephan Gerhold "); MODULE_DESCRIPTION("Q6Voice driver"); MODULE_LICENSE("GPL v2"); diff --git a/sound/soc/qcom/qdsp6/q6voice.h b/sound/soc/qcom/qdsp6/q6voice.h index a1e3300ffbe9c0..3d002a9a5a2773 100644 --- a/sound/soc/qcom/qdsp6/q6voice.h +++ b/sound/soc/qcom/qdsp6/q6voice.h @@ -25,4 +25,9 @@ int q6voice_get_port(struct q6voice *v, enum q6voice_path_type path, void q6voice_set_port(struct q6voice *v, enum q6voice_path_type path, bool capture, int index); +u32 q6voice_get_topology(struct q6voice *v, enum q6voice_path_type path, + bool capture); +void q6voice_set_topology(struct q6voice *v, enum q6voice_path_type path, + bool capture, u32 topo); + #endif /*_Q6_VOICE_H */ From 854a13702c216e32091eac2585552ad943845386 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Tue, 8 Oct 2024 19:30:45 -0400 Subject: [PATCH 18/28] ASoC: qdsp6: q6voice-dai: add ALSA controls for the topology ID The topology IDs are different between sound card models (smartphones in the context of the Pixel 3a), and even codecs. As a result, the topology IDs are not defined by the device tree, but rather by the ALSA configuration. Add controls to ALSA for the topology ID so they can be configured per codec. Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6voice-dai.c | 50 ++++++++++++++++++++++++++++++ 1 file changed, 50 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6voice-dai.c b/sound/soc/qcom/qdsp6/q6voice-dai.c index e8fb390b182570..0da168ffd58a47 100644 --- a/sound/soc/qcom/qdsp6/q6voice-dai.c +++ b/sound/soc/qcom/qdsp6/q6voice-dai.c @@ -2,6 +2,7 @@ // Copyright (c) 2012-2017, The Linux Foundation. All rights reserved. // Copyright (c) 2020, Stephan Gerhold +#include #include #include #include @@ -187,6 +188,53 @@ static int q6voice_put_mixer_playback(struct snd_kcontrol *kcontrol, struct snd_ return q6voice_put_mixer(kcontrol, ucontrol, false); } +static int q6voice_get_topology_kctrl(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct q6voice *v = snd_soc_component_get_drvdata(c); + enum q6voice_path_type path = q6voice_get_path(mc->reg); + bool capture = !!mc->shift; + + if (path >= Q6VOICE_PATH_COUNT) { + dev_err(c->dev, "Invalid DAI ID %u\n", mc->reg); + return -EINVAL; + } + + ucontrol->value.integer.value[0] = + q6voice_get_topology(v, path, capture); + + return 0; +} + +static int q6voice_put_topology_kctrl(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol) +{ + struct snd_soc_component *c = snd_kcontrol_chip(kcontrol); + struct soc_mixer_control *mc = + (struct soc_mixer_control *)kcontrol->private_value; + struct q6voice *v = snd_soc_component_get_drvdata(c); + u32 val = ucontrol->value.integer.value[0]; + enum q6voice_path_type path = q6voice_get_path(mc->reg); + bool capture = !!mc->shift; + + if (path >= Q6VOICE_PATH_COUNT) { + dev_err(c->dev, "Invalid DAI ID %u\n", mc->reg); + return -EINVAL; + } + + q6voice_set_topology(v, path, capture, val); + + return 1; +} + +static const struct snd_kcontrol_new q6voice_kcontrols[] = { + SOC_SINGLE_EXT("VoiceMMode1 TX Topology", VOICEMMODE1, 1, S32_MAX, 0, + q6voice_get_topology_kctrl, q6voice_put_topology_kctrl), + SOC_SINGLE_EXT("VoiceMMode1 RX Topology", VOICEMMODE1, 0, S32_MAX, 0, + q6voice_get_topology_kctrl, q6voice_put_topology_kctrl), +}; + static const struct snd_kcontrol_new cs_voice_tx_mixer_controls[] = { SOC_SINGLE_EXT("PRI_MI2S_TX", PRIMARY_MI2S_TX, CS_VOICE, 1, 0, q6voice_get_mixer_capture, q6voice_put_mixer_capture), @@ -326,6 +374,8 @@ static const struct snd_soc_component_driver q6voice_dai_component = { .name = DRV_NAME, .open = q6voice_dai_open, + .controls = q6voice_kcontrols, + .num_controls = ARRAY_SIZE(q6voice_kcontrols), .dapm_widgets = q6voice_dapm_widgets, .num_dapm_widgets = ARRAY_SIZE(q6voice_dapm_widgets), .dapm_routes = q6voice_dapm_routes, From 6dd9df22c3f1201823548cdeec8ec3e1fde0cd45 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Sat, 7 Mar 2026 08:19:19 -0500 Subject: [PATCH 19/28] ASoC: qdsp6: q6voice: Constify APR callback response data The client callback for q6voice should not modify the response, as noted for the other clients in commit 86af3c229245 ("ASoC: qcom: Constify APR callback response data") upstream. Mark the response data as const in q6voice to be compatible with the API change. Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6voice-common.c | 4 ++-- sound/soc/qcom/qdsp6/q6voice-common.h | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6voice-common.c b/sound/soc/qcom/qdsp6/q6voice-common.c index f821d619e16aaa..e0a5e4eea84e36 100644 --- a/sound/soc/qcom/qdsp6/q6voice-common.c +++ b/sound/soc/qcom/qdsp6/q6voice-common.c @@ -162,7 +162,7 @@ q6voice_session_create(enum q6voice_service_type type, EXPORT_SYMBOL_GPL(q6voice_session_create); static void q6voice_session_callback(struct q6voice_session *s, - struct apr_resp_pkt *data) + const struct apr_resp_pkt *data) { struct aprv2_ibasic_rsp_result_t *result = data->payload; unsigned long flags; @@ -197,7 +197,7 @@ static void q6voice_session_callback(struct q6voice_session *s, wake_up(&s->wait); } -int q6voice_common_callback(struct apr_device *adev, struct apr_resp_pkt *data) +int q6voice_common_callback(struct apr_device *adev, const struct apr_resp_pkt *data) { struct device *dev = &adev->dev; struct q6voice_service *v = dev_get_drvdata(dev); diff --git a/sound/soc/qcom/qdsp6/q6voice-common.h b/sound/soc/qcom/qdsp6/q6voice-common.h index 6fb3c9560b2e25..48735a935c3fbb 100644 --- a/sound/soc/qcom/qdsp6/q6voice-common.h +++ b/sound/soc/qcom/qdsp6/q6voice-common.h @@ -33,7 +33,7 @@ struct q6voice_session { int q6voice_common_probe(struct apr_device *adev, enum q6voice_service_type type); void q6voice_common_remove(struct apr_device *adev); -int q6voice_common_callback(struct apr_device *adev, struct apr_resp_pkt *data); +int q6voice_common_callback(struct apr_device *adev, const struct apr_resp_pkt *data); int q6voice_common_send(struct q6voice_session *s, struct apr_hdr *hdr); struct q6voice_session *q6voice_session_create(enum q6voice_service_type type, From 5eb95f5d956eedb3d00886ab88ccdc448d945d79 Mon Sep 17 00:00:00 2001 From: Richard Acayan Date: Mon, 27 Apr 2026 19:22:54 -0400 Subject: [PATCH 20/28] ASoC: qdsp6: q6voice: rename snd_soc_dapm_kcontrol_dapm() to snd_soc_kcontrol_to_dapm() The name of the function changed upstream in commit 253204186530 ("ASoC: soc-dapm: rename snd_soc_dapm_kcontrol_dapm() to snd_soc_dapm_kcontrol_to_dapm()"). Signed-off-by: Richard Acayan --- sound/soc/qcom/qdsp6/q6voice-dai.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/sound/soc/qcom/qdsp6/q6voice-dai.c b/sound/soc/qcom/qdsp6/q6voice-dai.c index 0da168ffd58a47..f1ca597b3fd104 100644 --- a/sound/soc/qcom/qdsp6/q6voice-dai.c +++ b/sound/soc/qcom/qdsp6/q6voice-dai.c @@ -127,7 +127,7 @@ static int q6voice_dai_open(struct snd_soc_component *component, static int q6voice_get_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, bool capture) { - struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol); struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; @@ -146,7 +146,7 @@ static int q6voice_get_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_ static int q6voice_put_mixer(struct snd_kcontrol *kcontrol, struct snd_ctl_elem_value *ucontrol, bool capture) { - struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_dapm(kcontrol); + struct snd_soc_dapm_context *dapm = snd_soc_dapm_kcontrol_to_dapm(kcontrol); struct snd_soc_component *c = snd_soc_dapm_to_component(dapm); struct soc_mixer_control *mc = (struct soc_mixer_control *)kcontrol->private_value; From ddc906c5a9dfc72d89cbf5851d0206df35e14c23 Mon Sep 17 00:00:00 2001 From: WrenIX Date: Fri, 1 May 2026 07:18:39 +0200 Subject: [PATCH 21/28] --- sc7280 related patches --- From 49045626702dc559719d3af50f31669161ada78c Mon Sep 17 00:00:00 2001 From: Bhushan Shah Date: Tue, 3 Feb 2026 15:16:58 +0530 Subject: [PATCH 22/28] ASoC: qdsp6: q6voice-dai: add routes for TX_CODEC_DMA_TX to VOICEMMODE1 Signed-off-by: Bhushan Shah --- sound/soc/qcom/qdsp6/q6voice-dai.c | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6voice-dai.c b/sound/soc/qcom/qdsp6/q6voice-dai.c index f1ca597b3fd104..2468380e3b1533 100644 --- a/sound/soc/qcom/qdsp6/q6voice-dai.c +++ b/sound/soc/qcom/qdsp6/q6voice-dai.c @@ -259,6 +259,14 @@ static const struct snd_kcontrol_new voicemmode1_tx_mixer_controls[] = { q6voice_get_mixer_capture, q6voice_put_mixer_capture), SOC_SINGLE_EXT("QUIN_MI2S_TX", QUINARY_MI2S_TX, VOICEMMODE1, 1, 0, q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_1", TX_CODEC_DMA_TX_1, VOICEMMODE1, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_2", TX_CODEC_DMA_TX_2, VOICEMMODE1, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_3", TX_CODEC_DMA_TX_3, VOICEMMODE1, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("TX_CODEC_DMA_TX_4", TX_CODEC_DMA_TX_4, VOICEMMODE1, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), }; static const struct snd_kcontrol_new primary_mi2s_rx_mixer_controls[] = { @@ -336,6 +344,10 @@ static const struct snd_soc_dapm_route q6voice_dapm_routes[] = { { "VoiceMMode1 Capture Mixer", "TERT_MI2S_TX", "TERT_MI2S_TX" }, { "VoiceMMode1 Capture Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX" }, { "VoiceMMode1 Capture Mixer", "QUIN_MI2S_TX", "QUIN_MI2S_TX" }, + { "VoiceMMode1 Capture Mixer", "TX_CODEC_DMA_TX_1", "TX_CODEC_DMA_TX_1" }, + { "VoiceMMode1 Capture Mixer", "TX_CODEC_DMA_TX_2", "TX_CODEC_DMA_TX_2" }, + { "VoiceMMode1 Capture Mixer", "TX_CODEC_DMA_TX_3", "TX_CODEC_DMA_TX_3" }, + { "VoiceMMode1 Capture Mixer", "TX_CODEC_DMA_TX_4", "TX_CODEC_DMA_TX_4" }, { "VOICEMMODE1_UL1", NULL, "VoiceMMode1 Capture Mixer" }, { "PRI_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, From c719bd29ecb5d7e93b2ddcd832d35be8fe464690 Mon Sep 17 00:00:00 2001 From: Val Packett Date: Mon, 6 Apr 2026 23:50:24 -0300 Subject: [PATCH 23/28] ASoC: qdsp6: q6voice-dai: add routes for Senary MI2S Signed-off-by: Val Packett --- sound/soc/qcom/qdsp6/q6voice-dai.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/sound/soc/qcom/qdsp6/q6voice-dai.c b/sound/soc/qcom/qdsp6/q6voice-dai.c index 2468380e3b1533..16af108e0a96f9 100644 --- a/sound/soc/qcom/qdsp6/q6voice-dai.c +++ b/sound/soc/qcom/qdsp6/q6voice-dai.c @@ -246,6 +246,8 @@ static const struct snd_kcontrol_new cs_voice_tx_mixer_controls[] = { q6voice_get_mixer_capture, q6voice_put_mixer_capture), SOC_SINGLE_EXT("QUIN_MI2S_TX", QUINARY_MI2S_TX, CS_VOICE, 1, 0, q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("SEN_MI2S_TX", SENARY_MI2S_TX, CS_VOICE, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), }; static const struct snd_kcontrol_new voicemmode1_tx_mixer_controls[] = { @@ -267,6 +269,8 @@ static const struct snd_kcontrol_new voicemmode1_tx_mixer_controls[] = { q6voice_get_mixer_capture, q6voice_put_mixer_capture), SOC_SINGLE_EXT("TX_CODEC_DMA_TX_4", TX_CODEC_DMA_TX_4, VOICEMMODE1, 1, 0, q6voice_get_mixer_capture, q6voice_put_mixer_capture), + SOC_SINGLE_EXT("SEN_MI2S_TX", SENARY_MI2S_TX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_capture, q6voice_put_mixer_capture), }; static const struct snd_kcontrol_new primary_mi2s_rx_mixer_controls[] = { @@ -304,6 +308,13 @@ static const struct snd_kcontrol_new quinary_mi2s_rx_mixer_controls[] = { q6voice_get_mixer_playback, q6voice_put_mixer_playback), }; +static const struct snd_kcontrol_new senary_mi2s_rx_mixer_controls[] = { + SOC_SINGLE_EXT("CS-Voice", SENARY_MI2S_RX, CS_VOICE, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), + SOC_SINGLE_EXT("VoiceMMode1", SENARY_MI2S_RX, VOICEMMODE1, 1, 0, + q6voice_get_mixer_playback, q6voice_put_mixer_playback), +}; + static const struct snd_soc_dapm_widget q6voice_dapm_widgets[] = { SND_SOC_DAPM_AIF_IN("CS-VOICE_DL1", "CS-VOICE Playback", 0, SND_SOC_NOPM, 0, 0), SND_SOC_DAPM_AIF_OUT("CS-VOICE_UL1", "CS-VOICE Capture", 0, SND_SOC_NOPM, 0, 0), @@ -330,6 +341,9 @@ static const struct snd_soc_dapm_widget q6voice_dapm_widgets[] = { SND_SOC_DAPM_MIXER("QUIN_MI2S_RX Voice Mixer", SND_SOC_NOPM, 0, 0, quinary_mi2s_rx_mixer_controls, ARRAY_SIZE(quinary_mi2s_rx_mixer_controls)), + SND_SOC_DAPM_MIXER("SEN_MI2S_RX Voice Mixer", SND_SOC_NOPM, 0, 0, + senary_mi2s_rx_mixer_controls, + ARRAY_SIZE(senary_mi2s_rx_mixer_controls)), }; static const struct snd_soc_dapm_route q6voice_dapm_routes[] = { @@ -338,6 +352,7 @@ static const struct snd_soc_dapm_route q6voice_dapm_routes[] = { { "CS-Voice Capture Mixer", "TERT_MI2S_TX", "TERT_MI2S_TX" }, { "CS-Voice Capture Mixer", "QUAT_MI2S_TX", "QUAT_MI2S_TX" }, { "CS-Voice Capture Mixer", "QUIN_MI2S_TX", "QUIN_MI2S_TX" }, + { "CS-Voice Capture Mixer", "SEN_MI2S_TX", "SEN_MI2S_TX" }, { "CS-VOICE_UL1", NULL, "CS-Voice Capture Mixer" }, { "VoiceMMode1 Capture Mixer", "PRI_MI2S_TX", "PRI_MI2S_TX" }, { "VoiceMMode1 Capture Mixer", "SEC_MI2S_TX", "SEC_MI2S_TX" }, @@ -348,6 +363,7 @@ static const struct snd_soc_dapm_route q6voice_dapm_routes[] = { { "VoiceMMode1 Capture Mixer", "TX_CODEC_DMA_TX_2", "TX_CODEC_DMA_TX_2" }, { "VoiceMMode1 Capture Mixer", "TX_CODEC_DMA_TX_3", "TX_CODEC_DMA_TX_3" }, { "VoiceMMode1 Capture Mixer", "TX_CODEC_DMA_TX_4", "TX_CODEC_DMA_TX_4" }, + { "VoiceMMode1 Capture Mixer", "SEN_MI2S_TX", "SEN_MI2S_TX" }, { "VOICEMMODE1_UL1", NULL, "VoiceMMode1 Capture Mixer" }, { "PRI_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, @@ -355,17 +371,20 @@ static const struct snd_soc_dapm_route q6voice_dapm_routes[] = { { "TERT_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, { "QUAT_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, { "QUIN_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, + { "SEN_MI2S_RX Voice Mixer", "CS-Voice", "CS-VOICE_DL1" }, { "PRI_MI2S_RX Voice Mixer", "VoiceMMode1", "VOICEMMODE1_DL1" }, { "SEC_MI2S_RX Voice Mixer", "VoiceMMode1", "VOICEMMODE1_DL1" }, { "TERT_MI2S_RX Voice Mixer", "VoiceMMode1", "VOICEMMODE1_DL1" }, { "QUAT_MI2S_RX Voice Mixer", "VoiceMMode1", "VOICEMMODE1_DL1" }, { "QUIN_MI2S_RX Voice Mixer", "VoiceMMode1", "VOICEMMODE1_DL1" }, + { "SEN_MI2S_RX Voice Mixer", "VoiceMMode1", "VOICEMMODE1_DL1" }, { "PRI_MI2S_RX", NULL, "PRI_MI2S_RX Voice Mixer" }, { "SEC_MI2S_RX", NULL, "SEC_MI2S_RX Voice Mixer" }, { "TERT_MI2S_RX", NULL, "TERT_MI2S_RX Voice Mixer" }, { "QUAT_MI2S_RX", NULL, "QUAT_MI2S_RX Voice Mixer" }, { "QUIN_MI2S_RX", NULL, "QUIN_MI2S_RX Voice Mixer" }, + { "SEN_MI2S_RX", NULL, "SEN_MI2S_RX Voice Mixer" }, }; static unsigned int q6voice_reg_read(struct snd_soc_component *component, From de327858a4adc308aabc6c15570e371c7d18ad8b Mon Sep 17 00:00:00 2001 From: Val Packett Date: Mon, 20 Apr 2026 15:54:45 -0300 Subject: [PATCH 24/28] ASoC: qdsp6: q6voice: default to NONE topology for TX On kodiak + wcd9375, only this topology seems to work so far for the mic. Signed-off-by: Val Packett --- sound/soc/qcom/qdsp6/q6voice.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/sound/soc/qcom/qdsp6/q6voice.c b/sound/soc/qcom/qdsp6/q6voice.c index 461a5eceb3cf87..bb27cb7c0d0b82 100644 --- a/sound/soc/qcom/qdsp6/q6voice.c +++ b/sound/soc/qcom/qdsp6/q6voice.c @@ -13,6 +13,7 @@ #include "q6mvm.h" #include "q6voice-common.h" +#define VSS_IVOCPROC_TOPOLOGY_ID_NONE 0x00010F70 #define VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS 0x00010F71 #define VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT 0x00010F77 @@ -267,7 +268,7 @@ struct q6voice *q6voice_create(struct device *dev, bool cvd_v2_3) p->v = v; p->type = path; - p->tx_topo = VSS_IVOCPROC_TOPOLOGY_ID_TX_SM_ECNS; + p->tx_topo = VSS_IVOCPROC_TOPOLOGY_ID_NONE; p->rx_topo = VSS_IVOCPROC_TOPOLOGY_ID_RX_DEFAULT; mutex_init(&p->lock); } From ecd66e3be1fab512d1db49f8d713619b642c4aab Mon Sep 17 00:00:00 2001 From: Bhushan Shah Date: Mon, 2 Feb 2026 19:07:57 +0530 Subject: [PATCH 25/28] arm64: dts: qcom: kodiak: add q6voice related nodes Signed-off-by: Bhushan Shah --- arch/arm64/boot/dts/qcom/kodiak.dtsi | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/kodiak.dtsi b/arch/arm64/boot/dts/qcom/kodiak.dtsi index d201da6720bcd2..d28084ff8607a5 100644 --- a/arch/arm64/boot/dts/qcom/kodiak.dtsi +++ b/arch/arm64/boot/dts/qcom/kodiak.dtsi @@ -4550,6 +4550,35 @@ #sound-dai-cells = <0>; }; }; + + q6mvm: apr-service@9 { + compatible = "qcom,q6mvm"; + reg = ; + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; + status = "disabled"; + + q6voicedai: dais { + compatible = "qcom,q6voice-dais"; + #address-cells = <1>; + #size-cells = <0>; + #sound-dai-cells = <1>; + }; + }; + + q6cvs: apr-service@a { + compatible = "qcom,q6cvs"; + reg = ; + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; + status = "disabled"; + }; + + q6cvp: apr-service@b { + compatible = "qcom,q6cvp"; + reg = ; + qcom,protection-domain = "avs/audio", "msm/adsp/audio_pd"; + status = "disabled"; + }; + }; fastrpc { From 0c96d8af661744ef97245849e2d752549562f00a Mon Sep 17 00:00:00 2001 From: Bhushan Shah Date: Mon, 2 Feb 2026 19:10:45 +0530 Subject: [PATCH 26/28] arm64: dts: qcom: qcm6490-fairphone-fp5: add q6voice related nodes Signed-off-by: Bhushan Shah --- .../boot/dts/qcom/qcm6490-fairphone-fp5.dts | 41 +++++++++++++++++++ 1 file changed, 41 insertions(+) diff --git a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts index 29d6265a123d35..d48cf38b768a0b 100644 --- a/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts +++ b/arch/arm64/boot/dts/qcom/qcm6490-fairphone-fp5.dts @@ -17,6 +17,8 @@ #include #include #include +#include + #include "kodiak.dtsi" #include "pm7250b.dtsi" #include "pm7325.dtsi" @@ -1298,7 +1300,38 @@ status = "okay"; }; +&q6cvp { + status = "okay"; +}; + +&q6cvs { + status = "okay"; +}; + +&q6mvm { + status = "okay"; +}; + + +&q6voicedai { + dai@1 { + reg = ; + }; +}; + &q6afedai { + dai@104 { + reg = ; + }; + + dai@113 { + reg = ; + }; + + dai@120 { + reg = ; + }; + dai@127 { reg = ; qcom,sd-lines = <0>; @@ -1483,6 +1516,14 @@ sound-dai = <&q6routing>; }; }; + + voicemmode1-dai-link { + link-name = "VoiceMMode1"; + cpu { + sound-dai = <&q6voicedai VOICEMMODE1>; + }; + }; + }; &spi13 { From 88d755cbb15576ecf9c35bca17c3e82374e3241d Mon Sep 17 00:00:00 2001 From: Christian Duerr Date: Tue, 11 Aug 2026 01:57:37 +0200 Subject: [PATCH 27/28] ASoC: codecs: aw88261: Remove power-on mute code This removes the code responsible for keeping the aw88261 audio muted until power-on, since it appears to be unnecessary and prevents the bottom speaker from muting during calls on the Fairphone 5. --- sound/soc/codecs/aw88261.c | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/sound/soc/codecs/aw88261.c b/sound/soc/codecs/aw88261.c index b14ca416304324..b3302522f1b2dd 100644 --- a/sound/soc/codecs/aw88261.c +++ b/sound/soc/codecs/aw88261.c @@ -417,17 +417,7 @@ static int aw88261_dev_reg_update(struct aw88261 *aw88261, break; } - ret = aw88261_dev_set_vcalb(aw_dev); - if (ret) - return ret; - - if (aw_dev->prof_cur != aw_dev->prof_index) - vol_desc->ctl_volume = 0; - - /* keep min volume */ - aw88261_dev_set_volume(aw_dev, vol_desc->mute_volume); - - return ret; + return aw88261_dev_set_vcalb(aw_dev); } static int aw88261_dev_get_prof_name(struct aw_device *aw_dev, int index, char **prof_name) From 6c1c703999e3f5438fa050fc24271ef6e9e7f972 Mon Sep 17 00:00:00 2001 From: Luca Weiss Date: Fri, 21 Aug 2026 11:41:04 +0200 Subject: [PATCH 28/28] fp5_defconfig: enable SND_SOC_QDSP6_Q6VOICE --- arch/arm64/configs/fp5_defconfig | 1 + 1 file changed, 1 insertion(+) diff --git a/arch/arm64/configs/fp5_defconfig b/arch/arm64/configs/fp5_defconfig index 03c1dd5d7c3981..aac647d82c392e 100644 --- a/arch/arm64/configs/fp5_defconfig +++ b/arch/arm64/configs/fp5_defconfig @@ -373,6 +373,7 @@ CONFIG_SND_SOC=y CONFIG_SND_SOC_USB=y CONFIG_SND_SOC_QCOM=y CONFIG_SND_SOC_QDSP6_USB=y +CONFIG_SND_SOC_QDSP6_Q6VOICE=y CONFIG_SND_SOC_SM8250=y CONFIG_SND_SOC_AW88261=y CONFIG_SND_SOC_WCD938X_SDW=y