From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: Adam Pigg <adam@piggz.co.uk>
Date: Tue, 12 Mar 2024 18:52:32 +0000
Subject: [PATCH 1/1] Add voicecall support to the qmimodem driver

Adds a call_list helper which could be shared across drivers
---
 Makefile.am                  |   6 +-
 drivers/common/call_list.c   | 151 ++++++++++++
 drivers/common/call_list.h   |  49 ++++
 drivers/qmimodem/qmi.h       |  14 ++
 drivers/qmimodem/voice.c     | 365 +++++++++++++++++++++++++++++
 drivers/qmimodem/voice.h     | 242 ++++++++++++++++---
 drivers/qmimodem/voicecall.c | 435 ++++++++++++++++++++++++++++++++++-
 src/common.h                 |   5 +
 8 files changed, 1223 insertions(+), 44 deletions(-)
 create mode 100644 drivers/common/call_list.c
 create mode 100644 drivers/common/call_list.h
 create mode 100644 drivers/qmimodem/voice.c

diff --git a/Makefile.am b/Makefile.am
index 636546189a50f338f77706ec465cded268a89210..7f8a691d6f0679eb94ccb64bf418cf20a98db689 100644
--- a/Makefile.am
+++ b/Makefile.am
@@ -373,8 +373,8 @@ qmi_sources = drivers/qmimodem/qmi.h drivers/qmimodem/qmi.c \
 					drivers/qmimodem/pds.h \
 					drivers/qmimodem/common.h \
 					drivers/qmimodem/wda.h \
-					drivers/qmimodem/voice.h
-
+					drivers/qmimodem/voice.h \
+					drivers/qmimodem/voice.c
 builtin_sources += $(qmi_sources) \
 			drivers/qmimodem/util.h \
 			drivers/qmimodem/devinfo.c \
@@ -579,6 +579,8 @@ builtin_sources += plugins/ublox.c
 builtin_sources += plugins/xmm7xxx.c
 builtin_sources += plugins/droid.c
 
+builtin_sources += drivers/common/call_list.c
+
 builtin_modules += stemgr
 builtin_sources += plugins/stemgr.c
 
diff --git a/drivers/common/call_list.c b/drivers/common/call_list.c
new file mode 100644
index 0000000000000000000000000000000000000000..13aa8dc57ada0931bdddac3e23ea944bf3be04d7
--- /dev/null
+++ b/drivers/common/call_list.c
@@ -0,0 +1,151 @@
+/*
+ *
+ *  oFono - Open Source Telephony
+ *
+ *  Copyright (C) 2008-2011  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2019 Alexander Couzens <lynxis@fe80.eu>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifdef HAVE_CONFIG_H
+#include <config.h>
+#endif
+
+#include <ell/ell.h>
+
+#include <ofono/types.h>
+
+#include <ofono/types.h>
+#include <ofono/log.h>
+#include <ofono/voicecall.h>
+
+#include "src/common.h"
+
+#include <drivers/common/call_list.h>
+
+int ofono_call_compare(const void *a, const void *b, void *data)
+{
+	const struct ofono_call *ca = a;
+	const struct ofono_call *cb = b;
+
+	if (ca->id < cb->id)
+		return -1;
+
+	if (ca->id > cb->id)
+		return 1;
+
+	return 0;
+}
+
+bool ofono_call_compare_by_status(const void *a, const void *b)
+{
+	const struct ofono_call *call = a;
+	int status = L_PTR_TO_INT(b);
+
+	return status == call->status;
+}
+
+bool ofono_call_compare_by_id(const void *a, const void *b)
+{
+	const struct ofono_call *call = a;
+	unsigned int id = L_PTR_TO_UINT(b);
+
+	return (call->id == id);
+}
+
+void ofono_call_list_dial_callback(struct ofono_voicecall *vc,
+				   struct l_queue **call_list,
+				   const struct ofono_phone_number *ph,
+				   int call_id)
+{
+	struct ofono_call *call;
+
+	/* check if call_id already present */
+	call = l_queue_find(*call_list,
+					 ofono_call_compare_by_id,
+				     L_UINT_TO_PTR(call_id));
+
+	if (call) {
+		return;
+	}
+
+	call = l_new(struct ofono_call, 1);
+	call->id = call_id;
+
+	memcpy(&call->called_number, ph, sizeof(*ph));
+	call->direction = CALL_DIRECTION_MOBILE_ORIGINATED;
+	call->status = CALL_STATUS_DIALING;
+	call->type = 0; /* voice */
+
+	l_queue_insert(*call_list, call, ofono_call_compare, NULL);
+
+	ofono_voicecall_notify(vc, call);
+}
+
+void ofono_call_list_notify(struct ofono_voicecall *vc,
+			    struct l_queue **call_list,
+			    struct l_queue *calls)
+{
+	struct l_queue  *old_calls = *call_list;
+	struct l_queue  *new_calls = calls;
+	struct ofono_call *new_call, *old_call;
+	const struct l_queue_entry *old_entry, *new_entry;
+
+	uint loop_length = MAX(l_queue_length(old_calls), l_queue_length(new_calls));
+
+	old_entry = l_queue_get_entries(old_calls);
+	new_entry = l_queue_get_entries(new_calls);
+
+	for (uint i = 0; i < loop_length; ++i) {
+		old_call = old_entry ? old_entry->data : NULL;
+		new_call = new_entry ? new_entry->data : NULL;
+
+		if (new_call && new_call->status == CALL_STATUS_DISCONNECTED) {
+			ofono_voicecall_disconnected(
+				vc,
+				new_call->id,
+				OFONO_DISCONNECT_REASON_REMOTE_HANGUP,
+				NULL);
+
+			l_queue_remove(calls, new_call);
+			l_free(new_call);
+			continue;
+		}
+
+		if (old_call && (new_call == NULL || (new_call->id > old_call->id))) {
+			ofono_voicecall_disconnected(
+				vc,
+				old_call->id,
+				OFONO_DISCONNECT_REASON_REMOTE_HANGUP,
+				NULL);
+		} else if (new_call && (old_call == NULL || (new_call->id < old_call->id))) {
+			DBG("Notify new call %d", new_call->id);
+			/* new call, signal it */
+			if (new_call->type == 0) {
+				ofono_voicecall_notify(vc, new_call);
+			}
+		} else {
+			if (memcmp(new_call, old_call, sizeof(*new_call))
+				&& new_call->type == 0)
+				ofono_voicecall_notify(vc, new_call);
+		}
+		if (old_entry) old_entry = old_entry->next;
+		if (new_entry) new_entry = new_entry->next;
+	}
+
+	l_queue_destroy(*call_list, l_free);
+	*call_list = calls;
+}
diff --git a/drivers/common/call_list.h b/drivers/common/call_list.h
new file mode 100644
index 0000000000000000000000000000000000000000..746008bb156ec72c1e4eac32afd8c1f467cadc8a
--- /dev/null
+++ b/drivers/common/call_list.h
@@ -0,0 +1,49 @@
+/*
+ *
+ *  oFono - Open Source Telephony
+ *
+ *  Copyright (C) 2017,2019 Alexander Couzens <lynxis@fe80.eu>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software
+ *  Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301  USA
+ *
+ */
+
+#ifndef __OFONO_DRIVER_COMMON_CALL_LIST
+#define __OFONO_DRIVER_COMMON_CALL_LIST
+
+struct ofono_voicecall;
+struct ofono_phone_number;
+
+int ofono_call_compare(const void *a, const void *b, void *data);
+bool ofono_call_compare_by_status(const void *a, const void *b);
+bool ofono_call_compare_by_id(const void *a, const void *b);
+
+/*
+ * Can be called by the driver in the dialing callback,
+ * when the new call id already known
+ */
+void ofono_call_list_dial_callback(struct ofono_voicecall *vc,
+				   struct l_queue **call_list,
+				   const struct ofono_phone_number *ph,
+				   int call_id);
+
+/*
+ * Called with a list of known calls e.g. clcc.
+ * Call list will take ownership of all ofono call within the calls.
+ */
+void ofono_call_list_notify(struct ofono_voicecall *vc,
+			    struct l_queue **call_list,
+			    struct l_queue *calls);
+
+#endif /* __OFONO_DRIVER_COMMON_CALL_LIST */
diff --git a/drivers/qmimodem/qmi.h b/drivers/qmimodem/qmi.h
index 506fed6b3a66c874ff82b26559e478fc9533201e..9caffa88da9559ef625045843c0db49e45c4bc87 100644
--- a/drivers/qmimodem/qmi.h
+++ b/drivers/qmimodem/qmi.h
@@ -19,6 +19,9 @@
  *
  */
 
+#ifndef __OFONO_QMI_QMI_H
+#define __OFONO_QMI_QMI_H
+
 #include <stdbool.h>
 #include <stdint.h>
 
@@ -54,6 +57,7 @@
 #define QMI_SERVICE_CAT_OLD	224	/* Card application toolkit service */
 #define QMI_SERVICE_RMS		225	/* Remote management service */
 #define QMI_SERVICE_OMA		226	/* OMA device management service */
+#define QMI_SERVICE_UPDATE  400
 
 enum qmi_device_expected_data_format {
 	QMI_DEVICE_EXPECTED_DATA_FORMAT_UNKNOWN,
@@ -176,3 +180,13 @@ uint16_t qmi_service_register(struct qmi_service *service,
 				void *user_data, qmi_destroy_func_t destroy);
 bool qmi_service_unregister(struct qmi_service *service, uint16_t id);
 bool qmi_service_unregister_all(struct qmi_service *service);
+
+
+/* FIXME: find a place for parse_error */
+enum parse_error {
+	NONE = 0,
+	MISSING_MANDATORY = 1,
+	INVALID_LENGTH = 2,
+};
+
+#endif /* __OFONO_QMI_QMI_H */
diff --git a/drivers/qmimodem/voice.c b/drivers/qmimodem/voice.c
new file mode 100644
index 0000000000000000000000000000000000000000..138b2f63c62a66cfd43a0ad7639f4ba9250bacc8
--- /dev/null
+++ b/drivers/qmimodem/voice.c
@@ -0,0 +1,365 @@
+/*
+ *
+ *  oFono - Open Source Telephony
+ *
+ *  Copyright (C) 2017 Alexander Couzens <lynxis@fe80.eu>
+ *
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License version 2 as
+ *  published by the Free Software Foundation.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ */
+
+#include <stdint.h>
+#include <string.h>
+
+#include "voice.h"
+
+#define _(X) case X: return #X
+
+const char *qmi_voice_call_state_name(enum qmi_voice_call_state value)
+{
+	switch (value) {
+		_(QMI_CALL_STATE_IDLE);
+		_(QMI_CALL_STATE_ORIG);
+		_(QMI_CALL_STATE_INCOMING);
+		_(QMI_CALL_STATE_CONV);
+		_(QMI_CALL_STATE_CC_IN_PROG);
+		_(QMI_CALL_STATE_ALERTING);
+		_(QMI_CALL_STATE_HOLD);
+		_(QMI_CALL_STATE_WAITING);
+		_(QMI_CALL_STATE_DISCONNECTING);
+		_(QMI_CALL_STATE_END);
+		_(QMI_CALL_STATE_SETUP);
+	}
+	return "QMI_CALL_STATE_<UNKNOWN>";
+}
+
+int qmi_to_ofono_status(uint8_t status, int *ret) {
+	int err = 0;
+	switch (status) {
+	case QMI_CALL_STATE_IDLE:
+	case QMI_CALL_STATE_END:
+	case QMI_CALL_STATE_DISCONNECTING:
+		*ret = CALL_STATUS_DISCONNECTED;
+		break;
+	case QMI_CALL_STATE_HOLD:
+		*ret = CALL_STATUS_HELD;
+		break;
+	case QMI_CALL_STATE_WAITING:
+		*ret = CALL_STATUS_WAITING;
+		break;
+	case QMI_CALL_STATE_ORIG:
+		*ret = CALL_STATUS_DIALING;
+		break;
+	case QMI_CALL_STATE_SETUP:
+	case QMI_CALL_STATE_INCOMING:
+		*ret = CALL_STATUS_INCOMING;
+		break;
+	case QMI_CALL_STATE_CONV:
+		*ret = CALL_STATUS_ACTIVE;
+		break;
+	case QMI_CALL_STATE_CC_IN_PROG:
+		*ret = CALL_STATUS_DIALING;
+		break;
+	case QMI_CALL_STATE_ALERTING:
+		*ret = CALL_STATUS_ALERTING;
+		break;
+	default:
+		err = 1;
+	}
+	return err;
+}
+
+uint8_t ofono_to_qmi_direction(enum call_direction ofono_direction) {
+	return ofono_direction + 1;
+}
+enum call_direction qmi_to_ofono_direction(uint8_t qmi_direction) {
+	return qmi_direction - 1;
+}
+
+int qmi_voice_dial_call(
+	struct qmi_voice_dial_call_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 (arg->calling_number_set) {
+		if (!qmi_param_append(param,
+			0x1,
+			strlen(arg->calling_number),
+							  arg->calling_number))
+			goto error;
+	}
+
+	if (arg->call_type_set)
+		qmi_param_append_uint8(param, 0x10, arg->call_type);
+
+	if (qmi_service_send(service,
+		0x20,
+		param,
+		func,
+		user_data,
+		destroy) > 0)
+		return 0;
+	error:
+	g_free(param);
+	return 1;
+}
+
+enum parse_error qmi_voice_dial_call_parse(
+	struct qmi_result *qmi_result,
+	struct qmi_voice_dial_call_result *result)
+{
+	int err = NONE;
+
+	/* mandatory */
+	if (qmi_result_get_uint8(qmi_result, 0x10, &result->call_id))
+		result->call_id_set = 1;
+	else
+		err = MISSING_MANDATORY;
+
+	return err;
+}
+
+int qmi_voice_end_call(
+	struct qmi_voice_end_call_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 (arg->call_id_set) {
+		if (!qmi_param_append_uint8(
+			param,
+			0x1,
+			arg->call_id))
+			goto error;
+	}
+
+	if (qmi_service_send(service,
+		0x21,
+		param,
+		func,
+		user_data,
+		destroy) > 0)
+		return 0;
+	error:
+	g_free(param);
+	return 1;
+}
+
+enum parse_error qmi_voice_end_call_parse(
+	struct qmi_result *qmi_result,
+	struct qmi_voice_end_call_result *result)
+{
+	int err = NONE;
+
+	/* optional */
+	if (qmi_result_get_uint8(qmi_result, 0x10, &result->call_id))
+		result->call_id_set = 1;
+
+	return err;
+}
+
+
+int qmi_voice_answer_call(
+	struct qmi_voice_answer_call_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 (arg->call_id_set) {
+		if (!qmi_param_append_uint8(
+			param,
+			0x1,
+			arg->call_id))
+			goto error;
+	}
+
+	if (qmi_service_send(service,
+		0x22,
+		param,
+		func,
+		user_data,
+		destroy) > 0)
+		return 0;
+	error:
+	g_free(param);
+	return 1;
+}
+
+
+enum parse_error qmi_voice_answer_call_parse(
+	struct qmi_result *qmi_result,
+	struct qmi_voice_answer_call_result *result)
+{
+	int err = NONE;
+
+	/* optional */
+	if (qmi_result_get_uint8(qmi_result, 0x10, &result->call_id))
+		result->call_id_set = 1;
+
+	return err;
+}
+
+enum parse_error qmi_voice_call_status(
+	struct qmi_result *qmi_result,
+	struct qmi_voice_all_call_status_ind *result)
+{
+	int err = NONE;
+	int offset;
+	uint16_t len;
+	bool ind = TRUE;
+	const struct qmi_voice_remote_party_number *remote_party_number;
+	const struct qmi_voice_call_information *call_information;
+
+	/* mandatory */
+	call_information = qmi_result_get(qmi_result, 0x01, &len);
+
+	/* This is so ugly! but TLV for indicator and response is different */
+	if (!call_information) {
+		call_information = qmi_result_get(qmi_result, 0x10, &len);
+		ind = FALSE;
+	}
+
+	if (call_information)
+	{
+		/* verify the length */
+		if (len < sizeof(call_information->size))
+			return INVALID_LENGTH;
+
+		if (len != call_information->size * sizeof(struct qmi_voice_call_information_instance)
+			+ sizeof(call_information->size))
+			return INVALID_LENGTH;
+		result->call_information_set = 1;
+		result->call_information = call_information;
+	} else
+		return MISSING_MANDATORY;
+
+	/* mandatory */
+	remote_party_number = qmi_result_get(qmi_result, ind ? 0x10 : 0x11, &len);
+
+	if (remote_party_number) {
+		const struct qmi_voice_remote_party_number_instance *instance;
+		int instance_size = sizeof(struct qmi_voice_remote_party_number_instance);
+		int i;
+
+		/* verify the length */
+		if (len < sizeof(remote_party_number->size))
+			return INVALID_LENGTH;
+
+		for (i = 0, offset = sizeof(remote_party_number->size);
+			 offset <= len && i < 16 && i < remote_party_number->size; i++)
+			 {
+				 if (offset == len) {
+					 break;
+				 } else if (offset + instance_size > len) {
+					 return INVALID_LENGTH;
+				 }
+
+				 instance = (void *)remote_party_number + offset;
+				 result->remote_party_number[i] = instance;
+				 offset += sizeof(struct qmi_voice_remote_party_number_instance) + instance->number_size;
+			 }
+			 result->remote_party_number_set = 1;
+			 result->remote_party_number_size = remote_party_number->size;
+	} else
+		return MISSING_MANDATORY;
+
+	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;
+}
diff --git a/drivers/qmimodem/voice.h b/drivers/qmimodem/voice.h
index 917e72f7134c077b2760608101a2d1148e67a4c2..5479939b2cb5d9e4e6726734e739e444e347bdbd 100644
--- a/drivers/qmimodem/voice.h
+++ b/drivers/qmimodem/voice.h
@@ -15,6 +15,15 @@
  *
  */
 
+#ifndef QMI_VOICE_H
+#define QMI_VOICE_H
+
+#include "src/common.h"
+
+#define QMI_VOICE_IND_ALL_STATUS 0x2e
+#define QMI_VOICE_GET_ALL_STATUS 0x2f
+
+
 #define QMI_VOICE_PARAM_USS_DATA 0x01
 
 #define QMI_VOICE_PARAM_ASYNC_USSD_ERROR 0x10
@@ -34,6 +43,8 @@
 /* default alphabet Language unspecific */
 #define USSD_DCS_UNSPECIFIC 0x0f
 
+#include <types.h>
+
 /* based on qmi ussd definition */
 enum qmi_ussd_dcs {
 	QMI_USSD_DCS_ASCII = 0x1,
@@ -48,22 +59,41 @@ enum qmi_ussd_user_required {
 
 /* QMI service voice. Using an enum to prevent doublicated entries */
 enum voice_commands {
-	QMI_VOICE_SUPS_NOTIFICATION_IND =	0x32,
-	QMI_VOICE_SET_SUPS_SERVICE =		0x33,
-	QMI_VOICE_GET_CALL_WAITING =		0x34,
-	QMI_VOICE_GET_CALL_BARRING =		0x35,
-	QMI_VOICE_GET_CLIP =			0x36,
-	QMI_VOICE_GET_CLIR =			0x37,
-	QMI_VOICE_GET_CALL_FWDING =		0x38,
-	QMI_VOICE_SET_CALL_BARRING_PWD =	0x39,
-	QMI_VOICE_CANCEL_USSD =			0x3c,
-	QMI_VOICE_USSD_RELEASE_IND =		0x3d,
-	QMI_VOICE_USSD_IND =			0x3e,
-	QMI_VOICE_SUPS_IND =			0x42,
-	QMI_VOICE_ASYNC_ORIG_USSD =		0x43,
-	QMI_VOICE_GET_COLP =			0x4b,
-	QMI_VOICE_GET_COLR =			0x4c,
-	QMI_VOICE_GET_CNAP =			0x4d
+QMI_VOICE_SUPS_NOTIFICATION_IND =       0x32,
+	QMI_VOICE_SET_SUPS_SERVICE =            0x33,
+	QMI_VOICE_GET_CALL_WAITING =            0x34,
+	QMI_VOICE_GET_CALL_BARRING =            0x35,
+	QMI_VOICE_GET_CLIP =                    0x36,
+	QMI_VOICE_GET_CLIR =                    0x37,
+	QMI_VOICE_GET_CALL_FWDING =             0x38,
+	QMI_VOICE_SET_CALL_BARRING_PWD =        0x39,
+	QMI_VOICE_CANCEL_USSD =                 0x3c,
+	QMI_VOICE_USSD_RELEASE_IND =            0x3d,
+	QMI_VOICE_USSD_IND =                    0x3e,
+	QMI_VOICE_SUPS_IND =                    0x42,
+	QMI_VOICE_ASYNC_ORIG_USSD =             0x43,
+	QMI_VOICE_GET_COLP =                    0x4b,
+	QMI_VOICE_GET_COLR =                    0x4c,
+	QMI_VOICE_GET_CNAP =                    0x4d
+};
+
+enum qmi_voice_call_state {
+	QMI_CALL_STATE_IDLE = 0x0,
+	QMI_CALL_STATE_ORIG,
+	QMI_CALL_STATE_INCOMING,
+	QMI_CALL_STATE_CONV,
+	QMI_CALL_STATE_CC_IN_PROG,
+	QMI_CALL_STATE_ALERTING,
+	QMI_CALL_STATE_HOLD,
+	QMI_CALL_STATE_WAITING,
+	QMI_CALL_STATE_DISCONNECTING,
+	QMI_CALL_STATE_END,
+	QMI_CALL_STATE_SETUP
+};
+
+enum qmi_voice_call_type {
+	QMI_CALL_TYPE_VOICE = 0x0,
+	QMI_CALL_TYPE_VOICE_FORCE,
 };
 
 struct qmi_ussd_data {
@@ -73,28 +103,166 @@ struct qmi_ussd_data {
 } __attribute__((__packed__));
 
 enum qmi_ss_action {
-	QMI_VOICE_SS_ACTION_ACTIVATE =		0x01,
-	QMI_VOICE_SS_ACTION_DEACTIVATE =	0x02,
-	QMI_VOICE_SS_ACTION_REGISTER =		0x03,
-	QMI_VOICE_SS_ACTION_ERASE =		0x04
+	QMI_VOICE_SS_ACTION_ACTIVATE =          0x01,
+	QMI_VOICE_SS_ACTION_DEACTIVATE =        0x02,
+	QMI_VOICE_SS_ACTION_REGISTER =          0x03,
+	QMI_VOICE_SS_ACTION_ERASE =             0x04
 };
 
 enum qmi_ss_reason {
-	QMI_VOICE_SS_RSN_FWD_UNCONDITIONAL =	0x01,
-	QMI_VOICE_SS_RSN_FWD_MOBILE_BUSY =	0x02,
-	QMI_VOICE_SS_RSN_FWD_NO_REPLY  =	0x03,
-	QMI_VOICE_SS_RSN_FWD_UNREACHABLE =	0x04,
-	QMI_VOICE_SS_RSN_FWD_ALL =		0x05,
-	QMI_VOICE_SS_RSN_FWD_ALL_CONDITIONAL =	0x06,
-	QMI_VOICE_SS_RSN_ALL_OUTGOING =		0x07,
-	QMI_VOICE_SS_RSN_OUT_INT =		0x08,
-	QMI_VOICE_SS_RSN_OUT_INT_EXT_TO_HOME =	0x09,
-	QMI_VOICE_SS_RSN_ALL_IN =		0x0A,
-	QMI_VOICE_SS_RSN_IN_ROAMING =		0x0B,
-	QMI_VOICE_SS_RSN_BAR_ALL =		0x0C,
-	QMI_VOICE_SS_RSN_BAR_ALL_OUTGOING =	0x0D,
-	QMI_VOICE_SS_RSN_BAR_ALL_IN =		0x0E,
-	QMI_VOICE_SS_RSN_CALL_WAITING =		0x0F,
-	QMI_VOICE_SS_RSN_CLIP =			0x10,
-	QMI_VOICE_SS_RSN_CLIR =			0x11
+	QMI_VOICE_SS_RSN_FWD_UNCONDITIONAL =    0x01,
+	QMI_VOICE_SS_RSN_FWD_MOBILE_BUSY =      0x02,
+	QMI_VOICE_SS_RSN_FWD_NO_REPLY  =        0x03,
+	QMI_VOICE_SS_RSN_FWD_UNREACHABLE =      0x04,
+	QMI_VOICE_SS_RSN_FWD_ALL =              0x05,
+	QMI_VOICE_SS_RSN_FWD_ALL_CONDITIONAL =  0x06,
+	QMI_VOICE_SS_RSN_ALL_OUTGOING =         0x07,
+	QMI_VOICE_SS_RSN_OUT_INT =              0x08,
+	QMI_VOICE_SS_RSN_OUT_INT_EXT_TO_HOME =  0x09,
+	QMI_VOICE_SS_RSN_ALL_IN =               0x0A,
+	QMI_VOICE_SS_RSN_IN_ROAMING =           0x0B,
+	QMI_VOICE_SS_RSN_BAR_ALL =              0x0C,
+	QMI_VOICE_SS_RSN_BAR_ALL_OUTGOING =     0x0D,
+	QMI_VOICE_SS_RSN_BAR_ALL_IN =           0x0E,
+	QMI_VOICE_SS_RSN_CALL_WAITING =         0x0F,
+	QMI_VOICE_SS_RSN_CLIP =                 0x10,
+	QMI_VOICE_SS_RSN_CLIR =                 0x11
+};
+
+const char *qmi_voice_call_state_name(enum qmi_voice_call_state value);
+uint8_t ofono_to_qmi_direction(enum call_direction ofono_direction);
+enum call_direction qmi_to_ofono_direction(uint8_t qmi_direction);
+int qmi_to_ofono_status(uint8_t status, int *ret);
+
+#include "qmi.h"
+
+struct qmi_voice_remote_party_number_instance {
+	uint8_t call_id;
+	uint8_t presentation_indicator;
+	uint8_t number_size;
+	char number[0];
+} __attribute__((__packed__));
+
+struct qmi_voice_remote_party_number {
+	uint8_t size;
+	struct qmi_voice_remote_party_number_instance instance[0];
+} __attribute__((__packed__));
+
+/* generator / parser */
+
+struct qmi_voice_dial_call_arg {
+	bool calling_number_set;
+	const char *calling_number;
+	bool call_type_set;
+	uint8_t call_type;
+};
+
+int qmi_voice_dial_call(
+	struct qmi_voice_dial_call_arg *arg,
+	struct qmi_service *service,
+	qmi_result_func_t func,
+	void *user_data,
+	qmi_destroy_func_t destroy);
+
+struct qmi_voice_dial_call_result {
+	bool call_id_set;
+	uint8_t call_id;
+};
+
+enum parse_error qmi_voice_dial_call_parse(
+	struct qmi_result *qmi_result,
+	struct qmi_voice_dial_call_result *result);
+
+struct qmi_voice_end_call_arg {
+	bool call_id_set;
+	uint8_t call_id;
 };
+
+int qmi_voice_end_call(
+	struct qmi_voice_end_call_arg *arg,
+	struct qmi_service *service,
+	qmi_result_func_t func,
+	void *user_data,
+	qmi_destroy_func_t destroy);
+
+struct qmi_voice_end_call_result {
+	bool call_id_set;
+	uint8_t call_id;
+};
+
+enum parse_error qmi_voice_end_call_parse(
+	struct qmi_result *qmi_result,
+	struct qmi_voice_end_call_result *result);
+
+struct qmi_voice_answer_call_arg {
+	bool call_id_set;
+	uint8_t call_id;
+};
+
+int qmi_voice_answer_call(
+	struct qmi_voice_answer_call_arg *arg,
+	struct qmi_service *service,
+	qmi_result_func_t func,
+	void *user_data,
+	qmi_destroy_func_t destroy);
+
+struct qmi_voice_answer_call_result {
+	bool call_id_set;
+	uint8_t call_id;
+};
+
+enum parse_error qmi_voice_answer_call_parse(
+	struct qmi_result *qmi_result,
+	struct qmi_voice_answer_call_result *result);
+
+struct qmi_voice_call_information_instance {
+	uint8_t id;
+	uint8_t state;
+	uint8_t type;
+	uint8_t direction;
+	uint8_t mode;
+	uint8_t multipart_indicator;
+	uint8_t als;
+} __attribute__((__packed__));
+
+struct qmi_voice_call_information {
+	uint8_t size;
+	struct qmi_voice_call_information_instance instance[0];
+} __attribute__((__packed__)) ;
+
+struct qmi_voice_all_call_status_ind {
+	bool call_information_set;
+	const struct qmi_voice_call_information *call_information;
+	bool remote_party_number_set;
+	uint8_t remote_party_number_size;
+	const struct qmi_voice_remote_party_number_instance *remote_party_number[16];
+};
+
+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
diff --git a/drivers/qmimodem/voicecall.c b/drivers/qmimodem/voicecall.c
index 059edbae9ac55774e452444dd6289e1875f5b7cf..6c893f4e5346eae9a72604f0cd66d7a9077df189 100644
--- a/drivers/qmimodem/voicecall.c
+++ b/drivers/qmimodem/voicecall.c
@@ -3,6 +3,7 @@
  *  oFono - Open Source Telephony
  *
  *  Copyright (C) 2011-2012  Intel Corporation. All rights reserved.
+ *  Copyright (C) 2017 Alexander Couzens <lynxis@fe80.eu>
  *
  *  This program is free software; you can redistribute it and/or modify
  *  it under the terms of the GNU General Public License version 2 as
@@ -23,20 +24,125 @@
 #include <config.h>
 #endif
 
+#include <string.h>
+
+#include <ofono.h>
 #include <ofono/log.h>
 #include <ofono/modem.h>
 #include <ofono/voicecall.h>
 
-#include "qmi.h"
+#include <drivers/common/call_list.h>
+#include "src/common.h"
 
+#include "qmi.h"
 #include "util.h"
+#include "voice.h"
+
+#ifndef ARRAY_SIZE
+#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
+#endif
+
+
+/* qmi protocol */
+
+
+/* end of qmi */
 
 struct voicecall_data {
 	struct qmi_service *voice;
 	uint16_t major;
 	uint16_t minor;
+	struct l_queue *call_list;
+	struct voicecall_static *vs;
+	struct ofono_phone_number dialed;
 };
 
+static void all_call_status_ind(struct qmi_result *result, void *user_data)
+{
+	struct ofono_voicecall *vc = user_data;
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+	struct l_queue *calls = NULL;
+	int i;
+	int size = 0;
+	struct qmi_voice_all_call_status_ind status_ind;
+
+	calls = l_queue_new();
+
+	DBG("");
+	if (qmi_voice_call_status(result, &status_ind) != NONE) {
+		DBG("Parsing of all call status indication failed");
+		return;
+	}
+
+	if (!status_ind.remote_party_number_set || !status_ind.call_information_set) {
+		DBG("Some required fields are not set");
+		return;
+	}
+
+	size = status_ind.call_information->size;
+	if (!size) {
+		DBG("No call informations received!");
+		return;
+	}
+
+	/* expect we have valid fields for every call */
+	if (size != status_ind.remote_party_number_size)  {
+		DBG("Not all fields have the same size");
+		return;
+	}
+
+	for (i = 0; i < size; i++) {
+		struct qmi_voice_call_information_instance call_info;
+		struct ofono_call *call;
+		const struct qmi_voice_remote_party_number_instance *remote_party = status_ind.remote_party_number[i];
+		int number_size;
+
+		call_info = status_ind.call_information->instance[i];
+		call = l_new(struct ofono_call, 1);
+		call->id = call_info.id;
+		call->direction = qmi_to_ofono_direction(call_info.direction);
+
+		if (qmi_to_ofono_status(call_info.state, &call->status)) {
+			DBG("Ignore call id %d, because can not convert QMI state 0x%x to ofono.",
+			    call_info.id, call_info.state);
+			continue;
+		}
+		DBG("Call %d in state %s(%d)",
+		    call_info.id,
+		    qmi_voice_call_state_name(call_info.state),
+		    call_info.state);
+
+		call->type = 0; /* always voice */
+		number_size = remote_party->number_size;
+		if (number_size > OFONO_MAX_PHONE_NUMBER_LENGTH)
+			number_size = OFONO_MAX_PHONE_NUMBER_LENGTH;
+		strncpy(call->phone_number.number, remote_party->number,
+				number_size);
+		/* FIXME: set phone_number_type */
+
+		if (strlen(call->phone_number.number) > 0)
+			call->clip_validity = 0;
+		else
+			call->clip_validity = 2;
+
+		l_queue_push_tail(calls, call);
+		DBG("%d", l_queue_length(calls));
+	}
+
+	ofono_call_list_notify(vc, &vd->call_list, calls);
+}
+
+static void event_update(struct qmi_result *result, void *user_data)
+{
+	struct ofono_voicecall *vc = user_data;
+	struct voicecall_data *data = ofono_voicecall_get_data(vc);
+
+	DBG("");
+
+	qmi_service_send(data->voice, QMI_VOICE_GET_ALL_STATUS, NULL,
+				all_call_status_ind, vc, NULL);
+}
+
 static void create_voice_cb(struct qmi_service *service, void *user_data)
 {
 	struct ofono_voicecall *vc = user_data;
@@ -58,6 +164,15 @@ static void create_voice_cb(struct qmi_service *service, void *user_data)
 
 	data->voice = qmi_service_ref(service);
 
+	/* FIXME: we should call indication_register to ensure we get notified on call events.
+	 * We rely at the moment on the default value of notifications
+	 */
+	qmi_service_register(data->voice, QMI_VOICE_IND_ALL_STATUS,
+			     all_call_status_ind, vc, NULL);
+
+	qmi_service_register(data->voice, QMI_SERVICE_UPDATE,
+					event_update, vc, NULL);
+
 	ofono_voicecall_register(vc);
 }
 
@@ -69,7 +184,8 @@ static int qmi_voicecall_probe(struct ofono_voicecall *vc,
 
 	DBG("");
 
-	data = g_new0(struct voicecall_data, 1);
+	data = l_new(struct voicecall_data, 1);
+	data->call_list = l_queue_new();
 
 	ofono_voicecall_set_data(vc, data);
 
@@ -77,7 +193,6 @@ static int qmi_voicecall_probe(struct ofono_voicecall *vc,
 					create_voice_cb, vc, NULL);
 
 	return 0;
-
 }
 
 static void qmi_voicecall_remove(struct ofono_voicecall *vc)
@@ -92,13 +207,323 @@ static void qmi_voicecall_remove(struct ofono_voicecall *vc)
 
 	qmi_service_unref(data->voice);
 
-	g_free(data);
+	l_queue_destroy(data->call_list, l_free);
+
+	l_free(data);
+}
+
+static void dial_cb(struct qmi_result *result, void *user_data)
+{
+	struct cb_data *cbd = user_data;
+	struct ofono_voicecall *vc = cbd->user;
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+	ofono_voicecall_cb_t cb = cbd->cb;
+	uint16_t error;
+	struct qmi_voice_dial_call_result dial_result;
+
+	if (qmi_result_set_error(result, &error)) {
+		DBG("QMI Error %d", error);
+		CALLBACK_WITH_FAILURE(cb, cbd->data);
+		return;
+	}
+
+	if (NONE != qmi_voice_dial_call_parse(result, &dial_result)) {
+		DBG("Received invalid Result");
+		CALLBACK_WITH_FAILURE(cb, cbd->data);
+		return;
+	}
+
+	if (!dial_result.call_id_set) {
+		DBG("Didn't receive a call id");
+		CALLBACK_WITH_FAILURE(cb, cbd->data);
+		return;
+	}
+
+	DBG("New call QMI id %d", dial_result.call_id);
+	ofono_call_list_dial_callback(vc,
+				      &vd->call_list,
+				      &vd->dialed,
+				      dial_result.call_id);
+
+
+	/* FIXME: create a timeout on this call_id */
+	CALLBACK_WITH_SUCCESS(cb, cbd->data);
+}
+
+static void dial(struct ofono_voicecall *vc, const struct ofono_phone_number *ph,
+		enum ofono_clir_option clir, ofono_voicecall_cb_t cb,
+		void *data)
+{
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+	struct cb_data *cbd = cb_data_new(cb, data);
+	struct qmi_voice_dial_call_arg arg;
+
+	cbd->user = vc;
+	arg.calling_number_set = true;
+	arg.calling_number = phone_number_to_string(ph);
+	memcpy(&vd->dialed, ph, sizeof(*ph));
+
+	arg.call_type_set = true;
+	arg.call_type = QMI_CALL_TYPE_VOICE;
+
+	if (!qmi_voice_dial_call(
+				&arg,
+				vd->voice,
+				dial_cb,
+				cbd,
+				l_free))
+		return;
+
+	CALLBACK_WITH_FAILURE(cb, data);
+	l_free(cbd);
+}
+
+static void answer_cb(struct qmi_result *result, void *user_data)
+{
+	struct cb_data *cbd = user_data;
+	ofono_voicecall_cb_t cb = cbd->cb;
+	uint16_t error;
+	struct qmi_voice_answer_call_result answer_result;
+
+	if (qmi_result_set_error(result, &error)) {
+		DBG("QMI Error %d", error);
+		CALLBACK_WITH_FAILURE(cb, cbd->data);
+		return;
+	}
+
+	/* TODO: what happens when calling it with no active call or wrong caller id? */
+	if (NONE != qmi_voice_answer_call_parse(result, &answer_result)) {
+		DBG("Received invalid Result");
+		CALLBACK_WITH_FAILURE(cb, cbd->data);
+		return;
+	}
+
+	CALLBACK_WITH_SUCCESS(cb, cbd->data);
+}
+
+static void answer(struct ofono_voicecall *vc, ofono_voicecall_cb_t cb, void *data)
+{
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+	struct cb_data *cbd = cb_data_new(cb, data);
+	struct qmi_voice_answer_call_arg arg;
+	struct ofono_call *call;
+
+	cbd->user = vc;
+
+	call = l_queue_find(vd->call_list,
+					ofono_call_compare_by_status,
+					L_UINT_TO_PTR(CALL_STATUS_INCOMING));
+
+	if (call == NULL) {
+		DBG("Can not find a call to answer");
+		goto err;
+	}
+
+	arg.call_id_set = true;
+	arg.call_id = call->id;
+
+	if (!qmi_voice_answer_call(
+				&arg,
+				vd->voice,
+				answer_cb,
+				cbd,
+				l_free))
+		return;
+err:
+	CALLBACK_WITH_FAILURE(cb, data);
+	l_free(cbd);
+}
+
+static void end_cb(struct qmi_result *result, void *user_data)
+{
+	struct cb_data *cbd = user_data;
+	ofono_voicecall_cb_t cb = cbd->cb;
+	uint16_t error;
+	struct qmi_voice_end_call_result end_result;
+
+	if (qmi_result_set_error(result, &error)) {
+		DBG("QMI Error %d", error);
+		CALLBACK_WITH_FAILURE(cb, cbd->data);
+		return;
+	}
+
+	if (NONE != qmi_voice_end_call_parse(result, &end_result)) {
+		DBG("Received invalid Result");
+		CALLBACK_WITH_FAILURE(cb, cbd->data);
+		return;
+	}
+
+	CALLBACK_WITH_SUCCESS(cb, cbd->data);
+}
+
+static void release_specific(struct ofono_voicecall *vc, int id,
+		ofono_voicecall_cb_t cb, void *data)
+{
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+	struct cb_data *cbd = cb_data_new(cb, data);
+	struct qmi_voice_end_call_arg arg;
+
+	DBG("");
+	cbd->user = vc;
+
+	arg.call_id_set = true;
+	arg.call_id = id;
+
+	if (!qmi_voice_end_call(&arg,
+				vd->voice,
+				end_cb,
+				cbd,
+				l_free))
+		return;
+
+	CALLBACK_WITH_FAILURE(cb, data);
+	l_free(cbd);
+}
+
+static void hangup_active(struct ofono_voicecall *vc,
+		ofono_voicecall_cb_t cb, void *data)
+{
+	struct voicecall_data *vd = ofono_voicecall_get_data(vc);
+	struct ofono_call *call;
+	enum call_status active[] = {
+		CALL_STATUS_ACTIVE,
+		CALL_STATUS_DIALING,
+		CALL_STATUS_ALERTING,
+		CALL_STATUS_INCOMING,
+	};
+	int i;
+
+	DBG("");
+	for (i = 0; i < ARRAY_SIZE(active); i++) {
+		call = l_queue_find(vd->call_list,
+					   ofono_call_compare_by_status,
+					   L_INT_TO_PTR(active[i]));
+
+		if (call)
+			break;
+	}
+
+	if (call == NULL) {
+		DBG("Can not find a call to hang up");
+		CALLBACK_WITH_FAILURE(cb, data);
+		return;
+	}
+
+	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,
+								  l_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);
+	l_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);
+		}
+		l_free((void *)send_one_dtmf_cb_data->full_dtmf);
+		l_free(send_one_dtmf_cb_data);
+		l_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 = l_new(struct send_one_dtmf_cb_data, 1);
+
+	send_one_dtmf_cb_data->full_dtmf = l_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 = {
 	.probe		= qmi_voicecall_probe,
 	.remove		= qmi_voicecall_remove,
+	.dial		= dial,
+	.answer		= answer,
+	.hangup_active  = hangup_active,
+	.release_specific  = release_specific,
+	.send_tones = send_dtmf,
 };
 
 OFONO_ATOM_DRIVER_BUILTIN(voicecall, qmimodem, &driver)
-
diff --git a/src/common.h b/src/common.h
index cede20d1de329a26dc7de7946de1f5b2a0f6810c..c226d468b8e2830cae09890fdfaae9d9d6523d97 100644
--- a/src/common.h
+++ b/src/common.h
@@ -19,6 +19,9 @@
  *
  */
 
+#ifndef OFONO_COMMON_H
+#define OFONO_COMMON_H
+
 #include <stdbool.h>
 #include <glib.h>
 
@@ -193,3 +196,5 @@ bool gprs_proto_from_string(const char *str, enum ofono_gprs_proto *proto);
 const char *gprs_auth_method_to_string(enum ofono_gprs_auth_method auth);
 bool gprs_auth_method_from_string(const char *str,
 					enum ofono_gprs_auth_method *auth);
+
+#endif
