From d0f88748239a2ebbc7e6773fa50dce88a2cdc9ad Mon Sep 17 00:00:00 2001
From: Stephan Gerhold <stephan@gerhold.net>
Date: Tue, 28 Apr 2020 16:13:01 +0200
Subject: [PATCH 41/78] 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.
---
 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 103070490..adc76ab35 100644
--- a/sound/soc/qcom/qdsp6/Makefile
+++ b/sound/soc/qcom/qdsp6/Makefile
@@ -9,3 +9,4 @@ 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
diff --git a/sound/soc/qcom/qdsp6/q6voice.c b/sound/soc/qcom/qdsp6/q6voice.c
new file mode 100644
index 000000000..2a6910d96
--- /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 <linux/device.h>
+#include <linux/module.h>
+#include <linux/mutex.h>
+#include <linux/slab.h>
+#include <linux/spinlock.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;
+};
+
+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 <stephan@gerhold.net>");
+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 000000000..b760b85ad
--- /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 */
-- 
2.31.1

