Merge remote-tracking branch 'cros/upstream' into 'cros/master'
diff --git a/.gitignore b/.gitignore
index 2ce4de0..08366fd 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1,18 +1,3 @@
-INSTALL
-Makefile
-Makefile.in
-aclocal.m4
-autom4te.cache*
-compile
-config.*
-configure
-depcomp
-install-sh
-ltmain.sh
-intltool-*
-missing
-stamp-h1
-gtk-doc.make
 *~
 *.o
 *.lo
@@ -20,153 +5,165 @@
 *.loT
 *.gcno
 *.gcda
-libtool
-.deps
-.libs
-*-glue.h
-*.tar.bz2
+*.tar.xz
 *.pyc
 *.dirstamp
-org.freedesktop.ModemManager.service
-org.freedesktop.ModemManager.conf
-callouts/mm-modem-probe
-test/lsudev
+.deps
+.libs
+Makefile
+Makefile.in
 
-include/ModemManager-version.h
+/INSTALL
+/aclocal.m4
+/autom4te.cache*
+/compile
+/config.*
+/configure
+/depcomp
+/install-sh
+/ltmain.sh
+/intltool-*
+/missing
+/stamp-h1
+/gtk-doc.make
+/libtool
+/TAGS
+/ABOUT-NLS
 
-libmm-glib/generated/mm-gdbus-*.[ch]
-libmm-glib/generated/mm-enums-types.[ch]
-libmm-glib/generated/mm-errors-types.[ch]
-libmm-glib/generated/mm-errors-quarks.c
-libmm-glib/generated/*.xml
-libmm-glib/generated/tests/mm-gdbus-*.[ch]
-libmm-glib/tests/test-common-helpers
-libmm-glib/*.gir
-libmm-glib/*.typelib
+/include/ModemManager-version.h
+/include/ModemManager-names.h
 
-src/ModemManager
-src/mm-daemon-enums-types.c
-src/mm-daemon-enums-types.h
-src/mm-port-enums-types.c
-src/mm-port-enums-types.h
-src/mm-marshal.[ch]
-src/tests/test-modem-helpers
-src/tests/test-modem-helpers-qmi
-src/tests/test-charsets
-src/tests/test-qcdm-serial-port
-src/tests/test-at-serial-port
-src/tests/test-sms-part-3gpp
-src/tests/test-sms-part-cdma
+/libmm-glib/generated/mm-gdbus-*.[ch]
+/libmm-glib/generated/mm-enums-types.[ch]
+/libmm-glib/generated/mm-errors-types.[ch]
+/libmm-glib/generated/mm-errors-quarks.c
+/libmm-glib/generated/*.xml
+/libmm-glib/generated/tests/mm-gdbus-*.[ch]
+/libmm-glib/tests/test-common-helpers
+/libmm-glib/*.gir
+/libmm-glib/*.typelib
 
-cli/mmcli
+/src/ModemManager
+/src/mm-daemon-enums-types.c
+/src/mm-daemon-enums-types.h
+/src/mm-port-enums-types.c
+/src/mm-port-enums-types.h
+/src/mm-marshal.[ch]
+/src/tests/test-modem-helpers
+/src/tests/test-modem-helpers-qmi
+/src/tests/test-charsets
+/src/tests/test-qcdm-serial-port
+/src/tests/test-at-serial-port
+/src/tests/test-sms-part-3gpp
+/src/tests/test-sms-part-cdma
 
-vapi/libmm-glib.vapi
+/cli/mmcli
 
-libqcdm/tests/test-qcdm
-libqcdm/tests/modepref
-libqcdm/tests/ipv6pref
-libqcdm/tests/reset
+/vapi/libmm-glib.vapi
 
-libwmc/tests/test-wmc
+/libqcdm/tests/test-qcdm
+/libqcdm/tests/modepref
+/libqcdm/tests/ipv6pref
+/libqcdm/tests/reset
 
-data/org.freedesktop.ModemManager1.conf
-data/org.freedesktop.ModemManager1.service
-data/org.freedesktop.ModemManager1.policy
-data/org.freedesktop.ModemManager1.policy.in
-data/ModemManager.service
-data/ModemManager.pc
-data/mm-common.pc
-data/mm-glib.pc
-data/tests/org.freedesktop.ModemManager1.service
+/libwmc/tests/test-wmc
 
-include/ModemManager-names.h
+/data/org.freedesktop.ModemManager1.conf
+/data/org.freedesktop.ModemManager1.service
+/data/org.freedesktop.ModemManager1.policy
+/data/org.freedesktop.ModemManager1.policy.in
+/data/ModemManager.service
+/data/ModemManager.pc
+/data/mm-common.pc
+/data/mm-glib.pc
+/data/tests/org.freedesktop.ModemManager1.service
 
-po/Makefile.in.in
-po/POTFILES
-po/stamp-it
-po/.intltool-merge-cache
-po/ModemManager.pot
-po/Makevars.template
-po/Rules-quot
-po/boldquot.sed
-po/en@boldquot.header
-po/en@quot.header
-po/insert-header.sin
-po/quot.sed
-po/remove-potcdate.sin
-po/*.gmo
+/po/Makefile.in.in
+/po/POTFILES
+/po/stamp-it
+/po/.intltool-merge-cache
+/po/ModemManager.pot
+/po/Makevars.template
+/po/Rules-quot
+/po/boldquot.sed
+/po/en@boldquot.header
+/po/en@quot.header
+/po/insert-header.sin
+/po/quot.sed
+/po/remove-potcdate.sin
+/po/*.gmo
 
-docs/reference/api/version.xml
-docs/reference/api/ModemManager.args
-docs/reference/api/ModemManager.hierarchy
-docs/reference/api/ModemManager.interfaces
-docs/reference/api/ModemManager.prerequisites
-docs/reference/api/ModemManager.signals
-docs/reference/api/*.stamp
-docs/reference/api/*.txt
-docs/reference/api/html
-docs/reference/api/tmpl
-docs/reference/api/xml
-docs/reference/api/*.png
+/docs/reference/api/version.xml
+/docs/reference/api/ModemManager.args
+/docs/reference/api/ModemManager.hierarchy
+/docs/reference/api/ModemManager.interfaces
+/docs/reference/api/ModemManager.prerequisites
+/docs/reference/api/ModemManager.signals
+/docs/reference/api/*.stamp
+/docs/reference/api/*.txt
+/docs/reference/api/html
+/docs/reference/api/tmpl
+/docs/reference/api/xml
+/docs/reference/api/*.png
 
-docs/reference/libmm-glib/version.xml
-docs/reference/libmm-glib/libmm-glib.args
-docs/reference/libmm-glib/libmm-glib.hierarchy
-docs/reference/libmm-glib/libmm-glib.interfaces
-docs/reference/libmm-glib/libmm-glib.prerequisites
-docs/reference/libmm-glib/libmm-glib.signals
-docs/reference/libmm-glib/*.stamp
-docs/reference/libmm-glib/*.txt
-docs/reference/libmm-glib/html
-docs/reference/libmm-glib/tmpl
-docs/reference/libmm-glib/xml
+/docs/reference/libmm-glib/version.xml
+/docs/reference/libmm-glib/libmm-glib.args
+/docs/reference/libmm-glib/libmm-glib.hierarchy
+/docs/reference/libmm-glib/libmm-glib.interfaces
+/docs/reference/libmm-glib/libmm-glib.prerequisites
+/docs/reference/libmm-glib/libmm-glib.signals
+/docs/reference/libmm-glib/*.stamp
+/docs/reference/libmm-glib/*.txt
+/docs/reference/libmm-glib/html
+/docs/reference/libmm-glib/tmpl
+/docs/reference/libmm-glib/xml
 
-m4/gtk-doc.m4
-m4/intltool.m4
-m4/libtool.m4
-m4/lt*.m4
-m4/codeset.m4
-m4/gettext.m4
-m4/glibc2.m4
-m4/glibc21.m4
-m4/iconv.m4
-m4/intdiv0.m4
-m4/intl.m4
-m4/intldir.m4
-m4/intlmacosx.m4
-m4/intmax.m4
-m4/inttypes-pri.m4
-m4/inttypes_h.m4
-m4/lcmessage.m4
-m4/lib-ld.m4
-m4/lib-link.m4
-m4/lib-prefix.m4
-m4/lock.m4
-m4/longlong.m4
-m4/nls.m4
-m4/po.m4
-m4/printf-posix.m4
-m4/progtest.m4
-m4/size_max.m4
-m4/stdint_h.m4
-m4/uintmax_t.m4
-m4/visibility.m4
-m4/wchar_t.m4
-m4/wint_t.m4
-m4/xsize.m4
+/m4/gtk-doc.m4
+/m4/intltool.m4
+/m4/libtool.m4
+/m4/lt*.m4
+/m4/codeset.m4
+/m4/gettext.m4
+/m4/glibc2.m4
+/m4/glibc21.m4
+/m4/iconv.m4
+/m4/intdiv0.m4
+/m4/intl.m4
+/m4/intldir.m4
+/m4/intlmacosx.m4
+/m4/intmax.m4
+/m4/inttypes-pri.m4
+/m4/inttypes_h.m4
+/m4/lcmessage.m4
+/m4/lib-ld.m4
+/m4/lib-link.m4
+/m4/lib-prefix.m4
+/m4/lock.m4
+/m4/longlong.m4
+/m4/nls.m4
+/m4/po.m4
+/m4/printf-posix.m4
+/m4/progtest.m4
+/m4/size_max.m4
+/m4/stdint_h.m4
+/m4/uintmax_t.m4
+/m4/visibility.m4
+/m4/wchar_t.m4
+/m4/wint_t.m4
+/m4/xsize.m4
 
-uml290/uml290mode
+/uml290/uml290mode
 
-plugins/test-suite.log
-plugins/test-modem-helpers-huawei*
-plugins/test-modem-helpers-altair*
-plugins/test-modem-helpers-cinterion*
-plugins/test-modem-helpers-icera*
-plugins/test-modem-helpers-mbm*
-plugins/test-service-*
+/plugins/test-suite.log
+/plugins/test-modem-helpers-huawei*
+/plugins/test-modem-helpers-altair*
+/plugins/test-modem-helpers-cinterion*
+/plugins/test-modem-helpers-icera*
+/plugins/test-modem-helpers-mbm*
+/plugins/test-service-*
 
-TAGS
-ABOUT-NLS
+/test/lsudev
+/test/mmtty
 
-ModemManager-*-coverage.info
-ModemManager-*-coverage/
+/ModemManager-*-coverage.info
+/ModemManager-*-coverage/
diff --git a/configure.ac b/configure.ac
index 22417cc..816d5c0 100644
--- a/configure.ac
+++ b/configure.ac
@@ -1,6 +1,9 @@
 AC_PREREQ([2.63])
 
-dnl The MM version number
+dnl-----------------------------------------------------------------------------
+dnl Package and library versioning support
+dnl
+
 m4_define([mm_major_version], [1])
 m4_define([mm_minor_version], [5])
 m4_define([mm_micro_version], [0])
@@ -19,12 +22,17 @@
 m4_define([mm_glib_lt_revision], [0])
 m4_define([mm_glib_lt_age],      [2])
 
-
+dnl-----------------------------------------------------------------------------
+dnl autoconf, automake, libtool initialization
+dnl
 AC_INIT([ModemManager],[mm_version],[modemmanager-devel@lists.freedesktop.org],[ModemManager])
-AM_INIT_AUTOMAKE([1.9 subdir-objects tar-ustar no-dist-gzip dist-xz -Wno-portability])
+AM_INIT_AUTOMAKE([1.11 subdir-objects tar-ustar no-dist-gzip dist-xz -Wno-portability])
 m4_ifdef([AM_SILENT_RULES], [AM_SILENT_RULES([yes])])
+
 AM_MAINTAINER_MODE([enable])
 
+AM_SILENT_RULES([yes])
+
 AC_CONFIG_MACRO_DIR([m4])
 
 AC_CONFIG_HEADERS(config.h)
@@ -39,7 +47,11 @@
 
 dnl Initialize libtool
 LT_PREREQ([2.2])
-LT_INIT
+LT_INIT([disable-static])
+
+dnl-----------------------------------------------------------------------------
+dnl Version definitions
+dnl
 
 dnl Version stuff
 MM_MAJOR_VERSION=mm_major_version
@@ -59,14 +71,16 @@
 AC_SUBST(MM_GLIB_LT_REVISION)
 AC_SUBST(MM_GLIB_LT_AGE)
 
-dnl
+dnl-----------------------------------------------------------------------------
 dnl Documentation
 dnl
+
 GTK_DOC_CHECK(1.0)
 
+dnl-----------------------------------------------------------------------------
+dnl i18n
 dnl
-dnl translation support
-dnl
+
 IT_PROG_INTLTOOL([0.40.0])
 
 AM_GNU_GETTEXT([external])
@@ -76,6 +90,10 @@
 AC_SUBST(GETTEXT_PACKAGE)
 AC_DEFINE_UNQUOTED(GETTEXT_PACKAGE,"$GETTEXT_PACKAGE", [Gettext package])
 
+dnl-----------------------------------------------------------------------------
+dnl Build dependencies
+dnl
+
 PKG_CHECK_MODULES(MM,
                   glib-2.0 >= 2.32
                   gmodule-2.0
@@ -104,27 +122,38 @@
 AC_SUBST(GUDEV_CFLAGS)
 AC_SUBST(GUDEV_LIBS)
 
-# Some required utilities
+dnl Some required utilities
 GLIB_MKENUMS=`$PKG_CONFIG --variable=glib_mkenums glib-2.0`
 AC_SUBST(GLIB_MKENUMS)
+
 GDBUS_CODEGEN=`$PKG_CONFIG --variable=gdbus_codegen gio-2.0`
 AC_SUBST(GDBUS_CODEGEN)
 
-# Code coverage (disabled by default)
+dnl-----------------------------------------------------------------------------
+dnl Testing support
+dnl
+
+dnl Code coverage (disabled by default)
 AX_CODE_COVERAGE
 
-# GObject Introspection
+dnl-----------------------------------------------------------------------------
+dnl Introspection and bindings
+dnl
+
+dnl GObject Introspection
 GOBJECT_INTROSPECTION_CHECK([0.9.6])
 
-# Vala bindings
+dnl Vala bindings
 VAPIGEN_CHECK(0.18)
-
-# Sanity check
 if test "x$enable_vala" = "xyes" -a ! -f "$VAPIGEN_MAKEFILE"; then
   AC_MSG_ERROR([Vala bindings enabled but Makefile.vapigen not found. Install vala-devel, or pass --disable-vala])
 fi
 
-# DBus system directory
+dnl-----------------------------------------------------------------------------
+dnl System paths
+dnl
+
+dnl DBus system directory
 AC_ARG_WITH(dbus-sys-dir, AS_HELP_STRING([--with-dbus-sys-dir=DIR], [where D-BUS system.d directory is]))
 if test -n "$with_dbus_sys_dir" ; then
     DBUS_SYS_DIR="$with_dbus_sys_dir"
@@ -133,7 +162,7 @@
 fi
 AC_SUBST(DBUS_SYS_DIR)
 
-# udev base directory
+dnl udev base directory
 AC_ARG_WITH(udev-base-dir, AS_HELP_STRING([--with-udev-base-dir=DIR], [where udev base directory is]))
 if test -n "$with_udev_base_dir" ; then
     UDEV_BASE_DIR="$with_udev_base_dir"
@@ -142,7 +171,7 @@
 fi
 AC_SUBST(UDEV_BASE_DIR)
 
-# systemd system unit directory
+dnl systemd system unit directory
 AC_ARG_WITH([systemdsystemunitdir], AS_HELP_STRING([--with-systemdsystemunitdir=DIR], [where systemd service files are]),
             [], [with_systemdsystemunitdir=$($PKG_CONFIG --variable=systemdsystemunitdir systemd)])
 if test "x$with_systemdsystemunitdir" != xno; then
@@ -150,14 +179,46 @@
 fi
 AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$SYSTEMD_UNIT_DIR" -a "$SYSTEMD_UNIT_DIR" != xno ])
 
-# PolicyKit
+dnl-----------------------------------------------------------------------------
+dnl Suspend/resume support
+dnl
+
+AC_ARG_WITH(suspend-resume, AS_HELP_STRING([--with-suspend-resume=no|upower|systemd], [Build ModemManager with specific suspend/resume support]))
+
+if test "x$with_suspend_resume" = "x"; then
+   with_suspend_resume="none"
+fi
+
+case $with_suspend_resume in
+    none)
+        AC_DEFINE(WITH_SUSPEND_RESUME, 0, [Define if you have suspend-resume support])
+        ;;
+    upower)
+        AC_DEFINE(WITH_SUSPEND_RESUME, 1, [Define if you have suspend-resume support])
+        ;;
+    systemd)
+        PKG_CHECK_MODULES(SYSTEMD_INHIBIT, [libsystemd >= 209],,
+                          [PKG_CHECK_MODULES(SYSTEMD_INHIBIT, [libsystemd-login >= 183])])
+        AC_DEFINE(WITH_SUSPEND_RESUME, 1, [Define if you have suspend-resume support])
+        ;;
+    *)
+        AC_MSG_ERROR(--with-suspend-resume must be one of [none, upower, systemd])
+        ;;
+esac
+
+AM_CONDITIONAL(SUSPEND_RESUME_UPOWER,  test "x$with_suspend_resume" = "xupower")
+AM_CONDITIONAL(SUSPEND_RESUME_SYSTEMD, test "x$with_suspend_resume" = "xsystemd")
+
+dnl-----------------------------------------------------------------------------
+dnl PolicyKit
+dnl
+
 PKG_CHECK_MODULES(POLKIT, [polkit-gobject-1 >= 0.97], [have_polkit=yes],[have_polkit=no])
 AC_ARG_WITH(polkit,
             AS_HELP_STRING([--with-polkit=(strict|permissive|none)],
                            [Enable PolicyKit support [[default=auto]]]),,
             [with_polkit=auto])
-# Handle 'auto' ('strict' if polkit found, 'none' otherwise),
-#  'yes' ('strict') and 'no' ('none')
+
 if test "x$with_polkit" = "xauto"; then
 	if test "x$have_polkit" = "xno"; then
         with_polkit="none"
@@ -169,7 +230,7 @@
 elif test "x$with_polkit" = "xyes"; then
     with_polkit=strict
 fi
-# Build policies context
+
 if test "x$with_polkit" = "xnone"; then
     AC_DEFINE(WITH_POLKIT, 0, [Define if you have PolicyKit support])
 else
@@ -197,9 +258,10 @@
 
 AM_CONDITIONAL(WITH_POLKIT, [test "x$with_polkit" != "xnone" ])
 
-dnl
+dnl-----------------------------------------------------------------------------
 dnl MBIM support (enabled by default)
 dnl
+
 AC_ARG_WITH(mbim, AS_HELP_STRING([--without-mbim], [Build without MBIM support]), [], [with_mbim=yes])
 AM_CONDITIONAL(WITH_MBIM, test "x$with_mbim" = "xyes")
 case $with_mbim in
@@ -218,14 +280,14 @@
         ;;
 esac
 
-dnl
+dnl-----------------------------------------------------------------------------
 dnl QMI support (enabled by default)
 dnl
 AC_ARG_WITH(qmi, AS_HELP_STRING([--without-qmi], [Build without QMI support]), [], [with_qmi=yes])
 AM_CONDITIONAL(WITH_QMI, test "x$with_qmi" = "xyes")
 case $with_qmi in
     yes)
-        PKG_CHECK_MODULES(QMI, [qmi-glib >= 1.11.1], [have_qmi=yes],[have_qmi=no])
+        PKG_CHECK_MODULES(QMI, [qmi-glib >= 1.13.4], [have_qmi=yes],[have_qmi=no])
         if test "x$have_qmi" = "xno"; then
             AC_MSG_ERROR([Couldn't find libqmi-glib. Install it, or otherwise configure using --without-qmi to disable QMI support.])
         else
@@ -241,8 +303,7 @@
 
 NM_COMPILER_WARNINGS
 
-
-dnl
+dnl-----------------------------------------------------------------------------
 dnl Distribution version string
 dnl
 AC_ARG_WITH(dist-version, AS_HELP_STRING([--with-dist-version=<mm-dist-version>], [Define the custom version (like distribution package name and revision)]), ac_distver=$withval, ac_distver="")
@@ -250,10 +311,17 @@
   AC_DEFINE_UNQUOTED(MM_DIST_VERSION, "$ac_distver", [Define the distribution version string])
 fi
 
-# Not building protocol libs standalone
+dnl-----------------------------------------------------------------------------
+dnl Protocol libs
+dnl
+
 AM_CONDITIONAL(QCDM_STANDALONE, test "yes" = "no")
 AM_CONDITIONAL(WMC_STANDALONE, test "yes" = "no")
 
+dnl-----------------------------------------------------------------------------
+dnl Protocol libs
+dnl
+
 AC_CONFIG_FILES([
 Makefile
 data/Makefile
@@ -302,24 +370,31 @@
     ModemManager $VERSION
     ==============================================
 
-    compiler:                ${CC}
-    cflags:                  ${CFLAGS}
-    Maintainer mode:         ${USE_MAINTAINER_MODE}
-    Code coverage:           ${CODE_COVERAGE_ENABLED}"
+    Build:
+      compiler:                ${CC}
+      cflags:                  ${CFLAGS}
+      ldflags:                 ${LDFLAGS}
+      maintainer mode:         ${USE_MAINTAINER_MODE}
+
+    System paths:
+      prefix:                  ${prefix}
+      D-Bus system directory:  ${DBUS_SYS_DIR}
+      udev base directory:     ${UDEV_BASE_DIR}
+      systemd unit directory:  ${with_systemdsystemunitdir}
+
+    Features:
+      policykit support:       ${with_polkit}
+      mbim support:            ${with_mbim}
+      qmi support:             ${with_qmi}
+      suspend/resume support:  ${with_suspend_resume}
+
+    Miscellaneous:
+      gobject introspection:   ${found_introspection}
+      vala bindings:           ${enable_vala}
+      documentation:           ${enable_gtk_doc}
+      code coverage:           ${CODE_COVERAGE_ENABLED}"
 if test "x${CODE_COVERAGE_ENABLED}" = "xyes"; then
-	echo "    Code coverage cflags:    ${CODE_COVERAGE_CFLAGS}"
-	echo "    Code coverage ldflags:   ${CODE_COVERAGE_LDFLAGS}"
+   echo "      code coverage cflags:    ${CODE_COVERAGE_CFLAGS}"
+   echo "      code coverage ldflags:   ${CODE_COVERAGE_LDFLAGS}"
 fi
-
-echo "
-    D-Bus system directory:  ${DBUS_SYS_DIR}
-    udev base directory:     ${UDEV_BASE_DIR}
-    systemd unit directory:  ${with_systemdsystemunitdir}
-
-    PolicyKit support:       ${with_polkit}
-    GObject Introspection:   ${found_introspection}
-    Vala Bindings:           ${enable_vala}
-    Documentation:           ${enable_gtk_doc}
-    MBIM support:            ${with_mbim}
-    QMI support:             ${with_qmi}
-"
+echo ""
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index a3733ee..1e30b1a 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -126,10 +126,10 @@
 	libmm-plugin-nokia-icera.la \
 	libmm-plugin-cinterion.la \
 	libmm-plugin-iridium.la \
-	libmm-plugin-gobi.la \
 	libmm-plugin-motorola.la \
 	libmm-plugin-novatel.la \
 	libmm-plugin-novatel-lte.la \
+	libmm-plugin-dell.la \
 	libmm-plugin-altair-lte.la \
 	libmm-plugin-samsung.la \
 	libmm-plugin-option.la \
@@ -144,6 +144,7 @@
 	libmm-plugin-pantech.la \
 	libmm-plugin-zte.la \
 	libmm-plugin-sierra.la \
+	libmm-plugin-sierra-legacy.la \
 	libmm-plugin-mbm.la \
 	libmm-plugin-via.la \
 	libmm-plugin-telit.la \
@@ -172,15 +173,6 @@
 libmm_plugin_motorola_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
 libmm_plugin_motorola_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
 
-# Gobi
-libmm_plugin_gobi_la_SOURCES = \
-	gobi/mm-plugin-gobi.c \
-	gobi/mm-plugin-gobi.h \
-	gobi/mm-broadband-modem-gobi.c \
-	gobi/mm-broadband-modem-gobi.h
-libmm_plugin_gobi_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
-libmm_plugin_gobi_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
-
 # Huawei
 libmm_plugin_huawei_la_SOURCES = \
 	huawei/mm-plugin-huawei.c \
@@ -211,10 +203,9 @@
        $(top_builddir)/src/libmodem-helpers.la
 test_modem_helpers_huawei_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
 
-# MBM
-libmm_plugin_mbm_la_SOURCES = \
-	mbm/mm-plugin-mbm.c \
-	mbm/mm-plugin-mbm.h \
+# Common Mbm modem support library
+noinst_LTLIBRARIES += libmm-utils-mbm.la
+libmm_utils_mbm_la_SOURCES = \
 	mbm/mm-broadband-modem-mbm.c \
 	mbm/mm-broadband-modem-mbm.h \
 	mbm/mm-broadband-bearer-mbm.c \
@@ -223,8 +214,20 @@
 	mbm/mm-modem-helpers-mbm.h \
 	mbm/mm-sim-mbm.c \
 	mbm/mm-sim-mbm.h
-libmm_plugin_mbm_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
+libmm_utils_mbm_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
+libmm_utils_mbm_la_LIBADD = $(GUDEV_LIBS) $(MM_LIBS)
+
+MBM_COMMON_COMPILER_FLAGS = -I$(top_srcdir)/plugins/mbm
+MBM_COMMON_LIBADD_FLAGS = $(builddir)/libmm-utils-mbm.la
+
+# MBM
+libmm_plugin_mbm_la_SOURCES = \
+	mbm/mm-plugin-mbm.c \
+	mbm/mm-plugin-mbm.h
+libmm_plugin_mbm_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS) $(MBM_COMMON_COMPILER_FLAGS)
 libmm_plugin_mbm_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
+libmm_plugin_mbm_la_LIBADD = $(MBM_COMMON_LIBADD_FLAGS)
+
 udevrules_DATA += mbm/77-mm-ericsson-mbm.rules
 
 noinst_PROGRAMS += test-modem-helpers-mbm
@@ -262,10 +265,9 @@
 libmm_plugin_hso_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
 libmm_plugin_hso_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
 
-# Sierra
-libmm_plugin_sierra_la_SOURCES = \
-	sierra/mm-plugin-sierra.c \
-	sierra/mm-plugin-sierra.h \
+# Common Sierra modem support library
+noinst_LTLIBRARIES += libmm-utils-sierra.la
+libmm_utils_sierra_la_SOURCES = \
 	sierra/mm-common-sierra.c \
 	sierra/mm-common-sierra.h \
 	sierra/mm-sim-sierra.c \
@@ -273,12 +275,29 @@
 	sierra/mm-broadband-bearer-sierra.c \
 	sierra/mm-broadband-bearer-sierra.h \
 	sierra/mm-broadband-modem-sierra.c \
-	sierra/mm-broadband-modem-sierra.h \
+	sierra/mm-broadband-modem-sierra.h
+libmm_utils_sierra_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
+libmm_utils_sierra_la_LIBADD = $(GUDEV_LIBS) $(MM_LIBS)
+
+SIERRA_COMMON_COMPILER_FLAGS = -I$(top_srcdir)/plugins/sierra
+SIERRA_COMMON_LIBADD_FLAGS = $(builddir)/libmm-utils-sierra.la
+
+# Sierra (new QMI or MBIM modems)
+libmm_plugin_sierra_la_SOURCES = \
+	sierra/mm-plugin-sierra.c \
+	sierra/mm-plugin-sierra.h
+libmm_plugin_sierra_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
+libmm_plugin_sierra_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
+
+# Sierra (legacy)
+libmm_plugin_sierra_legacy_la_SOURCES = \
+	sierra/mm-plugin-sierra-legacy.c \
+	sierra/mm-plugin-sierra-legacy.h \
 	sierra/mm-broadband-modem-sierra-icera.c \
 	sierra/mm-broadband-modem-sierra-icera.h
-libmm_plugin_sierra_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS) $(ICERA_COMMON_COMPILER_FLAGS)
-libmm_plugin_sierra_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
-libmm_plugin_sierra_la_LIBADD = $(ICERA_COMMON_LIBADD_FLAGS)
+libmm_plugin_sierra_legacy_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS) $(ICERA_COMMON_COMPILER_FLAGS) $(SIERRA_COMMON_COMPILER_FLAGS)
+libmm_plugin_sierra_legacy_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
+libmm_plugin_sierra_legacy_la_LIBADD = $(ICERA_COMMON_LIBADD_FLAGS) $(SIERRA_COMMON_LIBADD_FLAGS)
 
 # Wavecom (Sierra Airlink)
 libmm_plugin_wavecom_la_SOURCES = \
@@ -441,6 +460,19 @@
 libmm_plugin_iridium_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
 libmm_plugin_iridium_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
 
+# Common Novatel modem support library
+noinst_LTLIBRARIES += libmm-utils-novatel.la
+libmm_utils_novatel_la_SOURCES = \
+	novatel/mm-common-novatel.c \
+	novatel/mm-common-novatel.h \
+	novatel/mm-broadband-modem-novatel.c \
+	novatel/mm-broadband-modem-novatel.h
+libmm_utils_novatel_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
+libmm_utils_novatel_la_LIBADD = $(GUDEV_LIBS) $(MM_LIBS)
+
+NOVATEL_COMMON_COMPILER_FLAGS = -I$(top_srcdir)/plugins/novatel
+NOVATEL_COMMON_LIBADD_FLAGS = $(builddir)/libmm-utils-novatel.la
+
 # Novatel LTE modem
 libmm_plugin_novatel_lte_la_SOURCES = \
 	novatel/mm-plugin-novatel-lte.c \
@@ -457,11 +489,18 @@
 # Novatel non-LTE modem
 libmm_plugin_novatel_la_SOURCES = \
 	novatel/mm-plugin-novatel.c \
-	novatel/mm-plugin-novatel.h \
-	novatel/mm-broadband-modem-novatel.c \
-	novatel/mm-broadband-modem-novatel.h
-libmm_plugin_novatel_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS)
+	novatel/mm-plugin-novatel.h
+libmm_plugin_novatel_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS) $(NOVATEL_COMMON_COMPILER_FLAGS)
 libmm_plugin_novatel_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
+libmm_plugin_novatel_la_LIBADD = $(NOVATEL_COMMON_LIBADD_FLAGS)
+
+# Dell (e.g. Novatel or Sierra) modem
+libmm_plugin_dell_la_SOURCES = \
+	dell/mm-plugin-dell.c \
+	dell/mm-plugin-dell.h
+libmm_plugin_dell_la_CPPFLAGS = $(PLUGIN_COMMON_COMPILER_FLAGS) $(NOVATEL_COMMON_COMPILER_FLAGS) $(SIERRA_COMMON_COMPILER_FLAGS) $(MBM_COMMON_COMPILER_FLAGS)
+libmm_plugin_dell_la_LDFLAGS = $(PLUGIN_COMMON_LINKER_FLAGS)
+libmm_plugin_dell_la_LIBADD = $(NOVATEL_COMMON_LIBADD_FLAGS) $(SIERRA_COMMON_LIBADD_FLAGS) $(MBM_COMMON_LIBADD_FLAGS)
 
 # Altair LTE modem
 libmm_plugin_altair_lte_la_SOURCES = \
diff --git a/plugins/dell/mm-plugin-dell.c b/plugins/dell/mm-plugin-dell.c
new file mode 100644
index 0000000..0dff19b
--- /dev/null
+++ b/plugins/dell/mm-plugin-dell.c
@@ -0,0 +1,448 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <string.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-plugin-dell.h"
+#include "mm-common-novatel.h"
+#include "mm-private-boxed-types.h"
+#include "mm-broadband-modem.h"
+#include "mm-broadband-modem-novatel.h"
+#include "mm-common-novatel.h"
+#include "mm-broadband-modem-sierra.h"
+#include "mm-common-sierra.h"
+#include "mm-log.h"
+
+#if defined WITH_QMI
+#include "mm-broadband-modem-qmi.h"
+#endif
+
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim.h"
+#endif
+
+G_DEFINE_TYPE (MMPluginDell, mm_plugin_dell, MM_TYPE_PLUGIN)
+
+int mm_plugin_major_version = MM_PLUGIN_MAJOR_VERSION;
+int mm_plugin_minor_version = MM_PLUGIN_MINOR_VERSION;
+
+#define TAG_DELL_MANUFACTURER   "dell-manufacturer"
+typedef enum {
+    DELL_MANUFACTURER_UNKNOWN  = 0,
+    DELL_MANUFACTURER_NOVATEL  = 1,
+    DELL_MANUFACTURER_SIERRA   = 2,
+    DELL_MANUFACTURER_ERICSSON = 3
+} DellManufacturer;
+
+/*****************************************************************************/
+/* Custom init */
+
+typedef struct {
+    MMPortProbe *probe;
+    MMPortSerialAt *port;
+    GCancellable *cancellable;
+    GSimpleAsyncResult *result;
+    guint gmi_retries;
+    guint cgmi_retries;
+    guint ati_retries;
+} CustomInitContext;
+
+static void
+custom_init_context_complete_and_free (CustomInitContext *ctx)
+{
+    g_simple_async_result_complete_in_idle (ctx->result);
+
+    if (ctx->cancellable)
+        g_object_unref (ctx->cancellable);
+    g_object_unref (ctx->port);
+    g_object_unref (ctx->probe);
+    g_object_unref (ctx->result);
+    g_slice_free (CustomInitContext, ctx);
+}
+
+static gboolean
+dell_custom_init_finish (MMPortProbe *probe,
+                         GAsyncResult *result,
+                         GError **error)
+{
+    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+static void
+novatel_custom_init_ready (MMPortProbe       *probe,
+                           GAsyncResult      *res,
+                           CustomInitContext *ctx)
+{
+    GError *error = NULL;
+
+    if (!mm_common_novatel_custom_init_finish (probe, res, &error))
+        g_simple_async_result_take_error (ctx->result, error);
+    else
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+    custom_init_context_complete_and_free (ctx);
+}
+
+static void
+sierra_custom_init_ready (MMPortProbe       *probe,
+                          GAsyncResult      *res,
+                          CustomInitContext *ctx)
+{
+    GError *error = NULL;
+
+    if (!mm_common_sierra_custom_init_finish (probe, res, &error))
+        g_simple_async_result_take_error (ctx->result, error);
+    else
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+    custom_init_context_complete_and_free (ctx);
+}
+
+static void custom_init_step (CustomInitContext *ctx);
+
+static void
+custom_init_step_next_command (CustomInitContext *ctx)
+{
+    if (ctx->gmi_retries > 0)
+        ctx->gmi_retries = 0;
+    else if (ctx->cgmi_retries > 0)
+        ctx->cgmi_retries = 0;
+    else if (ctx->ati_retries > 0)
+        ctx->ati_retries = 0;
+    custom_init_step (ctx);
+}
+
+static void
+response_ready (MMPortSerialAt *port,
+                GAsyncResult *res,
+                CustomInitContext *ctx)
+{
+    const gchar *response;
+    GError *error = NULL;
+    gchar *lower;
+    DellManufacturer manufacturer;
+
+    response = mm_port_serial_at_command_finish (port, res, &error);
+    if (error) {
+        /* Non-timeout error, jump to next command */
+        if (!g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
+            mm_dbg ("(Dell) Error probing AT port: %s", error->message);
+            g_error_free (error);
+            custom_init_step_next_command (ctx);
+            return;
+        }
+        /* Directly retry same command on timeout */
+        custom_init_step (ctx);
+        g_error_free (error);
+        return;
+    }
+
+    /* Guess manufacturer from response */
+    lower = g_ascii_strdown (response, -1);
+    if (strstr (lower, "novatel"))
+        manufacturer = DELL_MANUFACTURER_NOVATEL;
+    else if (strstr (lower, "sierra"))
+        manufacturer = DELL_MANUFACTURER_SIERRA;
+    else if (strstr (lower, "ericsson"))
+        manufacturer = DELL_MANUFACTURER_ERICSSON;
+    else
+        manufacturer = DELL_MANUFACTURER_UNKNOWN;
+    g_free (lower);
+
+    /* Tag based on manufacturer */
+    if (manufacturer != DELL_MANUFACTURER_UNKNOWN) {
+        g_object_set_data (G_OBJECT (ctx->probe), TAG_DELL_MANUFACTURER, GUINT_TO_POINTER (manufacturer));
+
+        /* Run additional custom init, if needed */
+
+        if (manufacturer == DELL_MANUFACTURER_NOVATEL) {
+            mm_common_novatel_custom_init (ctx->probe,
+                                           ctx->port,
+                                           ctx->cancellable,
+                                           (GAsyncReadyCallback) novatel_custom_init_ready,
+                                           ctx);
+            return;
+        }
+
+        if (manufacturer == DELL_MANUFACTURER_SIERRA) {
+            mm_common_sierra_custom_init (ctx->probe,
+                                          ctx->port,
+                                          ctx->cancellable,
+                                          (GAsyncReadyCallback) sierra_custom_init_ready,
+                                          ctx);
+            return;
+        }
+
+        /* Finish custom_init */
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+        custom_init_context_complete_and_free (ctx);
+        return;
+    }
+
+    /* If we got a response, but we didn't get an expected string, try with next command */
+    custom_init_step_next_command (ctx);
+}
+
+static void
+custom_init_step (CustomInitContext *ctx)
+{
+    /* If cancelled, end */
+    if (g_cancellable_is_cancelled (ctx->cancellable)) {
+        mm_dbg ("(Dell) no need to keep on running custom init in (%s)",
+                mm_port_get_device (MM_PORT (ctx->port)));
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+        custom_init_context_complete_and_free (ctx);
+        return;
+    }
+
+#if defined WITH_QMI
+    /* If device has a QMI port, don't run anything else, as we don't care */
+    if (mm_port_probe_list_has_qmi_port (mm_device_peek_port_probe_list (mm_port_probe_peek_device (ctx->probe)))) {
+        mm_dbg ("(Dell) no need to run custom init in (%s): device has QMI port",
+                mm_port_get_device (MM_PORT (ctx->port)));
+        mm_port_probe_set_result_at (ctx->probe, FALSE);
+        mm_port_probe_set_result_qcdm (ctx->probe, FALSE);
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+        custom_init_context_complete_and_free (ctx);
+        return;
+    }
+#endif
+
+#if defined WITH_MBIM
+    /* If device has a MBIM port, don't run anything else, as we don't care */
+    if (mm_port_probe_list_has_mbim_port (mm_device_peek_port_probe_list (mm_port_probe_peek_device (ctx->probe)))) {
+        mm_dbg ("(Dell) no need to run custom init in (%s): device has MBIM port",
+                mm_port_get_device (MM_PORT (ctx->port)));
+        mm_port_probe_set_result_at (ctx->probe, FALSE);
+        mm_port_probe_set_result_qcdm (ctx->probe, FALSE);
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+        custom_init_context_complete_and_free (ctx);
+        return;
+    }
+#endif
+
+    if (ctx->gmi_retries > 0) {
+        ctx->gmi_retries--;
+        mm_port_serial_at_command (ctx->port,
+                                   "AT+GMI",
+                                   3,
+                                   FALSE, /* raw */
+                                   FALSE, /* allow_cached */
+                                   ctx->cancellable,
+                                   (GAsyncReadyCallback)response_ready,
+                                   ctx);
+        return;
+    }
+
+    if (ctx->cgmi_retries > 0) {
+        ctx->cgmi_retries--;
+        mm_port_serial_at_command (ctx->port,
+                                   "AT+CGMI",
+                                   3,
+                                   FALSE, /* raw */
+                                   FALSE, /* allow_cached */
+                                   ctx->cancellable,
+                                   (GAsyncReadyCallback)response_ready,
+                                   ctx);
+        return;
+    }
+
+    if (ctx->ati_retries > 0) {
+        ctx->ati_retries--;
+        /* Note: in Ericsson devices, ATI3 seems to reply the vendor string */
+        mm_port_serial_at_command (ctx->port,
+                                   "ATI1I2I3",
+                                   3,
+                                   FALSE, /* raw */
+                                   FALSE, /* allow_cached */
+                                   ctx->cancellable,
+                                   (GAsyncReadyCallback)response_ready,
+                                   ctx);
+        return;
+    }
+
+    /* Finish custom_init */
+    mm_dbg ("(Dell) couldn't flip secondary port to AT in (%s): all retries consumed",
+            mm_port_get_device (MM_PORT (ctx->port)));
+    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+    custom_init_context_complete_and_free (ctx);
+}
+
+static void
+dell_custom_init (MMPortProbe *probe,
+                  MMPortSerialAt *port,
+                  GCancellable *cancellable,
+                  GAsyncReadyCallback callback,
+                  gpointer user_data)
+{
+    CustomInitContext *ctx;
+
+    ctx = g_slice_new (CustomInitContext);
+    ctx->result = g_simple_async_result_new (G_OBJECT (probe),
+                                             callback,
+                                             user_data,
+                                             dell_custom_init);
+    ctx->probe = g_object_ref (probe);
+    ctx->port = g_object_ref (port);
+    ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+    ctx->gmi_retries = 3;
+    ctx->ati_retries = 3;
+
+    custom_init_step (ctx);
+}
+
+/*****************************************************************************/
+
+static gboolean
+port_probe_list_has_manufacturer_port (GList *probes,
+                                       DellManufacturer manufacturer)
+{
+    GList *l;
+
+    for (l = probes; l; l = g_list_next (l)) {
+        if (GPOINTER_TO_UINT (g_object_get_data (G_OBJECT (l->data), TAG_DELL_MANUFACTURER)) == manufacturer)
+            return TRUE;
+    }
+    return FALSE;
+}
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+              const gchar *sysfs_path,
+              const gchar **drivers,
+              guint16 vendor,
+              guint16 product,
+              GList *probes,
+              GError **error)
+{
+    /* Note: at this point we don't make any difference between different
+     * Dell-branded QMI or MBIM modems; they may come from Novatel, Ericsson or
+     * Sierra. */
+
+#if defined WITH_QMI
+    if (mm_port_probe_list_has_qmi_port (probes)) {
+        mm_dbg ("QMI-powered Dell-branded modem found...");
+        return MM_BASE_MODEM (mm_broadband_modem_qmi_new (sysfs_path,
+                                                          drivers,
+                                                          mm_plugin_get_name (self),
+                                                          vendor,
+                                                          product));
+    }
+#endif
+
+#if defined WITH_MBIM
+    if (mm_port_probe_list_has_mbim_port (probes)) {
+        mm_dbg ("MBIM-powered Dell-branded modem found...");
+        return MM_BASE_MODEM (mm_broadband_modem_mbim_new (sysfs_path,
+                                                           drivers,
+                                                           mm_plugin_get_name (self),
+                                                           vendor,
+                                                           product));
+    }
+#endif
+
+    if (port_probe_list_has_manufacturer_port (probes, DELL_MANUFACTURER_NOVATEL)) {
+        mm_dbg ("Novatel-powered Dell-branded modem found...");
+        return MM_BASE_MODEM (mm_broadband_modem_novatel_new (sysfs_path,
+                                                              drivers,
+                                                              mm_plugin_get_name (self),
+                                                              vendor,
+                                                              product));
+    }
+
+    if (port_probe_list_has_manufacturer_port (probes, DELL_MANUFACTURER_SIERRA)) {
+        mm_dbg ("Sierra-powered Dell-branded modem found...");
+        return MM_BASE_MODEM (mm_broadband_modem_sierra_new (sysfs_path,
+                                                             drivers,
+                                                             mm_plugin_get_name (self),
+                                                             vendor,
+                                                             product));
+    }
+
+    mm_dbg ("Dell-branded generic modem found...");
+    return MM_BASE_MODEM (mm_broadband_modem_new (sysfs_path,
+                                                  drivers,
+                                                  mm_plugin_get_name (self),
+                                                  vendor,
+                                                  product));
+}
+
+/*****************************************************************************/
+
+static gboolean
+grab_port (MMPlugin *self,
+           MMBaseModem *modem,
+           MMPortProbe *probe,
+           GError **error)
+{
+    /* Only Sierra needs custom grab port, due to the port type hints */
+    if (MM_IS_BROADBAND_MODEM_SIERRA (modem))
+        return mm_common_sierra_grab_port (self, modem, probe, error);
+
+    return mm_base_modem_grab_port (modem,
+                                    mm_port_probe_get_port_subsys (probe),
+                                    mm_port_probe_get_port_name (probe),
+                                    mm_port_probe_get_parent_path (probe),
+                                    mm_port_probe_get_port_type (probe),
+                                    MM_PORT_SERIAL_AT_FLAG_NONE,
+                                    error);
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+    static const gchar *subsystems[] = { "tty", "net", "usb", NULL };
+    static const guint16 vendors[] = { 0x413c, 0 };
+    static const MMAsyncMethod custom_init = {
+        .async  = G_CALLBACK (dell_custom_init),
+        .finish = G_CALLBACK (dell_custom_init_finish),
+    };
+
+    return MM_PLUGIN (
+        g_object_new (MM_TYPE_PLUGIN_DELL,
+                      MM_PLUGIN_NAME,                  "Dell",
+                      MM_PLUGIN_ALLOWED_SUBSYSTEMS,    subsystems,
+                      MM_PLUGIN_ALLOWED_VENDOR_IDS,    vendors,
+                      MM_PLUGIN_ALLOWED_AT,            TRUE,
+                      MM_PLUGIN_CUSTOM_INIT,           &custom_init,
+                      MM_PLUGIN_ALLOWED_QCDM,          TRUE,
+                      MM_PLUGIN_ALLOWED_QMI,           TRUE,
+                      MM_PLUGIN_ALLOWED_MBIM,          TRUE,
+                      NULL));
+}
+
+static void
+mm_plugin_dell_init (MMPluginDell *self)
+{
+}
+
+static void
+mm_plugin_dell_class_init (MMPluginDellClass *klass)
+{
+    MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+    plugin_class->create_modem = create_modem;
+    plugin_class->grab_port    = grab_port;
+}
diff --git a/plugins/dell/mm-plugin-dell.h b/plugins/dell/mm-plugin-dell.h
new file mode 100644
index 0000000..cc1a539
--- /dev/null
+++ b/plugins/dell/mm-plugin-dell.h
@@ -0,0 +1,46 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+
+/*
+ * This program is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU General Public License as
+ * published by the Free Software Foundation; either version 2 of the
+ * License, or (at your option) any later version.
+ *
+ * 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., 59 Temple Place - Suite 330,
+ * Boston, MA 02111-1307, USA.
+ *
+ * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_DELL_H
+#define MM_PLUGIN_DELL_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_DELL            (mm_plugin_dell_get_type ())
+#define MM_PLUGIN_DELL(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_DELL, MMPluginDell))
+#define MM_PLUGIN_DELL_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_PLUGIN_DELL, MMPluginDellClass))
+#define MM_IS_PLUGIN_DELL(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_DELL))
+#define MM_IS_PLUGIN_DELL_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_PLUGIN_DELL))
+#define MM_PLUGIN_DELL_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_PLUGIN_DELL, MMPluginDellClass))
+
+typedef struct {
+    MMPlugin parent;
+} MMPluginDell;
+
+typedef struct {
+    MMPluginClass parent;
+} MMPluginDellClass;
+
+GType mm_plugin_dell_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_DELL_H */
diff --git a/plugins/gobi/mm-broadband-modem-gobi.c b/plugins/gobi/mm-broadband-modem-gobi.c
deleted file mode 100644
index 27aded2..0000000
--- a/plugins/gobi/mm-broadband-modem-gobi.c
+++ /dev/null
@@ -1,124 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * 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:
- *
- * Copyright (C) 2008 - 2009 Novell, Inc.
- * Copyright (C) 2009 - 2011 Red Hat, Inc.
- * Copyright (C) 2011 - 2012 Google Inc.
- */
-
-#include <config.h>
-
-#include <stdlib.h>
-#include <stdio.h>
-#include <string.h>
-#include <unistd.h>
-#include <ctype.h>
-
-#include "ModemManager.h"
-#include "mm-modem-helpers.h"
-#include "mm-serial-parsers.h"
-#include "mm-log.h"
-#include "mm-errors-types.h"
-#include "mm-iface-modem.h"
-#include "mm-iface-modem-3gpp.h"
-#include "mm-base-modem-at.h"
-#include "mm-broadband-modem-gobi.h"
-
-static void iface_modem_init (MMIfaceModem *iface);
-
-G_DEFINE_TYPE_EXTENDED (MMBroadbandModemGobi, mm_broadband_modem_gobi, MM_TYPE_BROADBAND_MODEM, 0,
-                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init));
-
-/*****************************************************************************/
-/* Load access technologies (Modem interface) */
-
-static gboolean
-load_access_technologies_finish (MMIfaceModem *self,
-                                 GAsyncResult *res,
-                                 MMModemAccessTechnology *access_technologies,
-                                 guint *mask,
-                                 GError **error)
-{
-    const gchar *p;
-    const gchar *response;
-
-    response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
-    if (!response)
-        return FALSE;
-
-    p = mm_strip_tag (response, "*CNTI:");
-    p = strchr (p, ',');
-    if (p) {
-        /* We are reporting ALL 3GPP access technologies here */
-        *access_technologies = mm_string_to_access_tech (p + 1);
-        *mask = MM_IFACE_MODEM_3GPP_ALL_ACCESS_TECHNOLOGIES_MASK;
-        return TRUE;
-    }
-
-    g_set_error (error,
-                 MM_CORE_ERROR,
-                 MM_CORE_ERROR_FAILED,
-                 "Couldn't parse access technologies result: '%s'",
-                 response);
-    return FALSE;
-}
-
-static void
-load_access_technologies (MMIfaceModem *self,
-                          GAsyncReadyCallback callback,
-                          gpointer user_data)
-{
-    mm_base_modem_at_command (MM_BASE_MODEM (self),
-                              "*CNTI=0",
-                              3,
-                              FALSE,
-                              callback,
-                              user_data);
-}
-
-/*****************************************************************************/
-
-MMBroadbandModemGobi *
-mm_broadband_modem_gobi_new (const gchar *device,
-                             const gchar **drivers,
-                             const gchar *plugin,
-                             guint16 vendor_id,
-                             guint16 product_id)
-{
-    return g_object_new (MM_TYPE_BROADBAND_MODEM_GOBI,
-                         MM_BASE_MODEM_DEVICE, device,
-                         MM_BASE_MODEM_DRIVERS, drivers,
-                         MM_BASE_MODEM_PLUGIN, plugin,
-                         MM_BASE_MODEM_VENDOR_ID, vendor_id,
-                         MM_BASE_MODEM_PRODUCT_ID, product_id,
-                         NULL);
-}
-
-static void
-mm_broadband_modem_gobi_init (MMBroadbandModemGobi *self)
-{
-}
-
-static void
-iface_modem_init (MMIfaceModem *iface)
-{
-    iface->load_access_technologies = load_access_technologies;
-    iface->load_access_technologies_finish = load_access_technologies_finish;
-
-    iface->modem_power_down = NULL;
-    iface->modem_power_down_finish = NULL;
-}
-
-static void
-mm_broadband_modem_gobi_class_init (MMBroadbandModemGobiClass *klass)
-{
-}
diff --git a/plugins/gobi/mm-broadband-modem-gobi.h b/plugins/gobi/mm-broadband-modem-gobi.h
deleted file mode 100644
index 4164cfe..0000000
--- a/plugins/gobi/mm-broadband-modem-gobi.h
+++ /dev/null
@@ -1,49 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * 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:
- *
- * Copyright (C) 2008 - 2009 Novell, Inc.
- * Copyright (C) 2009 - 2011 Red Hat, Inc.
- * Copyright (C) 2011 Google Inc.
- */
-
-#ifndef MM_BROADBAND_MODEM_GOBI_H
-#define MM_BROADBAND_MODEM_GOBI_H
-
-#include "mm-broadband-modem.h"
-
-#define MM_TYPE_BROADBAND_MODEM_GOBI            (mm_broadband_modem_gobi_get_type ())
-#define MM_BROADBAND_MODEM_GOBI(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_GOBI, MMBroadbandModemGobi))
-#define MM_BROADBAND_MODEM_GOBI_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_BROADBAND_MODEM_GOBI, MMBroadbandModemGobiClass))
-#define MM_IS_BROADBAND_MODEM_GOBI(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_GOBI))
-#define MM_IS_BROADBAND_MODEM_GOBI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_BROADBAND_MODEM_GOBI))
-#define MM_BROADBAND_MODEM_GOBI_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_BROADBAND_MODEM_GOBI, MMBroadbandModemGobiClass))
-
-typedef struct _MMBroadbandModemGobi MMBroadbandModemGobi;
-typedef struct _MMBroadbandModemGobiClass MMBroadbandModemGobiClass;
-
-struct _MMBroadbandModemGobi {
-    MMBroadbandModem parent;
-};
-
-struct _MMBroadbandModemGobiClass{
-    MMBroadbandModemClass parent;
-};
-
-GType mm_broadband_modem_gobi_get_type (void);
-
-MMBroadbandModemGobi *mm_broadband_modem_gobi_new (const gchar *device,
-                                                   const gchar **drivers,
-                                                   const gchar *plugin,
-                                                   guint16 vendor_id,
-                                                   guint16 product_id);
-
-#endif /* MM_BROADBAND_MODEM_GOBI_H */
diff --git a/plugins/gobi/mm-plugin-gobi.c b/plugins/gobi/mm-plugin-gobi.c
deleted file mode 100644
index 823b95d..0000000
--- a/plugins/gobi/mm-plugin-gobi.c
+++ /dev/null
@@ -1,95 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * 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:
- *
- * Copyright (C) 2008 - 2009 Novell, Inc.
- * Copyright (C) 2011 Red Hat, Inc.
- */
-
-#include <string.h>
-#include <gmodule.h>
-
-#define _LIBMM_INSIDE_MM
-#include <libmm-glib.h>
-
-#include "mm-plugin-gobi.h"
-#include "mm-broadband-modem-gobi.h"
-#include "mm-log.h"
-
-#if defined WITH_QMI
-#include "mm-broadband-modem-qmi.h"
-#endif
-
-G_DEFINE_TYPE (MMPluginGobi, mm_plugin_gobi, MM_TYPE_PLUGIN)
-
-int mm_plugin_major_version = MM_PLUGIN_MAJOR_VERSION;
-int mm_plugin_minor_version = MM_PLUGIN_MINOR_VERSION;
-
-/*****************************************************************************/
-
-static MMBaseModem *
-create_modem (MMPlugin *self,
-              const gchar *sysfs_path,
-              const gchar **drivers,
-              guint16 vendor,
-              guint16 product,
-              GList *probes,
-              GError **error)
-{
-#if defined WITH_QMI
-    if (mm_port_probe_list_has_qmi_port (probes)) {
-        mm_dbg ("QMI-powered Gobi modem found...");
-        return MM_BASE_MODEM (mm_broadband_modem_qmi_new (sysfs_path,
-                                                          drivers,
-                                                          mm_plugin_get_name (self),
-                                                          vendor,
-                                                          product));
-    }
-#endif
-
-    return MM_BASE_MODEM (mm_broadband_modem_gobi_new (sysfs_path,
-                                                       drivers,
-                                                       mm_plugin_get_name (self),
-                                                       vendor,
-                                                       product));
-}
-
-/*****************************************************************************/
-
-G_MODULE_EXPORT MMPlugin *
-mm_plugin_create (void)
-{
-    static const gchar *subsystems[] = { "tty", "net", "usb", NULL };
-    static const gchar *drivers[] = { "qcserial", NULL };
-
-    return MM_PLUGIN (
-        g_object_new (MM_TYPE_PLUGIN_GOBI,
-                      MM_PLUGIN_NAME,               "Gobi",
-                      MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
-                      MM_PLUGIN_ALLOWED_DRIVERS,    drivers,
-                      MM_PLUGIN_ALLOWED_AT,         TRUE,
-                      MM_PLUGIN_ALLOWED_QCDM,       TRUE,
-                      MM_PLUGIN_ALLOWED_QMI,        TRUE,
-                      NULL));
-}
-
-static void
-mm_plugin_gobi_init (MMPluginGobi *self)
-{
-}
-
-static void
-mm_plugin_gobi_class_init (MMPluginGobiClass *klass)
-{
-    MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
-
-    plugin_class->create_modem = create_modem;
-}
diff --git a/plugins/gobi/mm-plugin-gobi.h b/plugins/gobi/mm-plugin-gobi.h
deleted file mode 100644
index c47b172..0000000
--- a/plugins/gobi/mm-plugin-gobi.h
+++ /dev/null
@@ -1,41 +0,0 @@
-/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * 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:
- *
- * Copyright (C) 2008 - 2009 Novell, Inc.
- * Copyright (C) 2009 Red Hat, Inc.
- */
-
-#ifndef MM_PLUGIN_GOBI_H
-#define MM_PLUGIN_GOBI_H
-
-#include "mm-plugin.h"
-
-#define MM_TYPE_PLUGIN_GOBI            (mm_plugin_gobi_get_type ())
-#define MM_PLUGIN_GOBI(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_GOBI, MMPluginGobi))
-#define MM_PLUGIN_GOBI_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_PLUGIN_GOBI, MMPluginGobiClass))
-#define MM_IS_PLUGIN_GOBI(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_GOBI))
-#define MM_IS_PLUGIN_GOBI_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_PLUGIN_GOBI))
-#define MM_PLUGIN_GOBI_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_PLUGIN_GOBI, MMPluginGobiClass))
-
-typedef struct {
-    MMPlugin parent;
-} MMPluginGobi;
-
-typedef struct {
-    MMPluginClass parent;
-} MMPluginGobiClass;
-
-GType mm_plugin_gobi_get_type (void);
-
-G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
-
-#endif /* MM_PLUGIN_GOBI_H */
diff --git a/plugins/huawei/mm-modem-helpers-huawei.c b/plugins/huawei/mm-modem-helpers-huawei.c
index 7612e64..5362c55 100644
--- a/plugins/huawei/mm-modem-helpers-huawei.c
+++ b/plugins/huawei/mm-modem-helpers-huawei.c
@@ -42,8 +42,8 @@
     GError *inner_error = NULL;
 
     if (!response ||
-        !(g_str_has_prefix (response, "^NDISSTAT:") ||
-          g_str_has_prefix (response, "^NDISSTATQRY:"))) {
+        !(g_ascii_strncasecmp (response, "^NDISSTAT:", strlen ("^NDISSTAT:")) == 0 ||
+          g_ascii_strncasecmp (response, "^NDISSTATQRY:", strlen ("^NDISSTATQRY:")) == 0)) {
         g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Missing ^NDISSTAT / ^NDISSTATQRY prefix");
         return FALSE;
     }
@@ -61,47 +61,81 @@
      * Or, in newer firmwares:
      *     ^NDISSTATQRY:0,,,"IPV4",0,,,"IPV6"
      *     OK
+     *
+     * Or, even (handled separately):
+     *     ^NDISSTATQry:1
+     *     OK
      */
-    r = g_regex_new ("\\^NDISSTAT(?:QRY)?:\\s*(\\d),([^,]*),([^,]*),([^,\\r\\n]*)(?:\\r\\n)?"
-                     "(?:\\^NDISSTAT:|\\^NDISSTATQRY:)?\\s*,?(\\d)?,?([^,]*)?,?([^,]*)?,?([^,\\r\\n]*)?(?:\\r\\n)?",
-                     G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
-                     0, NULL);
-    g_assert (r != NULL);
 
-    g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
-    if (!inner_error && g_match_info_matches (match_info)) {
-        guint ip_type_field = 4;
+    /* If multiple fields available, try first parsing method */
+    if (strchr (response, ',')) {
+        r = g_regex_new ("\\^NDISSTAT(?:QRY)?(?:Qry)?:\\s*(\\d),([^,]*),([^,]*),([^,\\r\\n]*)(?:\\r\\n)?"
+                         "(?:\\^NDISSTAT:|\\^NDISSTATQRY:)?\\s*,?(\\d)?,?([^,]*)?,?([^,]*)?,?([^,\\r\\n]*)?(?:\\r\\n)?",
+                         G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+                         0, NULL);
+        g_assert (r != NULL);
 
-        /* IPv4 and IPv6 are fields 4 and (if available) 8 */
+        g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+        if (!inner_error && g_match_info_matches (match_info)) {
+            guint ip_type_field = 4;
 
-        while (!inner_error && ip_type_field <= 8) {
-            gchar *ip_type_str;
+            /* IPv4 and IPv6 are fields 4 and (if available) 8 */
+
+            while (!inner_error && ip_type_field <= 8) {
+                gchar *ip_type_str;
+                guint connected;
+
+                ip_type_str = mm_get_string_unquoted_from_match_info (match_info, ip_type_field);
+                if (!ip_type_str)
+                    break;
+
+                if (!mm_get_uint_from_match_info (match_info, (ip_type_field - 3), &connected) ||
+                    (connected != 0 && connected != 1)) {
+                    inner_error = g_error_new (MM_CORE_ERROR,
+                                               MM_CORE_ERROR_FAILED,
+                                               "Couldn't parse ^NDISSTAT / ^NDISSTATQRY fields");
+                } else if (g_ascii_strcasecmp (ip_type_str, "IPV4") == 0) {
+                    *ipv4_available = TRUE;
+                    *ipv4_connected = (gboolean)connected;
+                } else if (g_ascii_strcasecmp (ip_type_str, "IPV6") == 0) {
+                    *ipv6_available = TRUE;
+                    *ipv6_connected = (gboolean)connected;
+                }
+
+                g_free (ip_type_str);
+                ip_type_field += 4;
+            }
+        }
+
+        g_match_info_free (match_info);
+        g_regex_unref (r);
+    }
+    /* No separate IPv4/IPv6 info given just connected/not connected */
+    else {
+        r = g_regex_new ("\\^NDISSTAT(?:QRY)?(?:Qry)?:\\s*(\\d)(?:\\r\\n)?",
+                         G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW,
+                         0, NULL);
+        g_assert (r != NULL);
+
+        g_regex_match_full (r, response, strlen (response), 0, 0, &match_info, &inner_error);
+        if (!inner_error && g_match_info_matches (match_info)) {
             guint connected;
 
-            ip_type_str = mm_get_string_unquoted_from_match_info (match_info, ip_type_field);
-            if (!ip_type_str)
-                break;
-
-            if (!mm_get_uint_from_match_info (match_info, (ip_type_field - 3), &connected) ||
+            if (!mm_get_uint_from_match_info (match_info, 1, &connected) ||
                 (connected != 0 && connected != 1)) {
                 inner_error = g_error_new (MM_CORE_ERROR,
                                            MM_CORE_ERROR_FAILED,
                                            "Couldn't parse ^NDISSTAT / ^NDISSTATQRY fields");
-            } else if (g_ascii_strcasecmp (ip_type_str, "IPV4") == 0) {
+            } else {
+                /* We'll assume IPv4 */
                 *ipv4_available = TRUE;
                 *ipv4_connected = (gboolean)connected;
-            } else if (g_ascii_strcasecmp (ip_type_str, "IPV6") == 0) {
-                *ipv6_available = TRUE;
-                *ipv6_connected = (gboolean)connected;
             }
-
-            g_free (ip_type_str);
-            ip_type_field += 4;
         }
-    }
 
-    g_match_info_free (match_info);
-    g_regex_unref (r);
+        g_match_info_free (match_info);
+        g_regex_unref (r);
+    }
 
     if (!ipv4_available && !ipv6_available) {
         inner_error = g_error_new (MM_CORE_ERROR,
@@ -1183,4 +1217,3 @@
 
     return ret;
 }
-
diff --git a/plugins/huawei/tests/test-modem-helpers-huawei.c b/plugins/huawei/tests/test-modem-helpers-huawei.c
index 9e92eee..19e6dec 100644
--- a/plugins/huawei/tests/test-modem-helpers-huawei.c
+++ b/plugins/huawei/tests/test-modem-helpers-huawei.c
@@ -101,6 +101,10 @@
     { "^NDISSTATQRY: 1,,,\"IPV4\",0,,,\"IPV6\"\r\n", TRUE,  TRUE,  TRUE,  FALSE },
     { "^NDISSTATQRY: 0,,,\"IPV4\",1,,,\"IPV6\"\r\n", TRUE,  FALSE, TRUE,  TRUE  },
     { "^NDISSTATQRY: 0,,,\"IPV4\",0,,,\"IPV6\"\r\n", TRUE,  FALSE, TRUE,  FALSE },
+    { "^NDISSTATQry:1",     TRUE, TRUE,  FALSE, FALSE },
+    { "^NDISSTATQry:1\r\n", TRUE, TRUE,  FALSE, FALSE },
+    { "^NDISSTATQry:0",     TRUE, FALSE, FALSE, FALSE },
+    { "^NDISSTATQry:0\r\n", TRUE, FALSE, FALSE, FALSE },
     { NULL, FALSE, FALSE, FALSE, FALSE }
 };
 
@@ -420,6 +424,7 @@
         found = mm_huawei_parse_prefmode_response (prefmode_response_tests[i].str,
                                                    combinations,
                                                    &error);
+        g_assert_no_error (error);
         g_assert (found != NULL);
         g_assert_cmpuint (found->allowed, ==, prefmode_response_tests[i].allowed);
         g_assert_cmpuint (found->preferred, ==, prefmode_response_tests[i].preferred);
@@ -672,6 +677,7 @@
                                                  combinations,
                                                  &error);
 
+        g_assert_no_error (error);
         g_assert (found != NULL);
         g_assert_cmpuint (found->allowed, ==, syscfg_response_tests[i].allowed);
         g_assert_cmpuint (found->preferred, ==, syscfg_response_tests[i].preferred);
@@ -993,6 +999,7 @@
                                                    combinations,
                                                    &error);
 
+        g_assert_no_error (error);
         g_assert (found != NULL);
         g_assert_cmpuint (found->allowed, ==, syscfgex_response_tests[i].allowed);
         g_assert_cmpuint (found->preferred, ==, syscfgex_response_tests[i].preferred);
@@ -1085,8 +1092,10 @@
             g_assert (nwtime_tests[i].leap_seconds == mm_network_timezone_get_leap_seconds (tz));
         }
 
-        if (iso8601)
-            g_free (iso8601);
+        g_free (iso8601);
+
+        if (tz)
+            g_object_unref (tz);
     }
 }
 
@@ -1125,11 +1134,10 @@
 
         g_assert (ret == time_tests[i].ret);
         g_assert (ret == (error ? FALSE : TRUE));
+        g_clear_error (&error);
 
         g_assert_cmpstr (time_tests[i].iso8601, ==, iso8601);
-
-        if (iso8601)
-            g_free (iso8601);
+        g_free (iso8601);
     }
 }
 
diff --git a/plugins/icera/tests/test-modem-helpers-icera.c b/plugins/icera/tests/test-modem-helpers-icera.c
index e7d1645..2e7b26f 100644
--- a/plugins/icera/tests/test-modem-helpers-icera.c
+++ b/plugins/icera/tests/test-modem-helpers-icera.c
@@ -141,6 +141,7 @@
                 g_assert_cmpint (dnslen, ==, 1);
             g_assert_cmpstr (dns[0], ==, ipdpaddr_tests[i].ipv4_dns1);
             g_assert_cmpstr (dns[1], ==, ipdpaddr_tests[i].ipv4_dns2);
+            g_object_unref (ipv4);
         } else
             g_assert (ipv4 == NULL);
 
@@ -166,6 +167,7 @@
             dnslen = g_strv_length ((gchar **) dns);
             g_assert_cmpint (dnslen, ==, 1);
             g_assert_cmpstr (dns[0], ==, ipdpaddr_tests[i].ipv6_dns1);
+            g_object_unref (ipv6);
         } else
             g_assert (ipv6 == NULL);
     }
diff --git a/plugins/mbm/tests/test-modem-helpers-mbm.c b/plugins/mbm/tests/test-modem-helpers-mbm.c
index 2e6dd1a..0c48894 100644
--- a/plugins/mbm/tests/test-modem-helpers-mbm.c
+++ b/plugins/mbm/tests/test-modem-helpers-mbm.c
@@ -96,6 +96,7 @@
                 g_assert_cmpint (dnslen, ==, 1);
             g_assert_cmpstr (dns[0], ==, tests[i].ipv4_dns1);
             g_assert_cmpstr (dns[1], ==, tests[i].ipv4_dns2);
+            g_object_unref (ipv4);
         } else
             g_assert (ipv4 == NULL);
 
@@ -122,6 +123,7 @@
                 g_assert_cmpint (dnslen, ==, 1);
             g_assert_cmpstr (dns[0], ==, tests[i].ipv6_dns1);
             g_assert_cmpstr (dns[1], ==, tests[i].ipv6_dns2);
+            g_object_unref (ipv6);
         } else
             g_assert (ipv6 == NULL);
     }
diff --git a/plugins/novatel/mm-broadband-modem-novatel.c b/plugins/novatel/mm-broadband-modem-novatel.c
index 765595e..9fd14e2 100644
--- a/plugins/novatel/mm-broadband-modem-novatel.c
+++ b/plugins/novatel/mm-broadband-modem-novatel.c
@@ -1231,7 +1231,7 @@
                                  GAsyncResult *res,
                                  GError **error)
 {
-    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
+    return !!mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
 }
 
 static void
diff --git a/plugins/novatel/mm-common-novatel.c b/plugins/novatel/mm-common-novatel.c
new file mode 100644
index 0000000..5305d12
--- /dev/null
+++ b/plugins/novatel/mm-common-novatel.c
@@ -0,0 +1,158 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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:
+ *
+ * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-common-novatel.h"
+#include "mm-log.h"
+
+/*****************************************************************************/
+/* Custom init */
+
+typedef struct {
+    MMPortProbe *probe;
+    MMPortSerialAt *port;
+    GCancellable *cancellable;
+    GSimpleAsyncResult *result;
+    guint nwdmat_retries;
+    guint wait_time;
+} CustomInitContext;
+
+static void
+custom_init_context_complete_and_free (CustomInitContext *ctx)
+{
+    g_simple_async_result_complete_in_idle (ctx->result);
+
+    if (ctx->cancellable)
+        g_object_unref (ctx->cancellable);
+    g_object_unref (ctx->port);
+    g_object_unref (ctx->probe);
+    g_object_unref (ctx->result);
+    g_slice_free (CustomInitContext, ctx);
+}
+
+gboolean
+mm_common_novatel_custom_init_finish (MMPortProbe *probe,
+                                      GAsyncResult *result,
+                                      GError **error)
+{
+    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+static void custom_init_step (CustomInitContext *ctx);
+
+static void
+nwdmat_ready (MMPortSerialAt *port,
+              GAsyncResult *res,
+              CustomInitContext *ctx)
+{
+    const gchar *response;
+    GError *error = NULL;
+
+    response = mm_port_serial_at_command_finish (port, res, &error);
+    if (error) {
+        if (g_error_matches (error,
+                             MM_SERIAL_ERROR,
+                             MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
+            custom_init_step (ctx);
+            goto out;
+        }
+
+        mm_dbg ("(Novatel) Error flipping secondary ports to AT mode: %s", error->message);
+    }
+
+    /* Finish custom_init */
+    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+    custom_init_context_complete_and_free (ctx);
+
+out:
+    if (error)
+        g_error_free (error);
+}
+
+static gboolean
+custom_init_wait_cb (CustomInitContext *ctx)
+{
+    custom_init_step (ctx);
+    return FALSE;
+}
+
+static void
+custom_init_step (CustomInitContext *ctx)
+{
+    /* If cancelled, end */
+    if (g_cancellable_is_cancelled (ctx->cancellable)) {
+        mm_dbg ("(Novatel) no need to keep on running custom init in (%s)",
+                mm_port_get_device (MM_PORT (ctx->port)));
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+        custom_init_context_complete_and_free (ctx);
+        return;
+    }
+
+    /* If device has a QMI port, don't run $NWDMAT */
+    if (mm_port_probe_list_has_qmi_port (mm_device_peek_port_probe_list (mm_port_probe_peek_device (ctx->probe)))) {
+        mm_dbg ("(Novatel) no need to run custom init in (%s): device has QMI port",
+                mm_port_get_device (MM_PORT (ctx->port)));
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+        custom_init_context_complete_and_free (ctx);
+        return;
+    }
+
+    if (ctx->wait_time > 0) {
+        ctx->wait_time--;
+        g_timeout_add_seconds (1, (GSourceFunc)custom_init_wait_cb, ctx);
+        return;
+    }
+
+    if (ctx->nwdmat_retries > 0) {
+        ctx->nwdmat_retries--;
+        mm_port_serial_at_command (ctx->port,
+                                   "$NWDMAT=1",
+                                   3,
+                                   FALSE, /* raw */
+                                   FALSE, /* allow_cached */
+                                   ctx->cancellable,
+                                   (GAsyncReadyCallback)nwdmat_ready,
+                                   ctx);
+        return;
+    }
+
+    /* Finish custom_init */
+    mm_dbg ("(Novatel) couldn't flip secondary port to AT in (%s): all retries consumed",
+            mm_port_get_device (MM_PORT (ctx->port)));
+    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+    custom_init_context_complete_and_free (ctx);
+}
+
+void
+mm_common_novatel_custom_init (MMPortProbe *probe,
+                               MMPortSerialAt *port,
+                               GCancellable *cancellable,
+                               GAsyncReadyCallback callback,
+                               gpointer user_data)
+{
+    CustomInitContext *ctx;
+
+    ctx = g_slice_new (CustomInitContext);
+    ctx->result = g_simple_async_result_new (G_OBJECT (probe),
+                                             callback,
+                                             user_data,
+                                             mm_common_novatel_custom_init);
+    ctx->probe = g_object_ref (probe);
+    ctx->port = g_object_ref (port);
+    ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+    ctx->nwdmat_retries = 3;
+    ctx->wait_time = 2;
+
+    custom_init_step (ctx);
+}
diff --git a/plugins/novatel/mm-common-novatel.h b/plugins/novatel/mm-common-novatel.h
new file mode 100644
index 0000000..70572fd
--- /dev/null
+++ b/plugins/novatel/mm-common-novatel.h
@@ -0,0 +1,31 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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:
+ *
+ * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_COMMON_NOVATEL_H
+#define MM_COMMON_NOVATEL_H
+
+#include "glib.h"
+#include "mm-plugin.h"
+
+void     mm_common_novatel_custom_init        (MMPortProbe *probe,
+                                               MMPortSerialAt *port,
+                                               GCancellable *cancellable,
+                                               GAsyncReadyCallback callback,
+                                               gpointer user_data);
+gboolean mm_common_novatel_custom_init_finish (MMPortProbe *probe,
+                                               GAsyncResult *result,
+                                               GError **error);
+
+#endif  /* MM_COMMON_NOVATEL_H */
diff --git a/plugins/novatel/mm-plugin-novatel.c b/plugins/novatel/mm-plugin-novatel.c
index b57c0e8..9476150 100644
--- a/plugins/novatel/mm-plugin-novatel.c
+++ b/plugins/novatel/mm-plugin-novatel.c
@@ -28,6 +28,7 @@
 #include <libmm-glib.h>
 
 #include "mm-plugin-novatel.h"
+#include "mm-common-novatel.h"
 #include "mm-private-boxed-types.h"
 #include "mm-broadband-modem-novatel.h"
 #include "mm-log.h"
@@ -42,147 +43,6 @@
 int mm_plugin_minor_version = MM_PLUGIN_MINOR_VERSION;
 
 /*****************************************************************************/
-/* Custom init */
-
-typedef struct {
-    MMPortProbe *probe;
-    MMPortSerialAt *port;
-    GCancellable *cancellable;
-    GSimpleAsyncResult *result;
-    guint nwdmat_retries;
-    guint wait_time;
-} CustomInitContext;
-
-static void
-custom_init_context_complete_and_free (CustomInitContext *ctx)
-{
-    g_simple_async_result_complete_in_idle (ctx->result);
-
-    if (ctx->cancellable)
-        g_object_unref (ctx->cancellable);
-    g_object_unref (ctx->port);
-    g_object_unref (ctx->probe);
-    g_object_unref (ctx->result);
-    g_slice_free (CustomInitContext, ctx);
-}
-
-static gboolean
-novatel_custom_init_finish (MMPortProbe *probe,
-                            GAsyncResult *result,
-                            GError **error)
-{
-    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
-}
-
-static void custom_init_step (CustomInitContext *ctx);
-
-static void
-nwdmat_ready (MMPortSerialAt *port,
-              GAsyncResult *res,
-              CustomInitContext *ctx)
-{
-    const gchar *response;
-    GError *error = NULL;
-
-    response = mm_port_serial_at_command_finish (port, res, &error);
-    if (error) {
-        if (g_error_matches (error,
-                             MM_SERIAL_ERROR,
-                             MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
-            custom_init_step (ctx);
-            goto out;
-        }
-
-        mm_dbg ("(Novatel) Error flipping secondary ports to AT mode: %s", error->message);
-    }
-
-    /* Finish custom_init */
-    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
-    custom_init_context_complete_and_free (ctx);
-
-out:
-    if (error)
-        g_error_free (error);
-}
-
-static gboolean
-custom_init_wait_cb (CustomInitContext *ctx)
-{
-    custom_init_step (ctx);
-    return FALSE;
-}
-
-static void
-custom_init_step (CustomInitContext *ctx)
-{
-    /* If cancelled, end */
-    if (g_cancellable_is_cancelled (ctx->cancellable)) {
-        mm_dbg ("(Novatel) no need to keep on running custom init in (%s)",
-                mm_port_get_device (MM_PORT (ctx->port)));
-        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
-        custom_init_context_complete_and_free (ctx);
-        return;
-    }
-
-    /* If device has a QMI port, don't run $NWDMAT */
-    if (mm_port_probe_list_has_qmi_port (mm_device_peek_port_probe_list (mm_port_probe_peek_device (ctx->probe)))) {
-        mm_dbg ("(Novatel) no need to run custom init in (%s): device has QMI port",
-                mm_port_get_device (MM_PORT (ctx->port)));
-        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
-        custom_init_context_complete_and_free (ctx);
-        return;
-    }
-
-    if (ctx->wait_time > 0) {
-        ctx->wait_time--;
-        g_timeout_add_seconds (1, (GSourceFunc)custom_init_wait_cb, ctx);
-        return;
-    }
-
-    if (ctx->nwdmat_retries > 0) {
-        ctx->nwdmat_retries--;
-        mm_port_serial_at_command (ctx->port,
-                                   "$NWDMAT=1",
-                                   3,
-                                   FALSE, /* raw */
-                                   FALSE, /* allow_cached */
-                                   ctx->cancellable,
-                                   (GAsyncReadyCallback)nwdmat_ready,
-                                   ctx);
-        return;
-    }
-
-    /* Finish custom_init */
-    mm_dbg ("(Novatel) couldn't flip secondary port to AT in (%s): all retries consumed",
-            mm_port_get_device (MM_PORT (ctx->port)));
-    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
-    custom_init_context_complete_and_free (ctx);
-}
-
-static void
-novatel_custom_init (MMPortProbe *probe,
-                     MMPortSerialAt *port,
-                     GCancellable *cancellable,
-                     GAsyncReadyCallback callback,
-                     gpointer user_data)
-{
-    CustomInitContext *ctx;
-
-    ctx = g_slice_new (CustomInitContext);
-    ctx->result = g_simple_async_result_new (G_OBJECT (probe),
-                                             callback,
-                                             user_data,
-                                             novatel_custom_init);
-    ctx->probe = g_object_ref (probe);
-    ctx->port = g_object_ref (port);
-    ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-    ctx->nwdmat_retries = 3;
-    ctx->wait_time = 2;
-
-    custom_init_step (ctx);
-}
-
-/*****************************************************************************/
 
 static MMBaseModem *
 create_modem (MMPlugin *self,
@@ -217,14 +77,12 @@
 mm_plugin_create (void)
 {
     static const gchar *subsystems[] = { "tty", "net", "usb", NULL };
-    static const guint16 vendors[] = { 0x1410, /* Novatel */
-                                       0x413c, /* Dell */
-                                       0 };
+    static const guint16 vendors[] = { 0x1410, 0 };
     static const mm_uint16_pair forbidden_products[] = { { 0x1410, 0x9010 }, /* Novatel E362 */
                                                          { 0, 0 } };
     static const MMAsyncMethod custom_init = {
-        .async  = G_CALLBACK (novatel_custom_init),
-        .finish = G_CALLBACK (novatel_custom_init_finish),
+        .async  = G_CALLBACK (mm_common_novatel_custom_init),
+        .finish = G_CALLBACK (mm_common_novatel_custom_init_finish),
     };
 
     return MM_PLUGIN (
diff --git a/plugins/sierra/mm-common-sierra.c b/plugins/sierra/mm-common-sierra.c
index 82a5d7b..e712093 100644
--- a/plugins/sierra/mm-common-sierra.c
+++ b/plugins/sierra/mm-common-sierra.c
@@ -15,6 +15,9 @@
  * Copyright (C) 2012 Lanedo GmbH
  */
 
+#include <stdlib.h>
+#include <string.h>
+
 #include "mm-common-sierra.h"
 #include "mm-base-modem-at.h"
 #include "mm-log.h"
@@ -24,6 +27,220 @@
 static MMIfaceModem *iface_modem_parent;
 
 /*****************************************************************************/
+/* Custom init and port type hints */
+
+#define TAG_SIERRA_APP_PORT       "sierra-app-port"
+#define TAG_SIERRA_APP1_PPP_OK    "sierra-app1-ppp-ok"
+
+gboolean
+mm_common_sierra_grab_port (MMPlugin *self,
+                            MMBaseModem *modem,
+                            MMPortProbe *probe,
+                            GError **error)
+{
+    MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
+    MMPortType ptype;
+
+    ptype = mm_port_probe_get_port_type (probe);
+
+    /* Is it a GSM secondary port? */
+    if (g_object_get_data (G_OBJECT (probe), TAG_SIERRA_APP_PORT)) {
+        if (g_object_get_data (G_OBJECT (probe), TAG_SIERRA_APP1_PPP_OK))
+            pflags = MM_PORT_SERIAL_AT_FLAG_PPP;
+        else
+            pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY;
+    } else if (ptype == MM_PORT_TYPE_AT)
+        pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
+
+    return mm_base_modem_grab_port (modem,
+                                    mm_port_probe_get_port_subsys (probe),
+                                    mm_port_probe_get_port_name (probe),
+                                    mm_port_probe_get_parent_path (probe),
+                                    ptype,
+                                    pflags,
+                                    error);
+}
+
+gboolean
+mm_common_sierra_port_probe_list_is_icera (GList *probes)
+{
+    GList *l;
+
+    for (l = probes; l; l = g_list_next (l)) {
+        /* Only assume the Icera probing check is valid IF the port is not
+         * secondary. This will skip the stupid ports which reply OK to every
+         * AT command, even the one we use to check for Icera support */
+        if (mm_port_probe_is_icera (MM_PORT_PROBE (l->data)) &&
+            !g_object_get_data (G_OBJECT (l->data), TAG_SIERRA_APP_PORT))
+            return TRUE;
+    }
+
+    return FALSE;
+}
+
+typedef struct {
+    MMPortProbe *probe;
+    MMPortSerialAt *port;
+    GCancellable *cancellable;
+    GSimpleAsyncResult *result;
+    guint retries;
+} SierraCustomInitContext;
+
+static void
+sierra_custom_init_context_complete_and_free (SierraCustomInitContext *ctx)
+{
+    g_simple_async_result_complete_in_idle (ctx->result);
+
+    if (ctx->cancellable)
+        g_object_unref (ctx->cancellable);
+    g_object_unref (ctx->port);
+    g_object_unref (ctx->probe);
+    g_object_unref (ctx->result);
+    g_slice_free (SierraCustomInitContext, ctx);
+}
+
+gboolean
+mm_common_sierra_custom_init_finish (MMPortProbe *probe,
+                                     GAsyncResult *result,
+                                     GError **error)
+{
+    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+static void sierra_custom_init_step (SierraCustomInitContext *ctx);
+
+static void
+gcap_ready (MMPortSerialAt *port,
+            GAsyncResult *res,
+            SierraCustomInitContext *ctx)
+{
+    const gchar *response;
+    GError *error = NULL;
+
+    response = mm_port_serial_at_command_finish (port, res, &error);
+    if (error) {
+        /* If consumed all tries and the last error was a timeout, assume the
+         * port is not AT */
+        if (ctx->retries == 0 &&
+            g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
+            mm_port_probe_set_result_at (ctx->probe, FALSE);
+        }
+        /* If reported a hard parse error, this port is definitely not an AT
+         * port, skip trying. */
+        else if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_PARSE_FAILED)) {
+            mm_port_probe_set_result_at (ctx->probe, FALSE);
+            ctx->retries = 0;
+        }
+        /* Some Icera-based devices (eg, USB305) have an AT-style port that
+         * replies to everything with ERROR, so tag as unsupported; sometimes
+         * the real AT ports do this too, so let a retry tag the port as
+         * supported if it responds correctly later. */
+        else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) {
+            mm_port_probe_set_result_at (ctx->probe, FALSE);
+        }
+
+        /* Just retry... */
+        sierra_custom_init_step (ctx);
+        goto out;
+    }
+
+    /* A valid reply to ATI tells us this is an AT port already */
+    mm_port_probe_set_result_at (ctx->probe, TRUE);
+
+    /* Sierra APPx ports have limited AT command parsers that just reply with
+     * "OK" to most commands.  These can sometimes be used for PPP while the
+     * main port is used for status and control, but older modems tend to crash
+     * or fail PPP.  So we whitelist modems that are known to allow PPP on the
+     * secondary APP ports.
+     */
+    if (strstr (response, "APP1")) {
+        g_object_set_data (G_OBJECT (ctx->probe), TAG_SIERRA_APP_PORT, GUINT_TO_POINTER (TRUE));
+
+        /* PPP-on-APP1-port whitelist */
+        if (strstr (response, "C885") ||
+            strstr (response, "USB 306") ||
+            strstr (response, "MC8790"))
+            g_object_set_data (G_OBJECT (ctx->probe), TAG_SIERRA_APP1_PPP_OK, GUINT_TO_POINTER (TRUE));
+
+        /* For debugging: let users figure out if their device supports PPP
+         * on the APP1 port or not.
+         */
+        if (getenv ("MM_SIERRA_APP1_PPP_OK")) {
+            mm_dbg ("Sierra: APP1 PPP OK '%s'", response);
+            g_object_set_data (G_OBJECT (ctx->probe), TAG_SIERRA_APP1_PPP_OK, GUINT_TO_POINTER (TRUE));
+        }
+    } else if (strstr (response, "APP2") ||
+               strstr (response, "APP3") ||
+               strstr (response, "APP4")) {
+        /* Additional APP ports don't support most AT commands, so they cannot
+         * be used as the primary port.
+         */
+        g_object_set_data (G_OBJECT (ctx->probe), TAG_SIERRA_APP_PORT, GUINT_TO_POINTER (TRUE));
+    }
+
+    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+    sierra_custom_init_context_complete_and_free (ctx);
+
+out:
+    if (error)
+        g_error_free (error);
+}
+
+static void
+sierra_custom_init_step (SierraCustomInitContext *ctx)
+{
+    /* If cancelled, end */
+    if (g_cancellable_is_cancelled (ctx->cancellable)) {
+        mm_dbg ("(Sierra) no need to keep on running custom init in '%s'",
+                mm_port_get_device (MM_PORT (ctx->port)));
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+        sierra_custom_init_context_complete_and_free (ctx);
+        return;
+    }
+
+    if (ctx->retries == 0) {
+        mm_dbg ("(Sierra) Couldn't get port type hints from '%s'",
+                mm_port_get_device (MM_PORT (ctx->port)));
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+        sierra_custom_init_context_complete_and_free (ctx);
+        return;
+    }
+
+    ctx->retries--;
+    mm_port_serial_at_command (
+        ctx->port,
+        "ATI",
+        3,
+        FALSE, /* raw */
+        FALSE, /* allow_cached */
+        ctx->cancellable,
+        (GAsyncReadyCallback)gcap_ready,
+        ctx);
+}
+
+void
+mm_common_sierra_custom_init (MMPortProbe *probe,
+                              MMPortSerialAt *port,
+                              GCancellable *cancellable,
+                              GAsyncReadyCallback callback,
+                              gpointer user_data)
+{
+    SierraCustomInitContext *ctx;
+
+    ctx = g_slice_new (SierraCustomInitContext);
+    ctx->result = g_simple_async_result_new (G_OBJECT (probe),
+                                             callback,
+                                             user_data,
+                                             mm_common_sierra_custom_init);
+    ctx->probe = g_object_ref (probe);
+    ctx->port = g_object_ref (port);
+    ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+    ctx->retries = 3;
+
+    sierra_custom_init_step (ctx);
+}
+
+/*****************************************************************************/
 /* Modem power up (Modem interface) */
 
 gboolean
diff --git a/plugins/sierra/mm-common-sierra.h b/plugins/sierra/mm-common-sierra.h
index ec206d2..22471c0 100644
--- a/plugins/sierra/mm-common-sierra.h
+++ b/plugins/sierra/mm-common-sierra.h
@@ -18,10 +18,27 @@
 #ifndef MM_COMMON_SIERRA_H
 #define MM_COMMON_SIERRA_H
 
+#include "mm-plugin.h"
 #include "mm-broadband-modem.h"
 #include "mm-iface-modem.h"
 #include "mm-base-sim.h"
 
+gboolean mm_common_sierra_grab_port (MMPlugin *self,
+                                     MMBaseModem *modem,
+                                     MMPortProbe *probe,
+                                     GError **error);
+
+gboolean mm_common_sierra_port_probe_list_is_icera (GList *probes);
+
+void     mm_common_sierra_custom_init        (MMPortProbe *probe,
+                                              MMPortSerialAt *port,
+                                              GCancellable *cancellable,
+                                              GAsyncReadyCallback callback,
+                                              gpointer user_data);
+gboolean mm_common_sierra_custom_init_finish (MMPortProbe *probe,
+                                              GAsyncResult *result,
+                                              GError **error);
+
 void              mm_common_sierra_load_power_state        (MMIfaceModem *self,
                                                             GAsyncReadyCallback callback,
                                                             gpointer user_data);
diff --git a/plugins/sierra/mm-plugin-sierra-legacy.c b/plugins/sierra/mm-plugin-sierra-legacy.c
new file mode 100644
index 0000000..9064bcf
--- /dev/null
+++ b/plugins/sierra/mm-plugin-sierra-legacy.c
@@ -0,0 +1,99 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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:
+ *
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2012 Red Hat, Inc.
+ * Copyright (C) 2012 Lanedo GmbH
+ * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include <stdlib.h>
+#include <gmodule.h>
+
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
+#include "mm-log.h"
+#include "mm-plugin-sierra-legacy.h"
+#include "mm-common-sierra.h"
+#include "mm-broadband-modem-sierra.h"
+#include "mm-broadband-modem-sierra-icera.h"
+
+G_DEFINE_TYPE (MMPluginSierraLegacy, mm_plugin_sierra_legacy, MM_TYPE_PLUGIN)
+
+int mm_plugin_major_version = MM_PLUGIN_MAJOR_VERSION;
+int mm_plugin_minor_version = MM_PLUGIN_MINOR_VERSION;
+
+/*****************************************************************************/
+
+static MMBaseModem *
+create_modem (MMPlugin *self,
+              const gchar *sysfs_path,
+              const gchar **drivers,
+              guint16 vendor,
+              guint16 product,
+              GList *probes,
+              GError **error)
+{
+    if (mm_common_sierra_port_probe_list_is_icera (probes))
+        return MM_BASE_MODEM (mm_broadband_modem_sierra_icera_new (sysfs_path,
+                                                                   drivers,
+                                                                   mm_plugin_get_name (self),
+                                                                   vendor,
+                                                                   product));
+
+    return MM_BASE_MODEM (mm_broadband_modem_sierra_new (sysfs_path,
+                                                         drivers,
+                                                         mm_plugin_get_name (self),
+                                                         vendor,
+                                                         product));
+}
+
+/*****************************************************************************/
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+    static const gchar *subsystems[] = { "tty", "net", NULL };
+    static const gchar *drivers[] = { "sierra", "sierra_net", NULL };
+    static const gchar *forbidden_drivers[] = { "qmi_wwan", "cdc_mbim", NULL };
+    static const MMAsyncMethod custom_init = {
+        .async  = G_CALLBACK (mm_common_sierra_custom_init),
+        .finish = G_CALLBACK (mm_common_sierra_custom_init_finish),
+    };
+
+    return MM_PLUGIN (
+        g_object_new (MM_TYPE_PLUGIN_SIERRA_LEGACY,
+                      MM_PLUGIN_NAME,                "Sierra (legacy)",
+                      MM_PLUGIN_ALLOWED_SUBSYSTEMS,  subsystems,
+                      MM_PLUGIN_ALLOWED_DRIVERS,     drivers,
+                      MM_PLUGIN_FORBIDDEN_DRIVERS,   forbidden_drivers,
+                      MM_PLUGIN_ALLOWED_AT,          TRUE,
+                      MM_PLUGIN_CUSTOM_INIT,         &custom_init,
+                      MM_PLUGIN_ICERA_PROBE,         TRUE,
+                      MM_PLUGIN_REMOVE_ECHO,         FALSE,
+                      NULL));
+}
+
+static void
+mm_plugin_sierra_legacy_init (MMPluginSierraLegacy *self)
+{
+}
+
+static void
+mm_plugin_sierra_legacy_class_init (MMPluginSierraLegacyClass *klass)
+{
+    MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
+
+    plugin_class->create_modem = create_modem;
+    plugin_class->grab_port = mm_common_sierra_grab_port;
+}
diff --git a/plugins/sierra/mm-plugin-sierra-legacy.h b/plugins/sierra/mm-plugin-sierra-legacy.h
new file mode 100644
index 0000000..787118d
--- /dev/null
+++ b/plugins/sierra/mm-plugin-sierra-legacy.h
@@ -0,0 +1,40 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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:
+ *
+ * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_PLUGIN_SIERRA_LEGACY_H
+#define MM_PLUGIN_SIERRA_LEGACY_H
+
+#include "mm-plugin.h"
+
+#define MM_TYPE_PLUGIN_SIERRA_LEGACY            (mm_plugin_sierra_legacy_get_type ())
+#define MM_PLUGIN_SIERRA_LEGACY(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_SIERRA_LEGACY, MMPluginSierraLegacy))
+#define MM_PLUGIN_SIERRA_LEGACY_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_PLUGIN_SIERRA_LEGACY, MMPluginSierraLegacyClass))
+#define MM_IS_PLUGIN_SIERRA_LEGACY(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_SIERRA_LEGACY))
+#define MM_IS_PLUGIN_SIERRA_LEGACY_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_PLUGIN_SIERRA_LEGACY))
+#define MM_PLUGIN_SIERRA_LEGACY_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_PLUGIN_SIERRA_LEGACY, MMPluginSierraLegacyClass))
+
+typedef struct {
+    MMPlugin parent;
+} MMPluginSierraLegacy;
+
+typedef struct {
+    MMPluginClass parent;
+} MMPluginSierraLegacyClass;
+
+GType mm_plugin_sierra_legacy_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_SIERRA_LEGACY_H */
diff --git a/plugins/sierra/mm-plugin-sierra.c b/plugins/sierra/mm-plugin-sierra.c
index 96f657a..62262ae 100644
--- a/plugins/sierra/mm-plugin-sierra.c
+++ b/plugins/sierra/mm-plugin-sierra.c
@@ -13,9 +13,9 @@
  * Copyright (C) 2008 - 2009 Novell, Inc.
  * Copyright (C) 2009 - 2012 Red Hat, Inc.
  * Copyright (C) 2012 Lanedo GmbH
+ * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
  */
 
-#include <string.h>
 #include <stdlib.h>
 #include <gmodule.h>
 
@@ -24,8 +24,6 @@
 
 #include "mm-log.h"
 #include "mm-plugin-sierra.h"
-#include "mm-broadband-modem-sierra.h"
-#include "mm-broadband-modem-sierra-icera.h"
 
 #if defined WITH_QMI
 #include "mm-broadband-modem-qmi.h"
@@ -41,191 +39,6 @@
 int mm_plugin_minor_version = MM_PLUGIN_MINOR_VERSION;
 
 /*****************************************************************************/
-/* Custom init */
-
-#define TAG_SIERRA_APP_PORT       "sierra-app-port"
-#define TAG_SIERRA_APP1_PPP_OK    "sierra-app1-ppp-ok"
-
-typedef struct {
-    MMPortProbe *probe;
-    MMPortSerialAt *port;
-    GCancellable *cancellable;
-    GSimpleAsyncResult *result;
-    guint retries;
-} SierraCustomInitContext;
-
-static void
-sierra_custom_init_context_complete_and_free (SierraCustomInitContext *ctx)
-{
-    g_simple_async_result_complete_in_idle (ctx->result);
-
-    if (ctx->cancellable)
-        g_object_unref (ctx->cancellable);
-    g_object_unref (ctx->port);
-    g_object_unref (ctx->probe);
-    g_object_unref (ctx->result);
-    g_slice_free (SierraCustomInitContext, ctx);
-}
-
-static gboolean
-sierra_custom_init_finish (MMPortProbe *probe,
-                           GAsyncResult *result,
-                           GError **error)
-{
-    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
-}
-
-static void sierra_custom_init_step (SierraCustomInitContext *ctx);
-
-static void
-gcap_ready (MMPortSerialAt *port,
-            GAsyncResult *res,
-            SierraCustomInitContext *ctx)
-{
-    const gchar *response;
-    GError *error = NULL;
-
-    response = mm_port_serial_at_command_finish (port, res, &error);
-    if (error) {
-        /* If consumed all tries and the last error was a timeout, assume the
-         * port is not AT */
-        if (ctx->retries == 0 &&
-            g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_RESPONSE_TIMEOUT)) {
-            mm_port_probe_set_result_at (ctx->probe, FALSE);
-        }
-        /* If reported a hard parse error, this port is definitely not an AT
-         * port, skip trying. */
-        else if (g_error_matches (error, MM_SERIAL_ERROR, MM_SERIAL_ERROR_PARSE_FAILED)) {
-            mm_port_probe_set_result_at (ctx->probe, FALSE);
-            ctx->retries = 0;
-        }
-        /* Some Icera-based devices (eg, USB305) have an AT-style port that
-         * replies to everything with ERROR, so tag as unsupported; sometimes
-         * the real AT ports do this too, so let a retry tag the port as
-         * supported if it responds correctly later. */
-        else if (g_error_matches (error, MM_MOBILE_EQUIPMENT_ERROR, MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN)) {
-            mm_port_probe_set_result_at (ctx->probe, FALSE);
-        }
-
-        /* Just retry... */
-        sierra_custom_init_step (ctx);
-        goto out;
-    }
-
-    /* A valid reply to ATI tells us this is an AT port already */
-    mm_port_probe_set_result_at (ctx->probe, TRUE);
-
-    /* Sierra APPx ports have limited AT command parsers that just reply with
-     * "OK" to most commands.  These can sometimes be used for PPP while the
-     * main port is used for status and control, but older modems tend to crash
-     * or fail PPP.  So we whitelist modems that are known to allow PPP on the
-     * secondary APP ports.
-     */
-    if (strstr (response, "APP1")) {
-        g_object_set_data (G_OBJECT (ctx->probe), TAG_SIERRA_APP_PORT, GUINT_TO_POINTER (TRUE));
-
-        /* PPP-on-APP1-port whitelist */
-        if (strstr (response, "C885") ||
-            strstr (response, "USB 306") ||
-            strstr (response, "MC8790"))
-            g_object_set_data (G_OBJECT (ctx->probe), TAG_SIERRA_APP1_PPP_OK, GUINT_TO_POINTER (TRUE));
-
-        /* For debugging: let users figure out if their device supports PPP
-         * on the APP1 port or not.
-         */
-        if (getenv ("MM_SIERRA_APP1_PPP_OK")) {
-            mm_dbg ("Sierra: APP1 PPP OK '%s'", response);
-            g_object_set_data (G_OBJECT (ctx->probe), TAG_SIERRA_APP1_PPP_OK, GUINT_TO_POINTER (TRUE));
-        }
-    } else if (strstr (response, "APP2") ||
-               strstr (response, "APP3") ||
-               strstr (response, "APP4")) {
-        /* Additional APP ports don't support most AT commands, so they cannot
-         * be used as the primary port.
-         */
-        g_object_set_data (G_OBJECT (ctx->probe), TAG_SIERRA_APP_PORT, GUINT_TO_POINTER (TRUE));
-    }
-
-    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
-    sierra_custom_init_context_complete_and_free (ctx);
-
-out:
-    if (error)
-        g_error_free (error);
-}
-
-static void
-sierra_custom_init_step (SierraCustomInitContext *ctx)
-{
-    /* If cancelled, end */
-    if (g_cancellable_is_cancelled (ctx->cancellable)) {
-        mm_dbg ("(Sierra) no need to keep on running custom init in '%s'",
-                mm_port_get_device (MM_PORT (ctx->port)));
-        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
-        sierra_custom_init_context_complete_and_free (ctx);
-        return;
-    }
-
-    if (ctx->retries == 0) {
-        mm_dbg ("(Sierra) Couldn't get port type hints from '%s'",
-                mm_port_get_device (MM_PORT (ctx->port)));
-        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
-        sierra_custom_init_context_complete_and_free (ctx);
-        return;
-    }
-
-    ctx->retries--;
-    mm_port_serial_at_command (
-        ctx->port,
-        "ATI",
-        3,
-        FALSE, /* raw */
-        FALSE, /* allow_cached */
-        ctx->cancellable,
-        (GAsyncReadyCallback)gcap_ready,
-        ctx);
-}
-
-static void
-sierra_custom_init (MMPortProbe *probe,
-                    MMPortSerialAt *port,
-                    GCancellable *cancellable,
-                    GAsyncReadyCallback callback,
-                    gpointer user_data)
-{
-    SierraCustomInitContext *ctx;
-
-    ctx = g_slice_new (SierraCustomInitContext);
-    ctx->result = g_simple_async_result_new (G_OBJECT (probe),
-                                             callback,
-                                             user_data,
-                                             sierra_custom_init);
-    ctx->probe = g_object_ref (probe);
-    ctx->port = g_object_ref (port);
-    ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
-    ctx->retries = 3;
-
-    sierra_custom_init_step (ctx);
-}
-
-/*****************************************************************************/
-
-static gboolean
-sierra_port_probe_list_is_icera (GList *probes)
-{
-    GList *l;
-
-    for (l = probes; l; l = g_list_next (l)) {
-        /* Only assume the Icera probing check is valid IF the port is not
-         * secondary. This will skip the stupid ports which reply OK to every
-         * AT command, even the one we use to check for Icera support */
-        if (mm_port_probe_is_icera (MM_PORT_PROBE (l->data)) &&
-            !g_object_get_data (G_OBJECT (l->data), TAG_SIERRA_APP_PORT))
-            return TRUE;
-    }
-
-    return FALSE;
-}
 
 static MMBaseModem *
 create_modem (MMPlugin *self,
@@ -258,47 +71,12 @@
     }
 #endif
 
-    if (sierra_port_probe_list_is_icera (probes))
-        return MM_BASE_MODEM (mm_broadband_modem_sierra_icera_new (sysfs_path,
-                                                                   drivers,
-                                                                   mm_plugin_get_name (self),
-                                                                   vendor,
-                                                                   product));
-
-    return MM_BASE_MODEM (mm_broadband_modem_sierra_new (sysfs_path,
-                                                         drivers,
-                                                         mm_plugin_get_name (self),
-                                                         vendor,
-                                                         product));
-}
-
-static gboolean
-grab_port (MMPlugin *self,
-           MMBaseModem *modem,
-           MMPortProbe *probe,
-           GError **error)
-{
-    MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
-    MMPortType ptype;
-
-    ptype = mm_port_probe_get_port_type (probe);
-
-    /* Is it a GSM secondary port? */
-    if (g_object_get_data (G_OBJECT (probe), TAG_SIERRA_APP_PORT)) {
-        if (g_object_get_data (G_OBJECT (probe), TAG_SIERRA_APP1_PPP_OK))
-            pflags = MM_PORT_SERIAL_AT_FLAG_PPP;
-        else
-            pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY;
-    } else if (ptype == MM_PORT_TYPE_AT)
-        pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
-
-    return mm_base_modem_grab_port (modem,
-                                    mm_port_probe_get_port_subsys (probe),
-                                    mm_port_probe_get_port_name (probe),
-                                    mm_port_probe_get_parent_path (probe),
-                                    ptype,
-                                    pflags,
-                                    error);
+    /* Fallback to default modem in the worst case */
+    return MM_BASE_MODEM (mm_broadband_modem_new (sysfs_path,
+                                                  drivers,
+                                                  mm_plugin_get_name (self),
+                                                  vendor,
+                                                  product));
 }
 
 /*****************************************************************************/
@@ -307,24 +85,19 @@
 mm_plugin_create (void)
 {
     static const gchar *subsystems[] = { "tty", "net", "usb", NULL };
-    static const gchar *drivers[] = { "sierra", "sierra_net", NULL };
-    static const MMAsyncMethod custom_init = {
-        .async  = G_CALLBACK (sierra_custom_init),
-        .finish = G_CALLBACK (sierra_custom_init_finish),
-    };
+    static const guint16 vendor_ids[] = { 0x1199, 0 };
+    static const gchar *drivers[] = { "qmi_wwan", "cdc_mbim", NULL };
 
     return MM_PLUGIN (
         g_object_new (MM_TYPE_PLUGIN_SIERRA,
                       MM_PLUGIN_NAME,               "Sierra",
                       MM_PLUGIN_ALLOWED_SUBSYSTEMS, subsystems,
+                      MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
                       MM_PLUGIN_ALLOWED_DRIVERS,    drivers,
                       MM_PLUGIN_ALLOWED_AT,         TRUE,
                       MM_PLUGIN_ALLOWED_QCDM,       TRUE,
                       MM_PLUGIN_ALLOWED_QMI,        TRUE,
                       MM_PLUGIN_ALLOWED_MBIM,       TRUE,
-                      MM_PLUGIN_CUSTOM_INIT,        &custom_init,
-                      MM_PLUGIN_ICERA_PROBE,        TRUE,
-                      MM_PLUGIN_REMOVE_ECHO,        FALSE,
                       NULL));
 }
 
@@ -339,5 +112,4 @@
     MMPluginClass *plugin_class = MM_PLUGIN_CLASS (klass);
 
     plugin_class->create_modem = create_modem;
-    plugin_class->grab_port = grab_port;
 }
diff --git a/plugins/telit/77-mm-telit-port-types.rules b/plugins/telit/77-mm-telit-port-types.rules
index f06822e..1efb0e1 100644
--- a/plugins/telit/77-mm-telit-port-types.rules
+++ b/plugins/telit/77-mm-telit-port-types.rules
@@ -41,6 +41,9 @@
 ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1011", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_TELIT_PORT_TYPE_MODEM}="1"
 ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1011", ENV{ID_MM_TELIT_TAGGED}="1"
 
+# HE910, UE910, UL865 (dynamic port identification supported)
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="0021", ENV{ID_MM_TELIT_TAGGED}="1", ENV{ID_MM_TELIT_PORTS_TAGGED}="1"
+
 # NOTE: Qualcomm Gobi-based devices like the LE920 should not be handled
 # by this plugin, but by the Gobi plugin.
 
diff --git a/plugins/telit/mm-broadband-modem-telit.c b/plugins/telit/mm-broadband-modem-telit.c
index 5e1dbff..971e85e 100644
--- a/plugins/telit/mm-broadband-modem-telit.c
+++ b/plugins/telit/mm-broadband-modem-telit.c
@@ -29,12 +29,15 @@
 #include "mm-modem-helpers.h"
 #include "mm-base-modem-at.h"
 #include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
 #include "mm-broadband-modem-telit.h"
 
 static void iface_modem_init (MMIfaceModem *iface);
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
 
 G_DEFINE_TYPE_EXTENDED (MMBroadbandModemTelit, mm_broadband_modem_telit, MM_TYPE_BROADBAND_MODEM, 0,
-                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init));
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init)
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init));
 
 /*****************************************************************************/
 /* Load access technologies (Modem interface) */
@@ -175,6 +178,92 @@
 }
 
 /*****************************************************************************/
+/* Flow control (Modem interface) */
+
+static gboolean
+setup_flow_control_finish (MMIfaceModem *self,
+                           GAsyncResult *res,
+                           GError **error)
+{
+    /* Completely ignore errors */
+    return TRUE;
+}
+
+static void
+setup_flow_control (MMIfaceModem *self,
+                    GAsyncReadyCallback callback,
+                    gpointer user_data)
+{
+    GSimpleAsyncResult *result;
+    gchar *cmd;
+    guint flow_control = 1; /* Default flow control: XON/XOFF */
+
+    switch (mm_base_modem_get_product_id (MM_BASE_MODEM (self)) & 0xFFFF) {
+    case 0x0021:
+        flow_control = 2; /* Telit IMC modems support only RTS/CTS mode */
+        break;
+    default:
+        break;
+    }
+
+    cmd = g_strdup_printf ("+IFC=%u,%u", flow_control, flow_control);
+    mm_base_modem_at_command (MM_BASE_MODEM (self),
+                              cmd,
+                              3,
+                              FALSE,
+                              NULL,
+                              NULL);
+    result = g_simple_async_result_new (G_OBJECT (self),
+                                        callback,
+                                        user_data,
+                                        setup_flow_control);
+    g_simple_async_result_set_op_res_gboolean (result, TRUE);
+    g_simple_async_result_complete_in_idle (result);
+    g_object_unref (result);
+    g_free (cmd);
+}
+
+/*****************************************************************************/
+/* Enabling unsolicited events (3GPP interface) */
+
+static gboolean
+modem_3gpp_enable_unsolicited_events_finish (MMIfaceModem3gpp *self,
+                                             GAsyncResult *res,
+                                             GError **error)
+{
+   /* Ignore errors */
+    mm_base_modem_at_sequence_full_finish (MM_BASE_MODEM (self),
+                                           res,
+                                           NULL,
+                                           NULL);
+    return TRUE;
+}
+
+static const MMBaseModemAtCommand unsolicited_enable_sequence[] = {
+    /* Enable +CIEV only for: signal, service, roam */
+    { "AT+CIND=0,1,1,0,0,0,1,0,0", 5, FALSE, NULL },
+    /* Telit modems +CMER command supports only <ind>=2 */
+    { "+CMER=3,0,0,2", 5, FALSE, NULL },
+    { NULL }
+};
+
+static void
+modem_3gpp_enable_unsolicited_events (MMIfaceModem3gpp *self,
+                                      GAsyncReadyCallback callback,
+                                      gpointer user_data)
+{
+    mm_base_modem_at_sequence_full (
+        MM_BASE_MODEM (self),
+        mm_base_modem_peek_port_secondary (MM_BASE_MODEM (self)),
+        unsolicited_enable_sequence,
+        NULL,  /* response_processor_context */
+        NULL,  /* response_processor_context_free */
+        NULL,  /* cancellable */
+        callback,
+        user_data);
+}
+
+/*****************************************************************************/
 
 MMBroadbandModemTelit *
 mm_broadband_modem_telit_new (const gchar *device,
@@ -202,6 +291,15 @@
 {
     iface->load_access_technologies = load_access_technologies;
     iface->load_access_technologies_finish = load_access_technologies_finish;
+    iface->setup_flow_control = setup_flow_control;
+    iface->setup_flow_control_finish = setup_flow_control_finish;
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+    iface->enable_unsolicited_events = modem_3gpp_enable_unsolicited_events;
+    iface->enable_unsolicited_events_finish = modem_3gpp_enable_unsolicited_events_finish;
 }
 
 static void
diff --git a/plugins/telit/mm-plugin-telit.c b/plugins/telit/mm-plugin-telit.c
index 4783095..d9793d1 100644
--- a/plugins/telit/mm-plugin-telit.c
+++ b/plugins/telit/mm-plugin-telit.c
@@ -21,6 +21,7 @@
 #define _LIBMM_INSIDE_MM
 #include <libmm-glib.h>
 
+#include "mm-port-enums-types.h"
 #include "mm-log.h"
 #include "mm-modem-helpers.h"
 #include "mm-plugin-telit.h"
@@ -33,6 +34,12 @@
 
 /*****************************************************************************/
 
+#define TAG_GETPORTCFG_SUPPORTED   "getportcfg-supported"
+
+#define TAG_TELIT_MODEM_PORT       "ID_MM_TELIT_PORT_TYPE_MODEM"
+#define TAG_TELIT_AUX_PORT         "ID_MM_TELIT_PORT_TYPE_AUX"
+#define TAG_TELIT_NMEA_PORT        "ID_MM_TELIT_PORT_TYPE_NMEA"
+
 static MMBaseModem *
 create_modem (MMPlugin *self,
               const gchar *sysfs_path,
@@ -56,31 +63,54 @@
            GError **error)
 {
     GUdevDevice *port;
+    MMDevice *device;
     MMPortType ptype;
     MMPortSerialAtFlag pflags = MM_PORT_SERIAL_AT_FLAG_NONE;
 
     port = mm_port_probe_peek_port (probe);
     ptype = mm_port_probe_get_port_type (probe);
+    device = mm_port_probe_peek_device (probe);
 
     /* Look for port type hints; just probing can't distinguish which port should
      * be the data/primary port on these devices.  We have to tag them based on
      * what the Windows .INF files say the port layout should be.
+     *
+     * If no udev rules are found, AT#PORTCFG (if supported) can be used for
+     * identifying the port layout
      */
-    if (g_udev_device_get_property_as_boolean (port, "ID_MM_TELIT_PORT_TYPE_MODEM")) {
+    if (g_udev_device_get_property_as_boolean (port, TAG_TELIT_MODEM_PORT)) {
         mm_dbg ("telit: AT port '%s/%s' flagged as primary",
                 mm_port_probe_get_port_subsys (probe),
                 mm_port_probe_get_port_name (probe));
         pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
-    } else if (g_udev_device_get_property_as_boolean (port, "ID_MM_TELIT_PORT_TYPE_AUX")) {
+    } else if (g_udev_device_get_property_as_boolean (port, TAG_TELIT_AUX_PORT)) {
         mm_dbg ("telit: AT port '%s/%s' flagged as secondary",
                 mm_port_probe_get_port_subsys (probe),
                 mm_port_probe_get_port_name (probe));
         pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY;
-    } else if (g_udev_device_get_property_as_boolean (port, "ID_MM_TELIT_PORT_TYPE_NMEA")) {
+    } else if (g_udev_device_get_property_as_boolean (port, TAG_TELIT_NMEA_PORT)) {
         mm_dbg ("telit: port '%s/%s' flagged as NMEA",
                 mm_port_probe_get_port_subsys (probe),
                 mm_port_probe_get_port_name (probe));
         ptype = MM_PORT_TYPE_GPS;
+    } else if (g_object_get_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED) != NULL) {
+        if (g_strcmp0 (g_udev_device_get_property (port, "ID_USB_INTERFACE_NUM"), g_object_get_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT)) == 0) {
+            mm_dbg ("telit: AT port '%s/%s' flagged as primary",
+                mm_port_probe_get_port_subsys (probe),
+                mm_port_probe_get_port_name (probe));
+            pflags = MM_PORT_SERIAL_AT_FLAG_PRIMARY;
+        } else if (g_strcmp0 (g_udev_device_get_property (port, "ID_USB_INTERFACE_NUM"), g_object_get_data (G_OBJECT (device), TAG_TELIT_AUX_PORT)) == 0) {
+            mm_dbg ("telit: AT port '%s/%s' flagged as secondary",
+                mm_port_probe_get_port_subsys (probe),
+                mm_port_probe_get_port_name (probe));
+            pflags = MM_PORT_SERIAL_AT_FLAG_SECONDARY;
+        } else if (g_strcmp0 (g_udev_device_get_property (port, "ID_USB_INTERFACE_NUM"), g_object_get_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT)) == 0) {
+            mm_dbg ("telit: port '%s/%s' flagged as NMEA",
+                mm_port_probe_get_port_subsys (probe),
+                mm_port_probe_get_port_name (probe));
+            ptype = MM_PORT_TYPE_GPS;
+        } else
+            ptype = MM_PORT_TYPE_IGNORED;
     } else {
         /* If the port was tagged by the udev rules but isn't a primary or secondary,
          * then ignore it to guard against race conditions if a device just happens
@@ -99,6 +129,225 @@
 }
 
 /*****************************************************************************/
+/* Custom init */
+
+typedef struct {
+    MMPortProbe *probe;
+    MMPortSerialAt *port;
+    GCancellable *cancellable;
+    GSimpleAsyncResult *result;
+    gboolean getportcfg_done;
+    guint getportcfg_retries;
+} TelitCustomInitContext;
+
+static gboolean
+telit_custom_init_finish (MMPortProbe *probe,
+                          GAsyncResult *result,
+                          GError **error)
+{
+    return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (result), error);
+}
+
+static void telit_custom_init_step (TelitCustomInitContext *ctx);
+
+static gboolean
+cache_port_mode (MMDevice *device,
+                 const gchar *reply)
+{
+    GRegex *r = NULL;
+    GRegexCompileFlags flags = G_REGEX_DOLLAR_ENDONLY | G_REGEX_RAW;
+    GMatchInfo *match_info = NULL;
+    GError *error = NULL;
+    gboolean ret = FALSE;
+    guint portcfg_current;
+
+    /* #PORTCFG: <requested>,<active> */
+    r = g_regex_new ("#PORTCFG:\\s*(\\d+),(\\d+)", flags, 0, NULL);
+    g_assert (r != NULL);
+
+    if (!g_regex_match_full (r, reply, strlen (reply), 0, 0, &match_info, &error))
+        goto out;
+
+    if (!mm_get_uint_from_match_info (match_info, 2, &portcfg_current)) {
+        mm_dbg ("telit: unrecognized #PORTCFG <active> value");
+        goto out;
+    }
+
+    /* Reference for port configurations:
+     * HE910/UE910/UL865 Families Ports Arrangements User Guide
+     */
+    switch (portcfg_current) {
+    case 0:
+    case 1:
+    case 4:
+    case 5:
+    case 7:
+    case 9:
+    case 10:
+    case 11:
+        g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, "00");
+        g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, "06");
+        break;
+    case 2:
+    case 3:
+    case 6:
+        g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, "00");
+        break;
+    case 8:
+    case 12:
+        g_object_set_data (G_OBJECT (device), TAG_TELIT_MODEM_PORT, "00");
+        g_object_set_data (G_OBJECT (device), TAG_TELIT_AUX_PORT, "06");
+        g_object_set_data (G_OBJECT (device), TAG_TELIT_NMEA_PORT, "0a");
+        break;
+    default:
+        /* portcfg value not supported */
+        goto out;
+    }
+    ret = TRUE;
+
+out:
+    g_match_info_free (match_info);
+    g_regex_unref (r);
+    if (error != NULL) {
+      mm_dbg ("telit: error while matching: %s", error->message);
+      g_error_free (error);
+    }
+    return ret;
+}
+
+static void
+getportcfg_ready (MMPortSerialAt *port,
+                  GAsyncResult *res,
+                  TelitCustomInitContext *ctx)
+{
+    const gchar *response;
+    GError *error = NULL;
+
+    response = mm_port_serial_at_command_finish (port, res, &error);
+    if (error) {
+        mm_dbg ("telit: couldn't get port mode: '%s'",
+                error->message);
+
+        /* If ERROR or COMMAND NOT SUPPORT occur then do not retry the
+         * command.
+         */
+        if (g_error_matches (error,
+                             MM_MOBILE_EQUIPMENT_ERROR,
+                             MM_MOBILE_EQUIPMENT_ERROR_UNKNOWN))
+            ctx->getportcfg_done = TRUE;
+    } else {
+        MMDevice *device;
+
+        device = mm_port_probe_peek_device (ctx->probe);
+
+        /* Results are cached in the parent device object */
+        if (g_object_get_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED) == NULL) {
+            mm_dbg ("telit: retrieving port mode layout");
+            if (cache_port_mode (device, response)) {
+                g_object_set_data (G_OBJECT (device), TAG_GETPORTCFG_SUPPORTED, GUINT_TO_POINTER (TRUE));
+                ctx->getportcfg_done = TRUE;
+            }
+        }
+
+        /* Port answered to #PORTCFG, so mark it as being AT already */
+        mm_port_probe_set_result_at (ctx->probe, TRUE);
+    }
+
+    if (error)
+        g_error_free (error);
+
+    telit_custom_init_step (ctx);
+}
+
+static void
+telit_custom_init_context_complete_and_free (TelitCustomInitContext *ctx)
+{
+    g_simple_async_result_complete_in_idle (ctx->result);
+
+    if (ctx->cancellable)
+        g_object_unref (ctx->cancellable);
+    g_object_unref (ctx->port);
+    g_object_unref (ctx->probe);
+    g_object_unref (ctx->result);
+    g_slice_free (TelitCustomInitContext, ctx);
+}
+
+static void
+telit_custom_init_step (TelitCustomInitContext *ctx)
+{
+    GUdevDevice *port;
+
+    /* If cancelled, end */
+    if (g_cancellable_is_cancelled (ctx->cancellable)) {
+        mm_dbg ("telit: no need to keep on running custom init in (%s)",
+                mm_port_get_device (MM_PORT (ctx->port)));
+        goto out;
+    }
+
+    /* Try to get a port configuration from the modem: usb interface 00
+     * is always linked to an AT port
+     */
+    port = mm_port_probe_peek_port (ctx->probe);
+    if (!ctx->getportcfg_done &&
+        g_strcmp0 (g_udev_device_get_property (port, "ID_USB_INTERFACE_NUM"), "00") == 0) {
+
+        if (ctx->getportcfg_retries == 0)
+            goto out;
+        ctx->getportcfg_retries--;
+
+        mm_port_serial_at_command (
+            ctx->port,
+            "AT#PORTCFG?",
+            2,
+            FALSE, /* raw */
+            FALSE, /* allow_cached */
+            ctx->cancellable,
+            (GAsyncReadyCallback)getportcfg_ready,
+            ctx);
+        return;
+    }
+
+out:
+    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+    telit_custom_init_context_complete_and_free (ctx);
+}
+
+static void
+telit_custom_init (MMPortProbe *probe,
+                   MMPortSerialAt *port,
+                   GCancellable *cancellable,
+                   GAsyncReadyCallback callback,
+                   gpointer user_data)
+{
+    MMDevice *device;
+    GUdevDevice *udevDevice;
+    TelitCustomInitContext *ctx;
+
+    device = mm_port_probe_peek_device (probe);
+    udevDevice = mm_port_probe_peek_port (probe);
+
+    ctx = g_slice_new (TelitCustomInitContext);
+    ctx->result = g_simple_async_result_new (G_OBJECT (probe),
+                                             callback,
+                                             user_data,
+                                             telit_custom_init);
+    ctx->probe = g_object_ref (probe);
+    ctx->port = g_object_ref (port);
+    ctx->cancellable = cancellable ? g_object_ref (cancellable) : NULL;
+    ctx->getportcfg_done = FALSE;
+    ctx->getportcfg_retries = 3;
+
+    /* If the device is tagged for supporting #PORTCFG do the custom init */
+    if (g_udev_device_get_property_as_boolean (udevDevice, "ID_MM_TELIT_PORTS_TAGGED")) {
+        telit_custom_init_step (ctx);
+        return;
+    }
+
+    g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+    telit_custom_init_context_complete_and_free (ctx);
+}
+
+/*****************************************************************************/
 
 G_MODULE_EXPORT MMPlugin *
 mm_plugin_create (void)
@@ -111,6 +360,11 @@
         "ID_MM_TELIT_TAGGED",
         NULL
     };
+    /* Custom init for port identification */
+    static const MMAsyncMethod custom_init = {
+        .async  = G_CALLBACK (telit_custom_init),
+        .finish = G_CALLBACK (telit_custom_init_finish),
+    };
 
     return MM_PLUGIN (
         g_object_new (MM_TYPE_PLUGIN_TELIT,
@@ -119,6 +373,7 @@
                       MM_PLUGIN_ALLOWED_VENDOR_IDS, vendor_ids,
                       MM_PLUGIN_ALLOWED_AT,         TRUE,
                       MM_PLUGIN_ALLOWED_UDEV_TAGS,  udev_tags,
+                      MM_PLUGIN_CUSTOM_INIT,        &custom_init,
                       NULL));
 }
 
diff --git a/plugins/zte/77-mm-zte-port-types.rules b/plugins/zte/77-mm-zte-port-types.rules
index f9d62d8..978a4a4 100644
--- a/plugins/zte/77-mm-zte-port-types.rules
+++ b/plugins/zte/77-mm-zte-port-types.rules
@@ -168,6 +168,9 @@
 ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0128", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_ZTE_PORT_TYPE_MODEM}="1"
 ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0128", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_ZTE_PORT_TYPE_AUX}="1"
 
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0156", ENV{.MM_USBIFNUM}=="02", ENV{ID_MM_ZTE_PORT_TYPE_MODEM}="1"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="0156", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_ZTE_PORT_TYPE_AUX}="1"
+
 ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1007", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_ZTE_PORT_TYPE_MODEM}="1"
 ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="1007", ENV{.MM_USBIFNUM}=="01", ENV{ID_MM_ZTE_PORT_TYPE_AUX}="1"
 
diff --git a/po/POTFILES.in b/po/POTFILES.in
index 9725a1e..80c4d32 100644
--- a/po/POTFILES.in
+++ b/po/POTFILES.in
@@ -2,3 +2,4 @@
 # List of source files containing translatable strings.
 # Please keep this file sorted alphabetically.
 data/org.freedesktop.ModemManager1.policy.in.in
+src/mm-sleep-monitor-systemd.c
diff --git a/src/77-mm-usb-device-blacklist.rules b/src/77-mm-usb-device-blacklist.rules
index 4a74714..51db4cd 100644
--- a/src/77-mm-usb-device-blacklist.rules
+++ b/src/77-mm-usb-device-blacklist.rules
@@ -138,4 +138,7 @@
 # Palmconnect
 ATTRS{idVendor}=="0830", ATTRS{idProduct}=="0080", ENV{ID_MM_DEVICE_IGNORE}="1"
 
+# IMC flashing device
+ATTRS{idVendor}=="058b", ATTRS{idProduct}=="0041", ENV{ID_MM_DEVICE_IGNORE}="1"
+
 LABEL="mm_usb_device_blacklist_end"
diff --git a/src/77-mm-usb-serial-adapters-greylist.rules b/src/77-mm-usb-serial-adapters-greylist.rules
index 3f3e090..d11df52 100644
--- a/src/77-mm-usb-serial-adapters-greylist.rules
+++ b/src/77-mm-usb-serial-adapters-greylist.rules
@@ -35,4 +35,14 @@
 # Netchip Technology, Inc. Linux-USB Serial Gadget (CDC ACM mode)
 ATTRS{idVendor}=="0525", ATTRS{idProduct}=="a4a7", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
 
+# Cypress Serial-USB devices
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0002", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0003", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0004", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0005", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0006", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0007", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="0009", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+ATTRS{idVendor}=="04B4", ATTRS{idProduct}=="000A", ENV{ID_MM_DEVICE_MANUAL_SCAN_ONLY}="1"
+
 LABEL="mm_usb_serial_adapters_greylist_end"
diff --git a/src/Makefile.am b/src/Makefile.am
index 3b545ee..ed4e238 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -96,7 +96,9 @@
 	mm-port-serial-qcdm.c \
 	mm-port-serial-qcdm.h \
 	mm-port-serial-gps.c \
-	mm-port-serial-gps.h
+	mm-port-serial-gps.h \
+	mm-serial-parsers.c \
+	mm-serial-parsers.h
 
 # Additional QMI support in libserial
 if WITH_QMI
@@ -166,6 +168,7 @@
 	mm-context.c \
 	mm-log.c \
 	mm-log.h \
+	mm-utils.h \
 	mm-private-boxed-types.h \
 	mm-private-boxed-types.c \
 	mm-auth.h \
@@ -218,8 +221,6 @@
 	mm-iface-modem-oma.c \
 	mm-broadband-modem.h \
 	mm-broadband-modem.c \
-	mm-serial-parsers.c \
-	mm-serial-parsers.h \
 	mm-port-probe.h \
 	mm-port-probe.c \
 	mm-port-probe-at.h \
@@ -239,6 +240,16 @@
 ModemManager_CPPFLAGS += $(POLKIT_CFLAGS)
 endif
 
+# Additional suspend/resume support via systemd
+if SUSPEND_RESUME_SYSTEMD
+ModemManager_SOURCES += mm-sleep-monitor.h mm-sleep-monitor-systemd.c
+endif
+
+# Additional suspend/resume support via upower
+if SUSPEND_RESUME_UPOWER
+ModemManager_SOURCES += mm-sleep-monitor.h mm-sleep-monitor-upower.c
+endif
+
 # Additional QMI support in ModemManager
 if WITH_QMI
 ModemManager_SOURCES += \
diff --git a/src/main.c b/src/main.c
index 47fa613..8a3ebab 100644
--- a/src/main.c
+++ b/src/main.c
@@ -30,6 +30,10 @@
 #include "mm-log.h"
 #include "mm-context.h"
 
+#if WITH_SUSPEND_RESUME
+# include "mm-sleep-monitor.h"
+#endif
+
 /* Maximum time to wait for all modems to get disabled and removed */
 #define MAX_SHUTDOWN_TIME_SECS 20
 
@@ -51,6 +55,24 @@
     return FALSE;
 }
 
+#if WITH_SUSPEND_RESUME
+
+static void
+sleeping_cb (MMSleepMonitor *sleep_monitor)
+{
+    mm_dbg ("Removing devices... (sleeping)");
+    mm_base_manager_shutdown (manager, FALSE);
+}
+
+static void
+resuming_cb (MMSleepMonitor *sleep_monitor)
+{
+    mm_dbg ("Re-scanning (resuming)");
+    mm_base_manager_start (manager, FALSE);
+}
+
+#endif
+
 static void
 bus_acquired_cb (GDBusConnection *connection,
                  const gchar *name,
@@ -144,6 +166,15 @@
                               name_lost_cb,
                               NULL,
                               NULL);
+#if WITH_SUSPEND_RESUME
+    {
+        MMSleepMonitor *sleep_monitor;
+
+        sleep_monitor = mm_sleep_monitor_get ();
+        g_signal_connect (sleep_monitor, MM_SLEEP_MONITOR_SLEEPING, G_CALLBACK (sleeping_cb), NULL);
+        g_signal_connect (sleep_monitor, MM_SLEEP_MONITOR_RESUMING, G_CALLBACK (resuming_cb), NULL);
+    }
+#endif
 
     /* Go into the main loop */
     loop = g_main_loop_new (NULL, FALSE);
@@ -157,7 +188,7 @@
     if (manager) {
         GTimer *timer;
 
-        mm_base_manager_shutdown (manager);
+        mm_base_manager_shutdown (manager, TRUE);
 
         /* Wait for all modems to be disabled and removed, but don't wait
          * forever: if disabling the modems takes longer than 20s, just
diff --git a/src/mm-base-manager.c b/src/mm-base-manager.c
index f5d4e28..fdcd037 100644
--- a/src/mm-base-manager.c
+++ b/src/mm-base-manager.c
@@ -148,7 +148,7 @@
     GError *error = NULL;
 
     if (!mm_plugin_manager_find_device_support_finish (plugin_manager, result, &error)) {
-        mm_warn ("Couldn't find support for device at '%s': %s",
+        mm_info ("Couldn't find support for device at '%s': %s",
                  mm_device_get_path (ctx->device),
                  error->message);
         g_error_free (error);
@@ -538,6 +538,7 @@
 
     device = find_device_by_modem (self, modem);
     if (device) {
+        g_cancellable_cancel (mm_base_modem_peek_cancellable (modem));
         mm_device_remove_modem (device);
         g_hash_table_remove (self->priv->devices, device);
     }
@@ -555,8 +556,23 @@
         mm_base_modem_disable (modem, (GAsyncReadyCallback)remove_disable_ready, self);
 }
 
+static gboolean
+foreach_remove (gpointer key,
+                MMDevice *device,
+                MMBaseManager *self)
+{
+    MMBaseModem *modem;
+
+    modem = mm_device_peek_modem (device);
+    if (modem)
+        g_cancellable_cancel (mm_base_modem_peek_cancellable (modem));
+    mm_device_remove_modem (device);
+    return TRUE;
+}
+
 void
-mm_base_manager_shutdown (MMBaseManager *self)
+mm_base_manager_shutdown (MMBaseManager *self,
+                          gboolean disable)
 {
     g_return_if_fail (self != NULL);
     g_return_if_fail (MM_IS_BASE_MANAGER (self));
@@ -564,12 +580,18 @@
     /* Cancel all ongoing auth requests */
     g_cancellable_cancel (self->priv->authp_cancellable);
 
-    g_hash_table_foreach (self->priv->devices, (GHFunc)foreach_disable, self);
+    if (disable) {
+        g_hash_table_foreach (self->priv->devices, (GHFunc)foreach_disable, self);
 
-    /* Disabling may take a few iterations of the mainloop, so the caller
-     * has to iterate the mainloop until all devices have been disabled and
-     * removed.
-     */
+        /* Disabling may take a few iterations of the mainloop, so the caller
+         * has to iterate the mainloop until all devices have been disabled and
+         * removed.
+         */
+        return;
+    }
+
+    /* Otherwise, just remove directly */
+    g_hash_table_foreach_remove (self->priv->devices, (GHRFunc)foreach_remove, self);
 }
 
 guint32
diff --git a/src/mm-base-manager.h b/src/mm-base-manager.h
index 0e4e97b..43e7cae 100644
--- a/src/mm-base-manager.h
+++ b/src/mm-base-manager.h
@@ -57,7 +57,8 @@
 void             mm_base_manager_start       (MMBaseManager *manager,
                                               gboolean manual_scan);
 
-void             mm_base_manager_shutdown    (MMBaseManager *manager);
+void             mm_base_manager_shutdown    (MMBaseManager *manager,
+                                              gboolean disable);
 
 guint32          mm_base_manager_num_modems  (MMBaseManager *manager);
 
diff --git a/src/mm-bearer-qmi.c b/src/mm-bearer-qmi.c
index 51abb30..db8e0ac 100644
--- a/src/mm-bearer-qmi.c
+++ b/src/mm-bearer-qmi.c
@@ -406,7 +406,6 @@
 
     /* IPv6 address */
     qmi_inet6_ntop (array, buf, sizeof (buf));
-    g_array_unref (array);
 
     mm_bearer_ip_config_set_address (config, buf);
     mm_bearer_ip_config_set_prefix (config, prefix);
@@ -416,8 +415,7 @@
     if (qmi_message_wds_get_current_settings_output_get_ipv6_gateway_address (output, &array, &prefix, &error)) {
         qmi_inet6_ntop (array, buf, sizeof (buf));
         mm_bearer_ip_config_set_gateway (config, buf);
-        mm_dbg ("    Gateway: %s", buf);
-        g_array_unref (array);
+        mm_dbg ("    Gateway: %s/%d", buf, prefix);
     } else {
         mm_dbg ("    Gateway: failed (%s)", error->message);
         g_clear_error (&error);
@@ -428,7 +426,6 @@
         qmi_inet6_ntop (array, buf, sizeof (buf));
         dns[dns_idx++] = buf;
         mm_dbg ("    DNS #1: %s", buf);
-        g_array_unref (array);
     } else {
         mm_dbg ("    DNS #1: failed (%s)", error->message);
         g_clear_error (&error);
@@ -439,7 +436,6 @@
         qmi_inet6_ntop (array, buf2, sizeof (buf2));
         dns[dns_idx++] = buf2;
         mm_dbg ("    DNS #2: %s", buf2);
-        g_array_unref (array);
     } else {
         mm_dbg ("    DNS #2: failed (%s)", error->message);
         g_clear_error (&error);
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c
index d43b71a..4d234c6 100644
--- a/src/mm-broadband-modem-qmi.c
+++ b/src/mm-broadband-modem-qmi.c
@@ -2481,6 +2481,32 @@
 /*****************************************************************************/
 /* Powering up the modem (Modem interface) */
 
+typedef enum {
+    SET_OPERATING_MODE_STEP_FIRST,
+    SET_OPERATING_MODE_STEP_FCC_AUTH,
+    SET_OPERATING_MODE_STEP_RETRY,
+    SET_OPERATING_MODE_STEP_LAST
+} SetOperatingModeStep;
+
+typedef struct {
+    MMBroadbandModemQmi *self;
+    QmiClientDms *client;
+    GSimpleAsyncResult *result;
+    QmiMessageDmsSetOperatingModeInput *input;
+    SetOperatingModeStep step;
+} SetOperatingModeContext;
+
+static void
+set_operating_mode_context_complete_and_free (SetOperatingModeContext *ctx)
+{
+    g_simple_async_result_complete (ctx->result);
+    g_object_unref (ctx->result);
+    g_object_unref (ctx->client);
+    g_object_unref (ctx->self);
+    qmi_message_dms_set_operating_mode_input_unref (ctx->input);
+    g_slice_free (SetOperatingModeContext, ctx);
+}
+
 static gboolean
 modem_power_up_down_off_finish (MMIfaceModem *self,
                                 GAsyncResult *res,
@@ -2489,38 +2515,125 @@
     return !g_simple_async_result_propagate_error (G_SIMPLE_ASYNC_RESULT (res), error);
 }
 
+static void set_operating_mode_context_step (SetOperatingModeContext *ctx);
+
+static void
+dms_set_fcc_authentication_ready (QmiClientDms *client,
+                                  GAsyncResult *res,
+                                  SetOperatingModeContext *ctx)
+{
+    QmiMessageDmsSetFccAuthenticationOutput *output = NULL;
+    GError *error = NULL;
+
+    output = qmi_client_dms_set_fcc_authentication_finish (client, res, &error);
+    if (!output || !qmi_message_dms_set_fcc_authentication_output_get_result (output, &error)) {
+        /* No hard errors */
+        mm_dbg ("Couldn't set FCC authentication: %s", error->message);
+        g_error_free (error);
+    }
+
+    if (output)
+        qmi_message_dms_set_fcc_authentication_output_unref (output);
+
+    /* Retry Set Operating Mode */
+    ctx->step++;
+    set_operating_mode_context_step (ctx);
+}
+
 static void
 dms_set_operating_mode_ready (QmiClientDms *client,
                               GAsyncResult *res,
-                              GSimpleAsyncResult *simple)
+                              SetOperatingModeContext *ctx)
 {
     QmiMessageDmsSetOperatingModeOutput *output = NULL;
     GError *error = NULL;
 
     output = qmi_client_dms_set_operating_mode_finish (client, res, &error);
     if (!output) {
-        if (g_error_matches (error,
-                             QMI_CORE_ERROR,
-                             QMI_CORE_ERROR_UNSUPPORTED)) {
+        /* If unsupported, just go out without errors */
+        if (g_error_matches (error, QMI_CORE_ERROR, QMI_CORE_ERROR_UNSUPPORTED)) {
             mm_dbg ("Device doesn't support operating mode setting. Ignoring power update.");
-            g_simple_async_result_set_op_res_gboolean (simple, TRUE);
             g_error_free (error);
-        } else {
-            g_prefix_error (&error, "QMI operation failed: ");
-            g_simple_async_result_take_error (simple, error);
+            ctx->step = SET_OPERATING_MODE_STEP_LAST;
+            set_operating_mode_context_step (ctx);
+            return;
         }
-    } else if (!qmi_message_dms_set_operating_mode_output_get_result (output, &error)) {
-        g_prefix_error (&error, "Couldn't set operating mode: ");
-        g_simple_async_result_take_error (simple, error);
-    } else {
-        g_simple_async_result_set_op_res_gboolean (simple, TRUE);
+
+        g_prefix_error (&error, "QMI operation failed: ");
+        g_simple_async_result_take_error (ctx->result, error);
+        set_operating_mode_context_complete_and_free (ctx);
+        return;
     }
 
-    if (output)
-        qmi_message_dms_set_operating_mode_output_unref (output);
+    if (!qmi_message_dms_set_operating_mode_output_get_result (output, &error)) {
+        QmiDmsOperatingMode mode;
 
-    g_simple_async_result_complete (simple);
-    g_object_unref (simple);
+        /* Some new devices, like the Dell DW5770, will return an internal error when
+         * trying to bring the power mode to online. We can avoid this by sending the
+         * magic "DMS Set FCC Auth" message before trying. */
+        if (ctx->step == SET_OPERATING_MODE_STEP_FIRST &&
+            qmi_message_dms_set_operating_mode_input_get_mode (ctx->input, &mode, NULL) &&
+            mode == QMI_DMS_OPERATING_MODE_ONLINE &&
+            g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INTERNAL)) {
+            g_error_free (error);
+            /* Go on to FCC auth */
+            ctx->step++;
+            set_operating_mode_context_step (ctx);
+            qmi_message_dms_set_operating_mode_output_unref (output);
+            return;
+        }
+
+        g_prefix_error (&error, "Couldn't set operating mode: ");
+        g_simple_async_result_take_error (ctx->result, error);
+        qmi_message_dms_set_operating_mode_output_unref (output);
+        set_operating_mode_context_complete_and_free (ctx);
+        return;
+    }
+
+    /* Good! we're done, go to last step */
+    ctx->step = SET_OPERATING_MODE_STEP_LAST;
+    set_operating_mode_context_step (ctx);
+}
+
+static void
+set_operating_mode_context_step (SetOperatingModeContext *ctx)
+{
+    switch (ctx->step) {
+    case SET_OPERATING_MODE_STEP_FIRST:
+        mm_dbg ("Setting device operating mode...");
+        qmi_client_dms_set_operating_mode (QMI_CLIENT_DMS (ctx->client),
+                                           ctx->input,
+                                           20,
+                                           NULL,
+                                           (GAsyncReadyCallback)dms_set_operating_mode_ready,
+                                           ctx);
+        return;
+    case SET_OPERATING_MODE_STEP_FCC_AUTH:
+        mm_dbg ("Setting FCC auth...");
+        qmi_client_dms_set_fcc_authentication (QMI_CLIENT_DMS (ctx->client),
+                                               NULL,
+                                               5,
+                                               NULL,
+                                               (GAsyncReadyCallback)dms_set_fcc_authentication_ready,
+                                               ctx);
+        return;
+    case SET_OPERATING_MODE_STEP_RETRY:
+        mm_dbg ("Setting device operating mode (retry)...");
+        qmi_client_dms_set_operating_mode (QMI_CLIENT_DMS (ctx->client),
+                                           ctx->input,
+                                           20,
+                                           NULL,
+                                           (GAsyncReadyCallback)dms_set_operating_mode_ready,
+                                           ctx);
+        return;
+    case SET_OPERATING_MODE_STEP_LAST:
+        /* Good! */
+        g_simple_async_result_set_op_res_gboolean (ctx->result, TRUE);
+        set_operating_mode_context_complete_and_free (ctx);
+        return;
+    default:
+        g_assert_not_reached ();
+    }
 }
 
 static void
@@ -2529,41 +2642,27 @@
                           GAsyncReadyCallback callback,
                           gpointer user_data)
 {
-    QmiMessageDmsSetOperatingModeInput *input;
-    GSimpleAsyncResult *result;
+    SetOperatingModeContext *ctx;
     QmiClient *client = NULL;
-    GError *error = NULL;
 
     if (!ensure_qmi_client (MM_BROADBAND_MODEM_QMI (self),
                             QMI_SERVICE_DMS, &client,
                             callback, user_data))
         return;
 
-    result = g_simple_async_result_new (G_OBJECT (self),
-                                        callback,
-                                        user_data,
-                                        common_power_up_down_off);
+    /* Setup context */
+    ctx = g_slice_new0 (SetOperatingModeContext);
+    ctx->self = g_object_ref (self);
+    ctx->client = g_object_ref (client);
+    ctx->result = g_simple_async_result_new (G_OBJECT (self),
+                                             callback,
+                                             user_data,
+                                             common_power_up_down_off);
+    ctx->input = qmi_message_dms_set_operating_mode_input_new ();
+    qmi_message_dms_set_operating_mode_input_set_mode (ctx->input, mode, NULL);
+    ctx->step = SET_OPERATING_MODE_STEP_FIRST;
 
-    input = qmi_message_dms_set_operating_mode_input_new ();
-    if (!qmi_message_dms_set_operating_mode_input_set_mode (
-            input,
-            mode,
-            &error)) {
-        qmi_message_dms_set_operating_mode_input_unref (input);
-        g_simple_async_result_take_error (result, error);
-        g_simple_async_result_complete_in_idle (result);
-        g_object_unref (result);
-        return;
-    }
-
-    mm_dbg ("Setting device operating mode...");
-    qmi_client_dms_set_operating_mode (QMI_CLIENT_DMS (client),
-                                       input,
-                                       20,
-                                       NULL,
-                                       (GAsyncReadyCallback)dms_set_operating_mode_ready,
-                                       result);
-    qmi_message_dms_set_operating_mode_input_unref (input);
+    set_operating_mode_context_step (ctx);
 }
 
 static void
diff --git a/src/mm-broadband-modem.c b/src/mm-broadband-modem.c
index 17c128c..feebbc6 100644
--- a/src/mm-broadband-modem.c
+++ b/src/mm-broadband-modem.c
@@ -7511,6 +7511,96 @@
 }
 
 /*****************************************************************************/
+/* Load network time (Time interface) */
+
+static gchar *
+modem_time_load_network_time_finish (MMIfaceModemTime *self,
+                                     GAsyncResult *res,
+                                     GError **error)
+{
+    const gchar *response;
+    gchar *result = NULL;
+
+    response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, error);
+    if (response)
+        mm_parse_cclk_response (response, &result, NULL, error);
+    return result;
+}
+
+static void
+modem_time_load_network_time (MMIfaceModemTime *self,
+                              GAsyncReadyCallback callback,
+                              gpointer user_data)
+{
+    mm_base_modem_at_command (MM_BASE_MODEM (self),
+                              "+CCLK?",
+                              3,
+                              FALSE,
+                              callback,
+                              user_data);
+}
+
+/*****************************************************************************/
+/* Load network timezone (Time interface) */
+
+static MMNetworkTimezone *
+modem_time_load_network_timezone_finish (MMIfaceModemTime *self,
+                                         GAsyncResult *res,
+                                         GError **error)
+{
+    const gchar *response;
+    MMNetworkTimezone *tz = NULL;
+
+    response = mm_base_modem_at_command_finish (MM_BASE_MODEM (self), res, NULL);
+    if (response)
+        mm_parse_cclk_response (response, NULL, &tz, error);
+    return tz;
+}
+
+static void
+modem_time_load_network_timezone (MMIfaceModemTime *self,
+                                  GAsyncReadyCallback callback,
+                                  gpointer user_data)
+{
+    mm_base_modem_at_command (MM_BASE_MODEM (self),
+                              "+CCLK?",
+                              3,
+                              FALSE,
+                              callback,
+                              user_data);
+}
+
+/*****************************************************************************/
+/* Check support (Time interface) */
+
+static const MMBaseModemAtCommand time_check_sequence[] = {
+    { "+CTZU=1",  3, TRUE, mm_base_modem_response_processor_no_result_continue },
+    { "+CCLK?",   3, TRUE, mm_base_modem_response_processor_string },
+    { NULL }
+};
+
+static gboolean
+modem_time_check_support_finish (MMIfaceModemTime *self,
+                                 GAsyncResult *res,
+                                 GError **error)
+{
+    return !!mm_base_modem_at_sequence_finish (MM_BASE_MODEM (self), res, NULL, error);
+}
+
+static void
+modem_time_check_support (MMIfaceModemTime *self,
+                          GAsyncReadyCallback callback,
+                          gpointer user_data)
+{
+    mm_base_modem_at_sequence (MM_BASE_MODEM (self),
+                               time_check_sequence,
+                               NULL, /* response_processor_context */
+                               NULL, /* response_processor_context_free */
+                               callback,
+                               user_data);
+}
+
+/*****************************************************************************/
 
 static const gchar *primary_init_sequence[] = {
     /* Ensure echo is off */
@@ -9824,6 +9914,12 @@
 static void
 iface_modem_time_init (MMIfaceModemTime *iface)
 {
+    iface->check_support = modem_time_check_support;
+    iface->check_support_finish = modem_time_check_support_finish;
+    iface->load_network_time = modem_time_load_network_time;
+    iface->load_network_time_finish = modem_time_load_network_time_finish;
+    iface->load_network_timezone = modem_time_load_network_timezone;
+    iface->load_network_timezone_finish = modem_time_load_network_timezone_finish;
 }
 
 static void
diff --git a/src/mm-iface-modem-3gpp.c b/src/mm-iface-modem-3gpp.c
index 919954c..199b880 100644
--- a/src/mm-iface-modem-3gpp.c
+++ b/src/mm-iface-modem-3gpp.c
@@ -130,24 +130,34 @@
     if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
         ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING)
         return ctx->cs;
-
     if (ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
         ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING)
         return ctx->ps;
-
     if (ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_HOME ||
         ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_ROAMING)
         return ctx->eps;
 
     if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING)
         return ctx->cs;
-
     if (ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING)
         return ctx->ps;
-
     if (ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_SEARCHING)
         return ctx->eps;
 
+    /* If one state is DENIED and the others are UNKNOWN, use DENIED */
+    if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_DENIED &&
+        ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN &&
+        ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN)
+        return ctx->cs;
+    if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN &&
+        ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_DENIED &&
+        ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN)
+        return ctx->ps;
+    if (ctx->cs == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN &&
+        ctx->ps == MM_MODEM_3GPP_REGISTRATION_STATE_UNKNOWN &&
+        ctx->eps == MM_MODEM_3GPP_REGISTRATION_STATE_DENIED)
+        return ctx->eps;
+
     return ctx->cs;
 }
 
diff --git a/src/mm-iface-modem.c b/src/mm-iface-modem.c
index 7c31f9a..0ed204c 100644
--- a/src/mm-iface-modem.c
+++ b/src/mm-iface-modem.c
@@ -3265,22 +3265,6 @@
         return;
     }
 
-    /* Don't allow trying to recover from a power off */
-    if (ctx->previous_real_power_state == MM_MODEM_POWER_STATE_OFF) {
-        g_simple_async_result_set_error (ctx->result,
-                                         MM_CORE_ERROR,
-                                         MM_CORE_ERROR_WRONG_STATE,
-                                         "Cannot recover from a power off");
-        set_power_state_context_complete_and_free (ctx);
-        return;
-    }
-
-    /* Supported transitions:
-     * UNKNOWN|LOW --> ON
-     * ON --> LOW
-     * ON|LOW --> OFF
-     */
-
     /* Fully powering off the modem? */
     if (ctx->power_state == MM_MODEM_POWER_STATE_OFF) {
         /* Error if unsupported */
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
index d40082d..9f0feea 100644
--- a/src/mm-modem-helpers.c
+++ b/src/mm-modem-helpers.c
@@ -2621,3 +2621,78 @@
 
     return success;
 }
+
+/*****************************************************************************/
+/* +CCLK response parser */
+
+gboolean
+mm_parse_cclk_response (const char *response,
+                        gchar **iso8601p,
+                        MMNetworkTimezone **tzp,
+                        GError **error)
+{
+    GRegex *r;
+    GMatchInfo *match_info = NULL;
+    GError *match_error = NULL;
+    guint year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0;
+    gint tz = 0;
+    gboolean ret = FALSE;
+
+    g_assert (iso8601p || tzp); /* at least one */
+
+    /* Sample reply: +CCLK: "15/03/05,14:14:26-32" */
+    r = g_regex_new ("[+]CCLK: \"(\\d+)/(\\d+)/(\\d+),(\\d+):(\\d+):(\\d+)([-+]\\d+)\"", 0, 0, NULL);
+    g_assert (r != NULL);
+
+    if (!g_regex_match_full (r, response, -1, 0, 0, &match_info, &match_error)) {
+        if (match_error) {
+            g_propagate_error (error, match_error);
+            g_prefix_error (error, "Could not parse +CCLK results: ");
+        } else {
+            g_set_error_literal (error,
+                                 MM_CORE_ERROR,
+                                 MM_CORE_ERROR_FAILED,
+                                 "Couldn't match +CCLK reply");
+        }
+    } else {
+        /* Remember that g_match_info_get_match_count() includes match #0 */
+        g_assert (g_match_info_get_match_count (match_info) >= 8);
+
+        if (mm_get_uint_from_match_info (match_info, 1, &year) &&
+            mm_get_uint_from_match_info (match_info, 2, &month) &&
+            mm_get_uint_from_match_info (match_info, 3, &day) &&
+            mm_get_uint_from_match_info (match_info, 4, &hour) &&
+            mm_get_uint_from_match_info (match_info, 5, &minute) &&
+            mm_get_uint_from_match_info (match_info, 6, &second) &&
+            mm_get_int_from_match_info  (match_info, 7, &tz)) {
+            /* adjust year */
+            year += 2000;
+            /*
+             * tz = timezone offset in 15 minute intervals
+             */
+            if (iso8601p) {
+                /* Return ISO-8601 format date/time string */
+                *iso8601p = mm_new_iso8601_time (year, month, day, hour,
+                                                 minute, second,
+                                                 TRUE, (tz * 15));
+            }
+            if (tzp) {
+                *tzp = mm_network_timezone_new ();
+                mm_network_timezone_set_offset (*tzp, tz * 15);
+            }
+
+            ret = TRUE;
+        } else {
+            g_set_error_literal (error,
+                                 MM_CORE_ERROR,
+                                 MM_CORE_ERROR_FAILED,
+                                 "Failed to parse +CCLK reply");
+        }
+    }
+
+    if (match_info)
+        g_match_info_free (match_info);
+    g_regex_unref (r);
+
+    return ret;
+}
diff --git a/src/mm-modem-helpers.h b/src/mm-modem-helpers.h
index 0ec59af..2e5d8e4 100644
--- a/src/mm-modem-helpers.h
+++ b/src/mm-modem-helpers.h
@@ -20,6 +20,9 @@
 
 #include <ModemManager.h>
 
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
 #include "glib-object.h"
 #include "mm-charsets.h"
 
@@ -246,4 +249,10 @@
                        gchar **out_meid,
                        gchar **out_esn);
 
+/* +CCLK response parser */
+gboolean mm_parse_cclk_response (const gchar *response,
+                                 gchar **iso8601p,
+                                 MMNetworkTimezone **tzp,
+                                 GError **error);
+
 #endif  /* MM_MODEM_HELPERS_H */
diff --git a/src/mm-plugin-manager.c b/src/mm-plugin-manager.c
index 73aa749..567555b 100644
--- a/src/mm-plugin-manager.c
+++ b/src/mm-plugin-manager.c
@@ -32,8 +32,12 @@
 /* Default time to defer probing checks */
 #define DEFER_TIMEOUT_SECS 3
 
-/* Time to wait for other ports to appear once the first port is exposed */
-#define MIN_PROBING_TIME_SECS 2
+/* Time to wait for ports to appear before starting to probe the first one */
+#define MIN_WAIT_TIME_MSECS 1500
+
+/* Time to wait for other ports to appear once the first port is exposed
+ * (needs to be > MIN_WAIT_TIME_MSECS!!) */
+#define MIN_PROBING_TIME_MSECS 2500
 
 static void initable_iface_init (GInitableIface *iface);
 
@@ -93,6 +97,7 @@
     gulong grabbed_id;
     gulong released_id;
 
+    gboolean min_wait_timeout_expired;
     GList *running_probes;
 } FindDeviceSupportContext;
 
@@ -100,6 +105,8 @@
     FindDeviceSupportContext *parent_ctx;
     GUdevDevice *port;
 
+    guint initial_timeout_id;
+
     GList *plugins;
     GList *current;
     MMPlugin *best_plugin;
@@ -648,24 +655,18 @@
 }
 
 static void
-device_port_grabbed_cb (MMDevice *device,
-                        GUdevDevice *port,
-                        FindDeviceSupportContext *ctx)
+port_probe_context_start (FindDeviceSupportContext *ctx,
+                          PortProbeContext *port_probe_ctx)
 {
-    PortProbeContext *port_probe_ctx;
-
-    /* Launch probing task on this port with the first plugin of the list */
-    port_probe_ctx = g_slice_new0 (PortProbeContext);
-    port_probe_ctx->parent_ctx = ctx;
-    port_probe_ctx->port = g_object_ref (port);
-
-    /* Setup plugins to probe and first one to check */
-    port_probe_ctx->plugins = build_plugins_list (ctx->self, device, port);
+    /* Setup plugins to probe and first one to check.
+     * Make sure this plugins list is built after the MIN WAIT TIME has been expired
+     * (so that per-driver filters work correctly) */
+    port_probe_ctx->plugins = build_plugins_list (ctx->self, ctx->device, port_probe_ctx->port);
     port_probe_ctx->current = port_probe_ctx->plugins;
 
     /* If we got one suggested, it will be the first one, unless it is the generic plugin */
-    port_probe_ctx->suggested_plugin = (!!mm_device_peek_plugin (device) ?
-                                        MM_PLUGIN (mm_device_get_plugin (device)) :
+    port_probe_ctx->suggested_plugin = (!!mm_device_peek_plugin (ctx->device) ?
+                                        MM_PLUGIN (mm_device_get_plugin (ctx->device)) :
                                         NULL);
     if (port_probe_ctx->suggested_plugin) {
         if (g_str_equal (mm_plugin_get_name (port_probe_ctx->suggested_plugin),
@@ -677,11 +678,27 @@
                                                    port_probe_ctx->suggested_plugin);
     }
 
-    /* Set as running */
+    port_probe_context_step (port_probe_ctx);
+}
+
+static void
+device_port_grabbed_cb (MMDevice *device,
+                        GUdevDevice *port,
+                        FindDeviceSupportContext *ctx)
+{
+    PortProbeContext *port_probe_ctx;
+
+    /* Launch probing task on this port with the first plugin of the list */
+    port_probe_ctx = g_slice_new0 (PortProbeContext);
+    port_probe_ctx->parent_ctx = ctx;
+    port_probe_ctx->port = g_object_ref (port);
+
+    /* Schedule in the list of probes to run */
     ctx->running_probes = g_list_prepend (ctx->running_probes, port_probe_ctx);
 
-    /* Launch supports check in the Plugin Manager */
-    port_probe_context_step (port_probe_ctx);
+    /* If port grabbed after the min wait timeout expired, launch probing directly */
+    if (ctx->min_wait_timeout_expired)
+        port_probe_context_start (ctx, port_probe_ctx);
 }
 
 static void
@@ -727,6 +744,30 @@
     return FALSE;
 }
 
+static gboolean
+min_wait_timeout_cb (FindDeviceSupportContext *ctx)
+{
+    GList *l;
+
+    ctx->min_wait_timeout_expired = TRUE;
+
+    /* Launch supports check for each port in the Plugin Manager */
+    for (l = ctx->running_probes; l; l = g_list_next (l))
+        port_probe_context_start (ctx, (PortProbeContext *)(l->data));
+
+    /* Schedule additional min probing timeout */
+
+    /* Set the initial timeout of 2s. We force the probing time of the device to
+     * be at least this amount of time, so that the kernel has enough time to
+     * bring up ports. Given that we launch this only when the first port of the
+     * device has been exposed in udev, this timeout effectively means that we
+     * leave up to 2s to the remaining ports to appear. */
+    ctx->timeout_id = g_timeout_add (MIN_PROBING_TIME_MSECS - MIN_WAIT_TIME_MSECS,
+                                     (GSourceFunc)min_probing_timeout_cb,
+                                     ctx);
+    return FALSE;
+}
+
 void
 mm_plugin_manager_find_device_support (MMPluginManager *self,
                                        MMDevice *device,
@@ -756,15 +797,15 @@
                                          G_CALLBACK (device_port_released_cb),
                                          ctx);
 
-    /* Set the initial timeout of 2s. We force the probing time of the device to
-     * be at least this amount of time, so that the kernel has enough time to
-     * bring up ports. Given that we launch this only when the first port of the
-     * device has been exposed in udev, this timeout effectively means that we
-     * leave up to 2s to the remaining ports to appear. */
     ctx->timer = g_timer_new ();
-    ctx->timeout_id = g_timeout_add_seconds (MIN_PROBING_TIME_SECS,
-                                             (GSourceFunc)min_probing_timeout_cb,
-                                             ctx);
+
+    /* Set the initial waiting timeout. We don't want to probe any port before
+     * this timeout expires, so that we get as many ports added in the device
+     * as possible. If we don't do this, some plugin filters won't work properly,
+     * like the 'forbidden-drivers' one */
+    ctx->timeout_id = g_timeout_add (MIN_WAIT_TIME_MSECS,
+                                     (GSourceFunc)min_wait_timeout_cb,
+                                     ctx);
 }
 
 /*****************************************************************************/
diff --git a/src/mm-plugin.c b/src/mm-plugin.c
index a83baea..7a3986c 100644
--- a/src/mm-plugin.c
+++ b/src/mm-plugin.c
@@ -214,9 +214,20 @@
     }
 
     /* The plugin may specify that only some drivers are supported, or that some
-     * drivers are not supported. If that is the case, filter by driver */
+     * drivers are not supported. If that is the case, filter by driver.
+     *
+     * The QMI and MBIM *forbidden* drivers filter is implicit. This is, if the
+     * plugin doesn't explicitly specify that QMI is allowed and we find a QMI
+     * port, the plugin will filter the device. Same for MBIM.
+     *
+     * The opposite, though, is not applicable. If the plugin specifies that QMI
+     * is allowed, we won't take that as a mandatory requirement to look for the
+     * QMI driver (as the plugin may handle non-QMI modems as well)
+     */
     if (self->priv->drivers ||
-        self->priv->forbidden_drivers) {
+        self->priv->forbidden_drivers ||
+        !self->priv->qmi ||
+        !self->priv->mbim) {
         static const gchar *virtual_drivers [] = { "virtual", NULL };
         const gchar **drivers;
 
@@ -254,8 +265,9 @@
                 return TRUE;
             }
         }
+
         /* Filtering by forbidden drivers */
-        else {
+        if (self->priv->forbidden_drivers) {
             for (i = 0; self->priv->forbidden_drivers[i]; i++) {
                 guint j;
 
@@ -270,6 +282,36 @@
                 }
             }
         }
+
+        /* Implicit filter for forbidden QMI driver */
+        if (!self->priv->qmi) {
+            guint j;
+
+            for (j = 0; drivers[j]; j++) {
+                /* If we match the QMI driver: unsupported */
+                if (g_str_equal (drivers[j], "qmi_wwan")) {
+                    mm_dbg ("(%s) [%s] filtered by implicit QMI driver",
+                            self->priv->name,
+                            g_udev_device_get_name (port));
+                    return TRUE;
+                }
+            }
+        }
+
+        /* Implicit filter for forbidden MBIM driver */
+        if (!self->priv->mbim) {
+            guint j;
+
+            for (j = 0; drivers[j]; j++) {
+                /* If we match the MBIM driver: unsupported */
+                if (g_str_equal (drivers[j], "cdc_mbim")) {
+                    mm_dbg ("(%s) [%s] filtered by implicit MBIM driver",
+                            self->priv->name,
+                            g_udev_device_get_name (port));
+                    return TRUE;
+                }
+            }
+        }
     }
 
     vendor = mm_device_get_vendor (device);
@@ -308,6 +350,14 @@
             if (!self->priv->product_ids[i].l)
                 product_filtered = TRUE;
         }
+
+        /* When both vendor ids and product ids are given, it may be the case that
+         * we're allowing a full VID1 and only a subset of another VID2, so try to
+         * handle that properly. */
+        if (vendor_filtered && !product_filtered)
+            vendor_filtered = FALSE;
+        if (product_filtered && self->priv->vendor_ids && !vendor_filtered)
+            product_filtered = FALSE;
     }
 
     /* If we got filtered by vendor or product IDs; mark it as unsupported only if:
diff --git a/src/mm-sleep-monitor-systemd.c b/src/mm-sleep-monitor-systemd.c
new file mode 100644
index 0000000..c5aae5a
--- /dev/null
+++ b/src/mm-sleep-monitor-systemd.c
@@ -0,0 +1,247 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/* This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2012 Red Hat, Inc.
+ * Author: Matthias Clasen <mclasen@redhat.com>
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <glib/gi18n.h>
+#include <gio/gio.h>
+#include <gio/gunixfdlist.h>
+
+#include "mm-log.h"
+#include "mm-utils.h"
+#include "mm-sleep-monitor.h"
+
+#define SD_NAME              "org.freedesktop.login1"
+#define SD_PATH              "/org/freedesktop/login1"
+#define SD_INTERFACE         "org.freedesktop.login1.Manager"
+
+
+struct _MMSleepMonitor {
+    GObject parent_instance;
+
+    GDBusProxy *sd_proxy;
+    gint inhibit_fd;
+};
+
+struct _MMSleepMonitorClass {
+    GObjectClass parent_class;
+
+    void (*sleeping) (MMSleepMonitor *monitor);
+    void (*resuming) (MMSleepMonitor *monitor);
+};
+
+
+enum {
+    SLEEPING,
+    RESUMING,
+    LAST_SIGNAL,
+};
+static guint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE (MMSleepMonitor, mm_sleep_monitor, G_TYPE_OBJECT);
+
+/********************************************************************/
+
+static gboolean
+drop_inhibitor (MMSleepMonitor *self)
+{
+    if (self->inhibit_fd >= 0) {
+        mm_dbg ("[sleep-monitor] dropping systemd sleep inhibitor");
+        close (self->inhibit_fd);
+        self->inhibit_fd = -1;
+        return TRUE;
+    }
+    return FALSE;
+}
+
+static void
+inhibit_done (GObject      *source,
+              GAsyncResult *result,
+              gpointer      user_data)
+{
+    GDBusProxy *sd_proxy = G_DBUS_PROXY (source);
+    MMSleepMonitor *self = user_data;
+    GError *error = NULL;
+    GVariant *res;
+    GUnixFDList *fd_list;
+
+    res = g_dbus_proxy_call_with_unix_fd_list_finish (sd_proxy, &fd_list, result, &error);
+    if (!res) {
+        mm_warn ("[sleep-monitor] inhibit failed: %s", error->message);
+        g_error_free (error);
+    } else {
+        if (!fd_list || g_unix_fd_list_get_length (fd_list) != 1)
+            mm_warn ("[sleep-monitor] didn't get a single fd back");
+
+        self->inhibit_fd = g_unix_fd_list_get (fd_list, 0, NULL);
+
+        mm_dbg ("[sleep-monitor] inhibitor fd is %d", self->inhibit_fd);
+        g_object_unref (fd_list);
+        g_variant_unref (res);
+    }
+}
+
+static void
+take_inhibitor (MMSleepMonitor *self)
+{
+    g_assert (self->inhibit_fd == -1);
+
+    mm_dbg ("[sleep-monitor] taking systemd sleep inhibitor");
+    g_dbus_proxy_call_with_unix_fd_list (self->sd_proxy,
+                                         "Inhibit",
+                                         g_variant_new ("(ssss)",
+                                                        "sleep",
+                                                        "ModemManager",
+                                                        _("ModemManager needs to reset devices"),
+                                                        "delay"),
+                                         0,
+                                         G_MAXINT,
+                                         NULL,
+                                         NULL,
+                                         inhibit_done,
+                                         self);
+}
+
+static void
+signal_cb (GDBusProxy  *proxy,
+           const gchar *sendername,
+           const gchar *signalname,
+           GVariant    *args,
+           gpointer     data)
+{
+    MMSleepMonitor *self = data;
+    gboolean is_about_to_suspend;
+
+    if (strcmp (signalname, "PrepareForSleep") != 0)
+        return;
+
+    g_variant_get (args, "(b)", &is_about_to_suspend);
+    mm_dbg ("[sleep-monitor] received PrepareForSleep signal: %d", is_about_to_suspend);
+
+    if (is_about_to_suspend) {
+        g_signal_emit (self, signals[SLEEPING], 0);
+        drop_inhibitor (self);
+    } else {
+        take_inhibitor (self);
+        g_signal_emit (self, signals[RESUMING], 0);
+    }
+}
+
+static void
+name_owner_cb (GObject    *object,
+               GParamSpec *pspec,
+               gpointer    user_data)
+{
+    GDBusProxy *proxy = G_DBUS_PROXY (object);
+    MMSleepMonitor *self = MM_SLEEP_MONITOR (user_data);
+    char *owner;
+
+    g_assert (proxy == self->sd_proxy);
+
+    owner = g_dbus_proxy_get_name_owner (proxy);
+    if (owner)
+        take_inhibitor (self);
+    else
+        drop_inhibitor (self);
+    g_free (owner);
+}
+
+static void
+on_proxy_acquired (GObject *object,
+                   GAsyncResult *res,
+                   MMSleepMonitor *self)
+{
+    GError *error = NULL;
+    char *owner;
+
+    self->sd_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+    if (!self->sd_proxy) {
+        mm_warn ("[sleep-monitor] failed to acquire logind proxy: %s", error->message);
+        g_clear_error (&error);
+        return;
+    }
+
+    g_signal_connect (self->sd_proxy, "notify::g-name-owner", G_CALLBACK (name_owner_cb), self);
+    g_signal_connect (self->sd_proxy, "g-signal", G_CALLBACK (signal_cb), self);
+
+    owner = g_dbus_proxy_get_name_owner (self->sd_proxy);
+    if (owner)
+        take_inhibitor (self);
+    g_free (owner);
+}
+
+static void
+mm_sleep_monitor_init (MMSleepMonitor *self)
+{
+    self->inhibit_fd = -1;
+    g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                              G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START |
+                              G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+                              NULL,
+                              SD_NAME, SD_PATH, SD_INTERFACE,
+                              NULL,
+                              (GAsyncReadyCallback) on_proxy_acquired, self);
+}
+
+static void
+finalize (GObject *object)
+{
+    MMSleepMonitor *self = MM_SLEEP_MONITOR (object);
+
+    drop_inhibitor (self);
+    if (self->sd_proxy)
+        g_object_unref (self->sd_proxy);
+
+    if (G_OBJECT_CLASS (mm_sleep_monitor_parent_class)->finalize != NULL)
+        G_OBJECT_CLASS (mm_sleep_monitor_parent_class)->finalize (object);
+}
+
+static void
+mm_sleep_monitor_class_init (MMSleepMonitorClass *klass)
+{
+    GObjectClass *gobject_class;
+
+    gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->finalize = finalize;
+
+    signals[SLEEPING] = g_signal_new (MM_SLEEP_MONITOR_SLEEPING,
+                                      MM_TYPE_SLEEP_MONITOR,
+                                      G_SIGNAL_RUN_LAST,
+                                      G_STRUCT_OFFSET (MMSleepMonitorClass, sleeping),
+                                      NULL,                   /* accumulator      */
+                                      NULL,                   /* accumulator data */
+                                      g_cclosure_marshal_VOID__VOID,
+                                      G_TYPE_NONE, 0);
+    signals[RESUMING] = g_signal_new (MM_SLEEP_MONITOR_RESUMING,
+                                      MM_TYPE_SLEEP_MONITOR,
+                                      G_SIGNAL_RUN_LAST,
+                                      G_STRUCT_OFFSET (MMSleepMonitorClass, resuming),
+                                      NULL,                   /* accumulator      */
+                                      NULL,                   /* accumulator data */
+                                      g_cclosure_marshal_VOID__VOID,
+                                      G_TYPE_NONE, 0);
+}
+
+MM_DEFINE_SINGLETON_GETTER (MMSleepMonitor, mm_sleep_monitor_get, MM_TYPE_SLEEP_MONITOR);
+
+/* ---------------------------------------------------------------------------------------------------- */
diff --git a/src/mm-sleep-monitor-upower.c b/src/mm-sleep-monitor-upower.c
new file mode 100644
index 0000000..237e3c4
--- /dev/null
+++ b/src/mm-sleep-monitor-upower.c
@@ -0,0 +1,150 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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 Street, Fifth Floor, Boston, MA 02110-1301 USA.
+ *
+ * (C) Copyright 2012 Red Hat, Inc.
+ * Author: Matthias Clasen <mclasen@redhat.com>
+ *
+ * Port to GDBus:
+ * (C) Copyright 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "config.h"
+
+#include <errno.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <gio/gio.h>
+
+#include "mm-log.h"
+#include "mm-utils.h"
+#include "mm-sleep-monitor.h"
+
+#define UPOWER_NAME       "org.freedesktop.UPower"
+#define UPOWER_PATH       "/org/freedesktop/UPower"
+#define UPOWER_INTERFACE  "org.freedesktop.UPower"
+
+struct _MMSleepMonitor {
+    GObject parent_instance;
+
+    GDBusProxy *upower_proxy;
+};
+
+struct _MMSleepMonitorClass {
+    GObjectClass parent_class;
+
+    void (*sleeping) (MMSleepMonitor *monitor);
+    void (*resuming) (MMSleepMonitor *monitor);
+};
+
+
+enum {
+    SLEEPING,
+    RESUMING,
+    LAST_SIGNAL,
+};
+static guint signals[LAST_SIGNAL] = {0};
+
+G_DEFINE_TYPE (MMSleepMonitor, mm_sleep_monitor, G_TYPE_OBJECT);
+
+/********************************************************************/
+
+static void
+signal_cb (GDBusProxy  *proxy,
+           const gchar *sendername,
+           const gchar *signalname,
+           GVariant    *args,
+           gpointer     data)
+{
+    MMSleepMonitor *self = data;
+
+    if (strcmp (signalname, "Sleeping") == 0) {
+        mm_dbg ("[sleep-monitor] received UPower sleeping signal");
+        g_signal_emit (self, signals[SLEEPING], 0);
+    } else if (strcmp (signalname, "Resuming") == 0) {
+        mm_dbg ("[sleep-monitor] received UPower resuming signal");
+        g_signal_emit (self, signals[RESUMING], 0);
+    }
+}
+
+static void
+on_proxy_acquired (GObject *object,
+                   GAsyncResult *res,
+                   MMSleepMonitor *self)
+{
+    GError *error = NULL;
+
+    self->upower_proxy = g_dbus_proxy_new_for_bus_finish (res, &error);
+    if (!self->upower_proxy) {
+        mm_warn ("[sleep-monitor] failed to acquire UPower proxy: %s", error->message);
+        g_clear_error (&error);
+        return;
+    }
+
+    g_signal_connect (self->upower_proxy, "g-signal", G_CALLBACK (signal_cb), self);
+}
+
+static void
+mm_sleep_monitor_init (MMSleepMonitor *self)
+{
+    g_dbus_proxy_new_for_bus (G_BUS_TYPE_SYSTEM,
+                              G_DBUS_PROXY_FLAGS_DO_NOT_AUTO_START |
+                              G_DBUS_PROXY_FLAGS_DO_NOT_LOAD_PROPERTIES,
+                              NULL,
+                              UPOWER_NAME, UPOWER_PATH, UPOWER_INTERFACE,
+                              NULL,
+                              (GAsyncReadyCallback) on_proxy_acquired, self);
+}
+
+static void
+finalize (GObject *object)
+{
+    MMSleepMonitor *self = MM_SLEEP_MONITOR (object);
+
+    if (self->upower_proxy)
+        g_object_unref (self->upower_proxy);
+
+    if (G_OBJECT_CLASS (mm_sleep_monitor_parent_class)->finalize != NULL)
+        G_OBJECT_CLASS (mm_sleep_monitor_parent_class)->finalize (object);
+}
+
+static void
+mm_sleep_monitor_class_init (MMSleepMonitorClass *klass)
+{
+    GObjectClass *gobject_class;
+
+    gobject_class = G_OBJECT_CLASS (klass);
+
+    gobject_class->finalize = finalize;
+
+    signals[SLEEPING] = g_signal_new (MM_SLEEP_MONITOR_SLEEPING,
+                                      MM_TYPE_SLEEP_MONITOR,
+                                      G_SIGNAL_RUN_LAST,
+                                      G_STRUCT_OFFSET (MMSleepMonitorClass, sleeping),
+                                      NULL,                   /* accumulator      */
+                                      NULL,                   /* accumulator data */
+                                      g_cclosure_marshal_VOID__VOID,
+                                      G_TYPE_NONE, 0);
+    signals[RESUMING] = g_signal_new (MM_SLEEP_MONITOR_RESUMING,
+                                      MM_TYPE_SLEEP_MONITOR,
+                                      G_SIGNAL_RUN_LAST,
+                                      G_STRUCT_OFFSET (MMSleepMonitorClass, resuming),
+                                      NULL,                   /* accumulator      */
+                                      NULL,                   /* accumulator data */
+                                      g_cclosure_marshal_VOID__VOID,
+                                      G_TYPE_NONE, 0);
+}
+
+MM_DEFINE_SINGLETON_GETTER (MMSleepMonitor, mm_sleep_monitor_get, MM_TYPE_SLEEP_MONITOR);
diff --git a/src/mm-sleep-monitor.h b/src/mm-sleep-monitor.h
new file mode 100644
index 0000000..2fac551
--- /dev/null
+++ b/src/mm-sleep-monitor.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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.
+ *
+ * (C) Copyright 2012 Red Hat, Inc.
+ * Author: Matthias Clasen <mclasen@redhat.com>
+ * Original code imported from NetworkManager.
+ */
+
+#ifndef __MM_SLEEP_MONITOR_H__
+#define __MM_SLEEP_MONITOR_H__
+
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MM_TYPE_SLEEP_MONITOR         (mm_sleep_monitor_get_type ())
+#define MM_SLEEP_MONITOR(o)           (G_TYPE_CHECK_INSTANCE_CAST ((o), MM_TYPE_SLEEP_MONITOR, MMSleepMonitor))
+#define MM_SLEEP_MONITOR_CLASS(k)     (G_TYPE_CHECK_CLASS_CAST ((k), MM_TYPE_SLEEP_MONITOR, MMSleepMonitorClass))
+#define MM_SLEEP_MONITOR_GET_CLASS(o) (G_TYPE_INSTANCE_GET_CLASS ((o), MM_TYPE_SLEEP_MONITOR, MMSleepMonitorClass))
+#define MM_IS_SLEEP_MONITOR(o)        (G_TYPE_CHECK_INSTANCE_TYPE ((o), MM_TYPE_SLEEP_MONITOR))
+#define MM_IS_SLEEP_MONITOR_CLASS(k)  (G_TYPE_CHECK_CLASS_TYPE ((k), MM_TYPE_SLEEP_MONITOR))
+
+#define MM_SLEEP_MONITOR_SLEEPING "sleeping"
+#define MM_SLEEP_MONITOR_RESUMING "resuming"
+
+typedef struct _MMSleepMonitor         MMSleepMonitor;
+typedef struct _MMSleepMonitorClass    MMSleepMonitorClass;
+
+GType           mm_sleep_monitor_get_type     (void) G_GNUC_CONST;
+MMSleepMonitor *mm_sleep_monitor_get          (void);
+
+G_END_DECLS
+
+#endif /* __MM_SLEEP_MONITOR_H__ */
diff --git a/src/mm-utils.h b/src/mm-utils.h
new file mode 100644
index 0000000..2b0a934
--- /dev/null
+++ b/src/mm-utils.h
@@ -0,0 +1,83 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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:
+ *
+ * Singleton support imported from NetworkManager.
+ *     (C) Copyright 2014 Red Hat, Inc.
+ */
+
+#ifndef MM_UTILS_H
+#define MM_UTILS_H
+
+#include <glib.h>
+#include <glib-object.h>
+
+#include "mm-log.h"
+
+/*****************************************************************************/
+
+#define MM_DEFINE_SINGLETON_INSTANCE(TYPE)      \
+    static TYPE *singleton_instance
+
+#define MM_DEFINE_SINGLETON_WEAK_REF(TYPE)                              \
+    MM_DEFINE_SINGLETON_INSTANCE (TYPE);                                \
+    static void                                                         \
+    _singleton_instance_weak_ref_cb (gpointer data,                     \
+                                     GObject *where_the_object_was)     \
+    {                                                                   \
+        mm_dbg ("disposing %s singleton (%p)", G_STRINGIFY (TYPE), singleton_instance); \
+        singleton_instance = NULL;                                      \
+    }                                                                   \
+    static inline void                                                  \
+    mm_singleton_instance_weak_ref_register (void)                      \
+    {                                                                   \
+        g_object_weak_ref (G_OBJECT (singleton_instance), _singleton_instance_weak_ref_cb, NULL); \
+    }
+
+#define MM_DEFINE_SINGLETON_DESTRUCTOR(TYPE)                            \
+    MM_DEFINE_SINGLETON_INSTANCE (TYPE);                                \
+    static void __attribute__((destructor))                             \
+    _singleton_destructor (void)                                        \
+    {                                                                   \
+        if (singleton_instance) {                                       \
+            if (G_OBJECT (singleton_instance)->ref_count > 1)           \
+                mm_dbg ("disown %s singleton (%p)", G_STRINGIFY (TYPE), singleton_instance); \
+            g_object_unref (singleton_instance);                        \
+        }                                                               \
+    }
+
+/* By default, the getter will assert that the singleton will be created only once. You can
+ * change this by redefining MM_DEFINE_SINGLETON_ALLOW_MULTIPLE. */
+#ifndef MM_DEFINE_SINGLETON_ALLOW_MULTIPLE
+#define MM_DEFINE_SINGLETON_ALLOW_MULTIPLE     FALSE
+#endif
+
+#define MM_DEFINE_SINGLETON_GETTER(TYPE, GETTER, GTYPE, ...)            \
+    MM_DEFINE_SINGLETON_INSTANCE (TYPE);                                \
+    MM_DEFINE_SINGLETON_WEAK_REF (TYPE);                                \
+    TYPE *                                                              \
+    GETTER (void)                                                       \
+    {                                                                   \
+        if (G_UNLIKELY (!singleton_instance)) {                         \
+            static char _already_created = FALSE;                       \
+                                                                        \
+            g_assert (!_already_created || (MM_DEFINE_SINGLETON_ALLOW_MULTIPLE));        \
+            _already_created = TRUE;                                                     \
+            singleton_instance = (g_object_new (GTYPE, ##__VA_ARGS__, NULL));            \
+            g_assert (singleton_instance);                                               \
+            mm_singleton_instance_weak_ref_register ();                                  \
+            mm_dbg ("create %s singleton (%p)", G_STRINGIFY (TYPE), singleton_instance); \
+        }                                                               \
+        return singleton_instance;                                      \
+    }                                                                   \
+    MM_DEFINE_SINGLETON_DESTRUCTOR(TYPE)
+
+#endif /* MM_UTILS_H */
diff --git a/src/tests/test-modem-helpers.c b/src/tests/test-modem-helpers.c
index b493e6b..9fd2501 100644
--- a/src/tests/test-modem-helpers.c
+++ b/src/tests/test-modem-helpers.c
@@ -2341,6 +2341,77 @@
 }
 
 /*****************************************************************************/
+/* Test +CCLK responses */
+
+typedef struct {
+    const gchar *str;
+    gboolean ret;
+    gboolean test_iso8601;
+    gboolean test_tz;
+    gchar *iso8601;
+    gint32 offset;
+} CclkTest;
+
+static const CclkTest cclk_tests[] = {
+    { "+CCLK: \"14/08/05,04:00:21+40\"", TRUE, TRUE, FALSE,
+        "2014-08-05T04:00:21+10:00", 600 },
+    { "+CCLK: \"14/08/05,04:00:21+40\"", TRUE, FALSE, TRUE,
+        "2014-08-05T04:00:21+10:00", 600 },
+    { "+CCLK: \"14/08/05,04:00:21+40\"", TRUE, TRUE, TRUE,
+        "2014-08-05T04:00:21+10:00", 600 },
+
+    { "+CCLK: \"15/02/28,20:30:40-32\"", TRUE, TRUE, FALSE,
+        "2015-02-28T20:30:40-08:00", -480 },
+    { "+CCLK: \"15/02/28,20:30:40-32\"", TRUE, FALSE, TRUE,
+        "2015-02-28T20:30:40-08:00", -480 },
+    { "+CCLK: \"15/02/28,20:30:40-32\"", TRUE, TRUE, TRUE,
+        "2015-02-28T20:30:40-08:00", -480 },
+
+    { "+CCLK: \"XX/XX/XX,XX:XX:XX+XX\"", FALSE, TRUE, FALSE,
+        NULL, MM_NETWORK_TIMEZONE_OFFSET_UNKNOWN },
+
+    { NULL, FALSE, FALSE, FALSE, NULL, MM_NETWORK_TIMEZONE_OFFSET_UNKNOWN }
+};
+
+static void
+test_cclk_response (void)
+{
+    guint i;
+
+    for (i = 0; cclk_tests[i].str; i++) {
+        GError *error = NULL;
+        gchar *iso8601 = NULL;
+        MMNetworkTimezone *tz = NULL;
+        gboolean ret;
+
+        ret = mm_parse_cclk_response (cclk_tests[i].str,
+                                      cclk_tests[i].test_iso8601 ? &iso8601 : NULL,
+                                      cclk_tests[i].test_tz ? &tz : NULL,
+                                      &error);
+
+        g_assert (ret == cclk_tests[i].ret);
+        g_assert (ret == (error ? FALSE : TRUE));
+
+        g_clear_error (&error);
+
+        if (cclk_tests[i].test_iso8601)
+            g_assert_cmpstr (cclk_tests[i].iso8601, ==, iso8601);
+
+        if (cclk_tests[i].test_tz) {
+            g_assert (mm_network_timezone_get_offset (tz) == cclk_tests[i].offset);
+            g_assert (mm_network_timezone_get_dst_offset (tz) == MM_NETWORK_TIMEZONE_OFFSET_UNKNOWN);
+            g_assert (mm_network_timezone_get_leap_seconds (tz) == MM_NETWORK_TIMEZONE_LEAP_SECONDS_UNKNOWN);
+        }
+
+        if (iso8601)
+            g_free (iso8601);
+
+        if (tz)
+            g_object_unref (tz);
+    }
+}
+
+/*****************************************************************************/
 
 void
 _mm_log (const char *loc,
@@ -2501,6 +2572,8 @@
 
     g_test_suite_add (suite, TESTCASE (test_supported_capability_filter, NULL));
 
+    g_test_suite_add (suite, TESTCASE (test_cclk_response, NULL));
+
     result = g_test_run ();
 
     reg_test_data_free (reg_data);
diff --git a/test/Makefile.am b/test/Makefile.am
index f0bfd18..6e6779a 100644
--- a/test/Makefile.am
+++ b/test/Makefile.am
@@ -1,9 +1,24 @@
 
-noinst_PROGRAMS = lsudev
+noinst_PROGRAMS = lsudev mmtty
 
 lsudev_SOURCES = lsudev.c
 lsudev_CPPFLAGS = $(GUDEV_CFLAGS)
 lsudev_LDADD = $(GUDEV_LIBS)
 
+mmtty_SOURCES = mmtty.c
+mmtty_CPPFLAGS = \
+	$(MM_CFLAGS) \
+	-I$(top_srcdir) \
+	-I$(top_srcdir)/src \
+	-I$(top_srcdir)/include \
+	-I$(top_builddir)/include \
+	-I$(top_srcdir)/libmm-glib \
+	-I$(top_srcdir)/libmm-glib/generated \
+	-I$(top_builddir)/libmm-glib/generated
+mmtty_LDADD = \
+	$(MM_LIBS) \
+	$(top_builddir)/src/libport.la \
+	$(top_builddir)/src/libmodem-helpers.la
+
 EXTRA_DIST = \
 	mmcli-test-sms
diff --git a/test/mmtty.c b/test/mmtty.c
new file mode 100644
index 0000000..677203a
--- /dev/null
+++ b/test/mmtty.c
@@ -0,0 +1,299 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * 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:
+ *
+ * Copyright (C) 2015 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "config.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <locale.h>
+#include <string.h>
+
+#include <glib.h>
+#include <gio/gio.h>
+
+#include <mm-log.h>
+#include <mm-port-serial.h>
+#include <mm-port-serial-at.h>
+#include <mm-serial-parsers.h>
+
+#define PROGRAM_NAME    "mmtty"
+#define PROGRAM_VERSION PACKAGE_VERSION
+
+/* Globals */
+static MMPortSerialAt *port;
+static GMainLoop      *loop;
+static GIOChannel     *input;
+static guint           input_watch_id;
+
+/* Context */
+static gchar    *device_str;
+static gboolean  no_flash_flag;
+static gboolean  no_echo_removal_flag;
+static gint64    send_delay = -1;
+static gboolean  verbose_flag;
+static gboolean  version_flag;
+
+static GOptionEntry main_entries[] = {
+    { "device", 'd', 0, G_OPTION_ARG_STRING, &device_str,
+      "Specify device path",
+      "[PATH]"
+    },
+    { "no-flash", 0, 0, G_OPTION_ARG_NONE, &no_flash_flag,
+      "Avoid flashing the port while opening",
+      NULL
+    },
+    { "no-echo-removal", 0, 0, G_OPTION_ARG_NONE, &no_echo_removal_flag,
+      "Avoid logic to remove echo",
+      NULL
+    },
+    { "send-delay", 0, 0, G_OPTION_ARG_INT64, &send_delay,
+      "Send delay for each byte in microseconds (default=1000)",
+      "[DELAY]"
+    },
+    { "verbose", 'v', 0, G_OPTION_ARG_NONE, &verbose_flag,
+      "Run action with verbose logs",
+      NULL
+    },
+    { "version", 'V', 0, G_OPTION_ARG_NONE, &version_flag,
+      "Print version",
+      NULL
+    },
+    { NULL }
+};
+
+static void
+signals_handler (int signum)
+{
+    if (loop && g_main_loop_is_running (loop)) {
+        g_printerr ("%s\n",
+                    "cancelling the main loop...\n");
+        g_main_loop_quit (loop);
+    }
+}
+
+void
+_mm_log (const char *loc,
+         const char *func,
+         guint32 level,
+         const char *fmt,
+         ...)
+{
+    va_list args;
+    gchar *msg;
+
+    if (!verbose_flag)
+        return;
+
+    va_start (args, fmt);
+    msg = g_strdup_vprintf (fmt, args);
+    va_end (args);
+    g_print ("%s\n", msg);
+    g_free (msg);
+}
+
+static void
+print_version_and_exit (void)
+{
+    g_print ("\n"
+             PROGRAM_NAME " " PROGRAM_VERSION "\n"
+             "Copyright (2015) Aleksander Morgado\n"
+             "License GPLv2+: GNU GPL version 2 or later <http://gnu.org/licenses/gpl-2.0.html>\n"
+             "This is free software: you are free to change and redistribute it.\n"
+             "There is NO WARRANTY, to the extent permitted by law.\n"
+             "\n");
+    exit (EXIT_SUCCESS);
+}
+
+static void
+at_command_ready (MMPortSerialAt *serial_at,
+                  GAsyncResult   *res)
+{
+    const gchar *response;
+    GError      *error = NULL;
+
+    response = mm_port_serial_at_command_finish (serial_at, res, &error);
+    if (response)
+        g_print ("%s\n", response);
+    if (error) {
+        g_printerr ("%s\n", error->message);
+        g_error_free (error);
+    }
+
+    g_print ("> ");
+}
+
+static gboolean
+input_callback (GIOChannel   *channel,
+                GIOCondition  condition)
+{
+    GError    *error = NULL;
+    GIOStatus  status;
+    gchar     *line = NULL;
+
+    status = g_io_channel_read_line (channel, &line, NULL, NULL, &error);
+
+    switch (status) {
+    case G_IO_STATUS_NORMAL:
+        mm_port_serial_at_command (port, line, 60, FALSE, FALSE, NULL,
+                                   (GAsyncReadyCallback) at_command_ready, NULL);
+        g_free (line);
+        return TRUE;
+
+    case G_IO_STATUS_ERROR:
+        g_printerr ("error: %s\n", error->message);
+        g_error_free (error);
+        return FALSE;
+
+    case G_IO_STATUS_EOF:
+        g_warning ("error: No input data available");
+        return TRUE;
+
+    case G_IO_STATUS_AGAIN:
+        return TRUE;
+
+    default:
+        g_assert_not_reached ();
+    }
+
+    return FALSE;
+}
+
+static void
+flash_ready (MMPortSerial *serial,
+             GAsyncResult *res)
+{
+    GError *error = NULL;
+
+    if (!mm_port_serial_flash_finish (serial, res, &error)) {
+        g_printerr ("error: cannot flash serial port: %s\n", error->message);
+        exit (EXIT_FAILURE);
+    }
+
+    g_print ("ready\n");
+    g_print ("> ");
+
+    /* Setup input reading */
+    input = g_io_channel_unix_new (STDIN_FILENO);
+    input_watch_id = g_io_add_watch (input, G_IO_IN, (GIOFunc) input_callback, NULL);
+}
+
+static void
+serial_buffer_full (void)
+{
+    g_printerr ("error: serial buffer full\n");
+}
+
+static gboolean
+start_cb (void)
+{
+    GError *error = NULL;
+    const gchar *device_name;
+
+    device_name = device_str;
+    if (g_str_has_prefix (device_name, "/dev/"))
+        device_name += strlen ("/dev/");
+
+    g_print ("creating AT capable serial port for device '%s'...\n", device_name);
+    port = mm_port_serial_at_new (device_name, MM_PORT_SUBSYS_TTY);
+
+    /* Setup send delay */
+    if (send_delay >= 0) {
+        if (send_delay > 0)
+            g_print ("updating send delay to %" G_GINT64_FORMAT "us...\n", send_delay);
+        else
+            g_print ("disabling send delay...\n");
+        g_object_set (port, MM_PORT_SERIAL_SEND_DELAY, send_delay, NULL);
+    }
+
+    /* Setup echo removal */
+    if (no_echo_removal_flag) {
+        g_print ("disabling echo removal...\n");
+        g_object_set (port, MM_PORT_SERIAL_AT_REMOVE_ECHO, FALSE, NULL);
+    }
+
+    /* Set common response parser */
+    mm_port_serial_at_set_response_parser (MM_PORT_SERIAL_AT (port),
+                                           mm_serial_parser_v1_parse,
+                                           mm_serial_parser_v1_new (),
+                                           mm_serial_parser_v1_destroy);
+
+    /* Try to open the port... */
+    g_print ("opening serial port...\n");
+    if (!mm_port_serial_open (MM_PORT_SERIAL (port), &error)) {
+        g_printerr ("error: cannot open serial port: %s\n", error->message);
+        exit (EXIT_FAILURE);
+    }
+
+    /* Warn on full buffer by default */
+    g_signal_connect (port, "buffer-full", G_CALLBACK (serial_buffer_full), NULL);
+
+    /* If we set FLASH_OK to FALSE, the flashing operation does nothing */
+    if (no_flash_flag) {
+        g_print ("disabling serial port flash...\n");
+        g_object_set (port, MM_PORT_SERIAL_FLASH_OK, FALSE, NULL);
+    } else
+        g_print ("flashing serial port...\n");
+    mm_port_serial_flash (MM_PORT_SERIAL (port),
+                          100,
+                          FALSE,
+                          (GAsyncReadyCallback) flash_ready,
+                          NULL);
+    return FALSE;
+}
+
+int main (int argc, char **argv)
+{
+    GOptionContext *context;
+
+    setlocale (LC_ALL, "");
+
+    g_type_init ();
+
+    /* Setup option context, process it and destroy it */
+    context = g_option_context_new ("- ModemManager TTY testing");
+    g_option_context_add_main_entries (context, main_entries, NULL);
+    g_option_context_parse (context, &argc, &argv, NULL);
+    g_option_context_free (context);
+
+    if (version_flag)
+        print_version_and_exit ();
+
+    /* No device path given? */
+    if (!device_str) {
+        g_printerr ("error: no device path specified\n");
+        exit (EXIT_FAILURE);
+    }
+
+    /* Setup signals */
+    signal (SIGINT, signals_handler);
+    signal (SIGHUP, signals_handler);
+    signal (SIGTERM, signals_handler);
+
+    /* Setup main loop and shedule start in idle */
+    loop = g_main_loop_new (NULL, FALSE);
+    g_idle_add ((GSourceFunc)start_cb, NULL);
+    g_main_loop_run (loop);
+
+    /* Cleanup */
+    g_main_loop_unref (loop);
+    if (port) {
+        if (mm_port_serial_is_open (MM_PORT_SERIAL (port)))
+            mm_port_serial_close (MM_PORT_SERIAL (port));
+        g_object_unref (port);
+    }
+    if (input)
+        g_io_channel_unref (input);
+    return 0;
+}