diff mbox series

[RFC] qmimodem voicecall support

Message ID 5612854.ZASKD2KPVS@adam-laptop-hp (mailing list archive)
State RFC
Headers show
Series [RFC] qmimodem voicecall support | expand

Commit Message

Adam Pigg March 18, 2024, 10:51 p.m. UTC
Hi

As previously mentioned, ive been working on updating Ofono in Sailfish.  There 
are several patches which probably are of no interest, but this one may be.  
It implements voicecall support for the qmimodem driver, and ive tested it on 
a PinephonePro, and can make/receive/hangup calls.

I cant take credit for the original implementation, it was taken from other 
ofono forks out there, such as https://github.com/maemo-leste-upstream-forks/
ofono/commit/9c8ec63c1bb41a03cd87d2733dd83ecd148d0105

The changes I have made include:
*moving voice_generated.* into voice.*
  This is because it isnt actually generated and reduces the files in the tree
*removed glib and replaced with ell
*tweaked the notify code in call_list.c
  Changed the disconnect type from UNKNOWN because in src/voicecall.c, if the 
reason is UNKNOWN then it doesnt then perform the dbus notify
  Made it call ofono_voicecall_disconnected when the state was DISCONNECTED 
(QMI state disconnecting and end)

The common/call_list.* files can potentially be used by other drivers.

Apologies for attaching the patch, git send-email is not my normal work flow! 
:)

Adam

Comments

Denis Kenzior March 20, 2024, 2:49 a.m. UTC | #1
Hi Adam,

On 3/18/24 17:51, Adam Pigg wrote:
> Hi
> 
> As previously mentioned, ive been working on updating Ofono in Sailfish.  There
> are several patches which probably are of no interest, but this one may be.
> It implements voicecall support for the qmimodem driver, and ive tested it on
> a PinephonePro, and can make/receive/hangup calls.
> 
> I cant take credit for the original implementation, it was taken from other
> ofono forks out there, such as https://github.com/maemo-leste-upstream-forks/
> ofono/commit/9c8ec63c1bb41a03cd87d2733dd83ecd148d0105
> 
> The changes I have made include:
> *moving voice_generated.* into voice.*
>    This is because it isnt actually generated and reduces the files in the tree
> *removed glib and replaced with ell
> *tweaked the notify code in call_list.c
>    Changed the disconnect type from UNKNOWN because in src/voicecall.c, if the
> reason is UNKNOWN then it doesnt then perform the dbus notify
>    Made it call ofono_voicecall_disconnected when the state was DISCONNECTED
> (QMI state disconnecting and end)
> 

That's an awesome start.  Some preliminary comments:

- Since you've made so many changes already, even based on work by other 
authors, it would be okay to add your copyright to the added / modified files.

- Coding style and formatting.  There's quite a bit of formatting problems in 
the file.  See oFono's doc/coding-style.txt for our coding style guidelines. 
Also, it may be a good idea to run this patch and future submissions through 
Linux Kernel's checkpatch.pl script to avoid going through multiple rounds of 
reviews due to style issues.  Some minor nitpicks I can fix in place (assuming 
the author doesn't mind) when applying the patch, but there's too many in this 
RFC at the moment.

- Also watch out for mixed tabs and space indentation, > 80 char lines, 
redundant double empty lines, etc.

- Redundant changes.  Many changes seem to be spurious / redundant, particularly 
in voice.h file.  This makes the diff bigger for no apparent reason.  Can this 
be fixed?

- Much of voice.c seems to be not useful.  The only user of these functions 
would be in the voicecall driver itself.  I'd rather not introduce intermediate 
APIs for no reason.  Could you inline the operations directly in the driver 
method implementation?  For example, static void dial() invokes 
qmi_voice_dial_call().  Just inline the qmi_voice_dial_call() implementation 
inside dial().

- I know this is an existing driver, but any chance you could split this up a 
bit into multiple patches?  Think of it as telling a story / writing a novel. 
Introduce new data structures / operations as you use them.  One strategy might 
be to break this up like this:
	- Dial implementation
	- Event processing / all_call_status_ind()
	- Answer implementation
	- Release specific implementation
	- hangup active implementation
	- DTMF implementation

> The common/call_list.* files can potentially be used by other drivers.
> 

Can you put call_list.[ch] into the voicecall.c for now?  I understand the 
reason for why this was invented / why this exists, but ultimately there's a 
much better approach that we can utilize.  We'll worry about this a bit later 
once the initial set is upstream.

> Apologies for attaching the patch, git send-email is not my normal work flow!
> :)

Please use git send-email for submissions.  Patchwork does not work well with 
attachments.  oFono CI (which is still a bit WIP) doesn't like attachments 
either.  It is also much easier to comment on patches sent via git send-email 
since the patch contents can be easily replied to in-line.

As a side benefit, the CI runs checkpatch, checks compilation on multiple 
platforms, etc.

Regards,
-Denis
diff mbox series

Patch

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