From 306a3a6f19546b7b9b6b8df973d75710ce21bc3c Mon Sep 17 00:00:00 2001
From: Joey Hewitt <joey@joeyhewitt.com>
Date: Mon, 4 Sep 2017 23:51:07 -0700
Subject: [PATCH] qmimodem: implement DTMF

The TLVs are documented in GobiAPI. I pass 0xff for the call ID, as the
stock RIL appears to always do. I would guess it means "current foreground
call."

The call ID is returned in TLV 0x10, but I didn't implement parsing of
that.

Acked-by: Alexey Andreyev <aa13q@ya.ru>

diff --git a/drivers/qmimodem/voice_generated.c b/drivers/qmimodem/voice_generated.c
index aef79c4..b579043 100644
--- a/drivers/qmimodem/voice_generated.c
+++ b/drivers/qmimodem/voice_generated.c
@@ -216,3 +216,72 @@ enum parse_error qmi_voice_call_status(
 
 	return err;
 }
+
+int qmi_voice_start_cont_dtmf(
+		struct qmi_voice_start_cont_dtmf_arg *arg,
+		struct qmi_service *service,
+		qmi_result_func_t func,
+		void *user_data,
+		qmi_destroy_func_t destroy)
+{
+	struct qmi_param *param = NULL;
+	uint8_t param_body[2];
+
+	param = qmi_param_new();
+	if (!param)
+		goto error;
+
+	param_body[0] = arg->call_id;
+	param_body[1] = arg->dtmf_char;
+
+	if (!qmi_param_append(
+			param,
+			0x1,
+			sizeof(param_body),
+			param_body))
+		goto error;
+
+	if (qmi_service_send(service,
+						 0x29,
+						 param,
+						 func,
+						 user_data,
+						 destroy) > 0)
+		return 0;
+
+error:
+	g_free(param);
+	return 1;
+}
+
+int qmi_voice_stop_cont_dtmf(
+		struct qmi_voice_stop_cont_dtmf_arg *arg,
+		struct qmi_service *service,
+		qmi_result_func_t func,
+		void *user_data,
+		qmi_destroy_func_t destroy)
+{
+	struct qmi_param *param = NULL;
+
+	param = qmi_param_new();
+	if (!param)
+		goto error;
+
+	if (!qmi_param_append_uint8(
+			param,
+			0x1,
+			arg->call_id))
+		goto error;
+
+	if (qmi_service_send(service,
+						 0x2a,
+						 param,
+						 func,
+						 user_data,
+						 destroy) > 0)
+		return 0;
+
+error:
+	g_free(param);
+	return 1;
+}
\ No newline at end of file
diff --git a/drivers/qmimodem/voice_generated.h b/drivers/qmimodem/voice_generated.h
index dc238ef..c627fe1 100644
--- a/drivers/qmimodem/voice_generated.h
+++ b/drivers/qmimodem/voice_generated.h
@@ -110,4 +110,27 @@ enum parse_error qmi_voice_call_status(
 		struct qmi_result *qmi_result,
 		struct qmi_voice_all_call_status_ind *result);
 
+struct qmi_voice_start_cont_dtmf_arg {
+	uint8_t call_id;
+	uint8_t dtmf_char;
+};
+
+int qmi_voice_start_cont_dtmf(
+		struct qmi_voice_start_cont_dtmf_arg *arg,
+		struct qmi_service *service,
+		qmi_result_func_t func,
+		void *user_data,
+		qmi_destroy_func_t destroy);
+
+struct qmi_voice_stop_cont_dtmf_arg {
+	uint8_t call_id;
+};
+
+int qmi_voice_stop_cont_dtmf(
+		struct qmi_voice_stop_cont_dtmf_arg *arg,
+		struct qmi_service *service,
+		qmi_result_func_t func,
+		void *user_data,
+		qmi_destroy_func_t destroy);
+
 #endif /* __OFONO_QMI_VOICE_GENERATED_H */
diff --git a/drivers/qmimodem/voicecall.c b/drivers/qmimodem/voicecall.c
index bc8ac2b..5d8d35f 100644
--- a/drivers/qmimodem/voicecall.c
+++ b/drivers/qmimodem/voicecall.c
@@ -413,6 +413,110 @@ static void hangup_active(struct ofono_voicecall *vc,
 	release_specific(vc, call->id, cb, data);
 }
 
+static void stop_cont_dtmf_cb(struct qmi_result *result, void *user_data)
+{
+	struct cb_data *cbd = user_data;
+	ofono_voicecall_cb_t cb = cbd->cb;
+
+	uint16_t error;
+
+	if (qmi_result_set_error(result, &error)) {
+		DBG("QMI Error %d", error);
+		CALLBACK_WITH_FAILURE(cb, cbd->data);
+		return;
+	}
+
+	CALLBACK_WITH_SUCCESS(cb, cbd->data);
+}
+
+static void start_cont_dtmf_cb(struct qmi_result *result, void *user_data)
+{
+	struct cb_data *cbd = user_data;
+	ofono_voicecall_cb_t cb = cbd->cb;
+	struct ofono_voicecall *vc = cbd->user;
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+	struct qmi_voice_stop_cont_dtmf_arg arg;
+	uint16_t error;
+
+	if (qmi_result_set_error(result, &error)) {
+		DBG("QMI Error %d", error);
+		CALLBACK_WITH_FAILURE(cb, cbd->data);
+		return;
+	}
+
+	arg.call_id = 0xff;
+
+	if (!qmi_voice_stop_cont_dtmf(&arg,
+								  vd->voice,
+								  stop_cont_dtmf_cb,
+								  cbd,
+								  g_free))
+		return;
+
+	CALLBACK_WITH_FAILURE(cb, cbd->data);
+}
+
+static void send_one_dtmf(struct ofono_voicecall *vc, const char dtmf,
+			ofono_voicecall_cb_t cb, void *data) {
+	struct qmi_voice_start_cont_dtmf_arg arg;
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+
+	arg.call_id = 0xff;
+	arg.dtmf_char = (uint8_t) dtmf;
+
+	struct cb_data *cbd = cb_data_new(cb, data);
+	cbd->user = vc;
+
+	if (!qmi_voice_start_cont_dtmf(&arg,
+								   vd->voice,
+								   start_cont_dtmf_cb,
+								   cbd,
+								   NULL))
+		return;
+
+	CALLBACK_WITH_FAILURE(cb, data);
+	g_free(cbd);
+}
+
+struct send_one_dtmf_cb_data {
+	const char *full_dtmf;
+	const char *next_dtmf;
+	struct ofono_voicecall *vc;
+};
+
+static void send_one_dtmf_cb(const struct ofono_error *error, void *data) {
+	struct cb_data *cbd = data;
+	ofono_voicecall_cb_t cb = cbd->cb;
+	struct send_one_dtmf_cb_data *send_one_dtmf_cb_data = cbd->user;
+
+	if (error->type != OFONO_ERROR_TYPE_NO_ERROR || *send_one_dtmf_cb_data->next_dtmf == 0) {
+		if (error->type == OFONO_ERROR_TYPE_NO_ERROR) {
+			CALLBACK_WITH_SUCCESS(cb, cbd->data);
+		} else {
+			CALLBACK_WITH_FAILURE(cb, cbd->data);
+		}
+		g_free((gpointer)send_one_dtmf_cb_data->full_dtmf);
+		g_free(send_one_dtmf_cb_data);
+		g_free(cbd);
+	} else {
+		send_one_dtmf(send_one_dtmf_cb_data->vc, *(send_one_dtmf_cb_data->next_dtmf++), send_one_dtmf_cb, data);
+	}
+}
+
+static void send_dtmf(struct ofono_voicecall *vc, const char *dtmf,
+			ofono_voicecall_cb_t cb, void *data)
+{
+	struct cb_data *cbd = cb_data_new(cb, data);
+	struct send_one_dtmf_cb_data *send_one_dtmf_cb_data = g_new(struct send_one_dtmf_cb_data, 1);
+
+	send_one_dtmf_cb_data->full_dtmf = g_strdup(dtmf);
+	send_one_dtmf_cb_data->next_dtmf = &send_one_dtmf_cb_data->full_dtmf[1];
+	send_one_dtmf_cb_data->vc = vc;
+	cbd->user = send_one_dtmf_cb_data;
+
+	send_one_dtmf(vc, *dtmf, send_one_dtmf_cb, cbd);
+}
+
 static const struct ofono_voicecall_driver driver = {
 	.name		= "qmimodem",
 	.probe		= qmi_voicecall_probe,
@@ -421,6 +525,7 @@ static const struct ofono_voicecall_driver driver = {
 	.answer		= answer,
 	.hangup_active  = hangup_active,
 	.release_specific  = release_specific,
+	.send_tones = send_dtmf,
 };
 
 void qmi_voicecall_init(void)