Merge "Merge cros/upstream to cros/master"
diff --git a/.gitignore b/.gitignore
index 05b9619..6b88d05 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,6 +61,7 @@
 /src/tests/test-sms-part-cdma
 /src/tests/test-udev-rules
 /src/tests/test-error-helpers
+/src/tests/test-kernel-device-helpers
 
 /cli/mmcli
 
diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml
index 6d87f29..bfc73ba 100644
--- a/.gitlab-ci.yml
+++ b/.gitlab-ci.yml
@@ -265,7 +265,8 @@
     - ninja -C build
     - ninja -C build install
     - popd
-    - meson setup build --buildtype=release --prefix=/usr -Dwerror=true -Dgtk_doc=false -Dpolkit=strict -Dsystemdsystemunitdir=/lib/systemd/system
+    - meson setup build --buildtype=release --prefix=/usr -Dwerror=true -Dgtk_doc=true -Dpolkit=strict -Dsystemdsystemunitdir=/lib/systemd/system
     - ninja -C build
+    - ninja -C build test
     - ninja -C build install
     - ninja -C build uninstall
diff --git a/cli/mmcli-modem.c b/cli/mmcli-modem.c
index 2ab6438..f8fa6c1 100644
--- a/cli/mmcli-modem.c
+++ b/cli/mmcli-modem.c
@@ -64,6 +64,7 @@
 static gchar *set_preferred_mode_str;
 static gchar *set_current_bands_str;
 static gint set_primary_sim_slot_int;
+static gboolean get_cell_info_flag;
 static gboolean inhibit_flag;
 
 static GOptionEntry entries[] = {
@@ -131,6 +132,10 @@
       "Switch to the selected SIM slot",
       "[SLOT NUMBER]"
     },
+    { "get-cell-info", 0, 0, G_OPTION_ARG_NONE, &get_cell_info_flag,
+      "Get cell info",
+      NULL
+    },
     { "inhibit", 0, 0, G_OPTION_ARG_NONE, &inhibit_flag,
       "Inhibit the modem",
       NULL
@@ -179,6 +184,7 @@
                  !!set_preferred_mode_str +
                  !!set_current_bands_str +
                  (set_primary_sim_slot_int > 0) +
+                 get_cell_info_flag +
                  inhibit_flag);
 
     if (n_actions == 0 && mmcli_get_common_modem_string ()) {
@@ -940,6 +946,35 @@
 }
 
 static void
+get_cell_info_process_reply (GList        *list,
+                             const GError *error)
+{
+    if (!list) {
+        g_printerr ("error: couldn't get cell info in the modem: '%s'\n",
+                    error ? error->message : "unknown error");
+        exit (EXIT_FAILURE);
+    }
+
+    mmcli_output_cell_info (list);
+    mmcli_output_dump ();
+
+    g_list_free_full (list, (GDestroyNotify) g_object_unref);
+}
+
+static void
+get_cell_info_ready (MMModem      *modem,
+                     GAsyncResult *result)
+{
+    GList             *list;
+    g_autoptr(GError)  error = NULL;
+
+    list = mm_modem_get_cell_info_finish (modem, result, &error);
+    get_cell_info_process_reply (list, error);
+
+    mmcli_async_operation_done ();
+}
+
+static void
 state_changed (MMModem                  *modem,
                MMModemState              old_state,
                MMModemState              new_state,
@@ -1191,6 +1226,15 @@
         return;
     }
 
+    /* Request to get cell info? */
+    if (get_cell_info_flag) {
+        mm_modem_get_cell_info (ctx->modem,
+                                ctx->cancellable,
+                                (GAsyncReadyCallback)get_cell_info_ready,
+                                NULL);
+        return;
+    }
+
     /* Request to inhibit the modem? */
     if (inhibit_flag) {
         gchar *uid;
@@ -1459,5 +1503,14 @@
         return;
     }
 
+    /* Request to get cell info? */
+    if (get_cell_info_flag) {
+        GList *list;
+
+        list = mm_modem_get_cell_info_sync (ctx->modem, NULL, &error);
+        get_cell_info_process_reply (list, error);
+        return;
+    }
+
     g_warn_if_reached ();
 }
diff --git a/cli/mmcli-output.c b/cli/mmcli-output.c
index 4d50daf..0b74246 100644
--- a/cli/mmcli-output.c
+++ b/cli/mmcli-output.c
@@ -21,6 +21,7 @@
 #include <stdio.h>
 #include <string.h>
 
+#define _LIBMM_INSIDE_MMCLI
 #include <libmm-glib.h>
 #include "mm-common-helpers.h"
 #include "mmcli-output.h"
@@ -41,6 +42,7 @@
     [MMC_S_MODEM_MODES]                = { "Modes"                },
     [MMC_S_MODEM_BANDS]                = { "Bands"                },
     [MMC_S_MODEM_IP]                   = { "IP"                   },
+    [MMC_S_MODEM_CELL_INFO]            = { "Cell info"            },
     [MMC_S_MODEM_3GPP]                 = { "3GPP"                 },
     [MMC_S_MODEM_3GPP_EPS]             = { "3GPP EPS"             },
     [MMC_S_MODEM_3GPP_5GNR]            = { "3GPP 5GNR"            },
@@ -127,6 +129,7 @@
     [MMC_F_BANDS_SUPPORTED]                          = { "modem.generic.supported-bands",                   "supported",                MMC_S_MODEM_BANDS,                },
     [MMC_F_BANDS_CURRENT]                            = { "modem.generic.current-bands",                     "current",                  MMC_S_MODEM_BANDS,                },
     [MMC_F_IP_SUPPORTED]                             = { "modem.generic.supported-ip-families",             "supported",                MMC_S_MODEM_IP,                   },
+    [MMC_F_CELL_INFO]                                = { "modem.generic.cell-info",                         "cells",                    MMC_S_MODEM_CELL_INFO,            },
     [MMC_F_3GPP_IMEI]                                = { "modem.3gpp.imei",                                 "imei",                     MMC_S_MODEM_3GPP,                 },
     [MMC_F_3GPP_ENABLED_LOCKS]                       = { "modem.3gpp.enabled-locks",                        "enabled locks",            MMC_S_MODEM_3GPP,                 },
     [MMC_F_3GPP_OPERATOR_ID]                         = { "modem.3gpp.operator-code",                        "operator id",              MMC_S_MODEM_3GPP,                 },
@@ -1116,6 +1119,33 @@
 }
 
 /******************************************************************************/
+/* (Custom) Cell info output */
+
+void
+mmcli_output_cell_info (GList *cell_info_list)
+{
+    gchar **cell_infos = NULL;
+
+    if (cell_info_list) {
+        GPtrArray *aux;
+        GList     *l;
+
+        aux = g_ptr_array_new ();
+        for (l = cell_info_list; l; l = g_list_next (l))
+            g_ptr_array_add (aux, mm_cell_info_build_string (MM_CELL_INFO (l->data)));
+        g_ptr_array_add (aux, NULL);
+        cell_infos = (gchar **) g_ptr_array_free (aux, FALSE);
+    }
+
+    /* When printing human result, we want to show some result even if no networks
+     * are found, so we force a explicit string result. */
+    if (selected_type == MMC_OUTPUT_TYPE_HUMAN && !cell_infos)
+        output_item_new_take_single (MMC_F_CELL_INFO, g_strdup ("n/a"));
+    else
+        output_item_new_take_multiple (MMC_F_CELL_INFO, cell_infos, TRUE);
+}
+
+/******************************************************************************/
 /* Human-friendly output */
 
 #define HUMAN_MAX_VALUE_LENGTH 60
diff --git a/cli/mmcli-output.h b/cli/mmcli-output.h
index bd06045..180debd 100644
--- a/cli/mmcli-output.h
+++ b/cli/mmcli-output.h
@@ -38,6 +38,7 @@
     MMC_S_MODEM_MODES,
     MMC_S_MODEM_BANDS,
     MMC_S_MODEM_IP,
+    MMC_S_MODEM_CELL_INFO,
     MMC_S_MODEM_3GPP,
     MMC_S_MODEM_3GPP_EPS,
     MMC_S_MODEM_3GPP_5GNR,
@@ -127,6 +128,8 @@
     MMC_F_BANDS_CURRENT,
     /* IP section */
     MMC_F_IP_SUPPORTED,
+    /* Cell info section */
+    MMC_F_CELL_INFO,
     /* 3GPP section */
     MMC_F_3GPP_IMEI,
     MMC_F_3GPP_ENABLED_LOCKS,
@@ -392,6 +395,7 @@
 void mmcli_output_preferred_networks (GList                     *preferred_nets_list);
 void mmcli_output_profile_list       (GList                     *profile_list);
 void mmcli_output_profile_set        (MM3gppProfile             *profile);
+void mmcli_output_cell_info          (GList                     *cell_info_list);
 
 /******************************************************************************/
 /* Dump output */
diff --git a/cli/mmcli.c b/cli/mmcli.c
index e10267a..b86c8d9 100644
--- a/cli/mmcli.c
+++ b/cli/mmcli.c
@@ -43,7 +43,7 @@
 static GMainLoop *loop;
 static GCancellable *cancellable;
 
-/* Context */
+/* Main context */
 static gboolean output_keyvalue_flag;
 static gboolean output_json_flag;
 static gboolean verbose_flag;
@@ -79,6 +79,31 @@
     { NULL }
 };
 
+/* Test context */
+static gboolean test_session_flag;
+
+static GOptionEntry test_entries[] = {
+    { "test-session", 0, 0, G_OPTION_ARG_NONE, &test_session_flag,
+      "Run in session DBus",
+      NULL
+    },
+    { NULL }
+};
+
+static GOptionGroup *
+test_get_option_group (void)
+{
+    GOptionGroup *group;
+
+    group = g_option_group_new ("test",
+                                "Test options:",
+                                "Show test options",
+                                NULL,
+                                NULL);
+    g_option_group_add_entries (group, test_entries);
+    return group;
+}
+
 static void
 signals_handler (int signum)
 {
@@ -243,6 +268,8 @@
                                 mmcli_sms_get_option_group ());
     g_option_context_add_group (context,
                                 mmcli_call_get_option_group ());
+    g_option_context_add_group (context,
+                                test_get_option_group ());
     g_option_context_add_main_entries (context, main_entries, NULL);
     g_option_context_parse (context, &argc, &argv, NULL);
     g_option_context_free (context);
@@ -281,7 +308,7 @@
     signal (SIGTERM, signals_handler);
 
     /* Setup dbus connection to use */
-    connection = g_bus_get_sync (G_BUS_TYPE_SYSTEM, NULL, &error);
+    connection = g_bus_get_sync (test_session_flag ? G_BUS_TYPE_SESSION : G_BUS_TYPE_SYSTEM, NULL, &error);
     if (!connection) {
         g_printerr ("error: couldn't get bus: %s\n",
                     error ? error->message : "unknown error");
diff --git a/configure.ac b/configure.ac
index d1e9684..6ffa512 100644
--- a/configure.ac
+++ b/configure.ac
@@ -6,7 +6,7 @@
 
 m4_define([mm_major_version], [1])
 m4_define([mm_minor_version], [19])
-m4_define([mm_micro_version], [0])
+m4_define([mm_micro_version], [1])
 m4_define([mm_version],
           [mm_major_version.mm_minor_version.mm_micro_version])
 
@@ -221,6 +221,12 @@
 fi
 AM_CONDITIONAL(HAVE_SYSTEMD, [test -n "$SYSTEMD_UNIT_DIR" -a "$SYSTEMD_UNIT_DIR" != xno ])
 
+
+dnl subdir where plugins are built w.r.t abs_top_builddir, just because it's
+dnl different to what meson does
+PLUGIN_BUILD_SUBDIR="plugins/.libs"
+AC_SUBST(PLUGIN_BUILD_SUBDIR)
+
 dnl-----------------------------------------------------------------------------
 dnl udev support (enabled by default)
 dnl
@@ -380,7 +386,7 @@
 dnl MBIM support (enabled by default)
 dnl
 
-LIBMBIM_VERSION=1.27.3
+LIBMBIM_VERSION=1.27.5
 
 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")
diff --git a/data/fcc-unlock/105b b/data/fcc-unlock/105b
index 21fe532..f276050 100644
--- a/data/fcc-unlock/105b
+++ b/data/fcc-unlock/105b
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 # SPDX-License-Identifier: CC0-1.0
 # 2021 Aleksander Morgado <aleksander@aleksander.es>
diff --git a/data/fcc-unlock/1199 b/data/fcc-unlock/1199
index 0109c6a..e1d3804 100644
--- a/data/fcc-unlock/1199
+++ b/data/fcc-unlock/1199
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 # SPDX-License-Identifier: CC0-1.0
 # 2021 Aleksander Morgado <aleksander@aleksander.es>
diff --git a/data/fcc-unlock/1eac b/data/fcc-unlock/1eac
index 1068d9c..d934285 100644
--- a/data/fcc-unlock/1eac
+++ b/data/fcc-unlock/1eac
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 # SPDX-License-Identifier: CC0-1.0
 # 2021 Aleksander Morgado <aleksander@aleksander.es>
diff --git a/data/meson.build b/data/meson.build
index 820bec7..d2a36b7 100644
--- a/data/meson.build
+++ b/data/meson.build
@@ -1,6 +1,8 @@
 # SPDX-License-Identifier: GPL-2.0-or-later
 # Copyright (C) 2021 Iñigo Martinez <inigomartinez@gmail.com>
 
+subdir('tests')
+
 service_conf = {
   'sbindir': mm_prefix / mm_sbindir,
   'MM_POLKIT_SERVICE': (enable_polkit ? 'polkit.service' : ''),
diff --git a/data/tests/meson.build b/data/tests/meson.build
new file mode 100644
index 0000000..fa0b2c0
--- /dev/null
+++ b/data/tests/meson.build
@@ -0,0 +1,13 @@
+# SPDX-License-Identifier: GPL-2.0-or-later
+# Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+
+test_conf = {
+  'abs_top_builddir': build_root,
+  'PLUGIN_BUILD_SUBDIR': 'plugins/',
+}
+
+configure_file(
+  input: 'org.freedesktop.ModemManager1.service.in',
+  output: '@BASENAME@',
+  configuration: test_conf,
+)
\ No newline at end of file
diff --git a/data/tests/org.freedesktop.ModemManager1.service.in b/data/tests/org.freedesktop.ModemManager1.service.in
index d7c1a00..d8a751b 100644
--- a/data/tests/org.freedesktop.ModemManager1.service.in
+++ b/data/tests/org.freedesktop.ModemManager1.service.in
@@ -2,4 +2,4 @@
 
 [D-BUS Service]
 Name=org.freedesktop.ModemManager1
-Exec=@abs_top_builddir@/src/ModemManager --test-session --no-auto-scan --test-enable --test-plugin-dir="@abs_top_builddir@/plugins/.libs" --debug
+Exec=@abs_top_builddir@/src/ModemManager --test-session --no-auto-scan --test-enable --test-plugin-dir="@abs_top_builddir@/@PLUGIN_BUILD_SUBDIR@" --debug
diff --git a/docs/reference/api/ModemManager-sections.txt b/docs/reference/api/ModemManager-sections.txt
index 5b155da..9635f35 100644
--- a/docs/reference/api/ModemManager-sections.txt
+++ b/docs/reference/api/ModemManager-sections.txt
@@ -63,6 +63,7 @@
 MMSimType
 MMSimEsimStatus
 MMSimRemovability
+MMCellType
 </SECTION>
 
 <SECTION>
diff --git a/docs/reference/libmm-glib/libmm-glib-docs.xml b/docs/reference/libmm-glib/libmm-glib-docs.xml
index 942559b..d963221 100644
--- a/docs/reference/libmm-glib/libmm-glib-docs.xml
+++ b/docs/reference/libmm-glib/libmm-glib-docs.xml
@@ -92,7 +92,15 @@
         <xi:include href="xml/mm-modem-cdma.xml"/>
         <xi:include href="xml/mm-cdma-manual-activation-properties.xml"/>
         <xi:include href="xml/mm-unlock-retries.xml"/>
+        <xi:include href="xml/mm-cell-info.xml"/>
+        <xi:include href="xml/mm-cell-info-cdma.xml"/>
+        <xi:include href="xml/mm-cell-info-gsm.xml"/>
+        <xi:include href="xml/mm-cell-info-umts.xml"/>
+        <xi:include href="xml/mm-cell-info-tdscdma.xml"/>
+        <xi:include href="xml/mm-cell-info-lte.xml"/>
+        <xi:include href="xml/mm-cell-info-nr5g.xml"/>
         <xi:include href="xml/mm-pco.xml"/>
+        <xi:include href="xml/mm-nr5g-registration-settings.xml"/>
       </section>
       <section>
         <title>Simple interface support</title>
diff --git a/docs/reference/libmm-glib/libmm-glib-sections.txt b/docs/reference/libmm-glib/libmm-glib-sections.txt
index 26bc73e..4a04b3d 100644
--- a/docs/reference/libmm-glib/libmm-glib-sections.txt
+++ b/docs/reference/libmm-glib/libmm-glib-sections.txt
@@ -230,6 +230,9 @@
 mm_modem_delete_bearer
 mm_modem_delete_bearer_finish
 mm_modem_delete_bearer_sync
+mm_modem_get_cell_info
+mm_modem_get_cell_info_finish
+mm_modem_get_cell_info_sync
 <SUBSECTION DebugMethods>
 mm_modem_command
 mm_modem_command_finish
@@ -278,6 +281,245 @@
 </SECTION>
 
 <SECTION>
+<FILE>mm-cell-info</FILE>
+<TITLE>MMCellInfo</TITLE>
+MMCellInfo
+<SUBSECTION Getters>
+mm_cell_info_get_cell_type
+mm_cell_info_get_serving
+<SUBSECTION Private>
+mm_cell_info_set_cell_type
+mm_cell_info_set_serving
+mm_cell_info_build_string
+mm_cell_info_get_dictionary
+mm_cell_info_new_from_dictionary
+MM_CELL_INFO_GET_DICTIONARY_INSERT
+MM_CELL_INFO_BUILD_STRING_APPEND
+MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET
+MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET
+<SUBSECTION Standard>
+MMCellInfoClass
+MMCellInfoPrivate
+MM_IS_CELL_INFO
+MM_IS_CELL_INFO_CLASS
+MM_TYPE_CELL_INFO
+MM_CELL_INFO
+MM_CELL_INFO_CLASS
+MM_CELL_INFO_GET_CLASS
+mm_cell_info_get_type
+</SECTION>
+
+<SECTION>
+<FILE>mm-cell-info-cdma</FILE>
+<TITLE>MMCellInfoCdma</TITLE>
+MMCellInfoCdma
+<SUBSECTION Getters>
+mm_cell_info_cdma_get_nid
+mm_cell_info_cdma_get_sid
+mm_cell_info_cdma_get_base_station_id
+mm_cell_info_cdma_get_ref_pn
+mm_cell_info_cdma_get_pilot_strength
+<SUBSECTION Private>
+mm_cell_info_cdma_new_from_dictionary
+mm_cell_info_cdma_set_nid
+mm_cell_info_cdma_set_sid
+mm_cell_info_cdma_set_base_station_id
+mm_cell_info_cdma_set_ref_pn
+mm_cell_info_cdma_set_pilot_strength
+<SUBSECTION Standard>
+MMCellInfoCdmaClass
+MMCellInfoCdmaPrivate
+MM_IS_CELL_INFO_CDMA
+MM_IS_CELL_INFO_CDMA_CLASS
+MM_TYPE_CELL_INFO_CDMA
+MM_CELL_INFO_CDMA
+MM_CELL_INFO_CDMA_CLASS
+MM_CELL_INFO_CDMA_GET_CLASS
+mm_cell_info_cdma_get_type
+</SECTION>
+
+<SECTION>
+<FILE>mm-cell-info-gsm</FILE>
+<TITLE>MMCellInfoGsm</TITLE>
+MMCellInfoGsm
+<SUBSECTION Getters>
+mm_cell_info_gsm_get_operator_id
+mm_cell_info_gsm_get_lac
+mm_cell_info_gsm_get_ci
+mm_cell_info_gsm_get_timing_advance
+mm_cell_info_gsm_get_arfcn
+mm_cell_info_gsm_get_base_station_id
+mm_cell_info_gsm_get_rx_level
+<SUBSECTION Private>
+mm_cell_info_gsm_new_from_dictionary
+mm_cell_info_gsm_set_operator_id
+mm_cell_info_gsm_set_lac
+mm_cell_info_gsm_set_ci
+mm_cell_info_gsm_set_timing_advance
+mm_cell_info_gsm_set_arfcn
+mm_cell_info_gsm_set_base_station_id
+mm_cell_info_gsm_set_rx_level
+<SUBSECTION Standard>
+MMCellInfoGsmClass
+MMCellInfoGsmPrivate
+MM_IS_CELL_INFO_GSM
+MM_IS_CELL_INFO_GSM_CLASS
+MM_TYPE_CELL_INFO_GSM
+MM_CELL_INFO_GSM
+MM_CELL_INFO_GSM_CLASS
+MM_CELL_INFO_GSM_GET_CLASS
+mm_cell_info_gsm_get_type
+</SECTION>
+
+<SECTION>
+<FILE>mm-cell-info-umts</FILE>
+<TITLE>MMCellInfoUmts</TITLE>
+MMCellInfoUmts
+<SUBSECTION Getters>
+mm_cell_info_umts_get_operator_id
+mm_cell_info_umts_get_lac
+mm_cell_info_umts_get_ci
+mm_cell_info_umts_get_frequency_fdd_ul
+mm_cell_info_umts_get_frequency_fdd_dl
+mm_cell_info_umts_get_frequency_tdd
+mm_cell_info_umts_get_uarfcn
+mm_cell_info_umts_get_psc
+mm_cell_info_umts_get_rscp
+mm_cell_info_umts_get_ecio
+mm_cell_info_umts_get_path_loss
+<SUBSECTION Private>
+mm_cell_info_umts_new_from_dictionary
+mm_cell_info_umts_set_operator_id
+mm_cell_info_umts_set_lac
+mm_cell_info_umts_set_ci
+mm_cell_info_umts_set_frequency_fdd_ul
+mm_cell_info_umts_set_frequency_fdd_dl
+mm_cell_info_umts_set_frequency_tdd
+mm_cell_info_umts_set_uarfcn
+mm_cell_info_umts_set_psc
+mm_cell_info_umts_set_rscp
+mm_cell_info_umts_set_ecio
+mm_cell_info_umts_set_path_loss
+<SUBSECTION Standard>
+MMCellInfoUmtsClass
+MMCellInfoUmtsPrivate
+MM_IS_CELL_INFO_UMTS
+MM_IS_CELL_INFO_UMTS_CLASS
+MM_TYPE_CELL_INFO_UMTS
+MM_CELL_INFO_UMTS
+MM_CELL_INFO_UMTS_CLASS
+MM_CELL_INFO_UMTS_GET_CLASS
+mm_cell_info_umts_get_type
+</SECTION>
+
+<SECTION>
+<FILE>mm-cell-info-tdscdma</FILE>
+<TITLE>MMCellInfoTdscdma</TITLE>
+MMCellInfoTdscdma
+<SUBSECTION Getters>
+mm_cell_info_tdscdma_get_operator_id
+mm_cell_info_tdscdma_get_lac
+mm_cell_info_tdscdma_get_ci
+mm_cell_info_tdscdma_get_uarfcn
+mm_cell_info_tdscdma_get_cell_parameter_id
+mm_cell_info_tdscdma_get_timing_advance
+mm_cell_info_tdscdma_get_rscp
+mm_cell_info_tdscdma_get_path_loss
+<SUBSECTION Private>
+mm_cell_info_tdscdma_new_from_dictionary
+mm_cell_info_tdscdma_set_operator_id
+mm_cell_info_tdscdma_set_lac
+mm_cell_info_tdscdma_set_ci
+mm_cell_info_tdscdma_set_uarfcn
+mm_cell_info_tdscdma_set_cell_parameter_id
+mm_cell_info_tdscdma_set_timing_advance
+mm_cell_info_tdscdma_set_rscp
+mm_cell_info_tdscdma_set_path_loss
+<SUBSECTION Standard>
+MMCellInfoTdscdmaClass
+MMCellInfoTdscdmaPrivate
+MM_IS_CELL_INFO_TDSCDMA
+MM_IS_CELL_INFO_TDSCDMA_CLASS
+MM_TYPE_CELL_INFO_TDSCDMA
+MM_CELL_INFO_TDSCDMA
+MM_CELL_INFO_TDSCDMA_CLASS
+MM_CELL_INFO_TDSCDMA_GET_CLASS
+mm_cell_info_tdscdma_get_type
+</SECTION>
+
+<SECTION>
+<FILE>mm-cell-info-lte</FILE>
+<TITLE>MMCellInfoLte</TITLE>
+MMCellInfoLte
+<SUBSECTION Getters>
+mm_cell_info_lte_get_operator_id
+mm_cell_info_lte_get_tac
+mm_cell_info_lte_get_ci
+mm_cell_info_lte_get_physical_ci
+mm_cell_info_lte_get_earfcn
+mm_cell_info_lte_get_rsrp
+mm_cell_info_lte_get_rsrq
+mm_cell_info_lte_get_timing_advance
+<SUBSECTION Private>
+mm_cell_info_lte_new_from_dictionary
+mm_cell_info_lte_set_operator_id
+mm_cell_info_lte_set_tac
+mm_cell_info_lte_set_ci
+mm_cell_info_lte_set_physical_ci
+mm_cell_info_lte_set_earfcn
+mm_cell_info_lte_set_rsrp
+mm_cell_info_lte_set_rsrq
+mm_cell_info_lte_set_timing_advance
+<SUBSECTION Standard>
+MMCellInfoLteClass
+MMCellInfoLtePrivate
+MM_IS_CELL_INFO_LTE
+MM_IS_CELL_INFO_LTE_CLASS
+MM_TYPE_CELL_INFO_LTE
+MM_CELL_INFO_LTE
+MM_CELL_INFO_LTE_CLASS
+MM_CELL_INFO_LTE_GET_CLASS
+mm_cell_info_lte_get_type
+</SECTION>
+
+<SECTION>
+<FILE>mm-cell-info-nr5g</FILE>
+<TITLE>MMCellInfoNr5g</TITLE>
+MMCellInfoNr5g
+<SUBSECTION Getters>
+mm_cell_info_nr5g_get_operator_id
+mm_cell_info_nr5g_get_tac
+mm_cell_info_nr5g_get_ci
+mm_cell_info_nr5g_get_physical_ci
+mm_cell_info_nr5g_get_nrarfcn
+mm_cell_info_nr5g_get_rsrp
+mm_cell_info_nr5g_get_rsrq
+mm_cell_info_nr5g_get_sinr
+mm_cell_info_nr5g_get_timing_advance
+<SUBSECTION Private>
+mm_cell_info_nr5g_new_from_dictionary
+mm_cell_info_nr5g_set_operator_id
+mm_cell_info_nr5g_set_tac
+mm_cell_info_nr5g_set_ci
+mm_cell_info_nr5g_set_physical_ci
+mm_cell_info_nr5g_set_nrarfcn
+mm_cell_info_nr5g_set_rsrp
+mm_cell_info_nr5g_set_rsrq
+mm_cell_info_nr5g_set_sinr
+mm_cell_info_nr5g_set_timing_advance
+<SUBSECTION Standard>
+MMCellInfoNr5gClass
+MMCellInfoNr5gPrivate
+MM_IS_CELL_INFO_NR5G
+MM_IS_CELL_INFO_NR5G_CLASS
+MM_TYPE_CELL_INFO_NR5G
+MM_CELL_INFO_NR5G
+MM_CELL_INFO_NR5G_CLASS
+MM_CELL_INFO_NR5G_GET_CLASS
+mm_cell_info_nr5g_get_type
+</SECTION>
+
+<SECTION>
 <FILE>mm-modem-3gpp</FILE>
 <TITLE>MMModem3gpp</TITLE>
 MMModem3gpp
@@ -311,6 +553,7 @@
 mm_modem_3gpp_peek_initial_eps_bearer_settings
 mm_modem_3gpp_get_packet_service_state
 mm_modem_3gpp_get_nr5g_registration_settings
+mm_modem_3gpp_peek_nr5g_registration_settings
 <SUBSECTION Methods>
 mm_modem_3gpp_register
 mm_modem_3gpp_register_finish
@@ -790,6 +1033,7 @@
 mm_firmware_update_settings_new
 mm_firmware_update_settings_new_from_variant
 mm_firmware_update_settings_set_fastboot_at
+mm_firmware_update_settings_set_method
 mm_firmware_update_settings_set_device_ids
 mm_firmware_update_settings_set_version
 <SUBSECTION Standard>
@@ -1171,6 +1415,7 @@
 mm_bearer_get_stats
 mm_bearer_get_connection_error
 mm_bearer_peek_connection_error
+mm_bearer_get_reload_stats_supported
 <SUBSECTION Methods>
 mm_bearer_connect
 mm_bearer_connect_finish
@@ -1684,6 +1929,8 @@
 <SUBSECTION Getters>
 mm_modem_3gpp_profile_manager_get_path
 mm_modem_3gpp_profile_manager_dup_path
+mm_modem_3gpp_profile_manager_get_index_field
+mm_modem_3gpp_profile_manager_dup_index_field
 <SUBSECTION Methods>
 mm_modem_3gpp_profile_manager_list
 mm_modem_3gpp_profile_manager_list_finish
@@ -1706,6 +1953,32 @@
 </SECTION>
 
 <SECTION>
+<FILE>mm-nr5g-registration-settings</FILE>
+<TITLE>MMNr5gRegistrationSettings</TITLE>
+MMNr5gRegistrationSettings
+mm_nr5g_registration_settings_new
+mm_nr5g_registration_settings_set_mico_mode
+mm_nr5g_registration_settings_get_mico_mode
+mm_nr5g_registration_settings_set_drx_cycle
+mm_nr5g_registration_settings_get_drx_cycle
+<SUBSECTION Private>
+mm_nr5g_registration_settings_new_from_string
+mm_nr5g_registration_settings_new_from_dictionary
+mm_nr5g_registration_settings_get_dictionary
+mm_nr5g_registration_settings_cmp
+<SUBSECTION Standard>
+MMNr5gRegistrationSettingsClass
+MMNr5gRegistrationSettingsPrivate
+MM_IS_NR5G_REGISTRATION_SETTINGS
+MM_IS_NR5G_REGISTRATION_SETTINGS_CLASS
+MM_NR5G_REGISTRATION_SETTINGS
+MM_NR5G_REGISTRATION_SETTINGS_CLASS
+MM_NR5G_REGISTRATION_SETTINGS_GET_CLASS
+MM_TYPE_NR5G_REGISTRATION_SETTINGS
+mm_nr5g_registration_settings_get_type
+</SECTION>
+
+<SECTION>
 <FILE>mm-enums-types</FILE>
 <TITLE>Flags and Enumerations</TITLE>
 mm_bearer_type_get_string
@@ -1761,6 +2034,7 @@
 mm_call_direction_get_string
 mm_call_state_get_string
 mm_call_state_reason_get_string
+mm_cell_type_get_string
 <SUBSECTION Private>
 mm_modem_capability_get_string
 mm_modem_lock_build_string_from_mask
@@ -1815,6 +2089,7 @@
 mm_call_state_build_string_from_mask
 mm_call_state_reason_build_string_from_mask
 mm_modem_firmware_update_method_get_string
+mm_cell_type_build_string_from_mask
 <SUBSECTION Standard>
 MM_TYPE_BEARER_TYPE
 MM_TYPE_BEARER_IP_FAMILY
@@ -1823,8 +2098,8 @@
 MM_TYPE_BEARER_MULTIPLEX_SUPPORT
 MM_TYPE_BEARER_APN_TYPE
 MM_TYPE_SIM_TYPE
-MM_TYPE_ESIM_STATUS
-MM_TYPE_SIM_REMOVAL_STATUS
+MM_TYPE_SIM_ESIM_STATUS
+MM_TYPE_SIM_REMOVABILITY
 MM_TYPE_BEARER_ACCESS_TYPE_PREFERENCE
 MM_TYPE_BEARER_ROAMING_ALLOWANCE
 MM_TYPE_BEARER_PROFILE_SOURCE
@@ -1869,6 +2144,7 @@
 MM_TYPE_CALL_STATE
 MM_TYPE_CALL_STATE_REASON
 MM_TYPE_MODEM_FIRMWARE_UPDATE_METHOD
+MM_TYPE_CELL_TYPE
 mm_bearer_type_get_type
 mm_bearer_ip_family_get_type
 mm_bearer_ip_method_get_type
@@ -1922,6 +2198,7 @@
 mm_call_state_get_type
 mm_call_state_reason_get_type
 mm_modem_firmware_update_method_get_type
+mm_cell_type_get_type
 </SECTION>
 
 <SECTION>
@@ -2007,6 +2284,7 @@
 mm_gdbus_bearer_get_profile_id
 mm_gdbus_bearer_get_stats
 mm_gdbus_bearer_dup_stats
+mm_gdbus_bearer_get_reload_stats_supported
 <SUBSECTION Methods>
 mm_gdbus_bearer_call_connect
 mm_gdbus_bearer_call_connect_finish
@@ -2028,6 +2306,7 @@
 mm_gdbus_bearer_set_profile_id
 mm_gdbus_bearer_set_stats
 mm_gdbus_bearer_set_multiplexed
+mm_gdbus_bearer_set_reload_stats_supported
 mm_gdbus_bearer_override_properties
 mm_gdbus_bearer_complete_connect
 mm_gdbus_bearer_complete_disconnect
@@ -2359,6 +2638,9 @@
 <TITLE>MmGdbusModem3gppProfileManager</TITLE>
 MmGdbusModem3gppProfileManager
 MmGdbusModem3gppProfileManagerIface
+<SUBSECTION Getters>
+mm_gdbus_modem3gpp_profile_manager_dup_index_field
+mm_gdbus_modem3gpp_profile_manager_get_index_field
 <SUBSECTION Methods>
 mm_gdbus_modem3gpp_profile_manager_call_delete
 mm_gdbus_modem3gpp_profile_manager_call_delete_finish
@@ -2376,6 +2658,7 @@
 mm_gdbus_modem3gpp_profile_manager_complete_set
 mm_gdbus_modem3gpp_profile_manager_interface_info
 mm_gdbus_modem3gpp_profile_manager_override_properties
+mm_gdbus_modem3gpp_profile_manager_set_index_field
 <SUBSECTION Standard>
 MM_GDBUS_IS_MODEM3GPP_PROFILE_MANAGER
 MM_GDBUS_MODEM3GPP_PROFILE_MANAGER
@@ -2527,6 +2810,9 @@
 mm_gdbus_modem_call_command
 mm_gdbus_modem_call_command_finish
 mm_gdbus_modem_call_command_sync
+mm_gdbus_modem_call_get_cell_info
+mm_gdbus_modem_call_get_cell_info_finish
+mm_gdbus_modem_call_get_cell_info_sync
 <SUBSECTION Private>
 mm_gdbus_modem_set_access_technologies
 mm_gdbus_modem_set_bearers
@@ -2576,6 +2862,7 @@
 mm_gdbus_modem_complete_set_current_bands
 mm_gdbus_modem_complete_set_current_capabilities
 mm_gdbus_modem_complete_set_primary_sim_slot
+mm_gdbus_modem_complete_get_cell_info
 mm_gdbus_modem_interface_info
 mm_gdbus_modem_override_properties
 <SUBSECTION Standard>
@@ -3626,6 +3913,9 @@
 mm_gdbus_sim_complete_set_preferred_networks
 mm_gdbus_sim_interface_info
 mm_gdbus_sim_override_properties
+mm_gdbus_sim_set_esim_status
+mm_gdbus_sim_set_removability
+mm_gdbus_sim_set_sim_type
 <SUBSECTION Standard>
 MM_GDBUS_IS_SIM
 MM_GDBUS_SIM
diff --git a/include/ModemManager-enums.h b/include/ModemManager-enums.h
index bb3c5c0..46f9143 100644
--- a/include/ModemManager-enums.h
+++ b/include/ModemManager-enums.h
@@ -659,6 +659,30 @@
 } MMModemPortType;
 
 /**
+ * MMCellType:
+ * @MM_CELL_TYPE_UNKNOWN: Unknown.
+ * @MM_CELL_TYPE_CDMA: CDMA cell.
+ * @MM_CELL_TYPE_GSM: GSM cell.
+ * @MM_CELL_TYPE_UMTS: UMTS cell.
+ * @MM_CELL_TYPE_TDSCDMA: TD-SCDMA cell.
+ * @MM_CELL_TYPE_LTE: LTE cell.
+ * @MM_CELL_TYPE_5GNR: 5GNR cell.
+ *
+ * Type of cell information reported.
+ *
+ * Since: 1.20
+ */
+typedef enum { /*< underscore_name=mm_cell_type >*/
+    MM_CELL_TYPE_UNKNOWN = 0,
+    MM_CELL_TYPE_CDMA    = 1,
+    MM_CELL_TYPE_GSM     = 2,
+    MM_CELL_TYPE_UMTS    = 3,
+    MM_CELL_TYPE_TDSCDMA = 4,
+    MM_CELL_TYPE_LTE     = 5,
+    MM_CELL_TYPE_5GNR    = 6,
+} MMCellType;
+
+/**
  * MMSmsPduType:
  * @MM_SMS_PDU_TYPE_UNKNOWN: Unknown type.
  * @MM_SMS_PDU_TYPE_DELIVER: 3GPP Mobile-Terminated (MT) message.
@@ -1619,6 +1643,7 @@
  * @MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC: Device supports QMI PDC based update.
  * @MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU: Device supports MBIM QDU based update. Since 1.18.
  * @MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE: Device supports Firehose based update. Since 1.18.
+ * @MM_MODEM_FIRMWARE_UPDATE_METHOD_SAHARA: Device supports Sahara protocol. Usually used in combination with Firehose. Since 1.20.
  *
  * Type of firmware update method supported by the module.
  *
@@ -1630,6 +1655,7 @@
     MM_MODEM_FIRMWARE_UPDATE_METHOD_QMI_PDC  = 1 << 1,
     MM_MODEM_FIRMWARE_UPDATE_METHOD_MBIM_QDU = 1 << 2,
     MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE = 1 << 3,
+    MM_MODEM_FIRMWARE_UPDATE_METHOD_SAHARA   = 1 << 4,
 } MMModemFirmwareUpdateMethod;
 
 /**
diff --git a/introspection/org.freedesktop.ModemManager1.Modem.Signal.xml b/introspection/org.freedesktop.ModemManager1.Modem.Signal.xml
index 93b2996..213db49 100644
--- a/introspection/org.freedesktop.ModemManager1.Modem.Signal.xml
+++ b/introspection/org.freedesktop.ModemManager1.Modem.Signal.xml
@@ -15,7 +15,7 @@
 
       This interface provides access to extended signal quality information.
 
-      This interface will only be available once the modem is ready to be
+      This interface will only be functional once the modem is ready to be
       registered in the cellular network. 3GPP devices will require a valid
       unlocked SIM card before any of the features in the interface can be
       used.
@@ -27,6 +27,10 @@
 
       Both Setup() and SetupThresholds() can also be used at the same time if
       required, e.g. if they report different signal quality measurement types.
+
+      The thresholds and polling setup will only be in effect if the modem is
+      in enabled state. Changing the settings with Setup() or SetupThresholds()
+      is also possible while in disabled state, though.
   -->
   <interface name="org.freedesktop.ModemManager1.Modem.Signal">
 
diff --git a/introspection/org.freedesktop.ModemManager1.Modem.xml b/introspection/org.freedesktop.ModemManager1.Modem.xml
index 80da06e..1e346b9 100644
--- a/introspection/org.freedesktop.ModemManager1.Modem.xml
+++ b/introspection/org.freedesktop.ModemManager1.Modem.xml
@@ -186,14 +186,14 @@
         @sim_slot: SIM slot number to set as primary.
 
         Selects which SIM slot to be considered as primary, on devices that expose
-	multiple slots in the #org.freedesktop.ModemManager1.Modem:SimSlots property.
+        multiple slots in the #org.freedesktop.ModemManager1.Modem:SimSlots property.
 
         When the switch happens the modem may require a full device reprobe, so the modem
-	object in DBus will get removed, and recreated once the selected SIM slot is in
-	use.
+        object in DBus will get removed, and recreated once the selected SIM slot is in
+        use.
 
         There is no limitation on which SIM slot to select, so the user may also set as
-	primary a slot that doesn't currently have any valid SIM card inserted.
+        primary a slot that doesn't currently have any valid SIM card inserted.
 
         Since: 1.16
     -->
@@ -201,6 +201,423 @@
       <arg name="sim_slot" type="u" direction="in" />
     </method>
 
+
+    <!--
+        GetCellInfo:
+
+        Get information for available cells in different access technologies,
+        either serving or neighboring.
+
+        An array of dictionaries is returned, where each dictionary reports information for
+        one single cell.
+
+        The dictionaries have mandatory keys that are always given, including:
+
+        <variablelist>
+          <varlistentry><term><literal>"cell-type"</literal></term>
+            <listitem>
+              The <link linkend="MMCellType">MMCellType</link>, given as an unsigned integer
+              value (signature <literal>"u"</literal>).
+            </listitem>
+          </varlistentry>
+          <varlistentry><term><literal>"serving"</literal></term>
+            <listitem>
+              Flag specifying whether the cell is a serving cell or otherwise a neighboring cell,
+              given as a boolean value (signature <literal>"b"</literal>).
+            </listitem>
+          </varlistentry>
+        </variablelist>
+
+        For each different cell type, other optional keys may be given.
+
+        <variablelist>
+          <varlistentry><term><link linkend="MM-CELL-TYPE-CDMA:CAPS">MM_CELL_TYPE_CDMA</link></term>
+            <listitem>
+              <para>
+                The CDMA cell information may include the following additional keys:
+              </para>
+              <variablelist>
+                <varlistentry><term><literal>"nid"</literal></term>
+                  <listitem>
+                    Network id, given as a string value (signature <literal>"s"</literal>)
+                    in upper-case hexadecimal format without leading zeros. E.g. <literal>"12345"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"sid"</literal></term>
+                  <listitem>
+                    System id, given as a string value (signature <literal>"s"</literal>)
+                    in upper-case hexadecimal format without leading zeros. E.g. <literal>"ABCD"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"base-station-id"</literal></term>
+                  <listitem>
+                    Base station id, given as a string value (signature <literal>"s"</literal>)
+                    in upper-case hexadecimal format without leading zeros. E.g. <literal>"3F"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"ref-pn"</literal></term>
+                  <listitem>
+                    Base station PN number, given as a string value (signature <literal>"s"</literal>)
+                    in upper-case hexadecimal format without leading zeros. E.g. <literal>"3F"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"pilot-strength"</literal></term>
+                  <listitem>
+                    The signal strength of the pilot, given in the same format and scale as the GSM
+                    SINR level, given as an unsigned integer value (signature <literal>"u"</literal>).
+                  </listitem>
+                </varlistentry>
+              </variablelist>
+            </listitem>
+          </varlistentry>
+          <varlistentry><term><link linkend="MM-CELL-TYPE-GSM:CAPS">MM_CELL_TYPE_GSM</link></term>
+            <listitem>
+              <para>
+                The GSM cell information may include the following additional keys:
+              </para>
+              <variablelist>
+                <varlistentry><term><literal>"operator-id"</literal></term>
+                  <listitem>
+                    PLMN MCC/MNC, given as a string value (signature <literal>"s"</literal>).
+                    E.g. <literal>21034</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"lac"</literal></term>
+                  <listitem>
+                    This is the two-byte Location Area Code of the base station, given
+                    as a string value (signature <literal>"s"</literal>) in upper-case
+                    hexadecimal format without leading zeros, as specified in 3GPP TS
+                    27.007. E.g. <literal>"84CD"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"ci"</literal></term>
+                  <listitem>
+                    This is the two- or four-byte Cell Identifier, given as a string value
+                    (signature <literal>"s"</literal>) in upper-case hexadecimal format
+                    without leading zeros, as specified in 3GPP TS 27.007.
+                    E.g. <literal>"2BAF"</literal> or <literal>"D30156"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"timing-advance"</literal></term>
+                  <listitem>
+                    Measured delay (in bit periods; 1 bit period = 48/13 microsecond)
+                    of an access burst transmission on the RACH or PRACH to the expected
+                    signal from a mobile station at zero distance under static channel
+                    conditions, given as an unsigned integer value (signature
+                    <literal>"u"</literal>). Only applicable for the serving cell (i.e.
+                    "serving" must be TRUE).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"arfcn"</literal></term>
+                  <listitem>
+                    Absolute RF channel number, given as an unsigned integer value (signature
+                    <literal>"u"</literal>).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"base-station-id"</literal></term>
+                  <listitem>
+                    Base station id, given as a string value (signature <literal>"s"</literal>)
+                    in upper-case hexadecimal format without leading zeros, as specified in
+                    3GPP TS 27.007. E.g. <literal>"3F"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"rx-level"</literal></term>
+                  <listitem>
+                    Serving cell Rx measurement, given as a unsigned integer value
+                    (signature <literal>"u"</literal>. Values range between 0 and 63.
+                  </listitem>
+                </varlistentry>
+              </variablelist>
+            </listitem>
+          </varlistentry>
+          <varlistentry><term><link linkend="MM-CELL-TYPE-UMTS:CAPS">MM_CELL_TYPE_UMTS</link></term>
+            <listitem>
+              <para>
+                The UMTS cell information may include the following additional keys:
+              </para>
+              <variablelist>
+                <varlistentry><term><literal>"operator-id"</literal></term>
+                  <listitem>
+                    PLMN MCC/MNC, given as a string value (signature <literal>"s"</literal>).
+                    E.g. <literal>21034</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"lac"</literal></term>
+                  <listitem>
+                    This is the two-byte Location Area Code of the base station, given
+                    as a string value (signature <literal>"s"</literal>) in upper-case
+                    hexadecimal format without leading zeros, as specified in 3GPP TS
+                    27.007. E.g. <literal>"84CD"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"ci"</literal></term>
+                  <listitem>
+                    This is the two- or four-byte Cell Identifier, given as a string value
+                    (signature <literal>"s"</literal>) in upper-case hexadecimal format
+                    without leading zeros, as specified in 3GPP TS 27.007.
+                    e.g. <literal>"2BAF"</literal> or <literal>"D30156"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"frequency-fdd-ul"</literal></term>
+                  <listitem>
+                    In FDD, the frequency of the uplink in kHz, given as an unsigned integer value
+                    (signature <literal>"u"</literal>). Values range between 0 and 16383.
+                    Only applicable for the serving cell (i.e. "serving" must be TRUE).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"frequency-fdd-dl"</literal></term>
+                  <listitem>
+                    In FDD, the frequency of the downlink in kHz, given as an unsigned integer value
+                    (signature <literal>"u"</literal>). Values range between 0 and 16383.
+                    Only applicable for the serving cell (i.e. "serving" must be TRUE).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"frequency-tdd"</literal></term>
+                  <listitem>
+                    In TDD, the frequency in kHz, given as an unsigned integer value
+                    (signature <literal>"u"</literal>). Values range between 0 and 16383.
+                    Only applicable for the serving cell (i.e. "serving" must be TRUE).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"uarfcn"</literal></term>
+                  <listitem>
+                    UTRA absolute RF channel number, given as an unsigned integer value
+                    (signature <literal>"u"</literal>).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"psc"</literal></term>
+                  <listitem>
+                    Primary scrambling code, given as an unsigned integer value
+                    (signature <literal>"u"</literal>).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"rscp"</literal></term>
+                  <listitem>
+                    Received signal code power; the received power on one code measured
+                    in dBm on the primary CPICH channel of the cell, given as a
+                    signed integer value (signature <literal>"d"</literal>).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"ecio"</literal></term>
+                  <listitem>
+                    ECIO; the received energy per chip divided by the power density in the
+                    band measured in dBm on the primary CPICH channel of the cell, given as a
+                    signed integer value (signature <literal>"d"</literal>).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"path-loss"</literal></term>
+                  <listitem>
+                    The path loss of the cell, given as an unsigned integer value (signature
+                    <literal>"u"</literal>.
+                    Only applicable for the serving cell (i.e. "serving" must be TRUE).
+                  </listitem>
+                </varlistentry>
+              </variablelist>
+            </listitem>
+          </varlistentry>
+          <varlistentry><term><link linkend="MM-CELL-TYPE-TDSCDMA:CAPS">MM_CELL_TYPE_TDSCDMA</link></term>
+            <listitem>
+              <para>
+                The TD-SCDMA cell information may include the following additional keys:
+              </para>
+              <variablelist>
+                <varlistentry><term><literal>"operator-id"</literal></term>
+                  <listitem>
+                    PLMN MCC/MNC, given as a string value (signature <literal>"s"</literal>).
+                    E.g. <literal>21034</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"lac"</literal></term>
+                  <listitem>
+                    This is the two-byte Location Area Code of the base station, given
+                    as a string value (signature <literal>"s"</literal>) in upper-case
+                    hexadecimal format without leading zeros, as specified in 3GPP TS
+                    27.007. E.g. <literal>"84CD"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"ci"</literal></term>
+                  <listitem>
+                    This is the two- or four-byte Cell Identifier, given as a string value
+                    (signature <literal>"s"</literal>) in upper-case hexadecimal format
+                    without leading zeros, as specified in 3GPP TS 27.007.
+                    e.g. <literal>"2BAF"</literal> or <literal>"D30156"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"uarfcn"</literal></term>
+                  <listitem>
+                    UTRA absolute RF channel number, given as an unsigned integer value
+                    (signature <literal>"u"</literal>).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"cell-parameter-id"</literal></term>
+                  <listitem>
+                    The cell parameter ID, given as an unsigned integer value
+                    (signature <literal>"u"</literal>).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"timing-advance"</literal></term>
+                  <listitem>
+                    Measured delay (in bit periods; 1 bit period = 48/13 microsecond)
+                    of an access burst transmission on the RACH or PRACH to the expected
+                    signal from an MS at zero distance under static channel conditions,
+                    given as a unsigned integer value (signature <literal>"u"</literal>).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"rscp"</literal></term>
+                  <listitem>
+                    Received signal code power; the received power on one code measured
+                    in dBm on the primary CPICH channel of the cell, given as a
+                    signed integer value (signature <literal>"d"</literal>).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"path-loss"</literal></term>
+                  <listitem>
+                    The path loss of the cell, given as an unsigned integer value (signature
+                    <literal>"u"</literal>.
+                  </listitem>
+                </varlistentry>
+              </variablelist>
+            </listitem>
+          </varlistentry>
+          <varlistentry><term><link linkend="MM-CELL-TYPE-LTE:CAPS">MM_CELL_TYPE_LTE</link></term>
+            <listitem>
+              <para>
+                The LTE cell information may include the following additional keys:
+              </para>
+              <variablelist>
+                <varlistentry><term><literal>"operator-id"</literal></term>
+                  <listitem>
+                    PLMN MCC/MNC, given as a string value (signature <literal>"s"</literal>).
+                    E.g. <literal>21034</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"tac"</literal></term>
+                  <listitem>
+                    This is the two- or three-byte Tracking Area Code of the base station, given
+                    as a string value (signature <literal>"s"</literal>) in upper-case
+                    hexadecimal format without leading zeros, as specified in 3GPP TS
+                    27.007. E.g. <literal>"84CD"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"ci"</literal></term>
+                  <listitem>
+                    This is the two- or four-byte Cell Identifier, given as a string value
+                    (signature <literal>"s"</literal>) in upper-case hexadecimal format
+                    without leading zeros, as specified in 3GPP TS 27.007.
+                    e.g. <literal>"2BAF"</literal> or <literal>"D30156"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"physical-ci"</literal></term>
+                  <listitem>
+                    The physical cell id, given as a string value
+                    (signature <literal>"s"</literal>) in upper-case hexadecimal format
+                    without leading zeros. E.g. <literal>"1A0"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"earfcn"</literal></term>
+                  <listitem>
+                    E-UTRA absolute RF channel number, given as an unsigned integer value
+                    (signature <literal>"u"</literal>).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"rsrp"</literal></term>
+                  <listitem>
+                    The average reference signal received power in dBm, given as a signed
+                    integer value (signature <literal>"d"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"rsrq"</literal></term>
+                  <listitem>
+                    The average reference signal received quality in dB, given as a signed
+                    integer value (signature <literal>"d"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"timing-advance"</literal></term>
+                  <listitem>
+                    The timing advance, given as an unsigned integer value (signature <literal>"u"</literal>).
+                    Only applicable for the serving cell (i.e. "serving" must be TRUE).
+                  </listitem>
+                </varlistentry>
+              </variablelist>
+            </listitem>
+          </varlistentry>
+          <varlistentry><term><link linkend="MM-CELL-TYPE-5GNR:CAPS">MM_CELL_TYPE_5GNR</link></term>
+            <listitem>
+              <para>
+                The 5GNR cell information may include the following additional keys:
+              </para>
+              <variablelist>
+                <varlistentry><term><literal>"operator-id"</literal></term>
+                  <listitem>
+                    PLMN MCC/MNC, given as a string value (signature <literal>"s"</literal>).
+                    E.g. <literal>21034</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"tac"</literal></term>
+                  <listitem>
+                    This is the two- or three-byte Tracking Area Code of the base station, given
+                    as a string value (signature <literal>"s"</literal>) in upper-case
+                    hexadecimal format without leading zeros, as specified in 3GPP TS
+                    27.007. E.g. <literal>"84CD"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"ci"</literal></term>
+                  <listitem>
+                    This is the two- or four-byte Cell Identifier, given as a string value
+                    (signature <literal>"s"</literal>) in upper-case hexadecimal format
+                    without leading zeros, as specified in 3GPP TS 27.007.
+                    e.g. <literal>"2BAF"</literal> or <literal>"D30156"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"physical-ci"</literal></term>
+                  <listitem>
+                    The physical cell id, given as a string value
+                    (signature <literal>"s"</literal>) in upper-case hexadecimal format
+                    without leading zeros. E.g. <literal>"1A0"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"nrarfcn"</literal></term>
+                  <listitem>
+                    NR absolute RF channel number, given as an unsigned integer value
+                    (signature <literal>"u"</literal>).
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"rsrp"</literal></term>
+                  <listitem>
+                    The average reference signal received power in dBm, given as a signed
+                    integer value (signature <literal>"d"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"rsrq"</literal></term>
+                  <listitem>
+                    The average reference signal received quality in dB, given as a signed
+                    integer value (signature <literal>"d"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"sinr"</literal></term>
+                  <listitem>
+                    The signal to interference and noise ratio, given as a signed integer value
+                    (signature <literal>"d"</literal>.
+                  </listitem>
+                </varlistentry>
+                <varlistentry><term><literal>"timing-advance"</literal></term>
+                  <listitem>
+                    The timing advance, given as an unsigned integer value (signature
+                    <literal>"u"</literal>).
+                    Only applicable for the serving cell (i.e. "serving" must be TRUE).
+                  </listitem>
+                </varlistentry>
+              </variablelist>
+            </listitem>
+          </varlistentry>
+        </variablelist>
+
+        Since: 1.20
+    -->
+    <method name="GetCellInfo">
+      <arg name="cell_info" type="aa{sv}" direction="out" />
+    </method>
+
     <!--
         Command:
         @cmd: The command string, e.g. "AT+GCAP" or "+GCAP" (leading AT is inserted if necessary).
diff --git a/libmm-glib/Makefile.am b/libmm-glib/Makefile.am
index ff44074..08d5ee2 100644
--- a/libmm-glib/Makefile.am
+++ b/libmm-glib/Makefile.am
@@ -101,6 +101,20 @@
 	mm-signal-threshold-properties.c \
 	mm-nr5g-registration-settings.h \
 	mm-nr5g-registration-settings.c \
+	mm-cell-info.h \
+	mm-cell-info.c \
+	mm-cell-info-cdma.h \
+	mm-cell-info-cdma.c \
+	mm-cell-info-gsm.h \
+	mm-cell-info-gsm.c \
+	mm-cell-info-umts.h \
+	mm-cell-info-umts.c \
+	mm-cell-info-tdscdma.h \
+	mm-cell-info-tdscdma.c \
+	mm-cell-info-lte.h \
+	mm-cell-info-lte.c \
+	mm-cell-info-nr5g.h \
+	mm-cell-info-nr5g.c \
 	mm-compat.h \
 	mm-compat.c \
 	$(NULL)
@@ -180,6 +194,13 @@
 	mm-3gpp-profile.h \
 	mm-signal-threshold-properties.h \
 	mm-nr5g-registration-settings.h \
+	mm-cell-info.h \
+	mm-cell-info-cdma.h \
+	mm-cell-info-gsm.h \
+	mm-cell-info-umts.h \
+	mm-cell-info-tdscdma.h \
+	mm-cell-info-lte.h \
+	mm-cell-info-nr5g.h \
 	mm-compat.h \
 	$(NULL)
 
diff --git a/libmm-glib/generated/meson.build b/libmm-glib/generated/meson.build
index a44115e..798fd24 100644
--- a/libmm-glib/generated/meson.build
+++ b/libmm-glib/generated/meson.build
@@ -63,12 +63,12 @@
   fhead: '#include <ModemManager.h>\n#include "mm-errors-types.h"\n',
 )
 
-gdbus_ifaces = [
-  ['bearer', mm_ifaces_bearer, [], false],
-  ['call', mm_ifaces_call, [], false],
-  ['manager', mm_ifaces, [], false],
-  ['sim', mm_ifaces_sim, [], false],
-]
+gdbus_ifaces = {
+  'bearer': {'sources': mm_ifaces_bearer, 'object_manager': false},
+  'call': {'sources':  mm_ifaces_call, 'object_manager': false},
+  'manager': {'sources': mm_ifaces, 'object_manager': false},
+  'sim': {'sources': mm_ifaces_sim, 'object_manager': false},
+}
 
 annotations = [
   ['org.freedesktop.ModemManager1.Modem.ModemCdma', 'org.gtk.GDBus.C.Name', 'ModemCdma'],
@@ -77,22 +77,20 @@
   ['org.freedesktop.ModemManager1.Modem.Modem3gpp.ProfileManager', 'org.gtk.GDBus.C.Name', 'Modem3gppProfileManager'],
 ]
 
-gdbus_ifaces += [['modem', mm_ifaces_modem, annotations, true]]
+gdbus_ifaces += {'modem': {'sources': mm_ifaces_modem, 'annotations': annotations, 'object_manager': true}}
 
 annotations = [['org.freedesktop.ModemManager1.Sms:Data', 'org.gtk.GDBus.C.ForceGVariant', 'True']]
 
-gdbus_ifaces += [['sms', mm_ifaces_sms, annotations, false]]
+gdbus_ifaces += {'sms': {'sources': mm_ifaces_sms, 'annotations': annotations, 'object_manager': false}}
 
-foreach gdbus_iface: gdbus_ifaces
+foreach name, kwargs: gdbus_ifaces
   gdbus_sources = gnome.gdbus_codegen(
-    'mm-gdbus-' + gdbus_iface[0],
-    sources: gdbus_iface[1],
+    'mm-gdbus-' + name,
     interface_prefix: 'org.freedesktop.ModemManager1.',
     namespace: 'MmGdbus',
     docbook: 'mm-gdbus-doc',
-    annotations: gdbus_iface[2],
-    object_manager: gdbus_iface[3],
     autocleanup: 'objects',
+    kwargs: kwargs,
     # FIXME: due to the lack of possibility to add `docbook targets` to the `expand_content_files`.
     build_by_default: true,
     install_header: true,
diff --git a/libmm-glib/libmm-glib.h b/libmm-glib/libmm-glib.h
index fa47fa6..a6490fd 100644
--- a/libmm-glib/libmm-glib.h
+++ b/libmm-glib/libmm-glib.h
@@ -86,6 +86,13 @@
 #include <mm-3gpp-profile.h>
 #include <mm-signal-threshold-properties.h>
 #include <mm-nr5g-registration-settings.h>
+#include <mm-cell-info.h>
+#include <mm-cell-info-cdma.h>
+#include <mm-cell-info-gsm.h>
+#include <mm-cell-info-umts.h>
+#include <mm-cell-info-tdscdma.h>
+#include <mm-cell-info-lte.h>
+#include <mm-cell-info-nr5g.h>
 #include <mm-compat.h>
 
 /* generated */
diff --git a/libmm-glib/meson.build b/libmm-glib/meson.build
index 30500ec..6451843 100644
--- a/libmm-glib/meson.build
+++ b/libmm-glib/meson.build
@@ -16,6 +16,13 @@
   'mm-call.h',
   'mm-call-properties.h',
   'mm-cdma-manual-activation-properties.h',
+  'mm-cell-info.h',
+  'mm-cell-info-cdma.h',
+  'mm-cell-info-gsm.h',
+  'mm-cell-info-lte.h',
+  'mm-cell-info-nr5g.h',
+  'mm-cell-info-tdscdma.h',
+  'mm-cell-info-umts.h',
   'mm-compat.h',
   'mm-firmware-properties.h',
   'mm-firmware-update-settings.h',
@@ -71,6 +78,13 @@
   'mm-call.c',
   'mm-call-properties.c',
   'mm-cdma-manual-activation-properties.c',
+  'mm-cell-info.c',
+  'mm-cell-info-cdma.c',
+  'mm-cell-info-gsm.c',
+  'mm-cell-info-lte.c',
+  'mm-cell-info-nr5g.c',
+  'mm-cell-info-tdscdma.c',
+  'mm-cell-info-umts.c',
   'mm-common-helpers.c',
   'mm-compat.c',
   'mm-firmware-properties.c',
@@ -113,8 +127,10 @@
 
 deps = [include_dep]
 
+libname = 'mm-glib'
+
 libmm_glib = shared_library(
-  'mm-glib',
+  libname,
   version: mm_glib_version,
   sources: sources,
   include_directories: top_inc,
@@ -133,7 +149,7 @@
 pkg.generate(
   libraries: libmm_glib,
   version: mm_version,
-  name: 'mm-glib',
+  name: libname,
   description: 'Library to control and monitor the ModemManager',
   subdirs: mm_glib_name,
   # FIXME: produced by the inhability of meson to use internal dependencies
@@ -166,7 +182,7 @@
     symbol_prefix: gir_prefix.to_lower(),
     extra_args: args,
     header: 'libmm-glib.h',
-    export_packages: gir_ns,
+    export_packages: libname,
     install: true,
   )
 
diff --git a/libmm-glib/mm-call-audio-format.c b/libmm-glib/mm-call-audio-format.c
index 452fa72..a3d617c 100644
--- a/libmm-glib/mm-call-audio-format.c
+++ b/libmm-glib/mm-call-audio-format.c
@@ -167,25 +167,24 @@
         return NULL;
 
     g_variant_builder_init (&builder, G_VARIANT_TYPE ("a{sv}"));
-    if (self) {
-        if (self->priv->encoding)
-            g_variant_builder_add (&builder,
-                                   "{sv}",
-                                   PROPERTY_ENCODING,
-                                   g_variant_new_string (self->priv->encoding));
 
-        if (self->priv->resolution)
-            g_variant_builder_add (&builder,
-                                   "{sv}",
-                                   PROPERTY_RESOLUTION,
-                                   g_variant_new_string (self->priv->resolution));
+    if (self->priv->encoding)
+        g_variant_builder_add (&builder,
+                               "{sv}",
+                               PROPERTY_ENCODING,
+                               g_variant_new_string (self->priv->encoding));
 
-        if (self->priv->rate)
-            g_variant_builder_add (&builder,
-                                   "{sv}",
-                                   PROPERTY_RATE,
-                                   g_variant_new_uint32 (self->priv->rate));
-    }
+    if (self->priv->resolution)
+        g_variant_builder_add (&builder,
+                               "{sv}",
+                               PROPERTY_RESOLUTION,
+                               g_variant_new_string (self->priv->resolution));
+
+    if (self->priv->rate)
+        g_variant_builder_add (&builder,
+                               "{sv}",
+                               PROPERTY_RATE,
+                               g_variant_new_uint32 (self->priv->rate));
 
     return g_variant_builder_end (&builder);
 }
diff --git a/libmm-glib/mm-cell-info-cdma.c b/libmm-glib/mm-cell-info-cdma.c
new file mode 100644
index 0000000..f288ee2
--- /dev/null
+++ b/libmm-glib/mm-cell-info-cdma.c
@@ -0,0 +1,308 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-helpers.h"
+#include "mm-cell-info-cdma.h"
+
+/**
+ * SECTION: mm-cell-info-cdma
+ * @title: MMCellInfoCdma
+ * @short_description: Helper object to report CDMA cell info
+ *
+ * The #MMCellInfoCdma is an object used to report CDMA cell
+ * information.
+ *
+ * The object inherits from the generic #MMCellInfo.
+ */
+
+G_DEFINE_TYPE (MMCellInfoCdma, mm_cell_info_cdma, MM_TYPE_CELL_INFO)
+
+#define PROPERTY_NID             "nid"
+#define PROPERTY_SID             "sid"
+#define PROPERTY_BASE_STATION_ID "base-station-id"
+#define PROPERTY_REF_PN          "ref-pn"
+#define PROPERTY_PILOT_STRENGTH  "pilot-strength"
+
+struct _MMCellInfoCdmaPrivate {
+    gchar *nid;
+    gchar *sid;
+    gchar *base_station_id;
+    gchar *ref_pn;
+    guint  pilot_strength;
+};
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_cdma_get_nid:
+ * @self: a #MMCellInfoCdma.
+ *
+ * Get the CDMA network id.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros.
+ *
+ * Returns: (transfer none): the CDMA network id, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_cdma_get_nid (MMCellInfoCdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_CDMA (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->nid);
+}
+
+/**
+ * mm_cell_info_cdma_set_nid: (skip)
+ */
+void
+mm_cell_info_cdma_set_nid (MMCellInfoCdma *self,
+                           const gchar    *nid)
+{
+    g_free (self->priv->nid);
+    self->priv->nid = g_strdup (nid);
+}
+
+/**
+ * mm_cell_info_cdma_get_sid:
+ * @self: a #MMCellInfoCdma.
+ *
+ * Get the CDMA system id.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros.
+ *
+ * Returns: (transfer none): the CDMA system id, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_cdma_get_sid (MMCellInfoCdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_CDMA (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->sid);
+}
+
+/**
+ * mm_cell_info_cdma_set_sid: (skip)
+ */
+void
+mm_cell_info_cdma_set_sid (MMCellInfoCdma *self,
+                           const gchar    *sid)
+{
+    g_free (self->priv->sid);
+    self->priv->sid = g_strdup (sid);
+}
+
+/**
+ * mm_cell_info_cdma_get_base_station_id:
+ * @self: a #MMCellInfoCdma.
+ *
+ * Get the CDMA base station id.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros.
+ *
+ * Returns: (transfer none): the CDMA base station id, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_cdma_get_base_station_id (MMCellInfoCdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_CDMA (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->base_station_id);
+}
+
+/**
+ * mm_cell_info_cdma_set_base_station_id: (skip)
+ */
+void
+mm_cell_info_cdma_set_base_station_id (MMCellInfoCdma *self,
+                                       const gchar    *base_station_id)
+{
+    g_free (self->priv->base_station_id);
+    self->priv->base_station_id = g_strdup (base_station_id);
+}
+
+/**
+ * mm_cell_info_cdma_get_ref_pn:
+ * @self: a #MMCellInfoCdma.
+ *
+ * Get the CDMA base station PN number.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros.
+ *
+ * Returns: (transfer none): the CDMA base station PN number, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_cdma_get_ref_pn (MMCellInfoCdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_CDMA (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->ref_pn);
+}
+
+/**
+ * mm_cell_info_cdma_set_ref_pn: (skip)
+ */
+void
+mm_cell_info_cdma_set_ref_pn (MMCellInfoCdma *self,
+                              const gchar    *ref_pn)
+{
+    g_free (self->priv->ref_pn);
+    self->priv->ref_pn = g_strdup (ref_pn);
+}
+
+/**
+ * mm_cell_info_cdma_get_pilot_strength:
+ * @self: a #MMCellInfoCdma.
+ *
+ * Get the signal strength of the pilot.
+ *
+ * Given in the same format and scale as the GSM SINR level.
+ *
+ * Returns: the pilot strength, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_cdma_get_pilot_strength (MMCellInfoCdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_CDMA (self), G_MAXUINT);
+
+    return self->priv->pilot_strength;
+}
+
+/**
+ * mm_cell_info_cdma_set_pilot_strength: (skip)
+ */
+void
+mm_cell_info_cdma_set_pilot_strength (MMCellInfoCdma *self,
+                                      guint           pilot_strength)
+{
+    self->priv->pilot_strength = pilot_strength;
+}
+
+/*****************************************************************************/
+
+static GString *
+build_string (MMCellInfo *_self)
+{
+    MMCellInfoCdma *self = MM_CELL_INFO_CDMA (_self);
+    GString        *str;
+
+    str = g_string_new (NULL);
+
+    MM_CELL_INFO_BUILD_STRING_APPEND ("nid",             "%s", nid,             NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("sid",             "%s", sid,             NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("base station id", "%s", base_station_id, NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("ref pn",          "%s", ref_pn,          NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("pilot strength",  "%u", pilot_strength,  G_MAXUINT);
+
+    return str;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_cdma_get_dictionary: (skip)
+ */
+static GVariantDict *
+get_dictionary (MMCellInfo *_self)
+{
+    MMCellInfoCdma *self = MM_CELL_INFO_CDMA (_self);
+    GVariantDict   *dict;
+
+    dict = g_variant_dict_new (NULL);
+
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (NID,             nid,             string, NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (SID,             sid,             string, NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (BASE_STATION_ID, base_station_id, string, NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (REF_PN,          ref_pn,          string, NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (PILOT_STRENGTH,  pilot_strength,  uint32, G_MAXUINT);
+
+    return dict;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_cdma_new_from_dictionary: (skip)
+ */
+MMCellInfo *
+mm_cell_info_cdma_new_from_dictionary (GVariantDict *dict)
+{
+    MMCellInfoCdma *self;
+
+    self = MM_CELL_INFO_CDMA (g_object_new (MM_TYPE_CELL_INFO_CDMA, NULL));
+
+    if (dict) {
+
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (cdma, NID,             nid);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (cdma, SID,             sid);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (cdma, BASE_STATION_ID, base_station_id);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (cdma, REF_PN,          ref_pn);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET    (cdma, PILOT_STRENGTH,  pilot_strength, UINT32, uint32);
+    }
+
+    return MM_CELL_INFO (self);
+}
+
+/*****************************************************************************/
+
+static void
+mm_cell_info_cdma_init (MMCellInfoCdma *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CELL_INFO_CDMA, MMCellInfoCdmaPrivate);
+    self->priv->pilot_strength = G_MAXUINT;
+}
+
+static void
+finalize (GObject *object)
+{
+    MMCellInfoCdma *self = MM_CELL_INFO_CDMA (object);
+
+    g_free (self->priv->sid);
+    g_free (self->priv->nid);
+    g_free (self->priv->base_station_id);
+    g_free (self->priv->ref_pn);
+
+    G_OBJECT_CLASS (mm_cell_info_cdma_parent_class)->finalize (object);
+}
+
+static void
+mm_cell_info_cdma_class_init (MMCellInfoCdmaClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    MMCellInfoClass *cell_info_class = MM_CELL_INFO_CLASS (klass);
+
+    g_type_class_add_private (object_class, sizeof (MMCellInfoCdmaPrivate));
+
+    object_class->finalize = finalize;
+    cell_info_class->get_dictionary = get_dictionary;
+    cell_info_class->build_string = build_string;
+
+}
diff --git a/libmm-glib/mm-cell-info-cdma.h b/libmm-glib/mm-cell-info-cdma.h
new file mode 100644
index 0000000..994fbcc
--- /dev/null
+++ b/libmm-glib/mm-cell-info-cdma.h
@@ -0,0 +1,98 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_CELL_INFO_CDMA_H
+#define MM_CELL_INFO_CDMA_H
+
+#if !defined (__LIBMM_GLIB_H_INSIDE__) && !defined (LIBMM_GLIB_COMPILATION)
+#error "Only <libmm-glib.h> can be included directly."
+#endif
+
+#include <ModemManager.h>
+#include <glib-object.h>
+
+#include "mm-cell-info.h"
+
+G_BEGIN_DECLS
+
+#define MM_TYPE_CELL_INFO_CDMA            (mm_cell_info_cdma_get_type ())
+#define MM_CELL_INFO_CDMA(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CELL_INFO_CDMA, MMCellInfoCdma))
+#define MM_CELL_INFO_CDMA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_CELL_INFO_CDMA, MMCellInfoCdmaClass))
+#define MM_IS_CELL_INFO_CDMA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CELL_INFO_CDMA))
+#define MM_IS_CELL_INFO_CDMA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_CELL_INFO_CDMA))
+#define MM_CELL_INFO_CDMA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_CELL_INFO_CDMA, MMCellInfoCdmaClass))
+
+typedef struct _MMCellInfoCdma MMCellInfoCdma;
+typedef struct _MMCellInfoCdmaClass MMCellInfoCdmaClass;
+typedef struct _MMCellInfoCdmaPrivate MMCellInfoCdmaPrivate;
+
+/**
+ * MMCellInfoCdma:
+ *
+ * The #MMCellInfoCdma structure contains private data and should only be
+ * accessed using the provided API.
+ */
+struct _MMCellInfoCdma {
+    /*< private >*/
+    MMCellInfo             parent;
+    MMCellInfoCdmaPrivate *priv;
+};
+
+struct _MMCellInfoCdmaClass {
+    /*< private >*/
+    MMCellInfoClass parent;
+};
+
+GType mm_cell_info_cdma_get_type (void);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCellInfoCdma, g_object_unref)
+
+const gchar *mm_cell_info_cdma_get_nid             (MMCellInfoCdma *self);
+const gchar *mm_cell_info_cdma_get_sid             (MMCellInfoCdma *self);
+const gchar *mm_cell_info_cdma_get_base_station_id (MMCellInfoCdma *self);
+const gchar *mm_cell_info_cdma_get_ref_pn          (MMCellInfoCdma *self);
+guint        mm_cell_info_cdma_get_pilot_strength  (MMCellInfoCdma *self);
+
+/*****************************************************************************/
+/* ModemManager/libmm-glib/mmcli specific methods */
+
+#if defined (_LIBMM_INSIDE_MM) ||    \
+    defined (_LIBMM_INSIDE_MMCLI) || \
+    defined (LIBMM_GLIB_COMPILATION)
+
+void mm_cell_info_cdma_set_nid             (MMCellInfoCdma *self,
+                                            const gchar    *nid);
+void mm_cell_info_cdma_set_sid             (MMCellInfoCdma *self,
+                                            const gchar    *sid);
+void mm_cell_info_cdma_set_base_station_id (MMCellInfoCdma *self,
+                                            const gchar    *base_station_id);
+void mm_cell_info_cdma_set_ref_pn          (MMCellInfoCdma *self,
+                                            const gchar    *ref_pn);
+void mm_cell_info_cdma_set_pilot_strength  (MMCellInfoCdma *self,
+                                            guint           pilot_strength);
+
+MMCellInfo *mm_cell_info_cdma_new_from_dictionary (GVariantDict *dict);
+
+#endif
+
+G_END_DECLS
+
+#endif /* MM_CELL_INFO_CDMA_H */
diff --git a/libmm-glib/mm-cell-info-gsm.c b/libmm-glib/mm-cell-info-gsm.c
new file mode 100644
index 0000000..9b0b8e1
--- /dev/null
+++ b/libmm-glib/mm-cell-info-gsm.c
@@ -0,0 +1,374 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-helpers.h"
+#include "mm-cell-info-gsm.h"
+
+/**
+ * SECTION: mm-cell-info-gsm
+ * @title: MMCellInfoGsm
+ * @short_description: Helper object to report GSM cell info
+ *
+ * The #MMCellInfoGsm is an object used to report GSM cell
+ * information.
+ *
+ * The object inherits from the generic #MMCellInfo.
+ */
+
+G_DEFINE_TYPE (MMCellInfoGsm, mm_cell_info_gsm, MM_TYPE_CELL_INFO)
+
+#define PROPERTY_OPERATOR_ID     "operator-id"
+#define PROPERTY_LAC             "lac"
+#define PROPERTY_CI              "ci"
+#define PROPERTY_TIMING_ADVANCE  "timing-advance"
+#define PROPERTY_ARFCN           "arfcn"
+#define PROPERTY_BASE_STATION_ID "base-station-id"
+#define PROPERTY_RX_LEVEL        "rx-level"
+
+struct _MMCellInfoGsmPrivate {
+    gchar *operator_id;
+    gchar *lac;
+    gchar *ci;
+    guint  timing_advance;
+    guint  arfcn;
+    gchar *base_station_id;
+    guint  rx_level;
+};
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_gsm_get_operator_id:
+ * @self: a #MMCellInfoGsm.
+ *
+ * Get the PLMN MCC/MNC.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_gsm_get_operator_id (MMCellInfoGsm *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_GSM (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->operator_id);
+}
+
+/**
+ * mm_cell_info_gsm_set_operator_id: (skip)
+ */
+void
+mm_cell_info_gsm_set_operator_id (MMCellInfoGsm *self,
+                                  const gchar   *operator_id)
+{
+    g_free (self->priv->operator_id);
+    self->priv->operator_id = g_strdup (operator_id);
+}
+
+/**
+ * mm_cell_info_gsm_get_lac:
+ * @self: a #MMCellInfoGsm.
+ *
+ * Get the two-byte Location Area Code of the base station.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_gsm_get_lac (MMCellInfoGsm *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_GSM (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->lac);
+}
+
+/**
+ * mm_cell_info_gsm_set_lac: (skip)
+ */
+void
+mm_cell_info_gsm_set_lac (MMCellInfoGsm *self,
+                          const gchar   *lac)
+{
+    g_free (self->priv->lac);
+    self->priv->lac = g_strdup (lac);
+}
+
+/**
+ * mm_cell_info_gsm_get_ci:
+ * @self: a #MMCellInfoGsm.
+ *
+ * Get the two- or four-byte Cell Identifier.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_gsm_get_ci (MMCellInfoGsm *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_GSM (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->ci);
+}
+
+/**
+ * mm_cell_info_gsm_set_ci: (skip)
+ */
+void
+mm_cell_info_gsm_set_ci (MMCellInfoGsm *self,
+                         const gchar   *ci)
+{
+    g_free (self->priv->ci);
+    self->priv->ci = g_strdup (ci);
+}
+
+/**
+ * mm_cell_info_gsm_get_timing_advance:
+ * @self: a #MMCellInfoGsm.
+ *
+ * Get the measured delay (in bit periods) of an access burst transmission
+ * on the RACH or PRACH to the expected signal from a mobile station at zero
+ * distance under static channel conditions.
+ *
+ * Returns: the timing advance, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_gsm_get_timing_advance (MMCellInfoGsm *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_GSM (self), G_MAXUINT);
+
+    return self->priv->timing_advance;
+}
+
+/**
+ * mm_cell_info_gsm_set_timing_advance: (skip)
+ */
+void
+mm_cell_info_gsm_set_timing_advance (MMCellInfoGsm *self,
+                                     guint          timing_advance)
+{
+    self->priv->timing_advance = timing_advance;
+}
+
+/**
+ * mm_cell_info_gsm_get_arfcn:
+ * @self: a #MMCellInfoGsm.
+ *
+ * Get the absolute RF channel number.
+ *
+ * Returns: the ARFCN, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_gsm_get_arfcn (MMCellInfoGsm *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_GSM (self), G_MAXUINT);
+
+    return self->priv->arfcn;
+}
+
+/**
+ * mm_cell_info_gsm_set_arfcn: (skip)
+ */
+void
+mm_cell_info_gsm_set_arfcn (MMCellInfoGsm *self,
+                            guint          arfcn)
+{
+    self->priv->arfcn = arfcn;
+}
+
+/**
+ * mm_cell_info_gsm_get_base_station_id:
+ * @self: a #MMCellInfoGsm.
+ *
+ * Get the GSM base station id, in upper-case hexadecimal format without leading
+ * zeros. E.g. "3F".
+ *
+ * Returns: (transfer none): the GSM base station id, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_gsm_get_base_station_id (MMCellInfoGsm *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_GSM (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->base_station_id);
+}
+
+/**
+ * mm_cell_info_gsm_set_base_station_id: (skip)
+ */
+void
+mm_cell_info_gsm_set_base_station_id (MMCellInfoGsm *self,
+                                      const gchar   *base_station_id)
+{
+    g_free (self->priv->base_station_id);
+    self->priv->base_station_id = g_strdup (base_station_id);
+}
+
+/**
+ * mm_cell_info_gsm_get_rx_level:
+ * @self: a #MMCellInfoGsm.
+ *
+ * Get the serving cell RX measurement.
+ *
+ * Returns: the rx level, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_gsm_get_rx_level (MMCellInfoGsm *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_GSM (self), G_MAXUINT);
+
+    return self->priv->rx_level;
+}
+
+/**
+ * mm_cell_info_gsm_set_rx_level: (skip)
+ */
+void
+mm_cell_info_gsm_set_rx_level (MMCellInfoGsm *self,
+                               guint          rx_level)
+{
+    self->priv->rx_level = rx_level;
+}
+
+/*****************************************************************************/
+
+static GString *
+build_string (MMCellInfo *_self)
+{
+    MMCellInfoGsm *self = MM_CELL_INFO_GSM (_self);
+    GString        *str;
+
+    str = g_string_new (NULL);
+
+    MM_CELL_INFO_BUILD_STRING_APPEND ("operator id",     "%s", operator_id,     NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("lac",             "%s", lac,             NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("ci",              "%s", ci,              NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("timing advance",  "%u", timing_advance,  G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("arfcn",           "%u", arfcn,           G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("base station id", "%s", base_station_id, NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("rx level",        "%u", rx_level,        G_MAXUINT);
+
+    return str;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_gsm_get_dictionary: (skip)
+ */
+static GVariantDict *
+get_dictionary (MMCellInfo *_self)
+{
+    MMCellInfoGsm *self = MM_CELL_INFO_GSM (_self);
+    GVariantDict   *dict;
+
+    dict = g_variant_dict_new (NULL);
+
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (OPERATOR_ID,     operator_id,     string, NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (LAC,             lac,             string, NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (CI,              ci,              string, NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (TIMING_ADVANCE,  timing_advance,  uint32, G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (ARFCN,           arfcn,           uint32, G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (BASE_STATION_ID, base_station_id, string, NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (RX_LEVEL,        rx_level,        uint32, G_MAXUINT);
+
+    return dict;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_gsm_new_from_dictionary: (skip)
+ */
+MMCellInfo *
+mm_cell_info_gsm_new_from_dictionary (GVariantDict *dict)
+{
+    MMCellInfoGsm *self;
+
+    self = MM_CELL_INFO_GSM (g_object_new (MM_TYPE_CELL_INFO_GSM, NULL));
+
+    if (dict) {
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (gsm, OPERATOR_ID,     operator_id);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (gsm, LAC,             lac);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (gsm, CI,              ci);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET    (gsm, TIMING_ADVANCE,  timing_advance, UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET    (gsm, ARFCN,           arfcn,          UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (gsm, BASE_STATION_ID, base_station_id);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET    (gsm, RX_LEVEL,        rx_level,       UINT32, uint32);
+    }
+
+    return MM_CELL_INFO (self);
+}
+
+/*****************************************************************************/
+
+static void
+mm_cell_info_gsm_init (MMCellInfoGsm *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CELL_INFO_GSM, MMCellInfoGsmPrivate);
+    self->priv->timing_advance = G_MAXUINT;
+    self->priv->arfcn = G_MAXUINT;
+    self->priv->rx_level = G_MAXUINT;
+}
+
+static void
+finalize (GObject *object)
+{
+    MMCellInfoGsm *self = MM_CELL_INFO_GSM (object);
+
+    g_free (self->priv->operator_id);
+    g_free (self->priv->lac);
+    g_free (self->priv->ci);
+    g_free (self->priv->base_station_id);
+
+    G_OBJECT_CLASS (mm_cell_info_gsm_parent_class)->finalize (object);
+}
+
+static void
+mm_cell_info_gsm_class_init (MMCellInfoGsmClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    MMCellInfoClass *cell_info_class = MM_CELL_INFO_CLASS (klass);
+
+    g_type_class_add_private (object_class, sizeof (MMCellInfoGsmPrivate));
+
+    object_class->finalize = finalize;
+    cell_info_class->get_dictionary = get_dictionary;
+    cell_info_class->build_string = build_string;
+
+}
diff --git a/libmm-glib/mm-cell-info-gsm.h b/libmm-glib/mm-cell-info-gsm.h
new file mode 100644
index 0000000..a583e32
--- /dev/null
+++ b/libmm-glib/mm-cell-info-gsm.h
@@ -0,0 +1,104 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_CELL_INFO_GSM_H
+#define MM_CELL_INFO_GSM_H
+
+#if !defined (__LIBMM_GLIB_H_INSIDE__) && !defined (LIBMM_GLIB_COMPILATION)
+#error "Only <libmm-glib.h> can be included directly."
+#endif
+
+#include <ModemManager.h>
+#include <glib-object.h>
+
+#include "mm-cell-info.h"
+
+G_BEGIN_DECLS
+
+#define MM_TYPE_CELL_INFO_GSM            (mm_cell_info_gsm_get_type ())
+#define MM_CELL_INFO_GSM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CELL_INFO_GSM, MMCellInfoGsm))
+#define MM_CELL_INFO_GSM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_CELL_INFO_GSM, MMCellInfoGsmClass))
+#define MM_IS_CELL_INFO_GSM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CELL_INFO_GSM))
+#define MM_IS_CELL_INFO_GSM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_CELL_INFO_GSM))
+#define MM_CELL_INFO_GSM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_CELL_INFO_GSM, MMCellInfoGsmClass))
+
+typedef struct _MMCellInfoGsm MMCellInfoGsm;
+typedef struct _MMCellInfoGsmClass MMCellInfoGsmClass;
+typedef struct _MMCellInfoGsmPrivate MMCellInfoGsmPrivate;
+
+/**
+ * MMCellInfoGsm:
+ *
+ * The #MMCellInfoGsm structure contains private data and should only be
+ * accessed using the provided API.
+ */
+struct _MMCellInfoGsm {
+    /*< private >*/
+    MMCellInfo            parent;
+    MMCellInfoGsmPrivate *priv;
+};
+
+struct _MMCellInfoGsmClass {
+    /*< private >*/
+    MMCellInfoClass parent;
+};
+
+GType mm_cell_info_gsm_get_type (void);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCellInfoGsm, g_object_unref)
+
+const gchar *mm_cell_info_gsm_get_operator_id     (MMCellInfoGsm *self);
+const gchar *mm_cell_info_gsm_get_lac             (MMCellInfoGsm *self);
+const gchar *mm_cell_info_gsm_get_ci              (MMCellInfoGsm *self);
+guint        mm_cell_info_gsm_get_timing_advance  (MMCellInfoGsm *self);
+guint        mm_cell_info_gsm_get_arfcn           (MMCellInfoGsm *self);
+const gchar *mm_cell_info_gsm_get_base_station_id (MMCellInfoGsm *self);
+guint        mm_cell_info_gsm_get_rx_level        (MMCellInfoGsm *self);
+
+/*****************************************************************************/
+/* ModemManager/libmm-glib/mmcli specific methods */
+
+#if defined (_LIBMM_INSIDE_MM) ||    \
+    defined (_LIBMM_INSIDE_MMCLI) || \
+    defined (LIBMM_GLIB_COMPILATION)
+
+void mm_cell_info_gsm_set_operator_id     (MMCellInfoGsm *self,
+                                           const gchar   *operator_id);
+void mm_cell_info_gsm_set_lac             (MMCellInfoGsm *self,
+                                           const gchar   *lac);
+void mm_cell_info_gsm_set_ci              (MMCellInfoGsm *self,
+                                           const gchar   *ci);
+void mm_cell_info_gsm_set_timing_advance  (MMCellInfoGsm *self,
+                                           guint          timing_advance);
+void mm_cell_info_gsm_set_arfcn           (MMCellInfoGsm *self,
+                                           guint          arfcn);
+void mm_cell_info_gsm_set_base_station_id (MMCellInfoGsm *self,
+                                           const gchar   *base_station_id);
+void mm_cell_info_gsm_set_rx_level        (MMCellInfoGsm *self,
+                                           guint          rx_level);
+
+MMCellInfo *mm_cell_info_gsm_new_from_dictionary (GVariantDict *dict);
+
+#endif
+
+G_END_DECLS
+
+#endif /* MM_CELL_INFO_GSM_H */
diff --git a/libmm-glib/mm-cell-info-lte.c b/libmm-glib/mm-cell-info-lte.c
new file mode 100644
index 0000000..1768c04
--- /dev/null
+++ b/libmm-glib/mm-cell-info-lte.c
@@ -0,0 +1,410 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-helpers.h"
+#include "mm-cell-info-lte.h"
+
+/**
+ * SECTION: mm-cell-info-lte
+ * @title: MMCellInfoLte
+ * @short_description: Helper object to report LTE cell info
+ *
+ * The #MMCellInfoLte is an object used to report LTE cell
+ * information.
+ *
+ * The object inherits from the generic #MMCellInfo.
+ */
+
+G_DEFINE_TYPE (MMCellInfoLte, mm_cell_info_lte, MM_TYPE_CELL_INFO)
+
+#define PROPERTY_OPERATOR_ID      "operator-id"
+#define PROPERTY_TAC              "tac"
+#define PROPERTY_CI               "ci"
+#define PROPERTY_PHYSICAL_CI      "physical-ci"
+#define PROPERTY_EARFCN           "earfcn"
+#define PROPERTY_RSRP             "rsrp"
+#define PROPERTY_RSRQ             "rsrq"
+#define PROPERTY_TIMING_ADVANCE   "timing-advance"
+
+
+struct _MMCellInfoLtePrivate {
+    gchar   *operator_id;
+    gchar   *tac;
+    gchar   *ci;
+    gchar   *physical_ci;
+    guint    earfcn;
+    gdouble  rsrp;
+    gdouble  rsrq;
+    guint    timing_advance;
+};
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_lte_get_operator_id:
+ * @self: a #MMCellInfoLte.
+ *
+ * Get the PLMN MCC/MNC.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_lte_get_operator_id (MMCellInfoLte *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_LTE (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->operator_id);
+}
+
+/**
+ * mm_cell_info_lte_set_operator_id: (skip)
+ */
+void
+mm_cell_info_lte_set_operator_id (MMCellInfoLte *self,
+                                  const gchar   *operator_id)
+{
+    g_free (self->priv->operator_id);
+    self->priv->operator_id = g_strdup (operator_id);
+}
+
+/**
+ * mm_cell_info_lte_get_tac:
+ * @self: a #MMCellInfoLte.
+ *
+ * Get the two- or three- byte Tracking Area Code of the base station.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_lte_get_tac (MMCellInfoLte *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_LTE (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->tac);
+}
+
+/**
+ * mm_cell_info_lte_set_tac: (skip)
+ */
+void
+mm_cell_info_lte_set_tac (MMCellInfoLte *self,
+                          const gchar   *tac)
+{
+    g_free (self->priv->tac);
+    self->priv->tac = g_strdup (tac);
+}
+
+/**
+ * mm_cell_info_lte_get_ci:
+ * @self: a #MMCellInfoLte.
+ *
+ * Get the two- or four-byte Cell Identifier.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_lte_get_ci (MMCellInfoLte *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_LTE (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->ci);
+}
+
+/**
+ * mm_cell_info_lte_set_ci: (skip)
+ */
+void
+mm_cell_info_lte_set_ci (MMCellInfoLte *self,
+                         const gchar   *ci)
+{
+    g_free (self->priv->ci);
+    self->priv->ci = g_strdup (ci);
+}
+
+/**
+ * mm_cell_info_lte_get_physical_ci:
+ * @self: a #MMCellInfoLte.
+ *
+ * Get the physical cell identifier.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_lte_get_physical_ci (MMCellInfoLte *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_LTE (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->physical_ci);
+}
+
+/**
+ * mm_cell_info_lte_set_physical_ci: (skip)
+ */
+void
+mm_cell_info_lte_set_physical_ci (MMCellInfoLte *self,
+                                  const gchar   *physical_ci)
+{
+    g_free (self->priv->physical_ci);
+    self->priv->physical_ci = g_strdup (physical_ci);
+}
+
+/**
+ * mm_cell_info_lte_get_earfcn:
+ * @self: a #MMCellInfoLte.
+ *
+ * Get the E-UTRA absolute RF channel number.
+ *
+ * Returns: the EARFCN, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_lte_get_earfcn (MMCellInfoLte *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_LTE (self), G_MAXUINT);
+
+    return self->priv->earfcn;
+}
+
+/**
+ * mm_cell_info_lte_set_earfcn: (skip)
+ */
+void
+mm_cell_info_lte_set_earfcn (MMCellInfoLte *self,
+                             guint          earfcn)
+{
+    self->priv->earfcn = earfcn;
+}
+
+/**
+ * mm_cell_info_lte_get_rsrp:
+ * @self: a #MMCellInfoLte.
+ *
+ * Get the average reference signal received power in dBm.
+ *
+ * Returns: the RSRP, or -%G_MAXDOUBLE if not available.
+ *
+ * Since: 1.20
+ */
+gdouble
+mm_cell_info_lte_get_rsrp (MMCellInfoLte *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_LTE (self), -G_MAXDOUBLE);
+
+    return self->priv->rsrp;
+}
+
+/**
+ * mm_cell_info_lte_set_rsrp: (skip)
+ */
+void
+mm_cell_info_lte_set_rsrp (MMCellInfoLte *self,
+                           gdouble        rsrp)
+{
+    self->priv->rsrp = rsrp;
+}
+
+/**
+ * mm_cell_info_lte_get_rsrq:
+ * @self: a #MMCellInfoLte.
+ *
+ * Get the average reference signal received quality in dB.
+ *
+ * Returns: the RSRQ, or -%G_MAXDOUBLE if not available.
+ *
+ * Since: 1.20
+ */
+gdouble
+mm_cell_info_lte_get_rsrq (MMCellInfoLte *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_LTE (self), -G_MAXDOUBLE);
+
+    return self->priv->rsrq;
+}
+
+/**
+ * mm_cell_info_lte_set_rsrq: (skip)
+ */
+void
+mm_cell_info_lte_set_rsrq (MMCellInfoLte *self,
+                           gdouble        rsrq)
+{
+    self->priv->rsrq = rsrq;
+}
+
+/**
+ * mm_cell_info_lte_get_timing_advance:
+ * @self: a #MMCellInfoLte.
+ *
+ * Get the timing advance.
+ *
+ * Returns: the timing advance, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_lte_get_timing_advance (MMCellInfoLte *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_LTE (self), G_MAXUINT);
+
+    return self->priv->timing_advance;
+}
+
+/**
+ * mm_cell_info_lte_set_timing_advance: (skip)
+ */
+void
+mm_cell_info_lte_set_timing_advance (MMCellInfoLte *self,
+                                     guint          timing_advance)
+{
+    self->priv->timing_advance = timing_advance;
+}
+
+/*****************************************************************************/
+
+static GString *
+build_string (MMCellInfo *_self)
+{
+    MMCellInfoLte *self = MM_CELL_INFO_LTE (_self);
+    GString       *str;
+
+    str = g_string_new (NULL);
+
+    MM_CELL_INFO_BUILD_STRING_APPEND ("operator id",    "%s",  operator_id,     NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("tac",            "%s",  tac,             NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("ci",             "%s",  ci,              NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("physical ci",    "%s",  physical_ci,     NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("earfcn",         "%u",  earfcn,          G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("rsrp",           "%lf", rsrp,           -G_MAXDOUBLE);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("rsrq",           "%lf", rsrq,           -G_MAXDOUBLE);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("timing advance", "%u",  timing_advance,  G_MAXUINT);
+
+    return str;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_lte_get_dictionary: (skip)
+ */
+static GVariantDict *
+get_dictionary (MMCellInfo *_self)
+{
+    MMCellInfoLte *self = MM_CELL_INFO_LTE (_self);
+    GVariantDict  *dict;
+
+    dict = g_variant_dict_new (NULL);
+
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (OPERATOR_ID,    operator_id,    string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (TAC,            tac,            string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (CI,             ci,             string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (PHYSICAL_CI,    physical_ci,    string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (EARFCN,         earfcn,         uint32,  G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (RSRP,           rsrp,           double, -G_MAXDOUBLE);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (RSRQ,           rsrq,           double, -G_MAXDOUBLE);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (TIMING_ADVANCE, timing_advance, uint32,  G_MAXUINT);
+
+    return dict;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_lte_new_from_dictionary: (skip)
+ */
+MMCellInfo *
+mm_cell_info_lte_new_from_dictionary (GVariantDict *dict)
+{
+    MMCellInfoLte *self;
+
+    self = MM_CELL_INFO_LTE (g_object_new (MM_TYPE_CELL_INFO_LTE, NULL));
+
+    if (dict) {
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (lte, OPERATOR_ID, operator_id);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (lte, TAC,         tac);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (lte, CI,          ci);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (lte, PHYSICAL_CI, physical_ci);
+
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (lte, EARFCN,         earfcn,         UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (lte, RSRP,           rsrp,           DOUBLE, double);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (lte, RSRQ,           rsrq,           DOUBLE, double);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (lte, TIMING_ADVANCE, timing_advance, UINT32, uint32);
+    }
+
+    return MM_CELL_INFO (self);
+}
+
+/*****************************************************************************/
+
+static void
+mm_cell_info_lte_init (MMCellInfoLte *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CELL_INFO_LTE, MMCellInfoLtePrivate);
+    self->priv->earfcn = G_MAXUINT;
+    self->priv->rsrp = -G_MAXDOUBLE;
+    self->priv->rsrq = -G_MAXDOUBLE;
+    self->priv->timing_advance = G_MAXUINT;
+}
+
+static void
+finalize (GObject *object)
+{
+    MMCellInfoLte *self = MM_CELL_INFO_LTE (object);
+
+    g_free (self->priv->operator_id);
+    g_free (self->priv->tac);
+    g_free (self->priv->ci);
+    g_free (self->priv->physical_ci);
+
+    G_OBJECT_CLASS (mm_cell_info_lte_parent_class)->finalize (object);
+}
+
+static void
+mm_cell_info_lte_class_init (MMCellInfoLteClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    MMCellInfoClass *cell_info_class = MM_CELL_INFO_CLASS (klass);
+
+    g_type_class_add_private (object_class, sizeof (MMCellInfoLtePrivate));
+
+    object_class->finalize = finalize;
+    cell_info_class->get_dictionary = get_dictionary;
+    cell_info_class->build_string = build_string;
+
+}
diff --git a/libmm-glib/mm-cell-info-lte.h b/libmm-glib/mm-cell-info-lte.h
new file mode 100644
index 0000000..b58625f
--- /dev/null
+++ b/libmm-glib/mm-cell-info-lte.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_CELL_INFO_LTE_H
+#define MM_CELL_INFO_LTE_H
+
+#if !defined (__LIBMM_GLIB_H_INSIDE__) && !defined (LIBMM_GLIB_COMPILATION)
+#error "Only <libmm-glib.h> can be included directly."
+#endif
+
+#include <ModemManager.h>
+#include <glib-object.h>
+
+#include "mm-cell-info.h"
+
+G_BEGIN_DECLS
+
+#define MM_TYPE_CELL_INFO_LTE            (mm_cell_info_lte_get_type ())
+#define MM_CELL_INFO_LTE(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CELL_INFO_LTE, MMCellInfoLte))
+#define MM_CELL_INFO_LTE_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_CELL_INFO_LTE, MMCellInfoLteClass))
+#define MM_IS_CELL_INFO_LTE(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CELL_INFO_LTE))
+#define MM_IS_CELL_INFO_LTE_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_CELL_INFO_LTE))
+#define MM_CELL_INFO_LTE_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_CELL_INFO_LTE, MMCellInfoLteClass))
+
+typedef struct _MMCellInfoLte MMCellInfoLte;
+typedef struct _MMCellInfoLteClass MMCellInfoLteClass;
+typedef struct _MMCellInfoLtePrivate MMCellInfoLtePrivate;
+
+/**
+ * MMCellInfoLte:
+ *
+ * The #MMCellInfoLte structure contains private data and should only be
+ * accessed using the provided API.
+ */
+struct _MMCellInfoLte {
+    /*< private >*/
+    MMCellInfo            parent;
+    MMCellInfoLtePrivate *priv;
+};
+
+struct _MMCellInfoLteClass {
+    /*< private >*/
+    MMCellInfoClass parent;
+};
+
+GType mm_cell_info_lte_get_type (void);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCellInfoLte, g_object_unref)
+
+const gchar *mm_cell_info_lte_get_operator_id    (MMCellInfoLte *self);
+const gchar *mm_cell_info_lte_get_tac            (MMCellInfoLte *self);
+const gchar *mm_cell_info_lte_get_ci             (MMCellInfoLte *self);
+const gchar *mm_cell_info_lte_get_physical_ci    (MMCellInfoLte *self);
+guint        mm_cell_info_lte_get_earfcn         (MMCellInfoLte *self);
+gdouble      mm_cell_info_lte_get_rsrp           (MMCellInfoLte *self);
+gdouble      mm_cell_info_lte_get_rsrq           (MMCellInfoLte *self);
+guint        mm_cell_info_lte_get_timing_advance (MMCellInfoLte *self);
+
+/*****************************************************************************/
+/* ModemManager/libmm-glib/mmcli specific methods */
+
+#if defined (_LIBMM_INSIDE_MM) ||    \
+    defined (_LIBMM_INSIDE_MMCLI) || \
+    defined (LIBMM_GLIB_COMPILATION)
+
+void mm_cell_info_lte_set_operator_id    (MMCellInfoLte *self,
+                                          const gchar   *operator_id);
+void mm_cell_info_lte_set_tac            (MMCellInfoLte *self,
+                                          const gchar   *tac);
+void mm_cell_info_lte_set_ci             (MMCellInfoLte *self,
+                                          const gchar   *ci);
+void mm_cell_info_lte_set_physical_ci    (MMCellInfoLte *self,
+                                          const gchar   *ci);
+void mm_cell_info_lte_set_earfcn         (MMCellInfoLte *self,
+                                          guint          earfcn);
+void mm_cell_info_lte_set_rsrp           (MMCellInfoLte *self,
+                                          gdouble        rsrp);
+void mm_cell_info_lte_set_rsrq           (MMCellInfoLte *self,
+                                          gdouble        rsrq);
+void mm_cell_info_lte_set_timing_advance (MMCellInfoLte *self,
+                                          guint          earfcn);
+
+MMCellInfo *mm_cell_info_lte_new_from_dictionary (GVariantDict *dict);
+
+#endif
+
+G_END_DECLS
+
+#endif /* MM_CELL_INFO_LTE_H */
diff --git a/libmm-glib/mm-cell-info-nr5g.c b/libmm-glib/mm-cell-info-nr5g.c
new file mode 100644
index 0000000..67b5338
--- /dev/null
+++ b/libmm-glib/mm-cell-info-nr5g.c
@@ -0,0 +1,444 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-helpers.h"
+#include "mm-cell-info-nr5g.h"
+
+/**
+ * SECTION: mm-cell-info-nr5g
+ * @title: MMCellInfoNr5g
+ * @short_description: Helper object to report 5GNR cell info
+ *
+ * The #MMCellInfoNr5g is an object used to report 5GNR cell
+ * information.
+ *
+ * The object inherits from the generic #MMCellInfo.
+ */
+
+G_DEFINE_TYPE (MMCellInfoNr5g, mm_cell_info_nr5g, MM_TYPE_CELL_INFO)
+
+#define PROPERTY_OPERATOR_ID      "operator-id"
+#define PROPERTY_TAC              "tac"
+#define PROPERTY_CI               "ci"
+#define PROPERTY_PHYSICAL_CI      "physical-ci"
+#define PROPERTY_NRARFCN          "nrarfcn"
+#define PROPERTY_RSRP             "rsrp"
+#define PROPERTY_RSRQ             "rsrq"
+#define PROPERTY_SINR             "sinr"
+#define PROPERTY_TIMING_ADVANCE   "timing-advance"
+
+
+struct _MMCellInfoNr5gPrivate {
+    gchar   *operator_id;
+    gchar   *tac;
+    gchar   *ci;
+    gchar   *physical_ci;
+    guint    nrarfcn;
+    gdouble  rsrp;
+    gdouble  rsrq;
+    gdouble  sinr;
+    guint    timing_advance;
+};
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_nr5g_get_operator_id:
+ * @self: a #MMCellInfoNr5g.
+ *
+ * Get the PLMN MCC/MNC.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_nr5g_get_operator_id (MMCellInfoNr5g *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_NR5G (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->operator_id);
+}
+
+/**
+ * mm_cell_info_nr5g_set_operator_id: (skip)
+ */
+void
+mm_cell_info_nr5g_set_operator_id (MMCellInfoNr5g *self,
+                                   const gchar    *operator_id)
+{
+    g_free (self->priv->operator_id);
+    self->priv->operator_id = g_strdup (operator_id);
+}
+
+/**
+ * mm_cell_info_nr5g_get_tac:
+ * @self: a #MMCellInfoNr5g.
+ *
+ * Get the two- or three- byte Tracking Area Code of the base station.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_nr5g_get_tac (MMCellInfoNr5g *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_NR5G (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->tac);
+}
+
+/**
+ * mm_cell_info_nr5g_set_tac: (skip)
+ */
+void
+mm_cell_info_nr5g_set_tac (MMCellInfoNr5g *self,
+                           const gchar    *tac)
+{
+    g_free (self->priv->tac);
+    self->priv->tac = g_strdup (tac);
+}
+
+/**
+ * mm_cell_info_nr5g_get_ci:
+ * @self: a #MMCellInfoNr5g.
+ *
+ * Get the two- or four-byte Cell Identifier.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_nr5g_get_ci (MMCellInfoNr5g *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_NR5G (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->ci);
+}
+
+/**
+ * mm_cell_info_nr5g_set_ci: (skip)
+ */
+void
+mm_cell_info_nr5g_set_ci (MMCellInfoNr5g *self,
+                          const gchar    *ci)
+{
+    g_free (self->priv->ci);
+    self->priv->ci = g_strdup (ci);
+}
+
+/**
+ * mm_cell_info_nr5g_get_physical_ci:
+ * @self: a #MMCellInfoNr5g.
+ *
+ * Get the physical cell identifier.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_nr5g_get_physical_ci (MMCellInfoNr5g *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_NR5G (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->physical_ci);
+}
+
+/**
+ * mm_cell_info_nr5g_set_physical_ci: (skip)
+ */
+void
+mm_cell_info_nr5g_set_physical_ci (MMCellInfoNr5g *self,
+                                   const gchar    *physical_ci)
+{
+    g_free (self->priv->physical_ci);
+    self->priv->physical_ci = g_strdup (physical_ci);
+}
+
+/**
+ * mm_cell_info_nr5g_get_nrarfcn:
+ * @self: a #MMCellInfoNr5g.
+ *
+ * Get the NR absolute RF channel number.
+ *
+ * Returns: the NRARFCN, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_nr5g_get_nrarfcn (MMCellInfoNr5g *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_NR5G (self), G_MAXUINT);
+
+    return self->priv->nrarfcn;
+}
+
+/**
+ * mm_cell_info_nr5g_set_nrarfcn: (skip)
+ */
+void
+mm_cell_info_nr5g_set_nrarfcn (MMCellInfoNr5g *self,
+                               guint           nrarfcn)
+{
+    self->priv->nrarfcn = nrarfcn;
+}
+
+/**
+ * mm_cell_info_nr5g_get_rsrp:
+ * @self: a #MMCellInfoNr5g.
+ *
+ * Get the average reference signal received power in dBm.
+ *
+ * Returns: the RSRP, or -%G_MAXDOUBLE if not available.
+ *
+ * Since: 1.20
+ */
+gdouble
+mm_cell_info_nr5g_get_rsrp (MMCellInfoNr5g *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_NR5G (self), -G_MAXDOUBLE);
+
+    return self->priv->rsrp;
+}
+
+/**
+ * mm_cell_info_nr5g_set_rsrp: (skip)
+ */
+void
+mm_cell_info_nr5g_set_rsrp (MMCellInfoNr5g *self,
+                            gdouble         rsrp)
+{
+    self->priv->rsrp = rsrp;
+}
+
+/**
+ * mm_cell_info_nr5g_get_rsrq:
+ * @self: a #MMCellInfoNr5g.
+ *
+ * Get the average reference signal received quality in dB.
+ *
+ * Returns: the RSRQ, or -%G_MAXDOUBLE if not available.
+ *
+ * Since: 1.20
+ */
+gdouble
+mm_cell_info_nr5g_get_rsrq (MMCellInfoNr5g *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_NR5G (self), -G_MAXDOUBLE);
+
+    return self->priv->rsrq;
+}
+
+/**
+ * mm_cell_info_nr5g_set_rsrq: (skip)
+ */
+void
+mm_cell_info_nr5g_set_rsrq (MMCellInfoNr5g *self,
+                            gdouble         rsrq)
+{
+    self->priv->rsrq = rsrq;
+}
+
+/**
+ * mm_cell_info_nr5g_get_sinr:
+ * @self: a #MMCellInfoNr5g.
+ *
+ * Get the signal to interference and noise ratio.
+ *
+ * Returns: the SINR, or -%G_MAXDOUBLE if not available.
+ *
+ * Since: 1.20
+ */
+gdouble
+mm_cell_info_nr5g_get_sinr (MMCellInfoNr5g *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_NR5G (self), -G_MAXDOUBLE);
+
+    return self->priv->sinr;
+}
+
+/**
+ * mm_cell_info_nr5g_set_sinr: (skip)
+ */
+void
+mm_cell_info_nr5g_set_sinr (MMCellInfoNr5g *self,
+                            gdouble         sinr)
+{
+    self->priv->sinr = sinr;
+}
+
+/**
+ * mm_cell_info_nr5g_get_timing_advance:
+ * @self: a #MMCellInfoNr5g.
+ *
+ * Get the timing advance.
+ *
+ * Returns: the timing advance, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_nr5g_get_timing_advance (MMCellInfoNr5g *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_NR5G (self), G_MAXUINT);
+
+    return self->priv->timing_advance;
+}
+
+/**
+ * mm_cell_info_nr5g_set_timing_advance: (skip)
+ */
+void
+mm_cell_info_nr5g_set_timing_advance (MMCellInfoNr5g *self,
+                                      guint           timing_advance)
+{
+    self->priv->timing_advance = timing_advance;
+}
+
+/*****************************************************************************/
+
+static GString *
+build_string (MMCellInfo *_self)
+{
+    MMCellInfoNr5g *self = MM_CELL_INFO_NR5G (_self);
+    GString        *str;
+
+    str = g_string_new (NULL);
+
+    MM_CELL_INFO_BUILD_STRING_APPEND ("operator id",    "%s",  operator_id,     NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("tac",            "%s",  tac,             NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("ci",             "%s",  ci,              NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("physical ci",    "%s",  physical_ci,     NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("nrarfcn",        "%u",  nrarfcn,         G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("rsrp",           "%lf", rsrp,           -G_MAXDOUBLE);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("rsrq",           "%lf", rsrq,           -G_MAXDOUBLE);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("sinr",           "%lf", sinr,           -G_MAXDOUBLE);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("timing advance", "%u",  timing_advance,  G_MAXUINT);
+
+    return str;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_nr5g_get_dictionary: (skip)
+ */
+static GVariantDict *
+get_dictionary (MMCellInfo *_self)
+{
+    MMCellInfoNr5g *self = MM_CELL_INFO_NR5G (_self);
+    GVariantDict  *dict;
+
+    dict = g_variant_dict_new (NULL);
+
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (OPERATOR_ID,    operator_id,    string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (TAC,            tac,            string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (CI,             ci,             string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (PHYSICAL_CI,    physical_ci,    string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (NRARFCN,        nrarfcn,        uint32,  G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (RSRP,           rsrp,           double, -G_MAXDOUBLE);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (RSRQ,           rsrq,           double, -G_MAXDOUBLE);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (SINR,           sinr,           double, -G_MAXDOUBLE);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (TIMING_ADVANCE, timing_advance, uint32,  G_MAXUINT);
+
+    return dict;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_nr5g_new_from_dictionary: (skip)
+ */
+MMCellInfo *
+mm_cell_info_nr5g_new_from_dictionary (GVariantDict *dict)
+{
+    MMCellInfoNr5g *self;
+
+    self = MM_CELL_INFO_NR5G (g_object_new (MM_TYPE_CELL_INFO_NR5G, NULL));
+
+    if (dict) {
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (nr5g, OPERATOR_ID, operator_id);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (nr5g, TAC,         tac);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (nr5g, CI,          ci);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (nr5g, PHYSICAL_CI, physical_ci);
+
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (nr5g, NRARFCN,        nrarfcn,        UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (nr5g, RSRP,           rsrp,           DOUBLE, double);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (nr5g, RSRQ,           rsrq,           DOUBLE, double);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (nr5g, SINR,           sinr,           DOUBLE, double);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (nr5g, TIMING_ADVANCE, timing_advance, UINT32, uint32);
+    }
+
+    return MM_CELL_INFO (self);
+}
+
+/*****************************************************************************/
+
+static void
+mm_cell_info_nr5g_init (MMCellInfoNr5g *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CELL_INFO_NR5G, MMCellInfoNr5gPrivate);
+    self->priv->nrarfcn = G_MAXUINT;
+    self->priv->rsrp = -G_MAXDOUBLE;
+    self->priv->rsrq = -G_MAXDOUBLE;
+    self->priv->sinr = -G_MAXDOUBLE;
+    self->priv->timing_advance = G_MAXUINT;
+}
+
+static void
+finalize (GObject *object)
+{
+    MMCellInfoNr5g *self = MM_CELL_INFO_NR5G (object);
+
+    g_free (self->priv->operator_id);
+    g_free (self->priv->tac);
+    g_free (self->priv->ci);
+    g_free (self->priv->physical_ci);
+
+    G_OBJECT_CLASS (mm_cell_info_nr5g_parent_class)->finalize (object);
+}
+
+static void
+mm_cell_info_nr5g_class_init (MMCellInfoNr5gClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    MMCellInfoClass *cell_info_class = MM_CELL_INFO_CLASS (klass);
+
+    g_type_class_add_private (object_class, sizeof (MMCellInfoNr5gPrivate));
+
+    object_class->finalize = finalize;
+    cell_info_class->get_dictionary = get_dictionary;
+    cell_info_class->build_string = build_string;
+
+}
diff --git a/libmm-glib/mm-cell-info-nr5g.h b/libmm-glib/mm-cell-info-nr5g.h
new file mode 100644
index 0000000..8b807bc
--- /dev/null
+++ b/libmm-glib/mm-cell-info-nr5g.h
@@ -0,0 +1,110 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_CELL_INFO_NR5G_H
+#define MM_CELL_INFO_NR5G_H
+
+#if !defined (__LIBMM_GLIB_H_INSIDE__) && !defined (LIBMM_GLIB_COMPILATION)
+#error "Only <libmm-glib.h> can be included directly."
+#endif
+
+#include <ModemManager.h>
+#include <glib-object.h>
+
+#include "mm-cell-info.h"
+
+G_BEGIN_DECLS
+
+#define MM_TYPE_CELL_INFO_NR5G            (mm_cell_info_nr5g_get_type ())
+#define MM_CELL_INFO_NR5G(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CELL_INFO_NR5G, MMCellInfoNr5g))
+#define MM_CELL_INFO_NR5G_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_CELL_INFO_NR5G, MMCellInfoNr5gClass))
+#define MM_IS_CELL_INFO_NR5G(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CELL_INFO_NR5G))
+#define MM_IS_CELL_INFO_NR5G_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_CELL_INFO_NR5G))
+#define MM_CELL_INFO_NR5G_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_CELL_INFO_NR5G, MMCellInfoNr5gClass))
+
+typedef struct _MMCellInfoNr5g MMCellInfoNr5g;
+typedef struct _MMCellInfoNr5gClass MMCellInfoNr5gClass;
+typedef struct _MMCellInfoNr5gPrivate MMCellInfoNr5gPrivate;
+
+/**
+ * MMCellInfoNr5g:
+ *
+ * The #MMCellInfoNr5g structure contains private data and should only be
+ * accessed using the provided API.
+ */
+struct _MMCellInfoNr5g {
+    /*< private >*/
+    MMCellInfo             parent;
+    MMCellInfoNr5gPrivate *priv;
+};
+
+struct _MMCellInfoNr5gClass {
+    /*< private >*/
+    MMCellInfoClass parent;
+};
+
+GType mm_cell_info_nr5g_get_type (void);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCellInfoNr5g, g_object_unref)
+
+const gchar *mm_cell_info_nr5g_get_operator_id    (MMCellInfoNr5g *self);
+const gchar *mm_cell_info_nr5g_get_tac            (MMCellInfoNr5g *self);
+const gchar *mm_cell_info_nr5g_get_ci             (MMCellInfoNr5g *self);
+const gchar *mm_cell_info_nr5g_get_physical_ci    (MMCellInfoNr5g *self);
+guint        mm_cell_info_nr5g_get_nrarfcn        (MMCellInfoNr5g *self);
+gdouble      mm_cell_info_nr5g_get_rsrp           (MMCellInfoNr5g *self);
+gdouble      mm_cell_info_nr5g_get_rsrq           (MMCellInfoNr5g *self);
+gdouble      mm_cell_info_nr5g_get_sinr           (MMCellInfoNr5g *self);
+guint        mm_cell_info_nr5g_get_timing_advance (MMCellInfoNr5g *self);
+
+/*****************************************************************************/
+/* ModemManager/libmm-glib/mmcli specific methods */
+
+#if defined (_LIBMM_INSIDE_MM) ||    \
+    defined (_LIBMM_INSIDE_MMCLI) || \
+    defined (LIBMM_GLIB_COMPILATION)
+
+void mm_cell_info_nr5g_set_operator_id    (MMCellInfoNr5g *self,
+                                           const gchar    *operator_id);
+void mm_cell_info_nr5g_set_tac            (MMCellInfoNr5g *self,
+                                           const gchar    *tac);
+void mm_cell_info_nr5g_set_ci             (MMCellInfoNr5g *self,
+                                           const gchar    *ci);
+void mm_cell_info_nr5g_set_physical_ci    (MMCellInfoNr5g *self,
+                                           const gchar    *ci);
+void mm_cell_info_nr5g_set_nrarfcn        (MMCellInfoNr5g *self,
+                                           guint           earfcn);
+void mm_cell_info_nr5g_set_rsrp           (MMCellInfoNr5g *self,
+                                           gdouble         rsrp);
+void mm_cell_info_nr5g_set_rsrq           (MMCellInfoNr5g *self,
+                                           gdouble         rsrq);
+void mm_cell_info_nr5g_set_sinr           (MMCellInfoNr5g *self,
+                                           gdouble         sinr);
+void mm_cell_info_nr5g_set_timing_advance (MMCellInfoNr5g *self,
+                                           guint           earfcn);
+
+MMCellInfo *mm_cell_info_nr5g_new_from_dictionary (GVariantDict *dict);
+
+#endif
+
+G_END_DECLS
+
+#endif /* MM_CELL_INFO_NR5G_H */
diff --git a/libmm-glib/mm-cell-info-tdscdma.c b/libmm-glib/mm-cell-info-tdscdma.c
new file mode 100644
index 0000000..dcf16e6
--- /dev/null
+++ b/libmm-glib/mm-cell-info-tdscdma.c
@@ -0,0 +1,407 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2021 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-helpers.h"
+#include "mm-cell-info-tdscdma.h"
+
+/**
+ * SECTION: mm-cell-info-tdscdma
+ * @title: MMCellInfoTdscdma
+ * @short_description: Helper object to report TDSCDMA cell info
+ *
+ * The #MMCellInfoTdscdma is an object used to report TDSCDMA cell
+ * information.
+ *
+ * The object inherits from the generic #MMCellInfo.
+ */
+
+G_DEFINE_TYPE (MMCellInfoTdscdma, mm_cell_info_tdscdma, MM_TYPE_CELL_INFO)
+
+#define PROPERTY_OPERATOR_ID       "operator-id"
+#define PROPERTY_LAC               "lac"
+#define PROPERTY_CI                "ci"
+#define PROPERTY_UARFCN            "uarfcn"
+#define PROPERTY_CELL_PARAMETER_ID "cell-parameter-id"
+#define PROPERTY_TIMING_ADVANCE    "timing-advance"
+#define PROPERTY_RSCP              "rscp"
+#define PROPERTY_PATH_LOSS         "path-loss"
+
+struct _MMCellInfoTdscdmaPrivate {
+    gchar   *operator_id;
+    gchar   *lac;
+    gchar   *ci;
+    guint    uarfcn;
+    guint    cell_parameter_id;
+    guint    timing_advance;
+    gdouble  rscp;
+    guint    path_loss;
+};
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_tdscdma_get_operator_id:
+ * @self: a #MMCellInfoTdscdma.
+ *
+ * Get the PLMN MCC/MNC.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_tdscdma_get_operator_id (MMCellInfoTdscdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_TDSCDMA (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->operator_id);
+}
+
+/**
+ * mm_cell_info_tdscdma_set_operator_id: (skip)
+ */
+void
+mm_cell_info_tdscdma_set_operator_id (MMCellInfoTdscdma *self,
+                                      const gchar       *operator_id)
+{
+    g_free (self->priv->operator_id);
+    self->priv->operator_id = g_strdup (operator_id);
+}
+
+/**
+ * mm_cell_info_tdscdma_get_lac:
+ * @self: a #MMCellInfoTdscdma.
+ *
+ * Get the two-byte Location Area Code of the base station.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_tdscdma_get_lac (MMCellInfoTdscdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_TDSCDMA (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->lac);
+}
+
+/**
+ * mm_cell_info_tdscdma_set_lac: (skip)
+ */
+void
+mm_cell_info_tdscdma_set_lac (MMCellInfoTdscdma *self,
+                              const gchar       *lac)
+{
+    g_free (self->priv->lac);
+    self->priv->lac = g_strdup (lac);
+}
+
+/**
+ * mm_cell_info_tdscdma_get_ci:
+ * @self: a #MMCellInfoTdscdma.
+ *
+ * Get the two- or four-byte Cell Identifier.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_tdscdma_get_ci (MMCellInfoTdscdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_TDSCDMA (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->ci);
+}
+
+/**
+ * mm_cell_info_tdscdma_set_ci: (skip)
+ */
+void
+mm_cell_info_tdscdma_set_ci (MMCellInfoTdscdma *self,
+                             const gchar       *ci)
+{
+    g_free (self->priv->ci);
+    self->priv->ci = g_strdup (ci);
+}
+
+/**
+ * mm_cell_info_tdscdma_get_uarfcn:
+ * @self: a #MMCellInfoTdscdma.
+ *
+ * Get the UTRA absolute RF channel number.
+ *
+ * Returns: the UARFCN, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_tdscdma_get_uarfcn (MMCellInfoTdscdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_TDSCDMA (self), G_MAXUINT);
+
+    return self->priv->uarfcn;
+}
+
+/**
+ * mm_cell_info_tdscdma_set_uarfcn: (skip)
+ */
+void
+mm_cell_info_tdscdma_set_uarfcn (MMCellInfoTdscdma *self,
+                                 guint              uarfcn)
+{
+    self->priv->uarfcn = uarfcn;
+}
+
+/**
+ * mm_cell_info_tdscdma_get_cell_parameter_id:
+ * @self: a #MMCellInfoTdscdma.
+ *
+ * Get the cell parameter id.
+ *
+ * Returns: the cell parameter id, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_tdscdma_get_cell_parameter_id (MMCellInfoTdscdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_TDSCDMA (self), G_MAXUINT);
+
+    return self->priv->cell_parameter_id;
+}
+
+/**
+ * mm_cell_info_tdscdma_set_cell_parameter_id: (skip)
+ */
+void
+mm_cell_info_tdscdma_set_cell_parameter_id (MMCellInfoTdscdma *self,
+                                            guint              cell_parameter_id)
+{
+    self->priv->cell_parameter_id = cell_parameter_id;
+}
+
+/**
+ * mm_cell_info_tdscdma_get_timing_advance:
+ * @self: a #MMCellInfoTdscdma.
+ *
+ * Get the measured delay (in bit periods) of an access burst transmission
+ * on the RACH or PRACH to the expected signal from a mobile station at zero
+ * distance under static channel conditions.
+ *
+ * Returns: the timing advance, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_tdscdma_get_timing_advance (MMCellInfoTdscdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_TDSCDMA (self), G_MAXUINT);
+
+    return self->priv->timing_advance;
+}
+
+/**
+ * mm_cell_info_tdscdma_set_timing_advance: (skip)
+ */
+void
+mm_cell_info_tdscdma_set_timing_advance (MMCellInfoTdscdma *self,
+                                         guint              timing_advance)
+{
+    self->priv->timing_advance = timing_advance;
+}
+
+/**
+ * mm_cell_info_tdscdma_get_rscp:
+ * @self: a #MMCellInfoTdscdma.
+ *
+ * Get the received signal code power.
+ *
+ * Returns: the RSCP, or -%G_MAXDOUBLE if not available.
+ *
+ * Since: 1.20
+ */
+gdouble
+mm_cell_info_tdscdma_get_rscp (MMCellInfoTdscdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_TDSCDMA (self), -G_MAXDOUBLE);
+
+    return self->priv->rscp;
+}
+
+/**
+ * mm_cell_info_tdscdma_set_rscp: (skip)
+ */
+void
+mm_cell_info_tdscdma_set_rscp (MMCellInfoTdscdma *self,
+                               gdouble            rscp)
+{
+    self->priv->rscp = rscp;
+}
+
+/**
+ * mm_cell_info_tdscdma_get_path_loss:
+ * @self: a #MMCellInfoTdscdma.
+ *
+ * Get the path loss of the cell.
+ *
+ * Returns: the path loss, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_tdscdma_get_path_loss (MMCellInfoTdscdma *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_TDSCDMA (self), G_MAXUINT);
+
+    return self->priv->path_loss;
+}
+
+/**
+ * mm_cell_info_tdscdma_set_path_loss: (skip)
+ */
+void
+mm_cell_info_tdscdma_set_path_loss (MMCellInfoTdscdma *self,
+                                    guint              path_loss)
+{
+    self->priv->path_loss = path_loss;
+}
+
+/*****************************************************************************/
+
+static GString *
+build_string (MMCellInfo *_self)
+{
+    MMCellInfoTdscdma *self = MM_CELL_INFO_TDSCDMA (_self);
+    GString           *str;
+
+    str = g_string_new (NULL);
+
+    MM_CELL_INFO_BUILD_STRING_APPEND ("operator id",       "%s",  operator_id,       NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("lac",               "%s",  lac,               NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("ci",                "%s",  ci,                NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("uarfcn",            "%u",  uarfcn,            G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("cell parameter id", "%u",  cell_parameter_id, G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("timing advance",    "%u",  timing_advance,    G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("rscp",              "%lf", rscp,             -G_MAXDOUBLE);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("path loss",         "%u",  path_loss,         G_MAXUINT);
+
+    return str;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_tdscdma_get_dictionary: (skip)
+ */
+static GVariantDict *
+get_dictionary (MMCellInfo *_self)
+{
+    MMCellInfoTdscdma *self = MM_CELL_INFO_TDSCDMA (_self);
+    GVariantDict   *dict;
+
+    dict = g_variant_dict_new (NULL);
+
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (OPERATOR_ID,       operator_id,       string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (LAC,               lac,               string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (CI,                ci,                string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (UARFCN,            uarfcn,            uint32,  G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (CELL_PARAMETER_ID, cell_parameter_id, uint32,  G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (TIMING_ADVANCE,    timing_advance,    uint32,  G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (RSCP,              rscp,              double, -G_MAXDOUBLE);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (PATH_LOSS,         path_loss,         uint32,  G_MAXUINT);
+
+    return dict;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_tdscdma_new_from_dictionary: (skip)
+ */
+MMCellInfo *
+mm_cell_info_tdscdma_new_from_dictionary (GVariantDict *dict)
+{
+    MMCellInfoTdscdma *self;
+
+    self = MM_CELL_INFO_TDSCDMA (g_object_new (MM_TYPE_CELL_INFO_TDSCDMA, NULL));
+
+    if (dict) {
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (tdscdma, OPERATOR_ID, operator_id);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (tdscdma, LAC,         lac);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (tdscdma, CI,          ci);
+
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (tdscdma, UARFCN,            uarfcn,            UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (tdscdma, CELL_PARAMETER_ID, cell_parameter_id, UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (tdscdma, TIMING_ADVANCE,    timing_advance,    UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (tdscdma, RSCP,              rscp,              DOUBLE, double);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (tdscdma, PATH_LOSS,         path_loss,         UINT32, uint32);
+    }
+
+    return MM_CELL_INFO (self);
+}
+
+/*****************************************************************************/
+
+static void
+mm_cell_info_tdscdma_init (MMCellInfoTdscdma *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CELL_INFO_TDSCDMA, MMCellInfoTdscdmaPrivate);
+    self->priv->uarfcn = G_MAXUINT;
+    self->priv->cell_parameter_id = G_MAXUINT;
+    self->priv->timing_advance = G_MAXUINT;
+    self->priv->rscp = -G_MAXDOUBLE;
+    self->priv->path_loss = G_MAXUINT;
+}
+
+static void
+finalize (GObject *object)
+{
+    MMCellInfoTdscdma *self = MM_CELL_INFO_TDSCDMA (object);
+
+    g_free (self->priv->operator_id);
+    g_free (self->priv->lac);
+    g_free (self->priv->ci);
+
+    G_OBJECT_CLASS (mm_cell_info_tdscdma_parent_class)->finalize (object);
+}
+
+static void
+mm_cell_info_tdscdma_class_init (MMCellInfoTdscdmaClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    MMCellInfoClass *cell_info_class = MM_CELL_INFO_CLASS (klass);
+
+    g_type_class_add_private (object_class, sizeof (MMCellInfoTdscdmaPrivate));
+
+    object_class->finalize = finalize;
+    cell_info_class->get_dictionary = get_dictionary;
+    cell_info_class->build_string = build_string;
+
+}
diff --git a/libmm-glib/mm-cell-info-tdscdma.h b/libmm-glib/mm-cell-info-tdscdma.h
new file mode 100644
index 0000000..098a6be
--- /dev/null
+++ b/libmm-glib/mm-cell-info-tdscdma.h
@@ -0,0 +1,107 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_CELL_INFO_TDSCDMA_H
+#define MM_CELL_INFO_TDSCDMA_H
+
+#if !defined (__LIBMM_GLIB_H_INSIDE__) && !defined (LIBMM_GLIB_COMPILATION)
+#error "Only <libmm-glib.h> can be included directly."
+#endif
+
+#include <ModemManager.h>
+#include <glib-object.h>
+
+#include "mm-cell-info.h"
+
+G_BEGIN_DECLS
+
+#define MM_TYPE_CELL_INFO_TDSCDMA            (mm_cell_info_tdscdma_get_type ())
+#define MM_CELL_INFO_TDSCDMA(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CELL_INFO_TDSCDMA, MMCellInfoTdscdma))
+#define MM_CELL_INFO_TDSCDMA_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_CELL_INFO_TDSCDMA, MMCellInfoTdscdmaClass))
+#define MM_IS_CELL_INFO_TDSCDMA(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CELL_INFO_TDSCDMA))
+#define MM_IS_CELL_INFO_TDSCDMA_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_CELL_INFO_TDSCDMA))
+#define MM_CELL_INFO_TDSCDMA_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_CELL_INFO_TDSCDMA, MMCellInfoTdscdmaClass))
+
+typedef struct _MMCellInfoTdscdma MMCellInfoTdscdma;
+typedef struct _MMCellInfoTdscdmaClass MMCellInfoTdscdmaClass;
+typedef struct _MMCellInfoTdscdmaPrivate MMCellInfoTdscdmaPrivate;
+
+/**
+ * MMCellInfoTdscdma:
+ *
+ * The #MMCellInfoTdscdma structure contains private data and should only be
+ * accessed using the provided API.
+ */
+struct _MMCellInfoTdscdma {
+    /*< private >*/
+    MMCellInfo                parent;
+    MMCellInfoTdscdmaPrivate *priv;
+};
+
+struct _MMCellInfoTdscdmaClass {
+    /*< private >*/
+    MMCellInfoClass parent;
+};
+
+GType mm_cell_info_tdscdma_get_type (void);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCellInfoTdscdma, g_object_unref)
+
+const gchar *mm_cell_info_tdscdma_get_operator_id       (MMCellInfoTdscdma *self);
+const gchar *mm_cell_info_tdscdma_get_lac               (MMCellInfoTdscdma *self);
+const gchar *mm_cell_info_tdscdma_get_ci                (MMCellInfoTdscdma *self);
+guint        mm_cell_info_tdscdma_get_uarfcn            (MMCellInfoTdscdma *self);
+guint        mm_cell_info_tdscdma_get_cell_parameter_id (MMCellInfoTdscdma *self);
+guint        mm_cell_info_tdscdma_get_timing_advance    (MMCellInfoTdscdma *self);
+gdouble      mm_cell_info_tdscdma_get_rscp              (MMCellInfoTdscdma *self);
+guint        mm_cell_info_tdscdma_get_path_loss         (MMCellInfoTdscdma *self);
+
+/*****************************************************************************/
+/* ModemManager/libmm-glib/mmcli specific methods */
+
+#if defined (_LIBMM_INSIDE_MM) ||    \
+    defined (_LIBMM_INSIDE_MMCLI) || \
+    defined (LIBMM_GLIB_COMPILATION)
+
+void mm_cell_info_tdscdma_set_operator_id       (MMCellInfoTdscdma *self,
+                                                 const gchar       *operator_id);
+void mm_cell_info_tdscdma_set_lac               (MMCellInfoTdscdma *self,
+                                                 const gchar       *lac);
+void mm_cell_info_tdscdma_set_ci                (MMCellInfoTdscdma *self,
+                                                 const gchar       *ci);
+void mm_cell_info_tdscdma_set_uarfcn            (MMCellInfoTdscdma *self,
+                                                 guint              uarfcn);
+void mm_cell_info_tdscdma_set_cell_parameter_id (MMCellInfoTdscdma *self,
+                                                 guint              cell_parameter_id);
+void mm_cell_info_tdscdma_set_timing_advance    (MMCellInfoTdscdma *self,
+                                                 guint              timing_advance);
+void mm_cell_info_tdscdma_set_rscp              (MMCellInfoTdscdma *self,
+                                                 gdouble            rscp);
+void mm_cell_info_tdscdma_set_path_loss         (MMCellInfoTdscdma *self,
+                                                 guint              path_loss);
+
+MMCellInfo *mm_cell_info_tdscdma_new_from_dictionary (GVariantDict *dict);
+
+#endif
+
+G_END_DECLS
+
+#endif /* MM_CELL_INFO_TDSCDMA_H */
diff --git a/libmm-glib/mm-cell-info-umts.c b/libmm-glib/mm-cell-info-umts.c
new file mode 100644
index 0000000..ccb6884
--- /dev/null
+++ b/libmm-glib/mm-cell-info-umts.c
@@ -0,0 +1,509 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-helpers.h"
+#include "mm-cell-info-umts.h"
+
+/**
+ * SECTION: mm-cell-info-umts
+ * @title: MMCellInfoUmts
+ * @short_description: Helper object to report UMTS cell info
+ *
+ * The #MMCellInfoUmts is an object used to report UMTS cell
+ * information.
+ *
+ * The object inherits from the generic #MMCellInfo.
+ */
+
+G_DEFINE_TYPE (MMCellInfoUmts, mm_cell_info_umts, MM_TYPE_CELL_INFO)
+
+#define PROPERTY_OPERATOR_ID      "operator-id"
+#define PROPERTY_LAC              "lac"
+#define PROPERTY_CI               "ci"
+#define PROPERTY_FREQUENCY_FDD_UL "frequency-fdd-ul"
+#define PROPERTY_FREQUENCY_FDD_DL "frequency-fdd-dl"
+#define PROPERTY_FREQUENCY_TDD    "frequency-tdd"
+#define PROPERTY_UARFCN           "uarfcn"
+#define PROPERTY_PSC              "psc"
+#define PROPERTY_RSCP             "rscp"
+#define PROPERTY_ECIO             "ecio"
+#define PROPERTY_PATH_LOSS        "path-loss"
+
+struct _MMCellInfoUmtsPrivate {
+    gchar   *operator_id;
+    gchar   *lac;
+    gchar   *ci;
+    guint    frequency_fdd_ul;
+    guint    frequency_fdd_dl;
+    guint    frequency_tdd;
+    guint    uarfcn;
+    guint    psc;
+    gdouble  rscp;
+    gdouble  ecio;
+    guint    path_loss;
+};
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_umts_get_operator_id:
+ * @self: a #MMCellInfoUmts.
+ *
+ * Get the PLMN MCC/MNC.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_umts_get_operator_id (MMCellInfoUmts *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_UMTS (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->operator_id);
+}
+
+/**
+ * mm_cell_info_umts_set_operator_id: (skip)
+ */
+void
+mm_cell_info_umts_set_operator_id (MMCellInfoUmts *self,
+                                   const gchar    *operator_id)
+{
+    g_free (self->priv->operator_id);
+    self->priv->operator_id = g_strdup (operator_id);
+}
+
+/**
+ * mm_cell_info_umts_get_lac:
+ * @self: a #MMCellInfoUmts.
+ *
+ * Get the two-byte Location Area Code of the base station.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_umts_get_lac (MMCellInfoUmts *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_UMTS (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->lac);
+}
+
+/**
+ * mm_cell_info_umts_set_lac: (skip)
+ */
+void
+mm_cell_info_umts_set_lac (MMCellInfoUmts *self,
+                           const gchar    *lac)
+{
+    g_free (self->priv->lac);
+    self->priv->lac = g_strdup (lac);
+}
+
+/**
+ * mm_cell_info_umts_get_ci:
+ * @self: a #MMCellInfoUmts.
+ *
+ * Get the two- or four-byte Cell Identifier.
+ *
+ * Encoded in upper-case hexadecimal format without leading zeros,
+ * as specified in 3GPP TS 27.007.
+ *
+ * Returns: (transfer none): the MCCMNC, or %NULL if not available.
+ *
+ * Since: 1.20
+ */
+const gchar *
+mm_cell_info_umts_get_ci (MMCellInfoUmts *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_UMTS (self), NULL);
+
+    RETURN_NON_EMPTY_CONSTANT_STRING (self->priv->ci);
+}
+
+/**
+ * mm_cell_info_umts_set_ci: (skip)
+ */
+void
+mm_cell_info_umts_set_ci (MMCellInfoUmts *self,
+                          const gchar    *ci)
+{
+    g_free (self->priv->ci);
+    self->priv->ci = g_strdup (ci);
+}
+
+/**
+ * mm_cell_info_umts_get_frequency_fdd_ul:
+ * @self: a #MMCellInfoUmts.
+ *
+ * Get the frequency of the uplink in kHz while in FDD.
+ *
+ * Returns: the frequency, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_umts_get_frequency_fdd_ul (MMCellInfoUmts *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_UMTS (self), G_MAXUINT);
+
+    return self->priv->frequency_fdd_ul;
+}
+
+/**
+ * mm_cell_info_umts_set_frequency_fdd_ul: (skip)
+ */
+void
+mm_cell_info_umts_set_frequency_fdd_ul (MMCellInfoUmts *self,
+                                        guint           frequency_fdd_ul)
+{
+    self->priv->frequency_fdd_ul = frequency_fdd_ul;
+}
+
+/**
+ * mm_cell_info_umts_get_frequency_fdd_dl:
+ * @self: a #MMCellInfoUmts.
+ *
+ * Get the frequency of the downlink in kHz while in FDD.
+ *
+ * Returns: the frequency, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_umts_get_frequency_fdd_dl (MMCellInfoUmts *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_UMTS (self), G_MAXUINT);
+
+    return self->priv->frequency_fdd_dl;
+}
+
+/**
+ * mm_cell_info_umts_set_frequency_fdd_dl: (skip)
+ */
+void
+mm_cell_info_umts_set_frequency_fdd_dl (MMCellInfoUmts *self,
+                                        guint           frequency_fdd_dl)
+{
+    self->priv->frequency_fdd_dl = frequency_fdd_dl;
+}
+
+
+/**
+ * mm_cell_info_umts_get_frequency_tdd:
+ * @self: a #MMCellInfoUmts.
+ *
+ * Get the frequency in kHz while in TDD.
+ *
+ * Returns: the frequency, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_umts_get_frequency_tdd (MMCellInfoUmts *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_UMTS (self), G_MAXUINT);
+
+    return self->priv->frequency_tdd;
+}
+
+/**
+ * mm_cell_info_umts_set_frequency_tdd: (skip)
+ */
+void
+mm_cell_info_umts_set_frequency_tdd (MMCellInfoUmts *self,
+                                     guint           frequency_tdd)
+{
+    self->priv->frequency_tdd = frequency_tdd;
+}
+
+/**
+ * mm_cell_info_umts_get_uarfcn:
+ * @self: a #MMCellInfoUmts.
+ *
+ * Get the UTRA absolute RF channel number.
+ *
+ * Returns: the UARFCN, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_umts_get_uarfcn (MMCellInfoUmts *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_UMTS (self), G_MAXUINT);
+
+    return self->priv->uarfcn;
+}
+
+/**
+ * mm_cell_info_umts_set_uarfcn: (skip)
+ */
+void
+mm_cell_info_umts_set_uarfcn (MMCellInfoUmts *self,
+                              guint           uarfcn)
+{
+    self->priv->uarfcn = uarfcn;
+}
+
+/**
+ * mm_cell_info_umts_get_psc:
+ * @self: a #MMCellInfoUmts.
+ *
+ * Get the primary scrambling code.
+ *
+ * Returns: the PSC, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_umts_get_psc (MMCellInfoUmts *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_UMTS (self), G_MAXUINT);
+
+    return self->priv->psc;
+}
+
+/**
+ * mm_cell_info_umts_set_psc: (skip)
+ */
+void
+mm_cell_info_umts_set_psc (MMCellInfoUmts *self,
+                           guint           psc)
+{
+    self->priv->psc = psc;
+}
+
+/**
+ * mm_cell_info_umts_get_rscp:
+ * @self: a #MMCellInfoUmts.
+ *
+ * Get the received signal code power.
+ *
+ * Returns: the RSCP, or -%G_MAXDOUBLE if not available.
+ *
+ * Since: 1.20
+ */
+gdouble
+mm_cell_info_umts_get_rscp (MMCellInfoUmts *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_UMTS (self), -G_MAXDOUBLE);
+
+    return self->priv->rscp;
+}
+
+/**
+ * mm_cell_info_umts_set_rscp: (skip)
+ */
+void
+mm_cell_info_umts_set_rscp (MMCellInfoUmts *self,
+                            gdouble         rscp)
+{
+    self->priv->rscp = rscp;
+}
+
+/**
+ * mm_cell_info_umts_get_ecio:
+ * @self: a #MMCellInfoUmts.
+ *
+ * Get the ECIO, the received energy per chip divided by the power density
+ * in the band measured in dBm on the primary CPICH channel of the cell.
+ *
+ * Returns: the ECIO, or -%G_MAXDOUBLE if not available.
+ *
+ * Since: 1.20
+ */
+gdouble
+mm_cell_info_umts_get_ecio (MMCellInfoUmts *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_UMTS (self), -G_MAXDOUBLE);
+
+    return self->priv->ecio;
+}
+
+/**
+ * mm_cell_info_umts_set_ecio: (skip)
+ */
+void
+mm_cell_info_umts_set_ecio (MMCellInfoUmts *self,
+                            gdouble         ecio)
+{
+    self->priv->ecio = ecio;
+}
+
+/**
+ * mm_cell_info_umts_get_path_loss:
+ * @self: a #MMCellInfoUmts.
+ *
+ * Get the path loss of the cell.
+ *
+ * Returns: the path loss, or %G_MAXUINT if not available.
+ *
+ * Since: 1.20
+ */
+guint
+mm_cell_info_umts_get_path_loss (MMCellInfoUmts *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO_UMTS (self), G_MAXUINT);
+
+    return self->priv->path_loss;
+}
+
+/**
+ * mm_cell_info_umts_set_path_loss: (skip)
+ */
+void
+mm_cell_info_umts_set_path_loss (MMCellInfoUmts *self,
+                                 guint           path_loss)
+{
+    self->priv->path_loss = path_loss;
+}
+
+/*****************************************************************************/
+
+static GString *
+build_string (MMCellInfo *_self)
+{
+    MMCellInfoUmts *self = MM_CELL_INFO_UMTS (_self);
+    GString        *str;
+
+    str = g_string_new (NULL);
+
+    MM_CELL_INFO_BUILD_STRING_APPEND ("operator id",      "%s",  operator_id,       NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("lac",              "%s",  lac,               NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("ci",               "%s",  ci,                NULL);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("frequency fdd ul", "%u",  frequency_fdd_ul,  G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("frequency fdd dl", "%u",  frequency_fdd_dl,  G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("frequency tdd",    "%u",  frequency_tdd,     G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("uarfcn",           "%u",  uarfcn,            G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("psc",              "%u",  psc,               G_MAXUINT);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("rscp",             "%lf", rscp,             -G_MAXDOUBLE);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("ecio",             "%lf", ecio,             -G_MAXDOUBLE);
+    MM_CELL_INFO_BUILD_STRING_APPEND ("path loss",        "%u",  path_loss,         G_MAXUINT);
+
+    return str;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_umts_get_dictionary: (skip)
+ */
+static GVariantDict *
+get_dictionary (MMCellInfo *_self)
+{
+    MMCellInfoUmts *self = MM_CELL_INFO_UMTS (_self);
+    GVariantDict   *dict;
+
+    dict = g_variant_dict_new (NULL);
+
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (OPERATOR_ID,      operator_id,      string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (LAC,              lac,              string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (CI,               ci,               string,  NULL);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (FREQUENCY_FDD_UL, frequency_fdd_ul, uint32,  G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (FREQUENCY_FDD_DL, frequency_fdd_dl, uint32,  G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (FREQUENCY_TDD,    frequency_tdd,    uint32,  G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (UARFCN,           uarfcn,           uint32,  G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (PSC,              psc,              uint32,  G_MAXUINT);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (RSCP,             rscp,             double, -G_MAXDOUBLE);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (ECIO,             ecio,             double, -G_MAXDOUBLE);
+    MM_CELL_INFO_GET_DICTIONARY_INSERT (PATH_LOSS,        path_loss,        uint32,  G_MAXUINT);
+
+    return dict;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_umts_new_from_dictionary: (skip)
+ */
+MMCellInfo *
+mm_cell_info_umts_new_from_dictionary (GVariantDict *dict)
+{
+    MMCellInfoUmts *self;
+
+    self = MM_CELL_INFO_UMTS (g_object_new (MM_TYPE_CELL_INFO_UMTS, NULL));
+
+    if (dict) {
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (umts, OPERATOR_ID, operator_id);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (umts, LAC,         lac);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET (umts, CI,          ci);
+
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (umts, FREQUENCY_FDD_UL, frequency_fdd_ul, UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (umts, FREQUENCY_FDD_DL, frequency_fdd_dl, UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (umts, FREQUENCY_TDD,    frequency_tdd,    UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (umts, UARFCN,           uarfcn,           UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (umts, PSC,              psc,              UINT32, uint32);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (umts, RSCP,             rscp,             DOUBLE, double);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (umts, ECIO,             ecio,             DOUBLE, double);
+        MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET (umts, PATH_LOSS,        path_loss,        UINT32, uint32);
+    }
+
+    return MM_CELL_INFO (self);
+}
+
+/*****************************************************************************/
+
+static void
+mm_cell_info_umts_init (MMCellInfoUmts *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CELL_INFO_UMTS, MMCellInfoUmtsPrivate);
+    self->priv->frequency_fdd_ul = G_MAXUINT;
+    self->priv->frequency_fdd_dl = G_MAXUINT;
+    self->priv->frequency_tdd = G_MAXUINT;
+    self->priv->uarfcn = G_MAXUINT;
+    self->priv->psc = G_MAXUINT;
+    self->priv->rscp = -G_MAXDOUBLE;
+    self->priv->ecio = -G_MAXDOUBLE;
+    self->priv->path_loss = G_MAXUINT;
+}
+
+static void
+finalize (GObject *object)
+{
+    MMCellInfoUmts *self = MM_CELL_INFO_UMTS (object);
+
+    g_free (self->priv->operator_id);
+    g_free (self->priv->lac);
+    g_free (self->priv->ci);
+
+    G_OBJECT_CLASS (mm_cell_info_umts_parent_class)->finalize (object);
+}
+
+static void
+mm_cell_info_umts_class_init (MMCellInfoUmtsClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+    MMCellInfoClass *cell_info_class = MM_CELL_INFO_CLASS (klass);
+
+    g_type_class_add_private (object_class, sizeof (MMCellInfoUmtsPrivate));
+
+    object_class->finalize = finalize;
+    cell_info_class->get_dictionary = get_dictionary;
+    cell_info_class->build_string = build_string;
+
+}
diff --git a/libmm-glib/mm-cell-info-umts.h b/libmm-glib/mm-cell-info-umts.h
new file mode 100644
index 0000000..d320fd8
--- /dev/null
+++ b/libmm-glib/mm-cell-info-umts.h
@@ -0,0 +1,116 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_CELL_INFO_UMTS_H
+#define MM_CELL_INFO_UMTS_H
+
+#if !defined (__LIBMM_GLIB_H_INSIDE__) && !defined (LIBMM_GLIB_COMPILATION)
+#error "Only <libmm-glib.h> can be included directly."
+#endif
+
+#include <ModemManager.h>
+#include <glib-object.h>
+
+#include "mm-cell-info.h"
+
+G_BEGIN_DECLS
+
+#define MM_TYPE_CELL_INFO_UMTS            (mm_cell_info_umts_get_type ())
+#define MM_CELL_INFO_UMTS(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CELL_INFO_UMTS, MMCellInfoUmts))
+#define MM_CELL_INFO_UMTS_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_CELL_INFO_UMTS, MMCellInfoUmtsClass))
+#define MM_IS_CELL_INFO_UMTS(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CELL_INFO_UMTS))
+#define MM_IS_CELL_INFO_UMTS_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_CELL_INFO_UMTS))
+#define MM_CELL_INFO_UMTS_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_CELL_INFO_UMTS, MMCellInfoUmtsClass))
+
+typedef struct _MMCellInfoUmts MMCellInfoUmts;
+typedef struct _MMCellInfoUmtsClass MMCellInfoUmtsClass;
+typedef struct _MMCellInfoUmtsPrivate MMCellInfoUmtsPrivate;
+
+/**
+ * MMCellInfoUmts:
+ *
+ * The #MMCellInfoUmts structure contains private data and should only be
+ * accessed using the provided API.
+ */
+struct _MMCellInfoUmts {
+    /*< private >*/
+    MMCellInfo             parent;
+    MMCellInfoUmtsPrivate *priv;
+};
+
+struct _MMCellInfoUmtsClass {
+    /*< private >*/
+    MMCellInfoClass parent;
+};
+
+GType mm_cell_info_umts_get_type (void);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCellInfoUmts, g_object_unref)
+
+const gchar *mm_cell_info_umts_get_operator_id      (MMCellInfoUmts *self);
+const gchar *mm_cell_info_umts_get_lac              (MMCellInfoUmts *self);
+const gchar *mm_cell_info_umts_get_ci               (MMCellInfoUmts *self);
+guint        mm_cell_info_umts_get_frequency_fdd_ul (MMCellInfoUmts *self);
+guint        mm_cell_info_umts_get_frequency_fdd_dl (MMCellInfoUmts *self);
+guint        mm_cell_info_umts_get_frequency_tdd    (MMCellInfoUmts *self);
+guint        mm_cell_info_umts_get_uarfcn           (MMCellInfoUmts *self);
+guint        mm_cell_info_umts_get_psc              (MMCellInfoUmts *self);
+gdouble      mm_cell_info_umts_get_rscp             (MMCellInfoUmts *self);
+gdouble      mm_cell_info_umts_get_ecio             (MMCellInfoUmts *self);
+guint        mm_cell_info_umts_get_path_loss        (MMCellInfoUmts *self);
+
+/*****************************************************************************/
+/* ModemManager/libmm-glib/mmcli specific methods */
+
+#if defined (_LIBMM_INSIDE_MM) ||    \
+    defined (_LIBMM_INSIDE_MMCLI) || \
+    defined (LIBMM_GLIB_COMPILATION)
+
+void mm_cell_info_umts_set_operator_id      (MMCellInfoUmts *self,
+                                             const gchar    *operator_id);
+void mm_cell_info_umts_set_lac              (MMCellInfoUmts *self,
+                                             const gchar    *lac);
+void mm_cell_info_umts_set_ci               (MMCellInfoUmts *self,
+                                             const gchar    *ci);
+void mm_cell_info_umts_set_frequency_fdd_ul (MMCellInfoUmts *self,
+                                             guint           frequency_fdd_ul);
+void mm_cell_info_umts_set_frequency_fdd_dl (MMCellInfoUmts *self,
+                                             guint           frequency_fdd_ul);
+void mm_cell_info_umts_set_frequency_tdd    (MMCellInfoUmts *self,
+                                             guint           frequency_tdd);
+void mm_cell_info_umts_set_uarfcn           (MMCellInfoUmts *self,
+                                             guint           uarfcn);
+void mm_cell_info_umts_set_psc              (MMCellInfoUmts *self,
+                                             guint           psc);
+void mm_cell_info_umts_set_rscp             (MMCellInfoUmts *self,
+                                             gdouble         rscp);
+void mm_cell_info_umts_set_ecio             (MMCellInfoUmts *self,
+                                             gdouble         ecio);
+void mm_cell_info_umts_set_path_loss        (MMCellInfoUmts *self,
+                                             guint           path_loss);
+
+MMCellInfo *mm_cell_info_umts_new_from_dictionary (GVariantDict *dict);
+
+#endif
+
+G_END_DECLS
+
+#endif /* MM_CELL_INFO_UMTS_H */
diff --git a/libmm-glib/mm-cell-info.c b/libmm-glib/mm-cell-info.c
new file mode 100644
index 0000000..9ba7755
--- /dev/null
+++ b/libmm-glib/mm-cell-info.c
@@ -0,0 +1,248 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#include "mm-cell-info.h"
+#include "mm-cell-info-cdma.h"
+#include "mm-cell-info-gsm.h"
+#include "mm-cell-info-umts.h"
+#include "mm-cell-info-tdscdma.h"
+#include "mm-cell-info-lte.h"
+#include "mm-cell-info-nr5g.h"
+
+#include "mm-enums-types.h"
+#include "mm-errors-types.h"
+
+/**
+ * SECTION: mm-cell-info
+ * @title: MMCellInfo
+ * @short_description: Helper base object to report cell info
+ *
+ * The #MMCellInfo is a base object used to report cell information.
+ *
+ * This object is retrieved from the #MMModem object with
+ * mm_modem_get_cell_info() or mm_modem_get_cell_info_sync().
+ */
+
+G_DEFINE_TYPE (MMCellInfo, mm_cell_info, G_TYPE_OBJECT)
+
+#define PROPERTY_CELL_TYPE "cell-type"
+#define PROPERTY_SERVING   "serving"
+
+struct _MMCellInfoPrivate {
+    MMCellType cell_type;
+    gboolean   serving;
+};
+
+/*****************************************************************************/
+
+static void
+ensure_cell_type (MMCellInfo *self)
+{
+    if (self->priv->cell_type != MM_CELL_TYPE_UNKNOWN)
+        return;
+
+    if (MM_IS_CELL_INFO_CDMA (self))
+        self->priv->cell_type = MM_CELL_TYPE_CDMA;
+    else if (MM_IS_CELL_INFO_GSM (self))
+        self->priv->cell_type = MM_CELL_TYPE_GSM;
+    else if (MM_IS_CELL_INFO_UMTS (self))
+        self->priv->cell_type = MM_CELL_TYPE_UMTS;
+    else if (MM_IS_CELL_INFO_TDSCDMA (self))
+        self->priv->cell_type = MM_CELL_TYPE_TDSCDMA;
+    else if (MM_IS_CELL_INFO_LTE (self))
+        self->priv->cell_type = MM_CELL_TYPE_LTE;
+    else if (MM_IS_CELL_INFO_NR5G (self))
+        self->priv->cell_type = MM_CELL_TYPE_5GNR;
+}
+
+/**
+ * mm_cell_info_get_cell_type:
+ * @self: a #MMCellInfo.
+ *
+ * Get the type of cell.
+ *
+ * Returns: a #MMCellType.
+ *
+ * Since: 1.20
+ */
+MMCellType
+mm_cell_info_get_cell_type (MMCellInfo *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO (self), MM_CELL_TYPE_UNKNOWN);
+
+    ensure_cell_type (self);
+
+    return self->priv->cell_type;
+}
+
+/**
+ * mm_cell_info_get_serving:
+ * @self: a #MMCellInfo.
+ *
+ * Get whether the cell is a serving cell or a neighboring cell.a
+ *
+ * Returns: %TRUE if the cell is a serving cell, %FALSE otherwise.
+ *
+ * Since: 1.20
+ */
+gboolean
+mm_cell_info_get_serving (MMCellInfo *self)
+{
+    g_return_val_if_fail (MM_IS_CELL_INFO (self), FALSE);
+
+    return self->priv->serving;
+}
+
+/**
+ * mm_cell_info_set_serving: (skip)
+ */
+void
+mm_cell_info_set_serving (MMCellInfo *self,
+                                gboolean         serving)
+{
+    g_return_if_fail (MM_IS_CELL_INFO (self));
+
+    self->priv->serving = serving;
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_get_dictionary: (skip)
+ */
+GVariant *
+mm_cell_info_get_dictionary (MMCellInfo *self)
+{
+    g_autoptr(GVariantDict) dict = NULL;
+
+    dict = MM_CELL_INFO_GET_CLASS (self)->get_dictionary (self);
+    g_assert (dict);
+
+    g_variant_dict_insert_value (dict, PROPERTY_SERVING,   g_variant_new_boolean (self->priv->serving));
+    g_variant_dict_insert_value (dict, PROPERTY_CELL_TYPE, g_variant_new_uint32 (mm_cell_info_get_cell_type (self)));
+
+    return g_variant_ref_sink (g_variant_dict_end (dict));
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_new_from_dictionary: (skip)
+ */
+MMCellInfo *
+mm_cell_info_new_from_dictionary (GVariant  *dictionary,
+                                  GError   **error)
+{
+    g_autoptr(MMCellInfo)    self = NULL;
+    g_autoptr(GVariantDict)  dict = NULL;
+    GVariant                *aux;
+
+    dict = g_variant_dict_new (dictionary);
+
+    aux = g_variant_dict_lookup_value (dict, PROPERTY_CELL_TYPE, G_VARIANT_TYPE_UINT32);
+    if (!aux) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+                     "missing '" PROPERTY_CELL_TYPE "' key in cell info");
+        return NULL;
+    }
+    switch (g_variant_get_uint32 (aux)) {
+        case MM_CELL_TYPE_CDMA:
+            self = mm_cell_info_cdma_new_from_dictionary (dict);
+            break;
+        case MM_CELL_TYPE_GSM:
+            self = mm_cell_info_gsm_new_from_dictionary (dict);
+            break;
+        case MM_CELL_TYPE_UMTS:
+            self = mm_cell_info_umts_new_from_dictionary (dict);
+            break;
+        case MM_CELL_TYPE_TDSCDMA:
+            self = mm_cell_info_tdscdma_new_from_dictionary (dict);
+            break;
+        case MM_CELL_TYPE_LTE:
+            self = mm_cell_info_lte_new_from_dictionary (dict);
+            break;
+        case MM_CELL_TYPE_5GNR:
+            self = mm_cell_info_nr5g_new_from_dictionary (dict);
+            break;
+        default:
+            break;
+    }
+    g_variant_unref (aux);
+
+    if (!self) {
+        g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_INVALID_ARGS,
+                     "unknown '" PROPERTY_CELL_TYPE "' key value in cell info");
+        return NULL;
+    }
+
+    aux = g_variant_dict_lookup_value (dict, PROPERTY_SERVING, G_VARIANT_TYPE_BOOLEAN);
+    if (aux) {
+        mm_cell_info_set_serving (self, g_variant_get_boolean (aux));
+        g_variant_unref (aux);
+    }
+
+    return g_steal_pointer (&self);
+}
+
+/*****************************************************************************/
+
+/**
+ * mm_cell_info_build_string: (skip)
+ */
+gchar *
+mm_cell_info_build_string (MMCellInfo *self)
+{
+    GString *str;
+    GString *substr;
+
+    substr = MM_CELL_INFO_GET_CLASS (self)->build_string (self);
+    g_assert (substr);
+
+    ensure_cell_type (self);
+
+    str = g_string_new (NULL);
+    g_string_append_printf (str, "cell type: %s, serving: %s",
+                            mm_cell_type_get_string (self->priv->cell_type),
+                            self->priv->serving ? "yes" : "no");
+    g_string_append_len (str, substr->str, (gssize)substr->len);
+
+    g_string_free (substr, TRUE);
+
+    return g_string_free (str, FALSE);
+}
+
+/*****************************************************************************/
+
+static void
+mm_cell_info_init (MMCellInfo *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self, MM_TYPE_CELL_INFO, MMCellInfoPrivate);
+    self->priv->cell_type = MM_CELL_TYPE_UNKNOWN;
+}
+
+static void
+mm_cell_info_class_init (MMCellInfoClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+    g_type_class_add_private (object_class, sizeof (MMCellInfoPrivate));
+}
diff --git a/libmm-glib/mm-cell-info.h b/libmm-glib/mm-cell-info.h
new file mode 100644
index 0000000..0720552
--- /dev/null
+++ b/libmm-glib/mm-cell-info.h
@@ -0,0 +1,125 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * libmm-glib -- Access modem status & information from glib applications
+ *
+ * This library is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 2 of the License, or (at your option) any later version.
+ *
+ * This library 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
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with this library; if not, write to the
+ * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
+ * Boston, MA 02110-1301 USA.
+ *
+ * Copyright (C) 2022 Aleksander Morgado <aleksander@aleksander.es>
+ */
+
+#ifndef MM_CELL_INFO_H
+#define MM_CELL_INFO_H
+
+#if !defined (__LIBMM_GLIB_H_INSIDE__) && !defined (LIBMM_GLIB_COMPILATION)
+#error "Only <libmm-glib.h> can be included directly."
+#endif
+
+#include <ModemManager.h>
+#include <glib-object.h>
+
+G_BEGIN_DECLS
+
+#define MM_TYPE_CELL_INFO            (mm_cell_info_get_type ())
+#define MM_CELL_INFO(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_CELL_INFO, MMCellInfo))
+#define MM_CELL_INFO_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_CELL_INFO, MMCellInfoClass))
+#define MM_IS_CELL_INFO(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_CELL_INFO))
+#define MM_IS_CELL_INFO_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_CELL_INFO))
+#define MM_CELL_INFO_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_CELL_INFO, MMCellInfoClass))
+
+typedef struct _MMCellInfo MMCellInfo;
+typedef struct _MMCellInfoClass MMCellInfoClass;
+typedef struct _MMCellInfoPrivate MMCellInfoPrivate;
+
+/**
+ * MMCellInfo:
+ *
+ * The #MMCellInfo structure contains private data and should only be
+ * accessed using the provided API.
+ */
+struct _MMCellInfo {
+    /*< private >*/
+    GObject            parent;
+    MMCellInfoPrivate *priv;
+};
+
+struct _MMCellInfoClass {
+    /*< private >*/
+    GObjectClass parent;
+
+    GVariantDict * (* get_dictionary) (MMCellInfo    *self);
+    GString      * (* build_string)   (MMCellInfo    *self);
+
+    /* class padding */
+    gpointer padding [5];
+};
+
+GType mm_cell_info_get_type (void);
+G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMCellInfo, g_object_unref)
+
+MMCellType mm_cell_info_get_cell_type (MMCellInfo *self);
+gboolean   mm_cell_info_get_serving   (MMCellInfo *self);
+
+/*****************************************************************************/
+/* ModemManager/libmm-glib/mmcli specific methods */
+
+#if defined (_LIBMM_INSIDE_MM) ||    \
+    defined (_LIBMM_INSIDE_MMCLI) || \
+    defined (LIBMM_GLIB_COMPILATION)
+
+void        mm_cell_info_set_serving         (MMCellInfo  *self,
+                                              gboolean     serving);
+GVariant   *mm_cell_info_get_dictionary      (MMCellInfo  *self);
+MMCellInfo *mm_cell_info_new_from_dictionary (GVariant    *dictionary,
+                                              GError     **error);
+gchar      *mm_cell_info_build_string        (MMCellInfo  *self);
+
+/* helpers to implement methods */
+
+#define MM_CELL_INFO_NEW_FROM_DICTIONARY_STRING_SET(celltype,NAME,name) do {               \
+        GVariant *aux;                                                                     \
+                                                                                           \
+        aux = g_variant_dict_lookup_value (dict, PROPERTY_##NAME, G_VARIANT_TYPE_STRING);  \
+        if (aux) {                                                                         \
+            mm_cell_info_##celltype##_set_##name (self, g_variant_get_string (aux, NULL)); \
+            g_variant_unref (aux);                                                         \
+        }                                                                                  \
+    } while (0)
+
+#define MM_CELL_INFO_NEW_FROM_DICTIONARY_NUM_SET(celltype,NAME,name,NUMTYPE,numtype) do {    \
+        GVariant *aux;                                                                       \
+                                                                                             \
+        aux = g_variant_dict_lookup_value (dict, PROPERTY_##NAME, G_VARIANT_TYPE_##NUMTYPE); \
+        if (aux) {                                                                           \
+            mm_cell_info_##celltype##_set_##name (self, g_variant_get_##numtype (aux));      \
+            g_variant_unref (aux);                                                           \
+        }                                                                                    \
+    } while (0)
+
+#define MM_CELL_INFO_GET_DICTIONARY_INSERT(NAME,name,vartype,INVALID) do {                                   \
+        if (self->priv->name != INVALID)                                                                     \
+            g_variant_dict_insert_value (dict, PROPERTY_##NAME, g_variant_new_##vartype (self->priv->name)); \
+    } while (0)
+
+#define MM_CELL_INFO_BUILD_STRING_APPEND(STR,FORMAT,name,INVALID) do {            \
+        if (self->priv->name != INVALID)                                          \
+            g_string_append_printf (str, ", " STR ": " FORMAT, self->priv->name); \
+    } while (0)
+
+#endif
+
+G_END_DECLS
+
+#endif /* MM_CELL_INFO_H */
diff --git a/libmm-glib/mm-firmware-update-settings.c b/libmm-glib/mm-firmware-update-settings.c
index 459aa89..3ed9246 100644
--- a/libmm-glib/mm-firmware-update-settings.c
+++ b/libmm-glib/mm-firmware-update-settings.c
@@ -70,6 +70,18 @@
     return self->priv->method;
 }
 
+/**
+ * mm_firmware_update_settings_set_method: (skip)
+ */
+void
+mm_firmware_update_settings_set_method (MMFirmwareUpdateSettings    *self,
+                                        MMModemFirmwareUpdateMethod  method)
+{
+    g_return_if_fail (MM_IS_FIRMWARE_UPDATE_SETTINGS (self));
+
+    self->priv->method = method;
+}
+
 /*****************************************************************************/
 
 /**
diff --git a/libmm-glib/mm-firmware-update-settings.h b/libmm-glib/mm-firmware-update-settings.h
index 9c0c896..459d57c 100644
--- a/libmm-glib/mm-firmware-update-settings.h
+++ b/libmm-glib/mm-firmware-update-settings.h
@@ -87,10 +87,12 @@
 GVariant *mm_firmware_update_settings_get_variant (MMFirmwareUpdateSettings *self);
 
 /* Generic */
-void mm_firmware_update_settings_set_device_ids (MMFirmwareUpdateSettings  *self,
-                                                 const gchar              **device_ids);
-void mm_firmware_update_settings_set_version    (MMFirmwareUpdateSettings  *self,
-                                                 const gchar               *version);
+void mm_firmware_update_settings_set_device_ids (MMFirmwareUpdateSettings     *self,
+                                                 const gchar                 **device_ids);
+void mm_firmware_update_settings_set_version    (MMFirmwareUpdateSettings     *self,
+                                                 const gchar                  *version);
+void mm_firmware_update_settings_set_method     (MMFirmwareUpdateSettings     *self,
+                                                 MMModemFirmwareUpdateMethod   method);
 
 /* Fastboot specific */
 void mm_firmware_update_settings_set_fastboot_at (MMFirmwareUpdateSettings *self,
diff --git a/libmm-glib/mm-modem-3gpp.c b/libmm-glib/mm-modem-3gpp.c
index 83a74a9..0d3944e 100644
--- a/libmm-glib/mm-modem-3gpp.c
+++ b/libmm-glib/mm-modem-3gpp.c
@@ -741,6 +741,8 @@
         g_variant_unref (dict);
     }
 
+    g_variant_unref (variant);
+
     return list;
 }
 
diff --git a/libmm-glib/mm-modem.c b/libmm-glib/mm-modem.c
index 5e24431..8e1a80f 100644
--- a/libmm-glib/mm-modem.c
+++ b/libmm-glib/mm-modem.c
@@ -23,7 +23,6 @@
 
 #include <gio/gio.h>
 #include <string.h>
-
 #include "mm-common-helpers.h"
 #include "mm-errors-types.h"
 #include "mm-helpers.h"
@@ -3247,6 +3246,135 @@
 
 /*****************************************************************************/
 
+static GList *
+create_cell_info_list (GVariant  *variant,
+                       GError   **error)
+{
+    GError       *inner_error = NULL;
+    GList        *list = NULL;
+    GVariantIter  dict_iter;
+    GVariant     *dict;
+
+    /* Input is aa{sv} */
+    g_variant_iter_init (&dict_iter, variant);
+    while ((dict = g_variant_iter_next_value (&dict_iter))) {
+        MMCellInfo *cell_info;
+
+        cell_info = mm_cell_info_new_from_dictionary (dict, &inner_error);
+        if (inner_error)
+            break;
+
+        list = g_list_prepend (list, cell_info);
+        g_variant_unref (dict);
+    }
+
+    if (inner_error) {
+        g_list_free_full (g_steal_pointer (&list), g_object_unref);
+        g_propagate_error (error, inner_error);
+    }
+
+    g_variant_unref (variant);
+
+    return list;
+}
+
+/**
+ * mm_modem_get_cell_info_finish:
+ * @self: A #MMModem.
+ * @res: The #GAsyncResult obtained from the #GAsyncReadyCallback passed to
+ *  mm_modem_get_cell_info().
+ * @error: Return location for error or %NULL.
+ *
+ * Finishes an operation started with mm_modem_get_cell_info().
+ *
+ * Returns: (transfer full) (element-type ModemManager.CellInfo): a list
+ * of #MMCellInfo objects, or #NULL if @error is set. The returned value
+ * should be freed with g_list_free_full() using g_object_unref() as
+ * #GDestroyNotify function.
+ *
+ * Since: 1.20
+ */
+GList *
+mm_modem_get_cell_info_finish (MMModem       *self,
+                               GAsyncResult  *res,
+                               GError       **error)
+{
+    GVariant *result = NULL;
+
+    g_return_val_if_fail (MM_IS_MODEM (self), FALSE);
+
+    if (!mm_gdbus_modem_call_get_cell_info_finish (MM_GDBUS_MODEM (self), &result, res, error))
+        return NULL;
+
+    return create_cell_info_list (result, error);
+}
+
+/**
+ * mm_modem_get_cell_info:
+ * @self: A #MMModem.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @callback: A #GAsyncReadyCallback to call when the request is satisfied or
+ *  %NULL.
+ * @user_data: User data to pass to @callback.
+ *
+ * Asynchronously requests to get info about serving and neighboring cells.
+ *
+ * When the operation is finished, @callback will be invoked in the
+ * <link linkend="g-main-context-push-thread-default">thread-default main loop</link>
+ * of the thread you are calling this method from. You can then call
+ * mm_modem_get_cell_info_finish() to get the result of the operation.
+ *
+ * See mm_modem_get_cell_info_sync() for the synchronous, blocking version of this
+ * method.
+ *
+ * Since: 1.20
+ */
+void
+mm_modem_get_cell_info (MMModem             *self,
+                        GCancellable        *cancellable,
+                        GAsyncReadyCallback  callback,
+                        gpointer             user_data)
+{
+    g_return_if_fail (MM_IS_MODEM (self));
+
+    mm_gdbus_modem_call_get_cell_info (MM_GDBUS_MODEM (self), cancellable, callback, user_data);
+}
+
+/**
+ * mm_modem_get_cell_info_sync:
+ * @self: A #MMModem.
+ * @cancellable: (allow-none): A #GCancellable or %NULL.
+ * @error: Return location for error or %NULL.
+ *
+ * Synchronously requests to get info about serving and neighboring cells.
+ *
+ * The calling thread is blocked until a reply is received. See
+ * mm_modem_get_cell_info() for the asynchronous version of this method.
+ *
+ * Returns: (transfer full) (element-type ModemManager.CellInfo): a list
+ * of #MMCellInfo objects, or #NULL if @error is set. The returned value
+ * should be freed with g_list_free_full() using g_object_unref() as
+ * #GDestroyNotify function.
+ *
+ * Since: 1.20
+ */
+GList *
+mm_modem_get_cell_info_sync (MMModem       *self,
+                             GCancellable  *cancellable,
+                             GError       **error)
+{
+    GVariant *result = NULL;
+
+    g_return_val_if_fail (MM_IS_MODEM (self), FALSE);
+
+    if (!mm_gdbus_modem_call_get_cell_info_sync (MM_GDBUS_MODEM (self), &result, cancellable, error))
+        return NULL;
+
+    return create_cell_info_list (result, error);
+}
+
+/*****************************************************************************/
+
 static void
 mm_modem_init (MMModem *self)
 {
diff --git a/libmm-glib/mm-modem.h b/libmm-glib/mm-modem.h
index 655e6f4..9d1a733 100644
--- a/libmm-glib/mm-modem.h
+++ b/libmm-glib/mm-modem.h
@@ -34,6 +34,7 @@
 #include "mm-unlock-retries.h"
 #include "mm-sim.h"
 #include "mm-bearer.h"
+#include "mm-cell-info.h"
 #include "mm-helper-types.h"
 
 G_BEGIN_DECLS
@@ -372,6 +373,17 @@
                                                GCancellable         *cancellable,
                                                GError              **error);
 
+void   mm_modem_get_cell_info        (MMModem              *self,
+                                      GCancellable         *cancellable,
+                                      GAsyncReadyCallback   callback,
+                                      gpointer              user_data);
+GList *mm_modem_get_cell_info_finish (MMModem              *self,
+                                      GAsyncResult         *res,
+                                      GError              **error);
+GList *mm_modem_get_cell_info_sync   (MMModem              *self,
+                                      GCancellable         *cancellable,
+                                      GError              **error);
+
 G_END_DECLS
 
 #endif /* _MM_MODEM_H_ */
diff --git a/libqcdm/src/commands.c b/libqcdm/src/commands.c
index a97cb9a..cd21281 100644
--- a/libqcdm/src/commands.c
+++ b/libqcdm/src/commands.c
@@ -156,7 +156,7 @@
 static char *
 bin2hexstr (const uint8_t *bytes, int len)
 {
-    static char hex_digits[] = "0123456789abcdef";
+    const char hex_digits[] = "0123456789abcdef";
     char *result;
     int i;
     size_t buflen = (len * 2) + 1;
diff --git a/libqcdm/src/errors.c b/libqcdm/src/errors.c
index 12b8d55..dd789d8 100644
--- a/libqcdm/src/errors.c
+++ b/libqcdm/src/errors.c
@@ -23,8 +23,8 @@
 _qcdm_log (const char *file,
            int line,
            const char *func,
-           int level,
            int domain,
+           int level,
            const char *format,
            ...)
 {
@@ -53,7 +53,7 @@
         prefix = "dbg";
 
     if (n >= 0) {
-        fprintf (stderr, "<%s> [%s:%u] %s(): %s\n", prefix, file, line, func, message);
+        fprintf (stderr, "<%s> [%s:%d] %s(): %s\n", prefix, file, line, func, message);
         free (message);
     }
 }
diff --git a/meson.build b/meson.build
index 5a1485a..17a11b3 100644
--- a/meson.build
+++ b/meson.build
@@ -3,7 +3,7 @@
 
 project(
   'ModemManager', 'c',
-  version: '1.19.0',
+  version: '1.19.1',
   license: 'GPL2',
   default_options: [
     'buildtype=debugoptimized',
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 5e2d4a1..93a9ce2 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -690,9 +690,19 @@
 
 pkglib_LTLIBRARIES += libmm-plugin-fibocom.la
 libmm_plugin_fibocom_la_SOURCES = \
+	fibocom/mm-broadband-bearer-fibocom-ecm.c \
+	fibocom/mm-broadband-bearer-fibocom-ecm.h \
+	fibocom/mm-broadband-modem-fibocom.c \
+	fibocom/mm-broadband-modem-fibocom.h \
 	fibocom/mm-plugin-fibocom.c \
 	fibocom/mm-plugin-fibocom.h \
 	$(NULL)
+if WITH_MBIM
+libmm_plugin_fibocom_la_SOURCES += \
+	fibocom/mm-broadband-modem-mbim-xmm-fibocom.c \
+	fibocom/mm-broadband-modem-mbim-xmm-fibocom.h \
+	$(NULL)
+endif
 libmm_plugin_fibocom_la_CPPFLAGS = \
 	$(PLUGIN_COMMON_COMPILER_FLAGS) \
 	$(XMM_COMMON_COMPILER_FLAGS) \
diff --git a/plugins/fibocom/77-mm-fibocom-port-types.rules b/plugins/fibocom/77-mm-fibocom-port-types.rules
index 790520c..544dc25 100755
--- a/plugins/fibocom/77-mm-fibocom-port-types.rules
+++ b/plugins/fibocom/77-mm-fibocom-port-types.rules
@@ -1,12 +1,16 @@
 # do not edit this file, it will be overwritten on update
 ACTION!="add|change|move|bind", GOTO="mm_fibocom_port_types_end"
 SUBSYSTEMS=="usb", ATTRS{idVendor}=="2cb7", GOTO="mm_fibocom_port_types"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="1782", GOTO="mm_fibocom_port_types"
 GOTO="mm_fibocom_port_types_end"
 
 LABEL="mm_fibocom_port_types"
 
 SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
 
+# Fibocom L850-GL attach APN with toggle modem power
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0007", ENV{ID_MM_FIBOCOM_INITIAL_EPS_OFF_ON}="1"
+
 # Fibocom L850-GL
 #  ttyACM0 (if #2): AT port
 #  ttyACM1 (if #4): debug port (ignore)
@@ -53,4 +57,28 @@
 ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_IGNORE}="1"
 ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="01a4", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
 
+# Fibocom MA510-GL (GTUSBMODE=31)
+#  ttyUSB0 (if #0): debug port (ignore)
+#  ttyUSB1 (if #1): AT port
+#  ttyUSB2 (if #2): AT port
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="2cb7", ATTRS{idProduct}=="0106", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# Fibocom L610 (GTUSBMODE=32)
+#  ttyUSB0 (if #2): AT port
+#  ttyUSB1 (if #3): NV
+#  ttyUSB2 (if #4): MOS
+#  ttyUSB3 (if #5): Diagnostic
+#  ttyUSB4 (if #6): Logging
+#  ttyUSB5 (if #7): AT port
+#  ttyUSB6 (if #8): AT port
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="04", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="05", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="06", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="07", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+ATTRS{idVendor}=="1782", ATTRS{idProduct}=="4d11", ENV{.MM_USBIFNUM}=="08", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
 LABEL="mm_fibocom_port_types_end"
diff --git a/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.c b/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.c
new file mode 100644
index 0000000..0cad000
--- /dev/null
+++ b/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.c
@@ -0,0 +1,270 @@
+/* -*- 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) 2022 Disruptive Technologies Research AS
+ */
+
+#include <config.h>
+
+#include "mm-broadband-bearer-fibocom-ecm.h"
+#include "mm-broadband-modem-fibocom.h"
+#include "mm-base-modem-at.h"
+#include "mm-iface-modem-3gpp.h"
+
+G_DEFINE_TYPE (MMBroadbandBearerFibocomEcm, mm_broadband_bearer_fibocom_ecm, MM_TYPE_BROADBAND_BEARER)
+
+/*****************************************************************************/
+/* Dial context and task                                                     */
+
+typedef struct {
+    MMBroadbandModem *modem;
+    MMPortSerialAt   *primary;
+    guint             cid;
+    MMPort           *data;
+} DialContext;
+
+static void
+dial_task_free (DialContext *ctx)
+{
+    g_object_unref (ctx->modem);
+    g_object_unref (ctx->primary);
+    if (ctx->data)
+        g_object_unref (ctx->data);
+    g_slice_free (DialContext, ctx);
+}
+
+static GTask *
+dial_task_new (MMBroadbandBearerFibocomEcm *self,
+               MMBroadbandModem         *modem,
+               MMPortSerialAt           *primary,
+               guint                     cid,
+               GCancellable             *cancellable,
+               GAsyncReadyCallback       callback,
+               gpointer                  user_data)
+{
+    DialContext *ctx;
+    GTask       *task;
+
+    ctx          = g_slice_new0 (DialContext);
+    ctx->modem   = g_object_ref (modem);
+    ctx->primary = g_object_ref (primary);
+    ctx->cid     = cid;
+
+    task = g_task_new (self, cancellable, callback, user_data);
+    g_task_set_task_data (task, ctx, (GDestroyNotify) dial_task_free);
+
+    ctx->data = mm_base_modem_get_best_data_port (MM_BASE_MODEM (modem), MM_PORT_TYPE_NET);
+    if (!ctx->data) {
+        g_task_return_new_error (task,
+                                 MM_CORE_ERROR,
+                                 MM_CORE_ERROR_NOT_FOUND,
+                                 "No valid data port found to launch connection");
+        g_object_unref (task);
+        return NULL;
+    }
+
+    return task;
+}
+
+/*****************************************************************************/
+/* 3GPP Dialing (sub-step of the 3GPP Connection sequence)                   */
+
+static MMPort *
+dial_3gpp_finish (MMBroadbandBearer *self,
+                  GAsyncResult *res,
+                  GError **error)
+{
+    return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+gtrndis_verify_ready (MMBaseModem  *modem,
+                      GAsyncResult *res,
+                      GTask        *task)
+{
+    DialContext *ctx;
+    GError      *error = NULL;
+    const gchar *response;
+
+    ctx = g_task_get_task_data (task);
+    response = mm_base_modem_at_command_finish (modem, res, &error);
+
+    if (!response)
+        g_task_return_error (task, error);
+    else {
+        response = mm_strip_tag (response, "+GTRNDIS:");
+        if (strtol (response, NULL, 10) != 1)
+            g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                     "Connection status verification failed");
+        else
+            g_task_return_pointer (task, g_object_ref (ctx->data), g_object_unref);
+    }
+
+    g_object_unref (task);
+}
+
+static void
+gtrndis_activate_ready (MMBaseModem  *modem,
+                        GAsyncResult *res,
+                        GTask        *task)
+{
+    GError *error = NULL;
+
+    if (!mm_base_modem_at_command_finish (modem, res, &error)) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    mm_base_modem_at_command (modem,
+                              "+GTRNDIS?",
+                              6, /* timeout [s] */
+                              FALSE, /* allow_cached */
+                              (GAsyncReadyCallback) gtrndis_verify_ready,
+                              task);
+}
+
+static void
+dial_3gpp (MMBroadbandBearer  *self,
+           MMBaseModem        *modem,
+           MMPortSerialAt     *primary,
+           guint               cid,
+           GCancellable       *cancellable,
+           GAsyncReadyCallback callback,
+           gpointer            user_data)
+{
+    GTask            *task;
+    g_autofree gchar *cmd = NULL;
+
+    task = dial_task_new (MM_BROADBAND_BEARER_FIBOCOM_ECM (self),
+                          MM_BROADBAND_MODEM (modem),
+                          primary,
+                          cid,
+                          cancellable,
+                          callback,
+                          user_data);
+    if (!task)
+        return;
+
+    cmd = g_strdup_printf ("+GTRNDIS=1,%u", cid);
+    mm_base_modem_at_command (modem,
+                              cmd,
+                              MM_BASE_BEARER_DEFAULT_CONNECTION_TIMEOUT,
+                              FALSE, /* allow_cached */
+                              (GAsyncReadyCallback) gtrndis_activate_ready,
+                              task);
+}
+
+/*****************************************************************************/
+/* 3GPP Disconnect sequence                                                  */
+
+static gboolean
+disconnect_3gpp_finish (MMBroadbandBearer *self,
+                        GAsyncResult *res,
+                        GError **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+gtrndis_deactivate_ready (MMBaseModem *modem,
+                          GAsyncResult *res,
+                          GTask *task)
+{
+    GError *error = NULL;
+
+    if (!mm_base_modem_at_command_finish (modem, res, &error))
+        g_task_return_error (task, error);
+    else
+        g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+disconnect_3gpp (MMBroadbandBearer *self,
+                 MMBroadbandModem *modem,
+                 MMPortSerialAt *primary,
+                 MMPortSerialAt *secondary,
+                 MMPort *data,
+                 guint cid,
+                 GAsyncReadyCallback callback,
+                 gpointer user_data)
+{
+    GTask *task;
+    g_autofree gchar *cmd = NULL;
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    cmd = g_strdup_printf ("+GTRNDIS=0,%u", cid);
+    mm_base_modem_at_command (MM_BASE_MODEM (modem),
+                              cmd,
+                              MM_BASE_BEARER_DEFAULT_DISCONNECTION_TIMEOUT,
+                              FALSE, /* allow_cached */
+                              (GAsyncReadyCallback) gtrndis_deactivate_ready,
+                              task);
+}
+
+/*****************************************************************************/
+
+MMBaseBearer *
+mm_broadband_bearer_fibocom_ecm_new_finish (GAsyncResult *res,
+                                            GError **error)
+{
+    GObject *bearer;
+    GObject *source;
+
+    source = g_async_result_get_source_object (res);
+    bearer = g_async_initable_new_finish (G_ASYNC_INITABLE (source), res, error);
+    g_object_unref (source);
+
+    if (!bearer)
+        return NULL;
+
+    /* Only export valid bearers */
+    mm_base_bearer_export (MM_BASE_BEARER (bearer));
+
+    return MM_BASE_BEARER (bearer);
+}
+
+void
+mm_broadband_bearer_fibocom_ecm_new (MMBroadbandModemFibocom *modem,
+                                     MMBearerProperties *config,
+                                     GCancellable *cancellable,
+                                     GAsyncReadyCallback callback,
+                                     gpointer user_data)
+{
+    g_async_initable_new_async (
+        MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM,
+        G_PRIORITY_DEFAULT,
+        cancellable,
+        callback,
+        user_data,
+        MM_BASE_BEARER_MODEM, modem,
+        MM_BASE_BEARER_CONFIG, config,
+        NULL);
+}
+
+static void
+mm_broadband_bearer_fibocom_ecm_init (MMBroadbandBearerFibocomEcm *self)
+{
+}
+
+static void
+mm_broadband_bearer_fibocom_ecm_class_init (MMBroadbandBearerFibocomEcmClass *klass)
+{
+    MMBroadbandBearerClass *broadband_bearer_class = MM_BROADBAND_BEARER_CLASS (klass);
+
+    broadband_bearer_class->dial_3gpp = dial_3gpp;
+    broadband_bearer_class->dial_3gpp_finish = dial_3gpp_finish;
+    broadband_bearer_class->disconnect_3gpp = disconnect_3gpp;
+    broadband_bearer_class->disconnect_3gpp_finish = disconnect_3gpp_finish;
+}
diff --git a/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.h b/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.h
new file mode 100644
index 0000000..ea367ae
--- /dev/null
+++ b/plugins/fibocom/mm-broadband-bearer-fibocom-ecm.h
@@ -0,0 +1,50 @@
+/* -*- 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) 2022 Disruptive Technologies Research AS
+ */
+
+#ifndef MM_BROADBAND_BEARER_FIBOCOM_ECM_H
+#define MM_BROADBAND_BEARER_FIBOCOM_ECM_H
+
+#include "mm-broadband-bearer.h"
+#include "mm-broadband-modem-fibocom.h"
+
+#define MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM            (mm_broadband_bearer_fibocom_ecm_get_type ())
+#define MM_BROADBAND_BEARER_FIBOCOM_ECM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM, MMBroadbandBearerFibocomEcm))
+#define MM_BROADBAND_BEARER_FIBOCOM_ECM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM, MMBroadbandBearerFibocomEcmClass))
+#define MM_IS_BROADBAND_BEARER_FIBOCOM_ECM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM))
+#define MM_IS_BROADBAND_BEARER_FIBOCOM_ECM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM))
+#define MM_BROADBAND_BEARER_FIBOCOM_ECM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_BROADBAND_BEARER_FIBOCOM_ECM, MMBroadbandBearerFibocomEcmClass))
+
+typedef struct _MMBroadbandBearerFibocomEcm MMBroadbandBearerFibocomEcm;
+typedef struct _MMBroadbandBearerFibocomEcmClass MMBroadbandBearerFibocomEcmClass;
+
+struct _MMBroadbandBearerFibocomEcm {
+    MMBroadbandBearer parent;
+};
+
+struct _MMBroadbandBearerFibocomEcmClass {
+    MMBroadbandBearerClass parent;
+};
+
+GType mm_broadband_bearer_fibocom_ecm_get_type (void);
+
+void          mm_broadband_bearer_fibocom_ecm_new        (MMBroadbandModemFibocom *modem,
+                                                          MMBearerProperties *properties,
+                                                          GCancellable *cancellable,
+                                                          GAsyncReadyCallback callback,
+                                                          gpointer user_data);
+MMBaseBearer *mm_broadband_bearer_fibocom_ecm_new_finish (GAsyncResult *res,
+                                                          GError **error);
+
+#endif /* MM_BROADBAND_BEARER_FIBOCOM_ECM_H */
diff --git a/plugins/fibocom/mm-broadband-modem-fibocom.c b/plugins/fibocom/mm-broadband-modem-fibocom.c
new file mode 100644
index 0000000..8292c18
--- /dev/null
+++ b/plugins/fibocom/mm-broadband-modem-fibocom.c
@@ -0,0 +1,207 @@
+/* -*- 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) 2022 Disruptive Technologies Research AS
+ */
+
+#include <config.h>
+
+#include "mm-broadband-modem-fibocom.h"
+#include "mm-broadband-bearer-fibocom-ecm.h"
+#include "mm-broadband-modem.h"
+#include "mm-iface-modem.h"
+#include "mm-base-modem-at.h"
+#include "mm-log.h"
+
+static void iface_modem_init (MMIfaceModem *iface);
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemFibocom, mm_broadband_modem_fibocom, MM_TYPE_BROADBAND_MODEM, 0,
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM, iface_modem_init))
+
+typedef enum {
+    FEATURE_SUPPORT_UNKNOWN,
+    FEATURE_NOT_SUPPORTED,
+    FEATURE_SUPPORTED,
+} FeatureSupport;
+
+struct _MMBroadbandModemFibocomPrivate {
+    FeatureSupport gtrndis_support;
+};
+
+/*****************************************************************************/
+/* Create Bearer (Modem interface) */
+
+static MMBaseBearer *
+modem_create_bearer_finish (MMIfaceModem *self,
+                            GAsyncResult *res,
+                            GError **error)
+{
+    return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+broadband_bearer_fibocom_ecm_new_ready (GObject *source,
+                                        GAsyncResult *res,
+                                        GTask *task)
+{
+    MMBaseBearer *bearer = NULL;
+    GError       *error = NULL;
+
+    bearer = mm_broadband_bearer_fibocom_ecm_new_finish (res, &error);
+    if (!bearer)
+        g_task_return_error (task, error);
+    else
+        g_task_return_pointer (task, bearer, g_object_unref);
+    g_object_unref (task);
+}
+
+static void
+broadband_bearer_new_ready (GObject *source,
+                            GAsyncResult *res,
+                            GTask *task)
+{
+    MMBaseBearer *bearer = NULL;
+    GError       *error = NULL;
+
+    bearer = mm_broadband_bearer_new_finish (res, &error);
+    if (!bearer)
+        g_task_return_error (task, error);
+    else
+        g_task_return_pointer (task, bearer, g_object_unref);
+    g_object_unref (task);
+}
+
+static void
+common_create_bearer (GTask *task)
+{
+    MMBroadbandModemFibocom *self;
+
+    self = g_task_get_source_object (task);
+
+    switch (self->priv->gtrndis_support) {
+    case FEATURE_SUPPORTED:
+        mm_obj_dbg (self, "+GTRNDIS supported, creating Fibocom ECM bearer");
+        mm_broadband_bearer_fibocom_ecm_new (self,
+                                             g_task_get_task_data (task),
+                                             NULL, /* cancellable */
+                                             (GAsyncReadyCallback) broadband_bearer_fibocom_ecm_new_ready,
+                                             task);
+        return;
+    case FEATURE_NOT_SUPPORTED:
+        mm_obj_dbg (self, "+GTRNDIS not supported, creating generic PPP bearer");
+        mm_broadband_bearer_new (MM_BROADBAND_MODEM (self),
+                                 g_task_get_task_data (task),
+                                 NULL, /* cancellable */
+                                 (GAsyncReadyCallback) broadband_bearer_new_ready,
+                                 task);
+        return;
+    case FEATURE_SUPPORT_UNKNOWN:
+    default:
+        g_assert_not_reached ();
+    }
+}
+
+static void
+gtrndis_test_ready (MMBaseModem  *_self,
+                    GAsyncResult *res,
+                    GTask        *task)
+{
+    MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self);
+
+    if (!mm_base_modem_at_command_finish (_self, res, NULL)) {
+        mm_obj_dbg (self, "+GTRNDIS unsupported");
+        self->priv->gtrndis_support = FEATURE_NOT_SUPPORTED;
+    } else {
+        mm_obj_dbg (self, "+GTRNDIS supported");
+        self->priv->gtrndis_support = FEATURE_SUPPORTED;
+    }
+
+    /* Go on and create the bearer */
+    common_create_bearer (task);
+}
+
+static void
+modem_create_bearer (MMIfaceModem       *_self,
+                     MMBearerProperties *properties,
+                     GAsyncReadyCallback callback,
+                     gpointer            user_data)
+{
+    MMBroadbandModemFibocom *self = MM_BROADBAND_MODEM_FIBOCOM (_self);
+    GTask                   *task;
+
+    task = g_task_new (self, NULL, callback, user_data);
+    g_task_set_task_data (task, g_object_ref (properties), g_object_unref);
+
+    if (self->priv->gtrndis_support != FEATURE_SUPPORT_UNKNOWN) {
+        common_create_bearer (task);
+        return;
+    }
+
+    if (!mm_base_modem_peek_best_data_port (MM_BASE_MODEM (self), MM_PORT_TYPE_NET)) {
+        mm_obj_dbg (self, "skipping +GTRNDIS check as no data port is available");
+        self->priv->gtrndis_support = FEATURE_NOT_SUPPORTED;
+        common_create_bearer (task);
+        return;
+    }
+
+    mm_obj_dbg (self, "checking +GTRNDIS support...");
+    mm_base_modem_at_command (MM_BASE_MODEM (self),
+                              "+GTRNDIS=?",
+                              6, /* timeout [s] */
+                              TRUE, /* allow_cached */
+                              (GAsyncReadyCallback) gtrndis_test_ready,
+                              task);
+}
+
+/*****************************************************************************/
+
+MMBroadbandModemFibocom *
+mm_broadband_modem_fibocom_new (const gchar  *device,
+                                const gchar **drivers,
+                                const gchar  *plugin,
+                                guint16       vendor_id,
+                                guint16       product_id)
+{
+    return g_object_new (MM_TYPE_BROADBAND_MODEM_FIBOCOM,
+                         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,
+                         MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+                         MM_BASE_MODEM_DATA_TTY_SUPPORTED, TRUE,
+                         NULL);
+}
+
+static void
+mm_broadband_modem_fibocom_init (MMBroadbandModemFibocom *self)
+{
+    self->priv = G_TYPE_INSTANCE_GET_PRIVATE (self,
+                                              MM_TYPE_BROADBAND_MODEM_FIBOCOM,
+                                              MMBroadbandModemFibocomPrivate);
+
+    self->priv->gtrndis_support = FEATURE_SUPPORT_UNKNOWN;
+}
+
+static void
+iface_modem_init (MMIfaceModem *iface)
+{
+    iface->create_bearer = modem_create_bearer;
+    iface->create_bearer_finish = modem_create_bearer_finish;
+}
+
+static void
+mm_broadband_modem_fibocom_class_init (MMBroadbandModemFibocomClass *klass)
+{
+    g_type_class_add_private (G_OBJECT_CLASS (klass),
+                              sizeof (MMBroadbandModemFibocomPrivate));
+}
diff --git a/plugins/fibocom/mm-broadband-modem-fibocom.h b/plugins/fibocom/mm-broadband-modem-fibocom.h
new file mode 100644
index 0000000..958841b
--- /dev/null
+++ b/plugins/fibocom/mm-broadband-modem-fibocom.h
@@ -0,0 +1,49 @@
+/* -*- 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) 2022 Disruptive Technologies Research AS
+ */
+
+#ifndef MM_BROADBAND_MODEM_FIBOCOM_H
+#define MM_BROADBAND_MODEM_FIBOCOM_H
+
+#include "mm-broadband-modem.h"
+
+#define MM_TYPE_BROADBAND_MODEM_FIBOCOM            (mm_broadband_modem_fibocom_get_type ())
+#define MM_BROADBAND_MODEM_FIBOCOM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_FIBOCOM, MMBroadbandModemFibocom))
+#define MM_BROADBAND_MODEM_FIBOCOM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_BROADBAND_MODEM_FIBOCOM, MMBroadbandModemFibocomClass))
+#define MM_IS_BROADBAND_MODEM_FIBOCOM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_FIBOCOM))
+#define MM_IS_BROADBAND_MODEM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_BROADBAND_MODEM_FIBOCOM))
+#define MM_BROADBAND_MODEM_FIBOCOM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_BROADBAND_MODEM_FIBOCOM, MMBroadbandModemFibocomClass))
+
+typedef struct _MMBroadbandModemFibocom MMBroadbandModemFibocom;
+typedef struct _MMBroadbandModemFibocomClass MMBroadbandModemFibocomClass;
+typedef struct _MMBroadbandModemFibocomPrivate MMBroadbandModemFibocomPrivate;
+
+struct _MMBroadbandModemFibocom {
+    MMBroadbandModem parent;
+    MMBroadbandModemFibocomPrivate *priv;
+};
+
+struct _MMBroadbandModemFibocomClass{
+    MMBroadbandModemClass parent;
+};
+
+GType mm_broadband_modem_fibocom_get_type (void);
+
+MMBroadbandModemFibocom *mm_broadband_modem_fibocom_new (const gchar  *device,
+                                                         const gchar **drivers,
+                                                         const gchar  *plugin,
+                                                         guint16       vendor_id,
+                                                         guint16       product_id);
+
+#endif /* MM_BROADBAND_MODEM_FIBOCOM_H */
diff --git a/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.c b/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.c
new file mode 100755
index 0000000..a434d4c
--- /dev/null
+++ b/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.c
@@ -0,0 +1,225 @@
+/* -*- 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) 2022 Fibocom Wireless Inc.
+ */
+
+#include <config.h>
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+#include <ctype.h>
+
+#include "ModemManager.h"
+#include "mm-log-object.h"
+#include "mm-iface-modem.h"
+#include "mm-iface-modem-3gpp.h"
+#include "mm-broadband-modem-mbim-xmm-fibocom.h"
+
+static void iface_modem_3gpp_init (MMIfaceModem3gpp *iface);
+
+static MMIfaceModem3gpp *iface_modem_3gpp_parent;
+
+G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimXmmFibocom, mm_broadband_modem_mbim_xmm_fibocom, MM_TYPE_BROADBAND_MODEM_MBIM_XMM, 0,
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_3GPP, iface_modem_3gpp_init))
+
+/*****************************************************************************/
+
+typedef struct {
+    MMBearerProperties *config;
+    gboolean            initial_eps_off_on;
+} SetInitialEpsBearerSettingsContext;
+
+static void
+set_initial_eps_bearer_settings_context_free (SetInitialEpsBearerSettingsContext *ctx)
+{
+    g_clear_object (&ctx->config);
+    g_slice_free (SetInitialEpsBearerSettingsContext, ctx);
+}
+
+static gboolean
+modem_3gpp_set_initial_eps_bearer_settings_finish (MMIfaceModem3gpp  *self,
+                                                   GAsyncResult      *res,
+                                                   GError           **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+after_attach_apn_modem_power_up_ready (MMIfaceModem *self,
+                                       GAsyncResult *res,
+                                       GTask        *task)
+{
+    GError *error = NULL;
+
+    if (!mm_iface_modem_set_power_state_finish (self, res, &error)) {
+        mm_obj_warn (self, "failed to power up modem after attach APN settings update: %s", error->message);
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    mm_obj_dbg (self, "success toggling modem power up after attach APN");
+    g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+parent_set_initial_eps_bearer_settings_ready (MMIfaceModem3gpp *self,
+                                              GAsyncResult     *res,
+                                              GTask            *task)
+{
+    SetInitialEpsBearerSettingsContext *ctx;
+    GError                             *error = NULL;
+
+    ctx = g_task_get_task_data (task);
+
+    if (!iface_modem_3gpp_parent->set_initial_eps_bearer_settings_finish (self, res, &error)) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    if (ctx->initial_eps_off_on) {
+        mm_obj_dbg (self, "toggle modem power up after attach APN");
+        mm_iface_modem_set_power_state (MM_IFACE_MODEM (self),
+                                        MM_MODEM_POWER_STATE_ON,
+                                        (GAsyncReadyCallback) after_attach_apn_modem_power_up_ready,
+                                        task);
+        return;
+    }
+
+    g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+parent_set_initial_eps_bearer_settings (GTask *task)
+{
+    MMBroadbandModemMbimXmmFibocom     *self;
+    SetInitialEpsBearerSettingsContext *ctx;
+
+    self = g_task_get_source_object (task);
+    ctx  = g_task_get_task_data (task);
+
+    g_assert (iface_modem_3gpp_parent->set_initial_eps_bearer_settings);
+    g_assert (iface_modem_3gpp_parent->set_initial_eps_bearer_settings_finish);
+
+    iface_modem_3gpp_parent->set_initial_eps_bearer_settings (MM_IFACE_MODEM_3GPP (self),
+                                                              ctx->config,
+                                                              (GAsyncReadyCallback)parent_set_initial_eps_bearer_settings_ready,
+                                                              task);
+}
+
+static void
+before_attach_apn_modem_power_down_ready (MMIfaceModem *self,
+                                          GAsyncResult *res,
+                                          GTask        *task)
+{
+    GError *error = NULL;
+
+    if (!mm_iface_modem_set_power_state_finish (self, res, &error)) {
+        mm_obj_warn (self, "failed to power down modem before attach APN settings update: %s", error->message);
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+    mm_obj_dbg (self, "success toggling modem power down before attach APN");
+
+    parent_set_initial_eps_bearer_settings (task);
+}
+
+static void
+modem_3gpp_set_initial_eps_bearer_settings (MMIfaceModem3gpp    *self,
+                                            MMBearerProperties  *config,
+                                            GAsyncReadyCallback  callback,
+                                            gpointer             user_data)
+{
+    SetInitialEpsBearerSettingsContext *ctx;
+    GTask                              *task;
+    MMPortMbim                         *port;
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    port = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
+    if (!port) {
+        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
+                                 "No valid MBIM port found");
+        g_object_unref (task);
+        return;
+    }
+
+    ctx = g_slice_new0 (SetInitialEpsBearerSettingsContext);
+    ctx->config = g_object_ref (config);
+    ctx->initial_eps_off_on = mm_kernel_device_get_property_as_boolean (mm_port_peek_kernel_device (MM_PORT (port)), "ID_MM_FIBOCOM_INITIAL_EPS_OFF_ON");
+    g_task_set_task_data (task, ctx, (GDestroyNotify)set_initial_eps_bearer_settings_context_free);
+
+
+    if (ctx->initial_eps_off_on) {
+        mm_obj_dbg (self, "toggle modem power down before attach APN");
+        mm_iface_modem_set_power_state (MM_IFACE_MODEM (self),
+                                        MM_MODEM_POWER_STATE_LOW,
+                                        (GAsyncReadyCallback) before_attach_apn_modem_power_down_ready,
+                                        task);
+        return;
+    }
+
+    parent_set_initial_eps_bearer_settings (task);
+}
+
+/******************************************************************************/
+
+MMBroadbandModemMbimXmmFibocom *
+mm_broadband_modem_mbim_xmm_fibocom_new (const gchar  *device,
+                                         const gchar **drivers,
+                                         const gchar  *plugin,
+                                         guint16       vendor_id,
+                                         guint16       product_id)
+{
+    return g_object_new (MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM,
+                         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,
+                         /* MBIM bearer supports NET only */
+                         MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
+                         MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE,
+                         MM_IFACE_MODEM_SIM_HOT_SWAP_SUPPORTED, TRUE,
+                         MM_IFACE_MODEM_SIM_HOT_SWAP_CONFIGURED, FALSE,
+                         MM_IFACE_MODEM_PERIODIC_SIGNAL_CHECK_DISABLED, TRUE,
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
+                         MM_BROADBAND_MODEM_MBIM_QMI_UNSUPPORTED, TRUE,
+#endif
+                         NULL);
+}
+
+static void
+mm_broadband_modem_mbim_xmm_fibocom_init (MMBroadbandModemMbimXmmFibocom *self)
+{
+}
+
+static void
+iface_modem_3gpp_init (MMIfaceModem3gpp *iface)
+{
+    iface_modem_3gpp_parent = g_type_interface_peek_parent (iface);
+
+    iface->set_initial_eps_bearer_settings         = modem_3gpp_set_initial_eps_bearer_settings;
+    iface->set_initial_eps_bearer_settings_finish  = modem_3gpp_set_initial_eps_bearer_settings_finish;
+}
+
+static void
+mm_broadband_modem_mbim_xmm_fibocom_class_init (MMBroadbandModemMbimXmmFibocomClass *klass)
+{
+}
diff --git a/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.h b/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.h
new file mode 100755
index 0000000..db51cfc
--- /dev/null
+++ b/plugins/fibocom/mm-broadband-modem-mbim-xmm-fibocom.h
@@ -0,0 +1,47 @@
+/* -*- 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) 2022 Fibocom Wireless Inc.
+ */
+
+#ifndef MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_H
+#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_H
+
+#include "mm-broadband-modem-mbim-xmm.h"
+
+#define MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM            (mm_broadband_modem_mbim_xmm_fibocom_get_type ())
+#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM, MMBroadbandModemMbimXmmFibocom))
+#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM, MMBroadbandModemMbimXmmFibocomClass))
+#define MM_IS_BROADBAND_MODEM_MBIM_XMM_FIBOCOM(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM))
+#define MM_IS_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM))
+#define MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_BROADBAND_MODEM_MBIM_XMM_FIBOCOM, MMBroadbandModemMbimXmmFibocomClass))
+
+typedef struct _MMBroadbandModemMbimXmmFibocom MMBroadbandModemMbimXmmFibocom;
+typedef struct _MMBroadbandModemMbimXmmFibocomClass MMBroadbandModemMbimXmmFibocomClass;
+
+struct _MMBroadbandModemMbimXmmFibocom {
+    MMBroadbandModemMbimXmm parent;
+};
+
+struct _MMBroadbandModemMbimXmmFibocomClass{
+    MMBroadbandModemMbimXmmClass parent;
+};
+
+GType mm_broadband_modem_mbim_xmm_fibocom_get_type (void);
+
+MMBroadbandModemMbimXmmFibocom *mm_broadband_modem_mbim_xmm_fibocom_new (const gchar  *device,
+                                                                         const gchar **drivers,
+                                                                         const gchar  *plugin,
+                                                                         guint16       vendor_id,
+                                                                         guint16       product_id);
+
+#endif /* MM_BROADBAND_MODEM_MBIM_XMM_FIBOCOM_H */
diff --git a/plugins/fibocom/mm-plugin-fibocom.c b/plugins/fibocom/mm-plugin-fibocom.c
index 1ff9f17..a9816d9 100644
--- a/plugins/fibocom/mm-plugin-fibocom.c
+++ b/plugins/fibocom/mm-plugin-fibocom.c
@@ -23,10 +23,12 @@
 #include "mm-plugin-fibocom.h"
 #include "mm-broadband-modem.h"
 #include "mm-broadband-modem-xmm.h"
+#include "mm-broadband-modem-fibocom.h"
 
 #if defined WITH_MBIM
 #include "mm-broadband-modem-mbim.h"
 #include "mm-broadband-modem-mbim-xmm.h"
+#include "mm-broadband-modem-mbim-xmm-fibocom.h"
 #endif
 
 #if defined WITH_QMI
@@ -53,11 +55,11 @@
     if (mm_port_probe_list_has_mbim_port (probes)) {
         if (mm_port_probe_list_is_xmm (probes)) {
             mm_obj_dbg (self, "MBIM-powered XMM-based Fibocom modem found...");
-            return MM_BASE_MODEM (mm_broadband_modem_mbim_xmm_new (uid,
-                                                                   drivers,
-                                                                   mm_plugin_get_name (self),
-                                                                   vendor,
-                                                                   product));
+            return MM_BASE_MODEM (mm_broadband_modem_mbim_xmm_fibocom_new (uid,
+                                                                           drivers,
+                                                                           mm_plugin_get_name (self),
+                                                                           vendor,
+                                                                           product));
         }
         mm_obj_dbg (self, "MBIM-powered Fibocom modem found...");
         return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid,
@@ -89,11 +91,11 @@
     }
 
     mm_obj_dbg (self, "Fibocom modem found...");
-    return MM_BASE_MODEM (mm_broadband_modem_new (uid,
-                                                  drivers,
-                                                  mm_plugin_get_name (self),
-                                                  vendor,
-                                                  product));
+    return MM_BASE_MODEM (mm_broadband_modem_fibocom_new (uid,
+                                                          drivers,
+                                                          mm_plugin_get_name (self),
+                                                          vendor,
+                                                          product));
 }
 
 /*****************************************************************************/
@@ -102,8 +104,8 @@
 mm_plugin_create (void)
 {
     static const gchar *subsystems[] = { "tty", "net", "usbmisc", NULL };
-    static const guint16 vendor_ids[] = { 0x2cb7, 0 };
-    static const gchar *drivers[] = { "cdc_mbim", "qmi_wwan", NULL };
+    static const guint16 vendor_ids[] = { 0x2cb7, 0x1782, 0 };
+    static const gchar *drivers[] = { "cdc_mbim", "qmi_wwan", "cdc_ether", NULL };
 
     return MM_PLUGIN (
         g_object_new (MM_TYPE_PLUGIN_FIBOCOM,
diff --git a/plugins/foxconn/mm-broadband-modem-mbim-foxconn.c b/plugins/foxconn/mm-broadband-modem-mbim-foxconn.c
index de69457..22ab0be 100644
--- a/plugins/foxconn/mm-broadband-modem-mbim-foxconn.c
+++ b/plugins/foxconn/mm-broadband-modem-mbim-foxconn.c
@@ -33,21 +33,21 @@
 #include "mm-iface-modem-location.h"
 #include "mm-broadband-modem-mbim-foxconn.h"
 
-#if defined WITH_QMI
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
 # include "mm-iface-modem-firmware.h"
 # include "mm-shared-qmi.h"
 #endif
 
 static void iface_modem_location_init (MMIfaceModemLocation *iface);
 
-#if defined WITH_QMI
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
 static void iface_modem_firmware_init (MMIfaceModemFirmware *iface);
 #endif
 
 static MMIfaceModemLocation *iface_modem_location_parent;
 
 G_DEFINE_TYPE_EXTENDED (MMBroadbandModemMbimFoxconn, mm_broadband_modem_mbim_foxconn, MM_TYPE_BROADBAND_MODEM_MBIM, 0,
-#if defined WITH_QMI
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
                         G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_FIRMWARE, iface_modem_firmware_init)
 #endif
                         G_IMPLEMENT_INTERFACE (MM_TYPE_IFACE_MODEM_LOCATION, iface_modem_location_init))
@@ -63,7 +63,7 @@
 };
 
 
-#if defined WITH_QMI
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
 
 /*****************************************************************************/
 /* Firmware update settings
@@ -497,7 +497,7 @@
     iface->disable_location_gathering_finish = disable_location_gathering_finish;
 }
 
-#if defined WITH_QMI
+#if defined WITH_QMI && QMI_MBIM_QMUX_SUPPORTED
 
 static void
 iface_modem_firmware_init (MMIfaceModemFirmware *iface)
diff --git a/plugins/meson.build b/plugins/meson.build
index ea40eba..6bcb364 100644
--- a/plugins/meson.build
+++ b/plugins/meson.build
@@ -21,7 +21,7 @@
   sources: sources,
   include_directories: top_inc,
   dependencies: deps + [gio_unix_dep],
-  c_args: '-DTEST_SERVICES="@0@"'.format(source_root / 'data/tests'),
+  c_args: '-DTEST_SERVICES="@0@"'.format(build_root / 'data/tests'),
 )
 
 libmm_test_common_dep = declare_dependency(
@@ -337,9 +337,17 @@
     '-DTESTUDEVRULESDIR_FIBOCOM="@0@"'.format(plugins_dir / 'fibocom'),
   ]
 
+  sources = files(
+    'fibocom/mm-broadband-bearer-fibocom-ecm.c',
+    'fibocom/mm-broadband-modem-fibocom.c',
+    'fibocom/mm-plugin-fibocom.c',
+  )
+  if enable_mbim
+    sources += files('fibocom/mm-broadband-modem-mbim-xmm-fibocom.c')
+  endif
   plugins += {'plugin-fibocom': {
     'plugin': true,
-    'module': {'sources': files('fibocom/mm-plugin-fibocom.c'), 'include_directories': plugins_incs + [xmm_inc], 'c_args': c_args},
+    'module': {'sources': sources, 'include_directories': plugins_incs + [xmm_inc], 'c_args': c_args},
   }}
 
   plugins_udev_rules += files('fibocom/77-mm-fibocom-port-types.rules')
@@ -368,24 +376,6 @@
 
 # plugin: generic
 if plugins_options['generic']
-  # FIXME
-  '''
-  15/16 test-service-generic                FAIL            0.02s   killed by signal 5 SIGTRAP
-  >>> MALLOC_PERTURB_=124 /ModemManager/_build/plugins/test-service-generic
-  ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――― ✀  ―――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――――
-  stdout:
-  # random seed: R02S5d0d577043f61f2806f319a6510e83a4
-  1..1
-  # Start of MM tests
-  # Start of Service tests
-  # Start of Generic tests
-  Bail out! FATAL-ERROR: Error starting ModemManager in test bus: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.ModemManager1 was not provided by any .service files
-  stderr:
-
-  ** (/ModemManager/_build/plugins/test-service-generic:36444): ERROR **: 21:06:16.248: Error starting ModemManager in test bus: GDBus.Error:org.freedesktop.DBus.Error.ServiceUnknown: The name org.freedesktop.ModemManager1 was not provided by any .service files
-  cleaning up pid 36446
-  '''
-
   plugins += {'plugin-generic': {
     'plugin': true,
     'module': {'sources': files('generic/mm-plugin-generic.c'), 'include_directories': plugins_incs, 'c_args': '-DMM_MODULE_NAME="generic"'},
diff --git a/plugins/quectel/77-mm-quectel-port-types.rules b/plugins/quectel/77-mm-quectel-port-types.rules
index ce49416..65993a2 100644
--- a/plugins/quectel/77-mm-quectel-port-types.rules
+++ b/plugins/quectel/77-mm-quectel-port-types.rules
@@ -1,9 +1,10 @@
 # do not edit this file, it will be overwritten on update
-ACTION!="add|change|move|bind", GOTO="mm_quectel_port_types_end"
-SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c7c", GOTO="mm_quectel_port_types"
-GOTO="mm_quectel_port_types_end"
+ACTION!="add|change|move|bind", GOTO="mm_quectel_end"
+SUBSYSTEMS=="usb", ATTRS{idVendor}=="2c7c", GOTO="mm_quectel_usb"
+SUBSYSTEMS=="pci", ATTRS{vendor}=="0x1eac", GOTO="mm_quectel_pci"
+GOTO="mm_quectel_end"
 
-LABEL="mm_quectel_port_types"
+LABEL="mm_quectel_usb"
 
 SUBSYSTEMS=="usb", ATTRS{bInterfaceNumber}=="?*", ENV{.MM_USBIFNUM}="$attr{bInterfaceNumber}"
 
@@ -67,4 +68,18 @@
 ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0800", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
 ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0800", ENV{.MM_USBIFNUM}=="03", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
 
-LABEL="mm_quectel_port_types_end"
+# Quectel EM05-G and EM05-CE with firehose support
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="030a", ENV{ID_MM_QUECTEL_FIREHOSE}="1"
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="030a", ENV{ID_MM_QUECTEL_SAHARA}="1"
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0127", ENV{ID_MM_QUECTEL_FIREHOSE}="1"
+ATTRS{idVendor}=="2c7c", ATTRS{idProduct}=="0127", ENV{ID_MM_QUECTEL_SAHARA}="1"
+
+GOTO="mm_quectel_end"
+
+LABEL="mm_quectel_pci"
+
+# Quectel EM120 and EM160 with firehose support
+ATTRS{vendor}=="0x1eac", ATTRS{device}=="0x1001", ENV{ID_MM_QUECTEL_FIREHOSE}="1"
+ATTRS{vendor}=="0x1eac", ATTRS{device}=="0x1002", ENV{ID_MM_QUECTEL_FIREHOSE}="1"
+
+LABEL="mm_quectel_end"
diff --git a/plugins/quectel/mm-broadband-modem-mbim-quectel.c b/plugins/quectel/mm-broadband-modem-mbim-quectel.c
index 2874e54..b3a3300 100644
--- a/plugins/quectel/mm-broadband-modem-mbim-quectel.c
+++ b/plugins/quectel/mm-broadband-modem-mbim-quectel.c
@@ -35,51 +35,6 @@
                         G_IMPLEMENT_INTERFACE (MM_TYPE_SHARED_QUECTEL, shared_quectel_init))
 
 /*****************************************************************************/
-/* Firmware update settings */
-
-static MMFirmwareUpdateSettings *
-firmware_load_update_settings_finish (MMIfaceModemFirmware  *self,
-                                      GAsyncResult          *res,
-                                      GError               **error)
-{
-    return g_task_propagate_pointer (G_TASK (res), error);
-}
-
-static void
-quectel_get_firmware_version_ready (MMBaseModem  *modem,
-                                    GAsyncResult *res,
-                                    GTask        *task)
-{
-    MMFirmwareUpdateSettings *update_settings;
-    const gchar              *version;
-
-    update_settings = mm_firmware_update_settings_new (MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE);
-
-    version = mm_base_modem_at_command_finish (modem, res, NULL);
-    if (version)
-        mm_firmware_update_settings_set_version (update_settings, version);
-    g_task_return_pointer (task, update_settings, g_object_unref);
-    g_object_unref (task);
-}
-
-static void
-firmware_load_update_settings (MMIfaceModemFirmware *self,
-                               GAsyncReadyCallback   callback,
-                               gpointer              user_data)
-{
-    GTask *task;
-
-    task = g_task_new (self, NULL, callback, user_data);
-
-    mm_base_modem_at_command (MM_BASE_MODEM (self),
-                              "+QGMR?",
-                              3,
-                              FALSE,
-                              (GAsyncReadyCallback) quectel_get_firmware_version_ready,
-                              task);
-}
-
-/*****************************************************************************/
 
 MMBroadbandModemMbimQuectel *
 mm_broadband_modem_mbim_quectel_new (const gchar  *device,
@@ -110,8 +65,8 @@
 static void
 iface_modem_firmware_init (MMIfaceModemFirmware *iface)
 {
-    iface->load_update_settings        = firmware_load_update_settings;
-    iface->load_update_settings_finish = firmware_load_update_settings_finish;
+    iface->load_update_settings        = mm_shared_quectel_firmware_load_update_settings;
+    iface->load_update_settings_finish = mm_shared_quectel_firmware_load_update_settings_finish;
 }
 
 static void
diff --git a/plugins/quectel/mm-broadband-modem-qmi-quectel.c b/plugins/quectel/mm-broadband-modem-qmi-quectel.c
index 8cb290c..676006f 100644
--- a/plugins/quectel/mm-broadband-modem-qmi-quectel.c
+++ b/plugins/quectel/mm-broadband-modem-qmi-quectel.c
@@ -52,6 +52,8 @@
                          MM_BASE_MODEM_PLUGIN, plugin,
                          MM_BASE_MODEM_VENDOR_ID, vendor_id,
                          MM_BASE_MODEM_PRODUCT_ID, product_id,
+                         /* exclude carrier information */
+                         MM_IFACE_MODEM_FIRMWARE_IGNORE_CARRIER, TRUE,
                          /* QMI bearer supports NET only */
                          MM_BASE_MODEM_DATA_NET_SUPPORTED, TRUE,
                          MM_BASE_MODEM_DATA_TTY_SUPPORTED, FALSE,
diff --git a/plugins/quectel/mm-plugin-quectel.c b/plugins/quectel/mm-plugin-quectel.c
index 4550615..9a7a1c6 100644
--- a/plugins/quectel/mm-plugin-quectel.c
+++ b/plugins/quectel/mm-plugin-quectel.c
@@ -61,21 +61,12 @@
 
 #if defined WITH_MBIM
     if (mm_port_probe_list_has_mbim_port (probes)) {
-        if (vendor == 0x1eac) {
-            mm_obj_dbg (self, "MBIM-powered PCI Quectel modem found...");
-            return MM_BASE_MODEM (mm_broadband_modem_mbim_quectel_new (uid,
-                                                                       drivers,
-                                                                       mm_plugin_get_name (self),
-                                                                       vendor,
-                                                                       product));
-        } else {
-            mm_obj_dbg (self, "MBIM-powered Quectel modem found...");
-            return MM_BASE_MODEM (mm_broadband_modem_mbim_new (uid,
-                                                               drivers,
-                                                               mm_plugin_get_name (self),
-                                                               vendor,
-                                                               product));
-        }
+        mm_obj_dbg (self, "MBIM-powered Quectel modem found...");
+        return MM_BASE_MODEM (mm_broadband_modem_mbim_quectel_new (uid,
+                                                                   drivers,
+                                                                   mm_plugin_get_name (self),
+                                                                   vendor,
+                                                                   product));
     }
 #endif
 
diff --git a/plugins/quectel/mm-shared-quectel.c b/plugins/quectel/mm-shared-quectel.c
index f8ccbbe..547775b 100644
--- a/plugins/quectel/mm-shared-quectel.c
+++ b/plugins/quectel/mm-shared-quectel.c
@@ -30,6 +30,10 @@
 #include "mm-shared-quectel.h"
 #include "mm-modem-helpers-quectel.h"
 
+#if defined WITH_MBIM
+#include "mm-broadband-modem-mbim.h"
+#endif
+
 /*****************************************************************************/
 /* Private context */
 
@@ -143,6 +147,78 @@
     return g_task_propagate_pointer (G_TASK (res), error);
 }
 
+static gboolean
+quectel_is_sahara_supported (MMBaseModem *modem,
+                             MMPort      *port)
+{
+    return mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_QUECTEL_SAHARA");
+}
+
+static gboolean
+quectel_is_firehose_supported (MMBaseModem *modem,
+                               MMPort      *port)
+{
+    return mm_kernel_device_get_global_property_as_boolean (mm_port_peek_kernel_device (port), "ID_MM_QUECTEL_FIREHOSE");
+}
+
+static MMModemFirmwareUpdateMethod
+quectel_get_firmware_update_methods (MMBaseModem *modem,
+                                     MMPort      *port)
+{
+    MMModemFirmwareUpdateMethod update_methods;
+
+    update_methods = MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE;
+
+    if (quectel_is_firehose_supported (modem, port))
+        update_methods |= MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE;
+    if (quectel_is_sahara_supported (modem, port))
+        update_methods |= MM_MODEM_FIRMWARE_UPDATE_METHOD_SAHARA;
+
+    return update_methods;
+}
+
+static void
+quectel_at_port_get_firmware_version_ready (MMBaseModem  *modem,
+                                            GAsyncResult *res,
+                                            GTask        *task)
+{
+    MMFirmwareUpdateSettings *update_settings;
+    const gchar              *version;
+
+    update_settings = g_task_get_task_data (task);
+
+    version = mm_base_modem_at_command_finish (modem, res, NULL);
+    if (version)
+        mm_firmware_update_settings_set_version (update_settings, version);
+
+    g_task_return_pointer (task, g_object_ref (update_settings), g_object_unref);
+    g_object_unref (task);
+}
+
+#if defined WITH_MBIM
+static void
+quectel_mbim_port_get_firmware_version_ready (MbimDevice   *device,
+                                              GAsyncResult *res,
+                                              GTask        *task)
+{
+    g_autoptr(MbimMessage)    response = NULL;
+    guint32                   version_id;
+    g_autofree gchar         *version_str = NULL;
+    MMFirmwareUpdateSettings *update_settings;
+
+    update_settings = g_task_get_task_data (task);
+
+    response = mbim_device_command_finish (device, res, NULL);
+    if (response && mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, NULL) &&
+        mbim_message_qdu_quectel_read_version_response_parse (response, &version_id, &version_str, NULL)) {
+        mm_firmware_update_settings_set_version (update_settings, version_str);
+    }
+
+    g_task_return_pointer (task, g_object_ref (update_settings), g_object_unref);
+    g_object_unref (task);
+}
+#endif
+
 static void
 qfastboot_test_ready (MMBaseModem  *self,
                       GAsyncResult *res,
@@ -150,15 +226,78 @@
 {
     MMFirmwareUpdateSettings *update_settings;
 
-    if (!mm_base_modem_at_command_finish (self, res, NULL))
-        update_settings = mm_firmware_update_settings_new (MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE);
-    else {
-        update_settings = mm_firmware_update_settings_new (MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT);
+    update_settings = g_task_get_task_data (task);
+
+    /* Set update method */
+    if (mm_base_modem_at_command_finish (self, res, NULL)) {
+        mm_firmware_update_settings_set_method (update_settings, MM_MODEM_FIRMWARE_UPDATE_METHOD_FASTBOOT);
         mm_firmware_update_settings_set_fastboot_at (update_settings, "AT+QFASTBOOT");
+    } else
+        mm_firmware_update_settings_set_method (update_settings, MM_MODEM_FIRMWARE_UPDATE_METHOD_NONE);
+
+    /* Fetch full firmware info */
+    mm_base_modem_at_command (MM_BASE_MODEM (self),
+                              "+QGMR?",
+                              3,
+                              FALSE,
+                              (GAsyncReadyCallback) quectel_at_port_get_firmware_version_ready,
+                              task);
+}
+
+static void
+quectel_at_port_get_firmware_revision_ready (MMBaseModem  *self,
+                                             GAsyncResult *res,
+                                             GTask        *task)
+{
+    MMFirmwareUpdateSettings    *update_settings;
+    MMModemFirmwareUpdateMethod  update_methods;
+    const gchar                 *revision;
+    const gchar                 *name;
+    const gchar                 *id;
+    g_autoptr(GPtrArray)         ids = NULL;
+    GError                      *error = NULL;
+
+    update_settings = g_task_get_task_data (task);
+    update_methods = mm_firmware_update_settings_get_method (update_settings);
+
+    /* Set device ids */
+    ids = mm_iface_firmware_build_generic_device_ids (MM_IFACE_MODEM_FIRMWARE (self), &error);
+    if (error) {
+        mm_obj_warn (self, "failed to build generic device ids: %s", error->message);
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
     }
 
-    g_task_return_pointer (task, update_settings, g_object_unref);
-    g_object_unref (task);
+    /* Add device id based on modem name */
+    revision = mm_base_modem_at_command_finish (self, res, NULL);
+    if (revision && g_utf8_validate (revision, -1, NULL)) {
+        name = g_strndup (revision, 7);
+        mm_obj_dbg (self, "revision %s converted to modem name %s", revision, name);
+        id = (const gchar *) g_ptr_array_index (ids, 0);
+        g_ptr_array_insert (ids, 0, g_strdup_printf ("%s&NAME_%s", id, name));
+    }
+
+    mm_firmware_update_settings_set_device_ids (update_settings, (const gchar **)ids->pdata);
+
+    /* Set update methods */
+    if (update_methods & MM_MODEM_FIRMWARE_UPDATE_METHOD_FIREHOSE) {
+        /* Fetch full firmware info */
+        mm_base_modem_at_command (self,
+                                  "+QGMR?",
+                                  3,
+                                  TRUE,
+                                  (GAsyncReadyCallback) quectel_at_port_get_firmware_version_ready,
+                                  task);
+    } else {
+        /* Check fastboot support */
+        mm_base_modem_at_command (self,
+                                  "AT+QFASTBOOT=?",
+                                  3,
+                                  TRUE,
+                                  (GAsyncReadyCallback) qfastboot_test_ready,
+                                  task);
+    }
 }
 
 void
@@ -167,14 +306,58 @@
                                                  gpointer              user_data)
 {
     GTask *task;
+    MMPortSerialAt *at_port;
+    MMModemFirmwareUpdateMethod update_methods;
+    MMFirmwareUpdateSettings *update_settings;
+#if defined WITH_MBIM
+    MMPortMbim *mbim;
+#endif
 
     task = g_task_new (self, NULL, callback, user_data);
-    mm_base_modem_at_command (MM_BASE_MODEM (self),
-                              "AT+QFASTBOOT=?",
-                              3,
-                              TRUE,
-                              (GAsyncReadyCallback)qfastboot_test_ready,
-                              task);
+
+    at_port = mm_base_modem_peek_best_at_port (MM_BASE_MODEM (self), NULL);
+    if (at_port) {
+    	update_methods = quectel_get_firmware_update_methods (MM_BASE_MODEM (self), MM_PORT (at_port));
+        update_settings = mm_firmware_update_settings_new (update_methods);
+        g_task_set_task_data (task, update_settings, g_object_unref);
+
+        /* Fetch modem name */
+        mm_base_modem_at_command (MM_BASE_MODEM (self),
+                                  "+CGMR",
+                                  3,
+                                  TRUE,
+                                  (GAsyncReadyCallback) quectel_at_port_get_firmware_revision_ready,
+                                  task);
+
+        return;
+    }
+
+#if defined WITH_MBIM
+    mbim = mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self));
+    if (mbim) {
+        g_autoptr(MbimMessage) message = NULL;
+
+        update_methods = quectel_get_firmware_update_methods (MM_BASE_MODEM (self), MM_PORT (mbim));
+        update_settings = mm_firmware_update_settings_new (update_methods);
+
+        /* Fetch firmware info */
+        g_task_set_task_data (task, update_settings, g_object_unref);
+        message = mbim_message_qdu_quectel_read_version_set_new (MBIM_QDU_QUECTEL_VERSION_TYPE_FW_BUILD_ID, NULL);
+        mbim_device_command (mm_port_mbim_peek_device (mbim),
+                             message,
+                             5,
+                             NULL,
+                             (GAsyncReadyCallback) quectel_mbim_port_get_firmware_version_ready,
+                             task);
+        return;
+    }
+#endif
+
+    g_task_return_new_error (task,
+                             MM_CORE_ERROR,
+                             MM_CORE_ERROR_FAILED,
+                             "Couldn't find a port to fetch firmware info");
+    g_object_unref (task);
 }
 
 /*****************************************************************************/
diff --git a/plugins/telit/77-mm-telit-port-types.rules b/plugins/telit/77-mm-telit-port-types.rules
index 212cce4..9ecc3f3 100644
--- a/plugins/telit/77-mm-telit-port-types.rules
+++ b/plugins/telit/77-mm-telit-port-types.rules
@@ -34,6 +34,18 @@
 # CE910-DUAL
 ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1011", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
 
+# LE910C1-EUX
+# The following port is ignored since it's a diagnostic port
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1031", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
+# LE910C1-EUX (ECM composition)
+# The following port is ignored since it's a diagnostic port
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="01", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_PRIMARY}="1"
+ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1033", ENV{.MM_USBIFNUM}=="02", SUBSYSTEM=="tty", ENV{ID_MM_PORT_TYPE_AT_SECONDARY}="1"
+
 # LE922, LM9x0
 ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="00", ENV{ID_MM_PORT_IGNORE}="1"
 ATTRS{idVendor}=="1bc7", ATTRS{idProduct}=="1040", ENV{.MM_USBIFNUM}=="03", ENV{ID_MM_PORT_IGNORE}="1"
diff --git a/src/mm-broadband-modem-mbim.c b/src/mm-broadband-modem-mbim.c
index 134f197..3a09257 100644
--- a/src/mm-broadband-modem-mbim.c
+++ b/src/mm-broadband-modem-mbim.c
@@ -121,6 +121,7 @@
     gboolean is_pco_supported;
     gboolean is_lte_attach_info_supported;
     gboolean is_nr5g_registration_settings_supported;
+    gboolean is_base_stations_info_supported;
     gboolean is_ussd_supported;
     gboolean is_atds_location_supported;
     gboolean is_atds_signal_supported;
@@ -168,6 +169,8 @@
     /* Multi-SIM support */
     guint32 executor_index;
     guint active_slot_index;
+
+    MMUnlockRetries *unlock_retries;
 };
 
 /*****************************************************************************/
@@ -1653,51 +1656,14 @@
             NULL,
             &remaining_attempts,
             &error)) {
-        MMIfaceModem *self;
+        MMBroadbandModemMbim *self;
         MMModemLock lock;
-        MMUnlockRetries *retries;
 
-        self = g_task_get_source_object (task);
+        self = MM_BROADBAND_MODEM_MBIM (g_task_get_source_object (task));
         lock = mm_modem_lock_from_mbim_pin_type (pin_type);
-        retries = mm_unlock_retries_new ();
 
-        /* If PIN1 is disabled and we have tried to enable it with a wrong PIN,
-         * the modem would have indicated the number of remaining attempts for
-         * PIN1 (unless PUK1 is engaged) in the response to the failed
-         * MBIM_CID_PIN set operation. Thus, MMSimMbim would have updated
-         * MMIfaceModem's MMUnlockRetries with information about PIN1.
-         *
-         * However, a MBIM_CID_PIN query may be issued (e.g. MMBaseSim calls
-         * mm_iface_modem_update_lock_info()) after the MBIM_CID_PIN set
-         * operation to query the number of remaining attempts for a PIN type.
-         * Unfortunately, we can't specify a particular PIN type in a
-         * MBIM_CID_PIN query. The modem may not reply with information about
-         * PIN1 if PIN1 is disabled. When that happens, we would like to
-         * preserve our knowledge about the number of remaining attempts for
-         * PIN1. Here we thus carry over any existing information on PIN1 from
-         * MMIfaceModem's MMUnlockRetries if the MBIM_CID_PIN query reports
-         * something other than PIN1. */
-        if (lock != MM_MODEM_LOCK_SIM_PIN) {
-            MMUnlockRetries *previous_retries;
-            guint previous_sim_pin_retries;
-
-            previous_retries = mm_iface_modem_get_unlock_retries (self);
-            previous_sim_pin_retries = mm_unlock_retries_get (previous_retries,
-                                                              MM_MODEM_LOCK_SIM_PIN);
-            if (previous_sim_pin_retries != MM_UNLOCK_RETRIES_UNKNOWN) {
-                mm_unlock_retries_set (retries,
-                                       MM_MODEM_LOCK_SIM_PIN,
-                                       previous_sim_pin_retries);
-            }
-            g_object_unref (previous_retries);
-        }
-
-        /* According to the MBIM specification, RemainingAttempts is set to
-         * 0xffffffff if the device does not support this information. */
-        if (remaining_attempts != G_MAXUINT32)
-            mm_unlock_retries_set (retries, lock, remaining_attempts);
-
-        g_task_return_pointer (task, retries, g_object_unref);
+        mm_broadband_modem_mbim_set_unlock_retries (self, lock, remaining_attempts);
+        g_task_return_pointer (task, g_object_ref (self->priv->unlock_retries), g_object_unref);
     } else
         g_task_return_error (task, error);
 
@@ -1731,6 +1697,24 @@
     mbim_message_unref (message);
 }
 
+void
+mm_broadband_modem_mbim_set_unlock_retries (MMBroadbandModemMbim *self,
+                                            MMModemLock           lock_type,
+                                            guint32               remaining_attempts)
+{
+    g_assert (MM_IS_BROADBAND_MODEM_MBIM (self));
+
+    if (!self->priv->unlock_retries)
+        self->priv->unlock_retries = mm_unlock_retries_new ();
+
+    /* According to the MBIM specification, RemainingAttempts is set to
+     * 0xffffffff if the device does not support this information. */
+    if (remaining_attempts != G_MAXUINT32)
+        mm_unlock_retries_set (self->priv->unlock_retries,
+                               lock_type,
+                               remaining_attempts);
+}
+
 /*****************************************************************************/
 /* Own numbers loading */
 
@@ -2234,6 +2218,400 @@
 }
 
 /*****************************************************************************/
+/* Cell info retrieval */
+
+static GList *
+modem_get_cell_info_finish (MMIfaceModem  *self,
+                            GAsyncResult  *res,
+                            GError       **error)
+{
+    return g_task_propagate_pointer (G_TASK (res), error);
+}
+
+static void
+cell_info_list_free (GList *list)
+{
+    g_list_free_full (list, (GDestroyNotify)g_object_unref);
+}
+
+static void
+base_stations_info_query_ready (MbimDevice   *device,
+                                GAsyncResult *res,
+                                GTask        *task)
+{
+    MMBroadbandModemMbim                          *self;
+    g_autoptr(MbimMessage)                         response = NULL;
+    GError                                        *error = NULL;
+    GList                                         *list = NULL;
+    MMCellInfo                                    *info = NULL;
+    g_autoptr(MbimCellInfoServingGsm)              gsm_serving_cell = NULL;
+    g_autoptr(MbimCellInfoServingUmts)             umts_serving_cell = NULL;
+    g_autoptr(MbimCellInfoServingTdscdma)          tdscdma_serving_cell = NULL;
+    g_autoptr(MbimCellInfoServingLte)              lte_serving_cell = NULL;
+    guint32                                        gsm_neighboring_cells_count = 0;
+    g_autoptr(MbimCellInfoNeighboringGsmArray)     gsm_neighboring_cells = NULL;
+    guint32                                        umts_neighboring_cells_count = 0;
+    g_autoptr(MbimCellInfoNeighboringUmtsArray)    umts_neighboring_cells = NULL;
+    guint32                                        tdscdma_neighboring_cells_count = 0;
+    g_autoptr(MbimCellInfoNeighboringTdscdmaArray) tdscdma_neighboring_cells = NULL;
+    guint32                                        lte_neighboring_cells_count = 0;
+    g_autoptr(MbimCellInfoNeighboringLteArray)     lte_neighboring_cells = NULL;
+    guint32                                        cdma_cells_count = 0;
+    g_autoptr(MbimCellInfoCdmaArray)               cdma_cells = NULL;
+    guint32                                        nr_serving_cells_count = 0;
+    g_autoptr(MbimCellInfoServingNrArray)          nr_serving_cells = NULL;
+    guint32                                        nr_neighboring_cells_count = 0;
+    g_autoptr(MbimCellInfoNeighboringNrArray)      nr_neighboring_cells = NULL;
+
+    self = g_task_get_source_object (task);
+
+    response = mbim_device_command_finish (device, res, &error);
+    if (!response || !mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error)) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    /* MBIMEx 3.0 support */
+    if (mbim_device_check_ms_mbimex_version (device, 3, 0)) {
+        if (!mbim_message_ms_basic_connect_extensions_v3_base_stations_info_response_parse (
+                response,
+                NULL, /* system_type_v3 */
+                NULL, /* system_subtype */
+                &gsm_serving_cell,
+                &umts_serving_cell,
+                &tdscdma_serving_cell,
+                &lte_serving_cell,
+                &gsm_neighboring_cells_count,
+                &gsm_neighboring_cells,
+                &umts_neighboring_cells_count,
+                &umts_neighboring_cells,
+                &tdscdma_neighboring_cells_count,
+                &tdscdma_neighboring_cells,
+                &lte_neighboring_cells_count,
+                &lte_neighboring_cells,
+                &cdma_cells_count,
+                &cdma_cells,
+                &nr_serving_cells_count,
+                &nr_serving_cells,
+                &nr_neighboring_cells_count,
+                &nr_neighboring_cells,
+                &error))
+            g_prefix_error (&error, "Failed processing MBIMEx v3.0 base stations info response: ");
+        else
+            mm_obj_dbg (self, "processed MBIMEx v3.0 base stations info response");
+    }
+    /* MBIMEx 1.0 support */
+    else {
+        if (!mbim_message_ms_basic_connect_extensions_base_stations_info_response_parse (
+                response,
+                NULL, /* system_type */
+                &gsm_serving_cell,
+                &umts_serving_cell,
+                &tdscdma_serving_cell,
+                &lte_serving_cell,
+                &gsm_neighboring_cells_count,
+                &gsm_neighboring_cells,
+                &umts_neighboring_cells_count,
+                &umts_neighboring_cells,
+                &tdscdma_neighboring_cells_count,
+                &tdscdma_neighboring_cells,
+                &lte_neighboring_cells_count,
+                &lte_neighboring_cells,
+                &cdma_cells_count,
+                &cdma_cells,
+                &error))
+            g_prefix_error (&error, "Failed processing MBIMEx v1.0 base stations info response: ");
+        else
+            mm_obj_dbg (self, "processed MBIMEx v1.0 base stations info response");
+    }
+
+    if (error) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+#define CELL_INFO_SET_STR(VALUE, SETTER, CELL_TYPE) do {    \
+        if (VALUE) {                                        \
+            mm_cell_info_##SETTER (CELL_TYPE (info), VALUE); \
+        }                                                   \
+    } while (0)
+
+#define CELL_INFO_SET_HEXSTR(VALUE, UNKNOWN, MODIFIER, SETTER, CELL_TYPE) do { \
+        if (VALUE != UNKNOWN) {                                                \
+            g_autofree gchar *str = NULL;                                      \
+                                                                               \
+            str = g_strdup_printf ("%" MODIFIER "X", VALUE);                   \
+            mm_cell_info_##SETTER (CELL_TYPE (info), str);                     \
+        }                                                                      \
+    } while (0)
+
+#define CELL_INFO_SET_UINT(VALUE, UNKNOWN, SETTER, CELL_TYPE) do { \
+        if (VALUE != UNKNOWN) {                                    \
+            mm_cell_info_##SETTER (CELL_TYPE (info), VALUE);       \
+        }                                                          \
+    } while (0)
+
+#define CELL_INFO_SET_INT_DOUBLE(VALUE, UNKNOWN, SETTER, CELL_TYPE) do { \
+        if (VALUE != (gint)UNKNOWN) {                                    \
+            mm_cell_info_##SETTER (CELL_TYPE (info), (gdouble)VALUE);    \
+        }                                                                \
+    } while (0)
+
+#define CELL_INFO_SET_UINT_DOUBLE_SCALED(VALUE, UNKNOWN, SCALE, SETTER, CELL_TYPE) do { \
+        if (VALUE != UNKNOWN) {                                                  \
+            mm_cell_info_##SETTER (CELL_TYPE (info), (gdouble)(VALUE + SCALE));        \
+        }                                                                              \
+    } while (0)
+
+    if (gsm_serving_cell) {
+        info = mm_cell_info_gsm_new_from_dictionary (NULL);
+        mm_cell_info_set_serving (info, TRUE);
+
+        CELL_INFO_SET_STR    (gsm_serving_cell->provider_id,                        gsm_set_operator_id,     MM_CELL_INFO_GSM);
+        CELL_INFO_SET_HEXSTR (gsm_serving_cell->location_area_code, 0xFFFFFFFF, "", gsm_set_lac,             MM_CELL_INFO_GSM);
+        CELL_INFO_SET_HEXSTR (gsm_serving_cell->cell_id,            0xFFFFFFFF, "", gsm_set_ci,              MM_CELL_INFO_GSM);
+        CELL_INFO_SET_UINT   (gsm_serving_cell->timing_advance,     0xFFFFFFFF,     gsm_set_timing_advance,  MM_CELL_INFO_GSM);
+        CELL_INFO_SET_UINT   (gsm_serving_cell->arfcn,              0xFFFFFFFF,     gsm_set_arfcn,           MM_CELL_INFO_GSM);
+        CELL_INFO_SET_HEXSTR (gsm_serving_cell->base_station_id,    0xFFFFFFFF, "", gsm_set_base_station_id, MM_CELL_INFO_GSM);
+        CELL_INFO_SET_UINT   (gsm_serving_cell->rx_level,           0xFFFFFFFF,     gsm_set_rx_level,        MM_CELL_INFO_GSM);
+
+        list = g_list_append (list, g_steal_pointer (&info));
+    }
+
+    if (gsm_neighboring_cells_count && gsm_neighboring_cells) {
+        guint i;
+
+        for (i = 0; i < gsm_neighboring_cells_count; i++) {
+            info = mm_cell_info_gsm_new_from_dictionary (NULL);
+            mm_cell_info_set_serving (info, FALSE);
+
+            CELL_INFO_SET_STR    (gsm_neighboring_cells[i]->provider_id,                        gsm_set_operator_id,     MM_CELL_INFO_GSM);
+            CELL_INFO_SET_HEXSTR (gsm_neighboring_cells[i]->location_area_code, 0xFFFFFFFF, "", gsm_set_lac,             MM_CELL_INFO_GSM);
+            CELL_INFO_SET_HEXSTR (gsm_neighboring_cells[i]->cell_id,            0xFFFFFFFF, "", gsm_set_ci,              MM_CELL_INFO_GSM);
+            CELL_INFO_SET_UINT   (gsm_neighboring_cells[i]->arfcn,              0xFFFFFFFF,     gsm_set_arfcn,           MM_CELL_INFO_GSM);
+            CELL_INFO_SET_HEXSTR (gsm_neighboring_cells[i]->base_station_id,    0xFFFFFFFF, "", gsm_set_base_station_id, MM_CELL_INFO_GSM);
+            CELL_INFO_SET_UINT   (gsm_neighboring_cells[i]->rx_level,           0xFFFFFFFF,     gsm_set_rx_level,        MM_CELL_INFO_GSM);
+
+            list = g_list_append (list, g_steal_pointer (&info));
+        }
+    }
+
+    if (umts_serving_cell) {
+        info = mm_cell_info_umts_new_from_dictionary (NULL);
+        mm_cell_info_set_serving (info, TRUE);
+
+        CELL_INFO_SET_STR        (umts_serving_cell->provider_id,                             umts_set_operator_id,      MM_CELL_INFO_UMTS);
+        CELL_INFO_SET_HEXSTR     (umts_serving_cell->location_area_code,      0xFFFFFFFF, "", umts_set_lac,              MM_CELL_INFO_UMTS);
+        CELL_INFO_SET_HEXSTR     (umts_serving_cell->cell_id,                 0xFFFFFFFF, "", umts_set_ci,               MM_CELL_INFO_UMTS);
+        CELL_INFO_SET_UINT       (umts_serving_cell->frequency_info_ul,       0xFFFFFFFF,     umts_set_frequency_fdd_ul, MM_CELL_INFO_UMTS);
+        CELL_INFO_SET_UINT       (umts_serving_cell->frequency_info_dl,       0xFFFFFFFF,     umts_set_frequency_fdd_dl, MM_CELL_INFO_UMTS);
+        CELL_INFO_SET_UINT       (umts_serving_cell->frequency_info_nt,       0xFFFFFFFF,     umts_set_frequency_tdd,    MM_CELL_INFO_UMTS);
+        CELL_INFO_SET_UINT       (umts_serving_cell->uarfcn,                  0xFFFFFFFF,     umts_set_uarfcn,           MM_CELL_INFO_UMTS);
+        CELL_INFO_SET_UINT       (umts_serving_cell->primary_scrambling_code, 0xFFFFFFFF,     umts_set_psc,              MM_CELL_INFO_UMTS);
+        CELL_INFO_SET_INT_DOUBLE (umts_serving_cell->rscp,                    0,              umts_set_rscp,             MM_CELL_INFO_UMTS);
+        CELL_INFO_SET_INT_DOUBLE (umts_serving_cell->ecno,                    1,              umts_set_ecio,             MM_CELL_INFO_UMTS);
+        CELL_INFO_SET_UINT       (umts_serving_cell->path_loss,               0xFFFFFFFF,     umts_set_path_loss,        MM_CELL_INFO_UMTS);
+
+        list = g_list_append (list, g_steal_pointer (&info));
+    }
+
+    if (umts_neighboring_cells_count && umts_neighboring_cells) {
+        guint i;
+
+        for (i = 0; i < umts_neighboring_cells_count; i++) {
+            info = mm_cell_info_umts_new_from_dictionary (NULL);
+            mm_cell_info_set_serving (info, FALSE);
+
+            CELL_INFO_SET_STR        (umts_neighboring_cells[i]->provider_id,                             umts_set_operator_id, MM_CELL_INFO_UMTS);
+            CELL_INFO_SET_HEXSTR     (umts_neighboring_cells[i]->location_area_code,      0xFFFFFFFF, "", umts_set_lac,         MM_CELL_INFO_UMTS);
+            CELL_INFO_SET_HEXSTR     (umts_neighboring_cells[i]->cell_id,                 0xFFFFFFFF, "", umts_set_ci,          MM_CELL_INFO_UMTS);
+            CELL_INFO_SET_UINT       (umts_neighboring_cells[i]->uarfcn,                  0xFFFFFFFF,     umts_set_uarfcn,      MM_CELL_INFO_UMTS);
+            CELL_INFO_SET_UINT       (umts_neighboring_cells[i]->primary_scrambling_code, 0xFFFFFFFF,     umts_set_psc,         MM_CELL_INFO_UMTS);
+            CELL_INFO_SET_INT_DOUBLE (umts_neighboring_cells[i]->rscp,                    0,              umts_set_rscp,        MM_CELL_INFO_UMTS);
+            CELL_INFO_SET_INT_DOUBLE (umts_neighboring_cells[i]->ecno,                    1,              umts_set_ecio,        MM_CELL_INFO_UMTS);
+            CELL_INFO_SET_UINT       (umts_neighboring_cells[i]->path_loss,               0xFFFFFFFF,     umts_set_path_loss,   MM_CELL_INFO_UMTS);
+
+            list = g_list_append (list, g_steal_pointer (&info));
+        }
+    }
+
+    if (tdscdma_serving_cell) {
+        info = mm_cell_info_tdscdma_new_from_dictionary (NULL);
+        mm_cell_info_set_serving (info, TRUE);
+
+        CELL_INFO_SET_STR        (tdscdma_serving_cell->provider_id,                        tdscdma_set_operator_id,       MM_CELL_INFO_TDSCDMA);
+        CELL_INFO_SET_HEXSTR     (tdscdma_serving_cell->location_area_code, 0xFFFFFFFF, "", tdscdma_set_lac,               MM_CELL_INFO_TDSCDMA);
+        CELL_INFO_SET_HEXSTR     (tdscdma_serving_cell->cell_id,            0xFFFFFFFF, "", tdscdma_set_ci,                MM_CELL_INFO_TDSCDMA);
+        CELL_INFO_SET_UINT       (tdscdma_serving_cell->uarfcn,             0xFFFFFFFF,     tdscdma_set_uarfcn,            MM_CELL_INFO_TDSCDMA);
+        CELL_INFO_SET_UINT       (tdscdma_serving_cell->cell_parameter_id,  0xFFFFFFFF,     tdscdma_set_cell_parameter_id, MM_CELL_INFO_TDSCDMA);
+        CELL_INFO_SET_UINT       (tdscdma_serving_cell->timing_advance,     0xFFFFFFFF,     tdscdma_set_timing_advance,    MM_CELL_INFO_TDSCDMA);
+        CELL_INFO_SET_INT_DOUBLE (tdscdma_serving_cell->rscp,               0xFFFFFFFF,     tdscdma_set_rscp,              MM_CELL_INFO_TDSCDMA);
+        CELL_INFO_SET_UINT       (tdscdma_serving_cell->path_loss,          0xFFFFFFFF,     tdscdma_set_path_loss,         MM_CELL_INFO_TDSCDMA);
+
+        list = g_list_append (list, g_steal_pointer (&info));
+    }
+
+    if (tdscdma_neighboring_cells_count && tdscdma_neighboring_cells) {
+        guint i;
+
+        for (i = 0; i < tdscdma_neighboring_cells_count; i++) {
+            info = mm_cell_info_tdscdma_new_from_dictionary (NULL);
+            mm_cell_info_set_serving (info, FALSE);
+
+            CELL_INFO_SET_STR        (tdscdma_neighboring_cells[i]->provider_id,                        tdscdma_set_operator_id,       MM_CELL_INFO_TDSCDMA);
+            CELL_INFO_SET_HEXSTR     (tdscdma_neighboring_cells[i]->location_area_code, 0xFFFFFFFF, "", tdscdma_set_lac,               MM_CELL_INFO_TDSCDMA);
+            CELL_INFO_SET_HEXSTR     (tdscdma_neighboring_cells[i]->cell_id,            0xFFFFFFFF, "", tdscdma_set_ci,                MM_CELL_INFO_TDSCDMA);
+            CELL_INFO_SET_UINT       (tdscdma_neighboring_cells[i]->uarfcn,             0xFFFFFFFF,     tdscdma_set_uarfcn,            MM_CELL_INFO_TDSCDMA);
+            CELL_INFO_SET_UINT       (tdscdma_neighboring_cells[i]->cell_parameter_id,  0xFFFFFFFF,     tdscdma_set_cell_parameter_id, MM_CELL_INFO_TDSCDMA);
+            CELL_INFO_SET_UINT       (tdscdma_neighboring_cells[i]->timing_advance,     0xFFFFFFFF,     tdscdma_set_timing_advance,    MM_CELL_INFO_TDSCDMA);
+            CELL_INFO_SET_INT_DOUBLE (tdscdma_neighboring_cells[i]->rscp,               0xFFFFFFFF,     tdscdma_set_rscp,              MM_CELL_INFO_TDSCDMA);
+            CELL_INFO_SET_UINT       (tdscdma_neighboring_cells[i]->path_loss,          0xFFFFFFFF,     tdscdma_set_path_loss,         MM_CELL_INFO_TDSCDMA);
+
+            list = g_list_append (list, g_steal_pointer (&info));
+        }
+    }
+
+    if (lte_serving_cell) {
+        info = mm_cell_info_lte_new_from_dictionary (NULL);
+        mm_cell_info_set_serving (info, TRUE);
+
+        CELL_INFO_SET_STR        (lte_serving_cell->provider_id,                      lte_set_operator_id,    MM_CELL_INFO_LTE);
+        CELL_INFO_SET_HEXSTR     (lte_serving_cell->tac,              0xFFFFFFFF, "", lte_set_tac,            MM_CELL_INFO_LTE);
+        CELL_INFO_SET_HEXSTR     (lte_serving_cell->cell_id,          0xFFFFFFFF, "", lte_set_ci,             MM_CELL_INFO_LTE);
+        CELL_INFO_SET_HEXSTR     (lte_serving_cell->physical_cell_id, 0xFFFFFFFF, "", lte_set_physical_ci,    MM_CELL_INFO_LTE);
+        CELL_INFO_SET_UINT       (lte_serving_cell->earfcn,           0xFFFFFFFF,     lte_set_earfcn,         MM_CELL_INFO_LTE);
+        CELL_INFO_SET_INT_DOUBLE (lte_serving_cell->rsrp,             0xFFFFFFFF,     lte_set_rsrp,           MM_CELL_INFO_LTE);
+        CELL_INFO_SET_INT_DOUBLE (lte_serving_cell->rsrq,             0xFFFFFFFF,     lte_set_rsrq,           MM_CELL_INFO_LTE);
+        CELL_INFO_SET_UINT       (lte_serving_cell->timing_advance,   0xFFFFFFFF,     lte_set_timing_advance, MM_CELL_INFO_LTE);
+
+        list = g_list_append (list, g_steal_pointer (&info));
+    }
+
+    if (lte_neighboring_cells_count && lte_neighboring_cells) {
+        guint i;
+
+        for (i = 0; i < lte_neighboring_cells_count; i++) {
+            info = mm_cell_info_lte_new_from_dictionary (NULL);
+            mm_cell_info_set_serving (info, FALSE);
+
+            CELL_INFO_SET_STR        (lte_neighboring_cells[i]->provider_id,                      lte_set_operator_id, MM_CELL_INFO_LTE);
+            CELL_INFO_SET_HEXSTR     (lte_neighboring_cells[i]->tac,              0xFFFFFFFF, "", lte_set_tac,         MM_CELL_INFO_LTE);
+            CELL_INFO_SET_HEXSTR     (lte_neighboring_cells[i]->cell_id,          0xFFFFFFFF, "", lte_set_ci,          MM_CELL_INFO_LTE);
+            CELL_INFO_SET_HEXSTR     (lte_neighboring_cells[i]->physical_cell_id, 0xFFFFFFFF, "", lte_set_physical_ci, MM_CELL_INFO_LTE);
+            CELL_INFO_SET_UINT       (lte_neighboring_cells[i]->earfcn,           0xFFFFFFFF,     lte_set_earfcn,      MM_CELL_INFO_LTE);
+            CELL_INFO_SET_INT_DOUBLE (lte_neighboring_cells[i]->rsrp,             0xFFFFFFFF,     lte_set_rsrp,        MM_CELL_INFO_LTE);
+            CELL_INFO_SET_INT_DOUBLE (lte_neighboring_cells[i]->rsrq,             0xFFFFFFFF,     lte_set_rsrq,        MM_CELL_INFO_LTE);
+
+            list = g_list_append (list, g_steal_pointer (&info));
+        }
+    }
+
+    if (cdma_cells_count && cdma_cells) {
+        guint i;
+
+        for (i = 0; i < cdma_cells_count; i++) {
+            info = mm_cell_info_cdma_new_from_dictionary (NULL);
+            mm_cell_info_set_serving (info, cdma_cells[i]->serving_cell_flag);
+
+            CELL_INFO_SET_HEXSTR     (cdma_cells[i]->nid,              0xFFFFFFFF, "", cdma_set_nid,             MM_CELL_INFO_CDMA);
+            CELL_INFO_SET_HEXSTR     (cdma_cells[i]->sid,              0xFFFFFFFF, "", cdma_set_sid,             MM_CELL_INFO_CDMA);
+            CELL_INFO_SET_HEXSTR     (cdma_cells[i]->base_station_id,  0xFFFFFFFF, "", cdma_set_base_station_id, MM_CELL_INFO_CDMA);
+            CELL_INFO_SET_HEXSTR     (cdma_cells[i]->ref_pn,           0xFFFFFFFF, "", cdma_set_ref_pn,          MM_CELL_INFO_CDMA);
+            CELL_INFO_SET_UINT       (cdma_cells[i]->pilot_strength,   0xFFFFFFFF,     cdma_set_pilot_strength,  MM_CELL_INFO_CDMA);
+
+            list = g_list_append (list, g_steal_pointer (&info));
+        }
+    }
+
+    if (nr_serving_cells_count && nr_serving_cells) {
+        guint i;
+
+        for (i = 0; i < nr_serving_cells_count; i++) {
+            info = mm_cell_info_nr5g_new_from_dictionary (NULL);
+            mm_cell_info_set_serving (info, TRUE);
+
+            CELL_INFO_SET_STR                (nr_serving_cells[i]->provider_id,                                             nr5g_set_operator_id,    MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_HEXSTR             (nr_serving_cells[i]->tac,              0xFFFFFFFF,         "",                nr5g_set_tac,            MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_HEXSTR             (nr_serving_cells[i]->nci,              0xFFFFFFFFFFFFFFFF, G_GINT64_MODIFIER, nr5g_set_ci,             MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_HEXSTR             (nr_serving_cells[i]->physical_cell_id, 0xFFFFFFFF,         "",                nr5g_set_physical_ci,    MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_UINT               (nr_serving_cells[i]->nrarfcn,          0xFFFFFFFF,                            nr5g_set_nrarfcn,        MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_serving_cells[i]->rsrp,             0xFFFFFFFF,         -156,              nr5g_set_rsrp,           MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_serving_cells[i]->rsrq,             0xFFFFFFFF,         -43,               nr5g_set_rsrq,           MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_serving_cells[i]->sinr,             0xFFFFFFFF,         -23,               nr5g_set_sinr,           MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_UINT               (nr_serving_cells[i]->timing_advance,   0xFFFFFFFFFFFFFFFF,                    nr5g_set_timing_advance, MM_CELL_INFO_NR5G);
+
+            list = g_list_append (list, g_steal_pointer (&info));
+        }
+    }
+
+    if (nr_neighboring_cells_count && nr_neighboring_cells) {
+        guint i;
+
+        for (i = 0; i < nr_neighboring_cells_count; i++) {
+            info = mm_cell_info_nr5g_new_from_dictionary (NULL);
+            mm_cell_info_set_serving (info, FALSE);
+
+            CELL_INFO_SET_STR                (nr_neighboring_cells[i]->provider_id,                        nr5g_set_operator_id, MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_HEXSTR             (nr_neighboring_cells[i]->tac,              0xFFFFFFFF, "",   nr5g_set_tac,         MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_STR                (nr_neighboring_cells[i]->cell_id,                            nr5g_set_ci,          MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_HEXSTR             (nr_neighboring_cells[i]->physical_cell_id, 0xFFFFFFFF, "",   nr5g_set_physical_ci, MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_neighboring_cells[i]->rsrp,             0xFFFFFFFF, -156, nr5g_set_rsrp,        MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_neighboring_cells[i]->rsrq,             0xFFFFFFFF, -43,  nr5g_set_rsrq,        MM_CELL_INFO_NR5G);
+            CELL_INFO_SET_UINT_DOUBLE_SCALED (nr_neighboring_cells[i]->sinr,             0xFFFFFFFF, -23,  nr5g_set_sinr,        MM_CELL_INFO_NR5G);
+
+            list = g_list_append (list, g_steal_pointer (&info));
+        }
+    }
+
+#undef CELL_INFO_SET_STR
+#undef CELL_INFO_SET_HEXSTR
+#undef CELL_INFO_SET_UINT
+#undef CELL_INFO_SET_INT_DOUBLE
+#undef CELL_INFO_SET_UINT_DOUBLE_SCALED
+
+    g_task_return_pointer (task, list, (GDestroyNotify)cell_info_list_free);
+    g_object_unref (task);
+}
+
+static void
+modem_get_cell_info (MMIfaceModem        *_self,
+                     GAsyncReadyCallback  callback,
+                     gpointer             user_data)
+{
+    MMBroadbandModemMbim *self = MM_BROADBAND_MODEM_MBIM (_self);
+    GTask                *task;
+    MbimDevice           *device;
+    MbimMessage          *message;
+
+    if (!peek_device (self, &device, callback, user_data))
+        return;
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    if (!self->priv->is_base_stations_info_supported) {
+        g_task_return_new_error (task, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+                                 "base stations info is not supported");
+        g_object_unref (task);
+        return;
+    }
+
+    /* Default capacity is 15 */
+    if (mbim_device_check_ms_mbimex_version (device, 3, 0))
+        message = mbim_message_ms_basic_connect_extensions_v3_base_stations_info_query_new (15, 15, 15, 15, 15, 15, NULL);
+    else
+        message = mbim_message_ms_basic_connect_extensions_base_stations_info_query_new (15, 15, 15, 15, 15, NULL);
+
+    mbim_device_command (device,
+                         message,
+                         300,
+                         NULL,
+                         (GAsyncReadyCallback)base_stations_info_query_ready,
+                         task);
+}
+
+/*****************************************************************************/
 /* Create Bearer (Modem interface) */
 
 static MMBaseBearer *
@@ -2635,6 +3013,9 @@
                     } else if (device_services[i]->cids[j] == MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_REGISTRATION_PARAMETERS) {
                         mm_obj_dbg (self, "5GNR registration settings are supported");
                         self->priv->is_nr5g_registration_settings_supported = TRUE;
+                    } else if (device_services[i]->cids[j] == MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_BASE_STATIONS_INFO) {
+                        mm_obj_dbg (self, "Base stations info is supported");
+                        self->priv->is_base_stations_info_supported = TRUE;
                     } else if (device_services[i]->cids[j] == MBIM_CID_MS_BASIC_CONNECT_EXTENSIONS_PROVISIONED_CONTEXTS) {
                         if (mm_context_get_test_mbimex_profile_management ()) {
                             mm_obj_dbg (self, "Profile management extension is supported");
@@ -4306,10 +4687,19 @@
     if (ready_state == MBIM_SUBSCRIBER_READY_STATE_INITIALIZED)
         mm_iface_modem_update_own_numbers (MM_IFACE_MODEM (self), telephone_numbers);
 
+    if ((self->priv->last_ready_state != MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE &&
+         ready_state == MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE) ||
+        (self->priv->last_ready_state == MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE &&
+         ready_state != MBIM_SUBSCRIBER_READY_STATE_NO_ESIM_PROFILE)) {
+        /* eSIM profiles have been added or removed, re-probe to ensure correct interfaces are exposed */
+        mm_obj_dbg (self, "eSIM profile updates detected");
+        mm_broadband_modem_sim_hot_swap_detected (MM_BROADBAND_MODEM (self));
+    }
+
     if ((self->priv->last_ready_state != MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED &&
          ready_state == MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED) ||
         (self->priv->last_ready_state == MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED &&
-               ready_state != MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED)) {
+         ready_state != MBIM_SUBSCRIBER_READY_STATE_SIM_NOT_INSERTED)) {
         /* SIM has been removed or reinserted, re-probe to ensure correct interfaces are exposed */
         mm_obj_dbg (self, "SIM hot swap detected");
         mm_broadband_modem_sim_hot_swap_detected (MM_BROADBAND_MODEM (self));
@@ -8640,6 +9030,8 @@
             mm_port_mbim_close (mbim, NULL, NULL);
     }
 
+    g_clear_object (&self->priv->unlock_retries);
+
     G_OBJECT_CLASS (mm_broadband_modem_mbim_parent_class)->dispose (object);
 }
 
@@ -8721,6 +9113,8 @@
     /* Additional actions */
     iface->load_signal_quality = modem_load_signal_quality;
     iface->load_signal_quality_finish = modem_load_signal_quality_finish;
+    iface->get_cell_info = modem_get_cell_info;
+    iface->get_cell_info_finish = modem_get_cell_info_finish;
 
     /* Unneeded things */
     iface->modem_after_power_up = NULL;
diff --git a/src/mm-broadband-modem-mbim.h b/src/mm-broadband-modem-mbim.h
index 529b337..9e92390 100644
--- a/src/mm-broadband-modem-mbim.h
+++ b/src/mm-broadband-modem-mbim.h
@@ -62,4 +62,8 @@
 MMPortMbim *mm_broadband_modem_mbim_get_port_mbim_for_data  (MMBroadbandModemMbim  *self,
                                                              MMPort                *data,
                                                              GError               **error);
+
+void mm_broadband_modem_mbim_set_unlock_retries (MMBroadbandModemMbim *self,
+                                                 MMModemLock           lock_type,
+                                                 guint32               remaining_attempts);
 #endif /* MM_BROADBAND_MODEM_MBIM_H */
diff --git a/src/mm-broadband-modem-qmi.c b/src/mm-broadband-modem-qmi.c
index 25b18da..db94e68 100644
--- a/src/mm-broadband-modem-qmi.c
+++ b/src/mm-broadband-modem-qmi.c
@@ -12,7 +12,7 @@
  *
  * Copyright (C) 2012 Google Inc.
  * Copyright (C) 2014 Aleksander Morgado <aleksander@aleksander.es>
- * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
+ * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
  */
 
 #include <config.h>
@@ -170,6 +170,14 @@
 
     /* For notifying when the qmi-proxy connection is dead */
     guint qmi_device_removed_id;
+
+    /* Power Set Operating Mode Helper */
+    GTask *set_operating_mode_task;
+
+    /* PDC Refresh notifications ID (3gpp Profile Manager) */
+    gboolean profile_manager_unsolicited_events_enabled;
+    gboolean profile_manager_unsolicited_events_setup;
+    guint refresh_indication_id;
 };
 
 /*****************************************************************************/
@@ -1708,7 +1716,23 @@
 }
 
 /*****************************************************************************/
-/* Powering up the modem (Modem interface) */
+/* Powering up/down/off the modem (Modem interface) */
+
+typedef struct {
+    QmiDmsOperatingMode  mode;
+    QmiClientDms        *client;
+    guint                indication_id;
+    guint                timeout_id;
+} SetOperatingModeContext;
+
+static void
+set_operating_mode_context_free (SetOperatingModeContext *ctx)
+{
+    g_assert (ctx->indication_id == 0);
+    g_assert (ctx->timeout_id == 0);
+    g_clear_object (&ctx->client);
+    g_slice_free (SetOperatingModeContext, ctx);
+}
 
 static gboolean
 modem_power_up_down_off_finish (MMIfaceModem  *self,
@@ -1719,28 +1743,99 @@
 }
 
 static void
-dms_set_operating_mode_ready (QmiClientDms *client,
-                              GAsyncResult *res,
-                              GTask        *task)
+set_operating_mode_complete (MMBroadbandModemQmi *self,
+                             GError              *error)
 {
-    MMBroadbandModemQmi                            *self;
-    QmiDmsOperatingMode                             mode;
-    GError                                         *error = NULL;
-    g_autoptr(QmiMessageDmsSetOperatingModeOutput)  output = NULL;
+    GTask                   *task;
+    SetOperatingModeContext *ctx;
 
-    self = g_task_get_source_object (task);
-    mode = GPOINTER_TO_UINT (g_task_get_task_data (task));
+    g_assert (self->priv->set_operating_mode_task);
+    task = g_steal_pointer (&self->priv->set_operating_mode_task);
+    ctx = g_task_get_task_data (task);
+
+    if (ctx->timeout_id) {
+        g_source_remove (ctx->timeout_id);
+        ctx->timeout_id = 0;
+    }
+
+    if (ctx->indication_id) {
+        g_autoptr(QmiMessageDmsSetEventReportInput) input = NULL;
+
+        g_signal_handler_disconnect (ctx->client, ctx->indication_id);
+        ctx->indication_id = 0;
+
+        input = qmi_message_dms_set_event_report_input_new ();
+        qmi_message_dms_set_event_report_input_set_operating_mode_reporting (input, FALSE, NULL);
+        qmi_client_dms_set_event_report (ctx->client, input, 5, NULL, NULL, NULL);
+    }
+
+    if (error)
+        g_task_return_error (task, error);
+    else
+        g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+dms_set_operating_mode_timeout_cb (MMBroadbandModemQmi *self)
+{
+    GError *error = NULL;
+
+    error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Power update operation timed out");
+    set_operating_mode_complete (self, error);
+}
+
+static void
+power_event_report_indication_cb (QmiClientDms                      *client,
+                                  QmiIndicationDmsEventReportOutput *output,
+                                  MMBroadbandModemQmi               *self)
+{
+    QmiDmsOperatingMode      state;
+    GError                  *error = NULL;
+    SetOperatingModeContext *ctx;
+
+    if (!qmi_indication_dms_event_report_output_get_operating_mode (output, &state, NULL)) {
+        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_FAILED, "Invalid power indication received");
+        set_operating_mode_complete (self, error);
+        return;
+    }
+
+    g_assert (self->priv->set_operating_mode_task);
+    ctx = g_task_get_task_data (self->priv->set_operating_mode_task);
+
+    if (ctx->mode == state) {
+        mm_obj_dbg (self, "Power state successfully updated: '%s'", qmi_dms_operating_mode_get_string (state));
+        set_operating_mode_complete (self, NULL);
+        return;
+    }
+
+    error = g_error_new (MM_CORE_ERROR,
+                         MM_CORE_ERROR_FAILED,
+                         "Requested mode (%s) and mode received (%s) did not match",
+                         qmi_dms_operating_mode_get_string (ctx->mode),
+                         qmi_dms_operating_mode_get_string (state));
+    set_operating_mode_complete (self, error);
+}
+
+static void
+dms_set_operating_mode_ready (QmiClientDms        *client,
+                              GAsyncResult        *res,
+                              MMBroadbandModemQmi *self) /* full reference */
+{
+    g_autoptr (QmiMessageDmsSetOperatingModeOutput)  output = NULL;
+    GError                                          *error = NULL;
+    SetOperatingModeContext                         *ctx;
+
+    if (!self->priv->set_operating_mode_task) {
+        /* We completed the operation already via indication */
+        g_object_unref (self);
+        return;
+    }
+
+    ctx = g_task_get_task_data (self->priv->set_operating_mode_task);
 
     output = qmi_client_dms_set_operating_mode_finish (client, res, &error);
-    if (!output) {
-        g_prefix_error (&error, "QMI operation failed: ");
-        /* If unsupported, just complete without errors */
-        if (g_error_matches (error, QMI_CORE_ERROR, QMI_CORE_ERROR_UNSUPPORTED)) {
-            mm_obj_dbg (self, "device doesn't support operating mode setting: ignoring power update");
-            g_clear_error (&error);
-        }
-    } else if (!qmi_message_dms_set_operating_mode_output_get_result (output, &error)) {
-        g_prefix_error (&error, "Couldn't set operating mode: ");
+    if (!output || !qmi_message_dms_set_operating_mode_output_get_result (output, &error)) {
         /*
          * Some new devices, like the Dell DW5770, will return an internal error when
          * trying to bring the power mode to online.
@@ -1752,47 +1847,142 @@
          * retrying. Notify this to upper layers with the special MM_CORE_ERROR_RETRY
          * error.
          */
-        if ((mode == QMI_DMS_OPERATING_MODE_ONLINE) &&
+        if ((ctx->mode == QMI_DMS_OPERATING_MODE_ONLINE) &&
             ((g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INTERNAL) ||
               g_error_matches (error, QMI_PROTOCOL_ERROR, QMI_PROTOCOL_ERROR_INVALID_TRANSITION)))) {
             g_clear_error (&error);
             error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_RETRY, "Invalid transition");
-        }
+        } else
+            g_prefix_error (&error, "Couldn't set operating mode: ");
     }
 
-    if (error)
-        g_task_return_error (task, error);
+    /* If unsupported, just complete without errors */
+    if (g_error_matches (error, QMI_CORE_ERROR, QMI_CORE_ERROR_UNSUPPORTED)) {
+        mm_obj_dbg (self, "device doesn't support operating mode setting: ignoring power update");
+        g_clear_error (&error);
+        set_operating_mode_complete (self, NULL);
+    } else if (error)
+        set_operating_mode_complete (self, error);
     else
-        g_task_return_boolean (task, TRUE);
-    g_object_unref (task);
+        mm_obj_dbg (self, "operating mode request sent, waiting for power update indication");
+
+    g_object_unref (self);
 }
 
 static void
-common_power_up_down_off (MMIfaceModem        *self,
-                          QmiDmsOperatingMode  mode,
-                          GAsyncReadyCallback  callback,
-                          gpointer             user_data)
+dms_set_operating_mode (MMBroadbandModemQmi *self)
 {
-    GTask                                         *task;
-    QmiClient                                     *client = NULL;
-    g_autoptr(QmiMessageDmsSetOperatingModeInput)  input = NULL;
+    g_autoptr (QmiMessageDmsSetOperatingModeInput)  input = NULL;
+    SetOperatingModeContext                        *ctx;
 
-    if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
-                                      QMI_SERVICE_DMS, &client,
-                                      callback, user_data))
-        return;
-
-    task = g_task_new (self, NULL, callback, user_data);
-    g_task_set_task_data (task, GUINT_TO_POINTER (mode), NULL);
+    g_assert (self->priv->set_operating_mode_task);
+    ctx = g_task_get_task_data (self->priv->set_operating_mode_task);
 
     input = qmi_message_dms_set_operating_mode_input_new ();
-    qmi_message_dms_set_operating_mode_input_set_mode (input, mode, NULL);
-    qmi_client_dms_set_operating_mode (QMI_CLIENT_DMS (client),
+    qmi_message_dms_set_operating_mode_input_set_mode (input, ctx->mode, NULL);
+    qmi_client_dms_set_operating_mode (ctx->client,
                                        input,
                                        20,
                                        NULL,
                                        (GAsyncReadyCallback)dms_set_operating_mode_ready,
-                                       task);
+                                       g_object_ref (self));
+
+    mm_obj_dbg (self, "Starting timeout for indication receiving for 10 seconds");
+    ctx->timeout_id = g_timeout_add_seconds (10,
+                                             (GSourceFunc) dms_set_operating_mode_timeout_cb,
+                                             self);
+}
+
+static void
+dms_set_event_report_operating_mode_activate_ready (QmiClientDms        *client,
+                                                    GAsyncResult        *res,
+                                                    MMBroadbandModemQmi *self) /* full reference */
+{
+    g_autoptr(QmiMessageDmsSetEventReportOutput)  output = NULL;
+    GError                                       *error = NULL;
+    SetOperatingModeContext                      *ctx;
+
+    g_assert (self->priv->set_operating_mode_task);
+    ctx = g_task_get_task_data (self->priv->set_operating_mode_task);
+
+    output = qmi_client_dms_set_event_report_finish (client, res, &error);
+    if (!output || !qmi_message_dms_set_event_report_output_get_result (output, &error)) {
+        g_prefix_error (&error, "Couldn't register for power indications: ");
+        set_operating_mode_complete (self, error);
+        g_object_unref (self);
+        return;
+    }
+
+    g_assert (ctx->indication_id == 0);
+    ctx->indication_id = g_signal_connect (client,
+                                           "event-report",
+                                           G_CALLBACK (power_event_report_indication_cb),
+                                           self);
+
+    mm_obj_dbg (self, "Power operation is pending");
+    dms_set_operating_mode (self);
+    g_object_unref (self);
+}
+
+static void
+modem_power_indication_register (MMBroadbandModemQmi *self)
+{
+    g_autoptr(QmiMessageDmsSetEventReportInput)  input = NULL;
+    SetOperatingModeContext                     *ctx;
+
+    g_assert (self->priv->set_operating_mode_task);
+    ctx = g_task_get_task_data (self->priv->set_operating_mode_task);
+
+    input = qmi_message_dms_set_event_report_input_new ();
+    qmi_message_dms_set_event_report_input_set_operating_mode_reporting (input, TRUE, NULL);
+    mm_obj_dbg (self, "Power indication registration request is sent");
+    qmi_client_dms_set_event_report (
+        ctx->client,
+        input,
+        5,
+        NULL,
+        (GAsyncReadyCallback)dms_set_event_report_operating_mode_activate_ready,
+        g_object_ref (self));
+}
+
+static void
+common_power_up_down_off (MMIfaceModem        *_self,
+                          QmiDmsOperatingMode  mode,
+                          GAsyncReadyCallback  callback,
+                          gpointer             user_data)
+{
+    MMBroadbandModemQmi     *self = MM_BROADBAND_MODEM_QMI (_self);
+    GError                  *error = NULL;
+    GTask                   *task;
+    SetOperatingModeContext *ctx;
+    QmiClient               *client;
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    if (self->priv->set_operating_mode_task) {
+        error = g_error_new (MM_CORE_ERROR, MM_CORE_ERROR_IN_PROGRESS, "Another operation in progress");
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    client = mm_shared_qmi_peek_client (MM_SHARED_QMI (self),
+                                        QMI_SERVICE_DMS,
+                                        MM_PORT_QMI_FLAG_DEFAULT,
+                                        &error);
+    if (!client) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    ctx = g_slice_new0 (SetOperatingModeContext);
+    ctx->mode = mode;
+    ctx->client = QMI_CLIENT_DMS (g_object_ref (client));
+    g_task_set_task_data (task, ctx, (GDestroyNotify)set_operating_mode_context_free);
+
+    self->priv->set_operating_mode_task = task;
+    modem_power_indication_register (self);
 }
 
 static void
@@ -6416,6 +6606,200 @@
 }
 
 /*****************************************************************************/
+/* PDC Refresh events (3gppProfileManager interface) */
+
+static void
+pdc_refresh_received (QmiClientPdc                  *client,
+                      QmiIndicationPdcRefreshOutput *output,
+                      MMBroadbandModemQmi           *self)
+{
+    mm_obj_dbg (self, "profile refresh indication was received");
+    mm_iface_modem_3gpp_profile_manager_updated (MM_IFACE_MODEM_3GPP_PROFILE_MANAGER (self));
+}
+
+/*****************************************************************************/
+/* Enable/Disable unsolicited events (3gppProfileManager interface) */
+
+static gboolean
+modem_3gpp_profile_manager_enable_disable_unsolicited_events_finish (MMIfaceModem3gppProfileManager  *self,
+                                                                     GAsyncResult                    *res,
+                                                                     GError                         **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+register_pdc_refresh_ready (QmiClientPdc *client,
+                            GAsyncResult *res,
+                            GTask        *task)
+{
+    g_autoptr(QmiMessagePdcRegisterOutput)  output = NULL;
+    MMBroadbandModemQmi                    *self;
+    gboolean                                enable;
+    GError                                 *error = NULL;
+
+    self = g_task_get_source_object (task);
+    enable = GPOINTER_TO_UINT (g_task_get_task_data (task));
+
+    output = qmi_client_pdc_register_finish (client, res, &error);
+    if (!output) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    if (!qmi_message_pdc_register_output_get_result (output, &error)) {
+        g_task_return_error (task, error);
+        g_object_unref (task);
+        return;
+    }
+
+    self->priv->profile_manager_unsolicited_events_enabled = enable;
+    mm_obj_dbg (self, "%s for refresh events", enable ? "registered" : "unregistered");
+
+    g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+common_enable_disable_unsolicited_events_3gpp_profile_manager (MMBroadbandModemQmi *self,
+                                                               gboolean             enable,
+                                                               GAsyncReadyCallback  callback,
+                                                               gpointer             user_data)
+{
+    g_autoptr(QmiMessagePdcRegisterInput)  input = NULL;
+    GTask                                 *task;
+    QmiClient                             *client = NULL;
+
+    if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
+                                      QMI_SERVICE_PDC, &client,
+                                      callback, user_data))
+        return;
+
+    task = g_task_new (self, NULL, callback, user_data);
+    g_task_set_task_data (task, GUINT_TO_POINTER (enable), NULL);
+
+    if (enable == self->priv->profile_manager_unsolicited_events_enabled) {
+        mm_obj_dbg (self, "profile manager unsolicited events already %s; skipping",
+                    enable ? "enabled" : "disabled");
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+
+    input = qmi_message_pdc_register_input_new ();
+    qmi_message_pdc_register_input_set_enable_reporting (input, enable, NULL);
+    qmi_message_pdc_register_input_set_enable_refresh (input, enable, NULL);
+    qmi_client_pdc_register (QMI_CLIENT_PDC (client),
+                             input,
+                             10,
+                             NULL,
+                             (GAsyncReadyCallback) register_pdc_refresh_ready,
+                             task);
+}
+
+static void
+modem_3gpp_profile_manager_disable_unsolicited_events (MMIfaceModem3gppProfileManager *self,
+                                                       GAsyncReadyCallback             callback,
+                                                       gpointer                        user_data)
+{
+    common_enable_disable_unsolicited_events_3gpp_profile_manager (MM_BROADBAND_MODEM_QMI (self),
+                                                                   FALSE,
+                                                                   callback,
+                                                                   user_data);
+}
+
+static void
+modem_3gpp_profile_manager_enable_unsolicited_events (MMIfaceModem3gppProfileManager *self,
+                                                      GAsyncReadyCallback             callback,
+                                                      gpointer                        user_data)
+{
+    common_enable_disable_unsolicited_events_3gpp_profile_manager (MM_BROADBAND_MODEM_QMI (self),
+                                                                   TRUE,
+                                                                   callback,
+                                                                   user_data);
+}
+
+/*****************************************************************************/
+/* Setup/cleanup unsolicited events (3gppProfileManager interface) */
+
+static gboolean
+modem_3gpp_profile_manager_setup_cleanup_unsolicited_events_finish (MMIfaceModem3gppProfileManager  *self,
+                                                                    GAsyncResult                    *res,
+                                                                    GError                         **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+common_setup_cleanup_unsolicited_events_3gpp_profile_manager (MMBroadbandModemQmi *self,
+                                                              gboolean             enable,
+                                                              GAsyncReadyCallback  callback,
+                                                              gpointer             user_data)
+
+{
+    GTask     *task;
+    QmiClient *client = NULL;
+
+    if (!mm_shared_qmi_ensure_client (MM_SHARED_QMI (self),
+                                      QMI_SERVICE_PDC, &client,
+                                      callback, user_data))
+        return;
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    if (enable == self->priv->profile_manager_unsolicited_events_setup) {
+        mm_obj_dbg (self, "profile manager unsolicited events already %s; skipping",
+                    enable ? "set up" : "cleaned up");
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+
+    self->priv->profile_manager_unsolicited_events_setup = enable;
+
+    if (enable) {
+        g_assert (self->priv->refresh_indication_id == 0);
+        self->priv->refresh_indication_id =
+            g_signal_connect (client,
+                              "refresh",
+                              G_CALLBACK (pdc_refresh_received),
+                              self);
+    } else {
+        g_assert (self->priv->refresh_indication_id != 0);
+        g_signal_handler_disconnect (client, self->priv->refresh_indication_id);
+        self->priv->refresh_indication_id = 0;
+    }
+
+    mm_obj_dbg (self, "%s profile events handler", enable ? "set up" : "cleaned up");
+
+    g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+modem_3gpp_profile_manager_cleanup_unsolicited_events (MMIfaceModem3gppProfileManager *self,
+                                                       GAsyncReadyCallback             callback,
+                                                       gpointer                        user_data)
+{
+    common_setup_cleanup_unsolicited_events_3gpp_profile_manager (MM_BROADBAND_MODEM_QMI (self),
+                                                                  FALSE,
+                                                                  callback,
+                                                                  user_data);
+}
+
+static void
+modem_3gpp_profile_manager_setup_unsolicited_events (MMIfaceModem3gppProfileManager *self,
+                                                     GAsyncReadyCallback             callback,
+                                                     gpointer                        user_data)
+{
+    common_setup_cleanup_unsolicited_events_3gpp_profile_manager (MM_BROADBAND_MODEM_QMI (self),
+                                                                  TRUE,
+                                                                  callback,
+                                                                  user_data);
+}
+
+/*****************************************************************************/
 /* Check support (Messaging interface) */
 
 static gboolean
@@ -12716,6 +13100,15 @@
     iface->check_support = NULL;
     iface->check_support_finish = NULL;
 
+    iface->setup_unsolicited_events = modem_3gpp_profile_manager_setup_unsolicited_events;
+    iface->setup_unsolicited_events_finish = modem_3gpp_profile_manager_setup_cleanup_unsolicited_events_finish;
+    iface->cleanup_unsolicited_events = modem_3gpp_profile_manager_cleanup_unsolicited_events;
+    iface->cleanup_unsolicited_events_finish = modem_3gpp_profile_manager_setup_cleanup_unsolicited_events_finish;
+    iface->enable_unsolicited_events = modem_3gpp_profile_manager_enable_unsolicited_events;
+    iface->enable_unsolicited_events_finish = modem_3gpp_profile_manager_enable_disable_unsolicited_events_finish;
+    iface->disable_unsolicited_events = modem_3gpp_profile_manager_disable_unsolicited_events;
+    iface->disable_unsolicited_events_finish = modem_3gpp_profile_manager_enable_disable_unsolicited_events_finish;
+
     /* Additional actions */
     iface->get_profile = modem_3gpp_profile_manager_get_profile;
     iface->get_profile_finish = modem_3gpp_profile_manager_get_profile_finish;
diff --git a/src/mm-iface-modem-3gpp.c b/src/mm-iface-modem-3gpp.c
index 40a5dd1..c1fa0e8 100644
--- a/src/mm-iface-modem-3gpp.c
+++ b/src/mm-iface-modem-3gpp.c
@@ -734,7 +734,7 @@
         g_variant_builder_close (&builder);
     }
 
-    return g_variant_ref (g_variant_builder_end (&builder));
+    return g_variant_ref_sink (g_variant_builder_end (&builder));
 }
 
 static void
diff --git a/src/mm-iface-modem-firmware.c b/src/mm-iface-modem-firmware.c
index 1b4a07a..fe1feb1 100644
--- a/src/mm-iface-modem-firmware.c
+++ b/src/mm-iface-modem-firmware.c
@@ -325,10 +325,9 @@
     return TRUE;
 }
 
-static gboolean
-add_generic_device_ids (MMBaseModem               *self,
-                        MMFirmwareUpdateSettings  *update_settings,
-                        GError                   **error)
+GPtrArray *
+mm_iface_firmware_build_generic_device_ids (MMIfaceModemFirmware  *self,
+                                            GError               **error)
 {
     static const gchar   *supported_subsystems[] = { "USB", "PCI" };
     guint16               vid;
@@ -341,8 +340,8 @@
     guint                 i;
     gboolean              ignore_carrier = FALSE;
 
-    vid = mm_base_modem_get_vendor_id (self);
-    pid = mm_base_modem_get_product_id (self);
+    vid = mm_base_modem_get_vendor_id (MM_BASE_MODEM (self));
+    pid = mm_base_modem_get_product_id (MM_BASE_MODEM (self));
 
 #if defined WITH_QMI
     if (MM_IS_BROADBAND_MODEM_QMI (self))
@@ -353,16 +352,15 @@
         primary = MM_PORT (mm_broadband_modem_mbim_peek_port_mbim (MM_BROADBAND_MODEM_MBIM (self)));
 #endif
     if (!primary)
-        primary = MM_PORT (mm_base_modem_peek_port_primary (self));
+        primary = MM_PORT (mm_base_modem_peek_port_primary (MM_BASE_MODEM (self)));
     g_assert (primary != NULL);
     rid = mm_kernel_device_get_physdev_revision (mm_port_peek_kernel_device (primary));
 
-
     subsystem = mm_kernel_device_get_physdev_subsystem (mm_port_peek_kernel_device (primary));
     if (!subsystem) {
         g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                      "Unknown device subsystem");
-        return FALSE;
+        return NULL;
     }
 
     for (i = 0; i < G_N_ELEMENTS (supported_subsystems); i++) {
@@ -372,7 +370,7 @@
     if (i == G_N_ELEMENTS (supported_subsystems)) {
         g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
                      "Unsupported subsystem: %s", subsystem);
-        return FALSE;
+        return NULL;
     }
 
     g_object_get (self,
@@ -398,8 +396,7 @@
                                            supported_subsystems[i], vid));
     g_ptr_array_add (ids, NULL);
 
-    mm_firmware_update_settings_set_device_ids (update_settings, (const gchar **)ids->pdata);
-    return TRUE;
+    return g_steal_pointer (&ids);
 }
 
 static void
@@ -411,6 +408,7 @@
     MMFirmwareUpdateSettings *update_settings;
     GError                   *error = NULL;
     GVariant                 *variant = NULL;
+    g_autoptr(GPtrArray)      ids = NULL;
 
     ctx = g_task_get_task_data (task);
 
@@ -422,12 +420,17 @@
     }
 
     /* If the plugin didn't specify custom device ids, add the default ones ourselves */
-    if (!mm_firmware_update_settings_get_device_ids (update_settings) &&
-        !add_generic_device_ids (MM_BASE_MODEM (self), update_settings, &error)) {
-        mm_obj_warn (self, "couldn't build device ids: %s", error->message);
-        g_error_free (error);
-        g_clear_object (&update_settings);
-        goto out;
+    if (!mm_firmware_update_settings_get_device_ids (update_settings)) {
+        mm_obj_dbg (self, "No device ids set by plugin, adding generic ids");
+        ids = mm_iface_firmware_build_generic_device_ids (self, &error);
+        if (error) {
+            mm_obj_warn (self, "couldn't build device ids: %s", error->message);
+            g_error_free (error);
+            g_clear_object (&update_settings);
+            goto out;
+        }
+
+        mm_firmware_update_settings_set_device_ids (update_settings, (const gchar **)ids->pdata);
     }
 
     /* If the plugin didn't specify custom version, add the default one ourselves */
diff --git a/src/mm-iface-modem-firmware.h b/src/mm-iface-modem-firmware.h
index 365bfb1..0e82382 100644
--- a/src/mm-iface-modem-firmware.h
+++ b/src/mm-iface-modem-firmware.h
@@ -72,6 +72,10 @@
 GType mm_iface_modem_firmware_get_type (void);
 G_DEFINE_AUTOPTR_CLEANUP_FUNC (MMIfaceModemFirmware, g_object_unref)
 
+/* Get generic device ids */
+GPtrArray *mm_iface_firmware_build_generic_device_ids (MMIfaceModemFirmware  *self,
+                                                       GError               **error);
+
 /* Initialize Firmware interface (async) */
 void     mm_iface_modem_firmware_initialize        (MMIfaceModemFirmware *self,
                                                     GCancellable *cancellable,
diff --git a/src/mm-iface-modem-signal.c b/src/mm-iface-modem-signal.c
index d204039..1116057 100644
--- a/src/mm-iface-modem-signal.c
+++ b/src/mm-iface-modem-signal.c
@@ -35,10 +35,12 @@
 static GQuark private_quark;
 
 typedef struct {
-    /* polling-based reporting enabled */
+    /* interface enabled */
+    gboolean enabled;
+    /* polling-based reporting  */
     guint    rate;
     guint    timeout_source;
-    /* threshold-based reporting enabled */
+    /* threshold-based reporting */
     guint    rssi_threshold;
     gboolean error_rate_threshold;
 } Private;
@@ -155,7 +157,7 @@
     Private *priv;
 
     priv = get_private (self);
-    if (!priv->rate && !priv->rssi_threshold && !priv->error_rate_threshold) {
+    if (!priv->enabled || (!priv->rate && !priv->rssi_threshold && !priv->error_rate_threshold)) {
         mm_obj_dbg (self, "skipping extended signal information update...");
         return;
     }
@@ -172,13 +174,14 @@
 
     priv = get_private (self);
 
-    if (!priv->rate && !priv->rssi_threshold && !priv->error_rate_threshold) {
+    if (!priv->enabled || (!priv->rate && !priv->rssi_threshold && !priv->error_rate_threshold)) {
         mm_obj_dbg (self, "reseting extended signal information...");
-        mm_iface_modem_signal_update (self, NULL, NULL, NULL, NULL, NULL, NULL);
+        internal_signal_update (self, NULL, NULL, NULL, NULL, NULL, NULL);
     }
 }
 
 /*****************************************************************************/
+/* Polling setup management */
 
 static void
 load_values_ready (MMIfaceModemSignal *self,
@@ -220,51 +223,96 @@
     return G_SOURCE_CONTINUE;
 }
 
-static gboolean
-polling_restart (MMIfaceModemSignal  *self,
-                 guint                rate,
-                 GError             **error)
+static void
+polling_restart (MMIfaceModemSignal *self)
 {
-    Private *priv;
+    Private  *priv;
+    gboolean  polling_setup;
 
     priv = get_private (self);
+    polling_setup = (priv->enabled && priv->rate);
 
-    /* Update the rate in the interface if it changed */
-    if (priv->rate != rate) {
-        g_autoptr(MmGdbusModemSignalSkeleton) skeleton = NULL;
+    mm_obj_dbg (self, "%s extended signal information polling: interface %s, rate %u seconds",
+                polling_setup ? "setting up" : "cleaning up",
+                priv->enabled ? "enabled" : "disabled",
+                priv->rate);
 
-        g_object_get (self,
-                      MM_IFACE_MODEM_SIGNAL_DBUS_SKELETON, &skeleton,
-                      NULL);
-        if (!skeleton) {
-            g_set_error (error, MM_CORE_ERROR, MM_CORE_ERROR_FAILED,
-                         "Couldn't get interface skeleton");
-            return FALSE;
-        }
-        mm_gdbus_modem_signal_set_rate (MM_GDBUS_MODEM_SIGNAL (skeleton), rate);
-        priv->rate = rate;
-    }
-
-    /* Polling disabled by user? */
-    if (rate == 0) {
-        mm_obj_dbg (self, "extended signal information polling disabled (rate: 0 seconds)");
+    /* Stop polling */
+    if (!polling_setup) {
         if (priv->timeout_source) {
             g_source_remove (priv->timeout_source);
             priv->timeout_source = 0;
         }
-        check_interface_reset (self);
-        return TRUE;
+        return;
     }
 
-    /* Restart polling */
-    mm_obj_dbg (self, "extended signal information reporting enabled (rate: %u seconds)", rate);
+    /* Start/restart polling */
     if (priv->timeout_source)
         g_source_remove (priv->timeout_source);
-    priv->timeout_source = g_timeout_add_seconds (rate, (GSourceFunc) polling_context_cb, self);
+    priv->timeout_source = g_timeout_add_seconds (priv->rate, (GSourceFunc) polling_context_cb, self);
 
     /* Also launch right away */
     polling_context_cb (self);
-    return TRUE;
+}
+
+/*****************************************************************************/
+/* Thresholds setup management */
+
+static gboolean
+thresholds_restart_finish (MMIfaceModemSignal  *self,
+                           GAsyncResult        *res,
+                           GError             **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+setup_thresholds_ready (MMIfaceModemSignal *self,
+                        GAsyncResult       *res,
+                        GTask              *task)
+{
+    GError *error = NULL;
+
+    if (!MM_IFACE_MODEM_SIGNAL_GET_INTERFACE (self)->setup_thresholds_finish (self, res, &error))
+        g_task_return_error (task, error);
+    else
+        g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+thresholds_restart (MMIfaceModemSignal  *self,
+                    GAsyncReadyCallback  callback,
+                    gpointer             user_data)
+{
+    GTask    *task;
+    Private  *priv;
+    gboolean  threshold_setup;
+
+    task = g_task_new (self, NULL, callback, user_data);
+
+    if (!MM_IFACE_MODEM_SIGNAL_GET_INTERFACE (self)->setup_thresholds ||
+        !MM_IFACE_MODEM_SIGNAL_GET_INTERFACE (self)->setup_thresholds_finish) {
+        g_task_return_boolean (task, TRUE);
+        g_object_unref (task);
+        return;
+    }
+
+    priv = get_private (self);
+    threshold_setup = (priv->enabled && (priv->rssi_threshold || priv->error_rate_threshold));
+
+    mm_obj_dbg (self, "%s extended signal information thresholds: interface %s, rssi threshold %u dBm, error rate threshold %s",
+                threshold_setup ? "setting up" : "cleaning up",
+                priv->enabled ? "enabled" : "disabled",
+                priv->rssi_threshold,
+                priv->error_rate_threshold ? "enabled" : "disabled");
+
+    MM_IFACE_MODEM_SIGNAL_GET_INTERFACE (self)->setup_thresholds (
+        self,
+        priv->rssi_threshold,
+        priv->error_rate_threshold,
+        (GAsyncReadyCallback)setup_thresholds_ready,
+        task);
 }
 
 /*****************************************************************************/
@@ -272,7 +320,6 @@
 typedef struct {
     GDBusMethodInvocation *invocation;
     MmGdbusModemSignal    *skeleton;
-    MMIfaceModemSignal    *self;
     guint                  rate;
 } HandleSetupContext;
 
@@ -281,18 +328,19 @@
 {
     g_object_unref (ctx->invocation);
     g_object_unref (ctx->skeleton);
-    g_object_unref (ctx->self);
     g_slice_free (HandleSetupContext, ctx);
 }
 
 static void
-handle_setup_auth_ready (MMBaseModem *self,
-                         GAsyncResult *res,
+handle_setup_auth_ready (MMBaseModem        *_self,
+                         GAsyncResult       *res,
                          HandleSetupContext *ctx)
 {
-    GError *error = NULL;
+    MMIfaceModemSignal *self = MM_IFACE_MODEM_SIGNAL (_self);
+    GError             *error = NULL;
+    Private            *priv;
 
-    if (!mm_base_modem_authorize_finish (self, res, &error)) {
+    if (!mm_base_modem_authorize_finish (_self, res, &error)) {
         g_dbus_method_invocation_take_error (ctx->invocation, error);
         handle_setup_context_free (ctx);
         return;
@@ -300,15 +348,17 @@
 
     if (mm_iface_modem_abort_invocation_if_state_not_reached (MM_IFACE_MODEM (self),
                                                               ctx->invocation,
-                                                              MM_MODEM_STATE_ENABLED)) {
+                                                              MM_MODEM_STATE_DISABLED)) {
         handle_setup_context_free (ctx);
         return;
     }
 
-    if (!polling_restart (ctx->self, ctx->rate, &error))
-        g_dbus_method_invocation_take_error (ctx->invocation, error);
-    else
-        mm_gdbus_modem_signal_complete_setup (ctx->skeleton, ctx->invocation);
+    priv = get_private (self);
+    priv->rate = ctx->rate;
+    polling_restart (self);
+    check_interface_reset (self);
+    mm_gdbus_modem_signal_set_rate (ctx->skeleton, ctx->rate);
+    mm_gdbus_modem_signal_complete_setup (ctx->skeleton, ctx->invocation);
     handle_setup_context_free (ctx);
 }
 
@@ -320,10 +370,9 @@
 {
     HandleSetupContext *ctx;
 
-    ctx = g_slice_new (HandleSetupContext);
+    ctx = g_slice_new0 (HandleSetupContext);
     ctx->invocation = g_object_ref (invocation);
     ctx->skeleton = g_object_ref (skeleton);
-    ctx->self = g_object_ref (self);
     ctx->rate = rate;
 
     mm_base_modem_authorize (MM_BASE_MODEM (self),
@@ -339,10 +388,9 @@
 typedef struct {
     GDBusMethodInvocation *invocation;
     MmGdbusModemSignal    *skeleton;
-    MMIfaceModemSignal    *self;
     GVariant              *settings;
-    guint32                rssi_threshold;
-    gboolean               error_rate_threshold;
+    guint                  previous_rssi_threshold;
+    gboolean               previous_error_rate_threshold;
 } HandleSetupThresholdsContext;
 
 static void
@@ -350,32 +398,29 @@
 {
     g_object_unref (ctx->invocation);
     g_object_unref (ctx->skeleton);
-    g_object_unref (ctx->self);
     if (ctx->settings)
         g_variant_unref (ctx->settings);
     g_slice_free (HandleSetupThresholdsContext, ctx);
 }
 
 static void
-setup_thresholds_ready (MMIfaceModemSignal           *self,
-                        GAsyncResult                 *res,
-                        HandleSetupThresholdsContext *ctx)
+setup_thresholds_restart_ready (MMIfaceModemSignal           *self,
+                                GAsyncResult                 *res,
+                                HandleSetupThresholdsContext *ctx)
 {
     GError  *error = NULL;
     Private *priv;
 
-    priv = get_private (MM_IFACE_MODEM_SIGNAL (self));
+    priv = get_private (self);
 
-    if (!MM_IFACE_MODEM_SIGNAL_GET_INTERFACE (ctx->self)->setup_thresholds_finish (ctx->self, res, &error))
+    if (!thresholds_restart_finish (self, res, &error)) {
+        priv->rssi_threshold = ctx->previous_rssi_threshold;
+        priv->error_rate_threshold = ctx->previous_error_rate_threshold;
         g_dbus_method_invocation_take_error (ctx->invocation, error);
-    else {
-        /* Update the properties with the latest threshold setting */
-        mm_gdbus_modem_signal_set_rssi_threshold (ctx->skeleton, ctx->rssi_threshold);
-        mm_gdbus_modem_signal_set_error_rate_threshold (ctx->skeleton, ctx->error_rate_threshold);
-        priv->rssi_threshold = ctx->rssi_threshold;
-        priv->error_rate_threshold = ctx->error_rate_threshold;
+    } else {
         check_interface_reset (self);
-
+        mm_gdbus_modem_signal_set_rssi_threshold (ctx->skeleton, priv->rssi_threshold);
+        mm_gdbus_modem_signal_set_error_rate_threshold (ctx->skeleton, priv->error_rate_threshold);
         mm_gdbus_modem_signal_complete_setup_thresholds (ctx->skeleton, ctx->invocation);
     }
 
@@ -383,24 +428,27 @@
 }
 
 static void
-handle_setup_thresholds_auth_ready (MMBaseModem                  *self,
+handle_setup_thresholds_auth_ready (MMBaseModem                  *_self,
                                     GAsyncResult                 *res,
                                     HandleSetupThresholdsContext *ctx)
 {
     g_autoptr(MMSignalThresholdProperties)  properties = NULL;
+    MMIfaceModemSignal                     *self = MM_IFACE_MODEM_SIGNAL (_self);
     GError                                 *error = NULL;
     Private                                *priv;
+    guint                                   new_rssi_threshold;
+    gboolean                                new_error_rate_threshold;
 
-    priv = get_private (MM_IFACE_MODEM_SIGNAL (self));
+    priv = get_private (self);
 
-    if (!mm_base_modem_authorize_finish (self, res, &error)) {
+    if (!mm_base_modem_authorize_finish (_self, res, &error)) {
         g_dbus_method_invocation_take_error (ctx->invocation, error);
         handle_setup_thresholds_context_free (ctx);
         return;
     }
 
-    if (!MM_IFACE_MODEM_SIGNAL_GET_INTERFACE (ctx->self)->setup_thresholds ||
-        !MM_IFACE_MODEM_SIGNAL_GET_INTERFACE (ctx->self)->setup_thresholds_finish) {
+    if (!MM_IFACE_MODEM_SIGNAL_GET_INTERFACE (self)->setup_thresholds ||
+        !MM_IFACE_MODEM_SIGNAL_GET_INTERFACE (self)->setup_thresholds_finish) {
         g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
                                                "Cannot setup thresholds: operation not supported");
         handle_setup_thresholds_context_free (ctx);
@@ -409,7 +457,7 @@
 
     if (mm_iface_modem_abort_invocation_if_state_not_reached (MM_IFACE_MODEM (self),
                                                               ctx->invocation,
-                                                              MM_MODEM_STATE_ENABLED)) {
+                                                              MM_MODEM_STATE_DISABLED)) {
         handle_setup_thresholds_context_free (ctx);
         return;
     }
@@ -420,31 +468,24 @@
         handle_setup_thresholds_context_free (ctx);
         return;
     }
-    ctx->rssi_threshold       = mm_signal_threshold_properties_get_rssi       (properties);
-    ctx->error_rate_threshold = mm_signal_threshold_properties_get_error_rate (properties);
+    new_rssi_threshold       = mm_signal_threshold_properties_get_rssi       (properties);
+    new_error_rate_threshold = mm_signal_threshold_properties_get_error_rate (properties);
 
-    /* Already there? */
-    if ((ctx->rssi_threshold == mm_gdbus_modem_signal_get_rssi_threshold (ctx->skeleton)) &&
-        (ctx->error_rate_threshold == mm_gdbus_modem_signal_get_error_rate_threshold (ctx->skeleton))) {
+    if ((new_rssi_threshold == priv->rssi_threshold) &&
+        (new_error_rate_threshold == priv->error_rate_threshold)) {
         mm_gdbus_modem_signal_complete_setup_thresholds (ctx->skeleton, ctx->invocation);
         handle_setup_thresholds_context_free (ctx);
         return;
     }
 
-    /* Same settings? */
-    if ((ctx->rssi_threshold == priv->rssi_threshold) &&
-        (ctx->error_rate_threshold == priv->error_rate_threshold)) {
-        mm_gdbus_modem_signal_complete_setup_thresholds (ctx->skeleton, ctx->invocation);
-        handle_setup_thresholds_context_free (ctx);
-        return;
-    }
+    ctx->previous_rssi_threshold = priv->rssi_threshold;
+    ctx->previous_error_rate_threshold = priv->error_rate_threshold;
+    priv->rssi_threshold = new_rssi_threshold;
+    priv->error_rate_threshold = new_error_rate_threshold;
 
-    MM_IFACE_MODEM_SIGNAL_GET_INTERFACE (ctx->self)->setup_thresholds (
-        ctx->self,
-        ctx->rssi_threshold,
-        ctx->error_rate_threshold,
-        (GAsyncReadyCallback)setup_thresholds_ready,
-        ctx);
+    thresholds_restart (self,
+                        (GAsyncReadyCallback)setup_thresholds_restart_ready,
+                        ctx);
 }
 
 static gboolean
@@ -458,7 +499,6 @@
     ctx = g_slice_new0 (HandleSetupThresholdsContext);
     ctx->invocation = g_object_ref (invocation);
     ctx->skeleton = g_object_ref (skeleton);
-    ctx->self = g_object_ref (self);
     ctx->settings = g_variant_ref (settings);
 
     mm_base_modem_authorize (MM_BASE_MODEM (self),
@@ -470,13 +510,62 @@
 }
 
 /*****************************************************************************/
+/* Common enable/disable */
+
+static gboolean
+common_enable_disable_finish (MMIfaceModemSignal  *self,
+                              GAsyncResult        *res,
+                              GError             **error)
+{
+    return g_task_propagate_boolean (G_TASK (res), error);
+}
+
+static void
+enable_disable_thresholds_restart_ready (MMIfaceModemSignal *self,
+                                         GAsyncResult       *res,
+                                         GTask              *task)
+{
+    GError *error = NULL;
+
+    if (!thresholds_restart_finish (self, res, &error))
+        g_task_return_error (task, error);
+    else
+        g_task_return_boolean (task, TRUE);
+    g_object_unref (task);
+}
+
+static void
+common_enable_disable (MMIfaceModemSignal  *self,
+                       gboolean             enabled,
+                       GCancellable        *cancellable,
+                       GAsyncReadyCallback  callback,
+                       gpointer             user_data)
+{
+    GTask   *task;
+    Private *priv;
+
+    task = g_task_new (self, cancellable, callback, user_data);
+
+    priv = get_private (MM_IFACE_MODEM_SIGNAL (self));
+    priv->enabled = enabled;
+
+    check_interface_reset (self);
+
+    polling_restart (self);
+
+    thresholds_restart (self,
+                        (GAsyncReadyCallback)enable_disable_thresholds_restart_ready,
+                        task);
+}
+
+/*****************************************************************************/
 
 gboolean
 mm_iface_modem_signal_disable_finish (MMIfaceModemSignal  *self,
                                       GAsyncResult        *res,
                                       GError             **error)
 {
-    return g_task_propagate_boolean (G_TASK (res), error);
+    return common_enable_disable_finish (self, res, error);
 }
 
 void
@@ -484,13 +573,7 @@
                                GAsyncReadyCallback  callback,
                                gpointer             user_data)
 {
-    GTask *task;
-
-    mm_iface_modem_signal_update (self, NULL, NULL, NULL, NULL, NULL, NULL);
-
-    task = g_task_new (self, NULL, callback, user_data);
-    g_task_return_boolean (task, TRUE);
-    g_object_unref (task);
+    common_enable_disable (self, FALSE, NULL, callback, user_data);
 }
 
 /*****************************************************************************/
@@ -500,7 +583,7 @@
                                      GAsyncResult        *res,
                                      GError             **error)
 {
-    return g_task_propagate_boolean (G_TASK (res), error);
+    return common_enable_disable_finish (self, res, error);
 }
 
 void
@@ -509,19 +592,7 @@
                               GAsyncReadyCallback  callback,
                               gpointer             user_data)
 {
-    GTask   *task;
-    GError  *error = NULL;
-    Private *priv;
-
-    priv = get_private (self);
-    task = g_task_new (self, cancellable, callback, user_data);
-
-    /* same rate as we had before */
-    if (!polling_restart (self, priv->rate, &error))
-        g_task_return_error (task, error);
-    else
-        g_task_return_boolean (task, TRUE);
-    g_object_unref (task);
+    common_enable_disable (self, TRUE, cancellable, callback, user_data);
 }
 
 /*****************************************************************************/
diff --git a/src/mm-iface-modem.c b/src/mm-iface-modem.c
index 1426b44..140af48 100644
--- a/src/mm-iface-modem.c
+++ b/src/mm-iface-modem.c
@@ -1234,6 +1234,115 @@
 
 /*****************************************************************************/
 
+typedef struct {
+    MmGdbusModem          *skeleton;
+    GDBusMethodInvocation *invocation;
+    MMIfaceModem          *self;
+} HandleGetCellInfoContext;
+
+static void
+handle_get_cell_info_context_free (HandleGetCellInfoContext *ctx)
+{
+    g_object_unref (ctx->skeleton);
+    g_object_unref (ctx->invocation);
+    g_object_unref (ctx->self);
+    g_free (ctx);
+}
+
+static GVariant *
+get_cell_info_build_result (GList *info_list)
+{
+    GList *l;
+    GVariantBuilder builder;
+
+    g_variant_builder_init (&builder, G_VARIANT_TYPE ("aa{sv}"));
+
+    for (l = info_list; l; l = g_list_next (l)) {
+        g_autoptr(GVariant) dict = NULL;
+
+        dict = mm_cell_info_get_dictionary (MM_CELL_INFO (l->data));
+        g_variant_builder_add_value (&builder, dict);
+    }
+
+    return g_variant_ref_sink (g_variant_builder_end (&builder));
+}
+
+static void
+get_cell_info_ready (MMIfaceModem             *self,
+                     GAsyncResult             *res,
+                     HandleGetCellInfoContext *ctx)
+{
+    GError *error = NULL;
+    GList  *info_list;
+
+    info_list = MM_IFACE_MODEM_GET_INTERFACE (self)->get_cell_info_finish (self, res, &error);
+    if (error)
+        g_dbus_method_invocation_take_error (ctx->invocation, error);
+    else {
+        g_autoptr(GVariant) dict_array = NULL;
+
+        dict_array = get_cell_info_build_result (info_list);
+        mm_gdbus_modem_complete_get_cell_info (ctx->skeleton, ctx->invocation, dict_array);
+    }
+
+    g_list_free_full (info_list, (GDestroyNotify)g_object_unref);
+    handle_get_cell_info_context_free (ctx);
+}
+
+static void
+handle_get_cell_info_auth_ready (MMBaseModem              *self,
+                                 GAsyncResult             *res,
+                                 HandleGetCellInfoContext *ctx)
+{
+    GError *error = NULL;
+
+    if (!mm_base_modem_authorize_finish (self, res, &error)) {
+        g_dbus_method_invocation_take_error (ctx->invocation, error);
+        handle_get_cell_info_context_free (ctx);
+        return;
+    }
+
+    /* If getting cell info is not implemented, report an error */
+    if (!MM_IFACE_MODEM_GET_INTERFACE (self)->get_cell_info ||
+        !MM_IFACE_MODEM_GET_INTERFACE (self)->get_cell_info_finish) {
+        g_dbus_method_invocation_return_error (ctx->invocation, MM_CORE_ERROR, MM_CORE_ERROR_UNSUPPORTED,
+                                               "Cannot get cell info: operation not supported");
+        handle_get_cell_info_context_free (ctx);
+        return;
+    }
+
+    if (mm_iface_modem_abort_invocation_if_state_not_reached (ctx->self, ctx->invocation, MM_MODEM_STATE_ENABLED)) {
+        handle_get_cell_info_context_free (ctx);
+        return;
+    }
+
+    MM_IFACE_MODEM_GET_INTERFACE (self)->get_cell_info (ctx->self,
+                                                        (GAsyncReadyCallback)get_cell_info_ready,
+                                                        ctx);
+}
+
+static gboolean
+handle_get_cell_info (MmGdbusModem          *skeleton,
+                      GDBusMethodInvocation *invocation,
+                      MMIfaceModem          *self)
+{
+    HandleGetCellInfoContext *ctx;
+
+    ctx = g_new (HandleGetCellInfoContext, 1);
+    ctx->skeleton = g_object_ref (skeleton);
+    ctx->invocation = g_object_ref (invocation);
+    ctx->self = g_object_ref (self);
+
+    mm_base_modem_authorize (MM_BASE_MODEM (self),
+                             invocation,
+                             MM_AUTHORIZATION_DEVICE_CONTROL,
+                             (GAsyncReadyCallback)handle_get_cell_info_auth_ready,
+                             ctx);
+    return TRUE;
+}
+
+/*****************************************************************************/
+
 void
 mm_iface_modem_update_access_technologies (MMIfaceModem *self,
                                            MMModemAccessTechnology new_access_tech,
@@ -3477,9 +3586,9 @@
     return unlock_retries;
 }
 
-void
-mm_iface_modem_update_unlock_retries (MMIfaceModem *self,
-                                      MMUnlockRetries *unlock_retries)
+static void
+update_unlock_retries (MMIfaceModem *self,
+                       MMUnlockRetries *unlock_retries)
 {
     MmGdbusModem *skeleton = NULL;
     GVariant *previous_dictionary;
@@ -3565,7 +3674,7 @@
         g_error_free (error);
     } else {
         /* Update the dictionary in the DBus interface */
-        mm_iface_modem_update_unlock_retries (self, unlock_retries);
+        update_unlock_retries (self, unlock_retries);
         g_object_unref (unlock_retries);
     }
 
@@ -5891,6 +6000,7 @@
                           "signal::handle-set-current-bands",        G_CALLBACK (handle_set_current_bands),        self,
                           "signal::handle-set-current-modes",        G_CALLBACK (handle_set_current_modes),        self,
                           "signal::handle-set-primary-sim-slot",     G_CALLBACK (handle_set_primary_sim_slot),     self,
+                          "signal::handle-get-cell-info",            G_CALLBACK (handle_get_cell_info),            self,
                           NULL);
 
         /* Finally, export the new interface, even if we got errors, but only if not
diff --git a/src/mm-iface-modem.h b/src/mm-iface-modem.h
index 2abadf2..a182d6d 100644
--- a/src/mm-iface-modem.h
+++ b/src/mm-iface-modem.h
@@ -405,6 +405,14 @@
     gboolean (* setup_carrier_config_finish) (MMIfaceModem         *self,
                                               GAsyncResult         *res,
                                               GError              **error);
+
+    /* Asynchronous cell info retrieval operation */
+    void    (* get_cell_info)        (MMIfaceModem         *self,
+                                      GAsyncReadyCallback   callback,
+                                      gpointer              user_data);
+    GList * (* get_cell_info_finish) (MMIfaceModem         *self,
+                                      GAsyncResult         *res,
+                                      GError              **error);
 };
 
 GType mm_iface_modem_get_type (void);
@@ -510,9 +518,6 @@
 MMModemLock      mm_iface_modem_get_unlock_required (MMIfaceModem *self);
 MMUnlockRetries *mm_iface_modem_get_unlock_retries  (MMIfaceModem *self);
 
-void mm_iface_modem_update_unlock_retries (MMIfaceModem *self,
-                                           MMUnlockRetries *unlock_retries);
-
 /* Request signal quality check update.
  * It will not only return the signal quality status, but also set the property
  * values in the DBus interface. */
diff --git a/src/mm-modem-helpers-qmi.c b/src/mm-modem-helpers-qmi.c
index d531636..d95fd50 100644
--- a/src/mm-modem-helpers-qmi.c
+++ b/src/mm-modem-helpers-qmi.c
@@ -1832,8 +1832,8 @@
  * as there would be no capability switching support.
  */
 MMModemCapability
-mm_modem_capability_from_qmi_capabilities_context (MMQmiCapabilitiesContext *ctx,
-                                                   gpointer                  log_object)
+mm_current_capability_from_qmi_current_capabilities_context (MMQmiCurrentCapabilitiesContext *ctx,
+                                                             gpointer                         log_object)
 {
     MMModemCapability tmp = MM_MODEM_CAPABILITY_NONE;
     g_autofree gchar *nas_ssp_mode_preference_str = NULL;
@@ -1842,8 +1842,7 @@
     g_autofree gchar *tmp_str = NULL;
 
     /* If not a multimode device, we're done */
-#define MULTIMODE (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO)
-    if ((ctx->dms_capabilities & MULTIMODE) != MULTIMODE)
+    if (!ctx->multimode)
         tmp = ctx->dms_capabilities;
     else {
         /* We have a multimode CDMA/EVDO+GSM/UMTS device, check SSP and TP */
@@ -1856,15 +1855,15 @@
         else if (ctx->nas_tp_mask != QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO)
             tmp = mm_modem_capability_from_qmi_radio_technology_preference (ctx->nas_tp_mask);
 
-        /* Final capabilities are the intersection between the Technology
-         * Preference or SSP and the device's capabilities.
+        /* Final capabilities are the union of the active multimode capability
+         * (GSM/UMTS or CDMA/EVDO or both or none) in TP or SSP and other supported device's capabilities.
          * If the Technology Preference was "auto" or unknown we just fall back
          * to the Get Capabilities response.
          */
         if (tmp == MM_MODEM_CAPABILITY_NONE)
             tmp = ctx->dms_capabilities;
         else
-            tmp &= ctx->dms_capabilities;
+            tmp = (tmp & MM_MODEM_CAPABILITY_MULTIMODE) | (MM_MODEM_CAPABILITY_MULTIMODE ^ ctx->dms_capabilities);
     }
 
     /* Log about the logic applied */
@@ -1886,6 +1885,147 @@
 }
 
 /*****************************************************************************/
+/* Utility to build list of supported capabilities */
+
+GArray *
+mm_supported_capabilities_from_qmi_supported_capabilities_context (MMQmiSupportedCapabilitiesContext *ctx,
+                                                                   gpointer                           log_object)
+{
+    GArray *supported_combinations;
+
+    supported_combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemCapability), 4);
+
+    /* Add all possible supported capability combinations.
+     * In order to avoid unnecessary modem reboots, we will only implement capabilities
+     * switching only when switching GSM/UMTS+CDMA/EVDO multimode devices, and only if
+     * we have support for the commands doing it.
+     */
+    if ((ctx->nas_tp_supported || ctx->nas_ssp_supported) && ctx->multimode) {
+        MMModemCapability single;
+
+        /* Multimode GSM/UMTS+CDMA/EVDO+(LTE/5GNR) device switched to GSM/UMTS+(LTE/5GNR) device */
+        single = MM_MODEM_CAPABILITY_GSM_UMTS | (MM_MODEM_CAPABILITY_MULTIMODE ^ ctx->dms_capabilities);
+        g_array_append_val (supported_combinations, single);
+        /* Multimode GSM/UMTS+CDMA/EVDO+(LTE/5GNR) device switched to CDMA/EVDO+(LTE/5GNR) device */
+        single = MM_MODEM_CAPABILITY_CDMA_EVDO | (MM_MODEM_CAPABILITY_MULTIMODE ^ ctx->dms_capabilities);
+        g_array_append_val (supported_combinations, single);
+        /*
+         * Multimode GSM/UMTS+CDMA/EVDO+(LTE/5GNR) device switched to (LTE/5GNR) device
+         *
+         * This case is required because we use the same methods and operations to
+         * switch capabilities and modes.
+         */
+        if ((single = (MM_MODEM_CAPABILITY_MULTIMODE ^ ctx->dms_capabilities)))
+            g_array_append_val (supported_combinations, single);
+    }
+
+    /* Add the full mask itself */
+    g_array_append_val (supported_combinations, ctx->dms_capabilities);
+
+    return supported_combinations;
+}
+
+/*****************************************************************************/
+/* Utility to build list of supported modes */
+
+GArray *
+mm_supported_modes_from_qmi_supported_modes_context (MMQmiSupportedModesContext *ctx,
+                                                     gpointer                    log_object)
+{
+    g_autoptr(GArray)       combinations = NULL;
+    g_autoptr(GArray)       all = NULL;
+    MMModemModeCombination  mode;
+
+    /* Start with a mode including ALL */
+    mode.allowed = ctx->all;
+    mode.preferred = MM_MODEM_MODE_NONE;
+    all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
+    g_array_append_val (all, mode);
+
+    /* If SSP and TP are not supported, ignore supported mode management */
+    if (!ctx->nas_ssp_supported && !ctx->nas_tp_supported)
+        return g_steal_pointer (&all);
+
+    combinations = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination));
+
+#define ADD_MODE_PREFERENCE(MODE1, MODE2, MODE3, MODE4) do {            \
+        mode.allowed = MODE1;                                           \
+        if (MODE2 != MM_MODEM_MODE_NONE) {                              \
+            mode.allowed |= MODE2;                                      \
+            if (MODE3 != MM_MODEM_MODE_NONE) {                          \
+                mode.allowed |= MODE3;                                  \
+                if (MODE4 != MM_MODEM_MODE_NONE)                        \
+                    mode.allowed |= MODE4;                              \
+            }                                                           \
+            if (ctx->nas_ssp_supported) {                               \
+                if (MODE3 != MM_MODEM_MODE_NONE) {                      \
+                    if (MODE4 != MM_MODEM_MODE_NONE) {                  \
+                        mode.preferred = MODE4;                         \
+                        g_array_append_val (combinations, mode);        \
+                    }                                                   \
+                    mode.preferred = MODE3;                             \
+                    g_array_append_val (combinations, mode);            \
+                }                                                       \
+                mode.preferred = MODE2;                                 \
+                g_array_append_val (combinations, mode);                \
+                mode.preferred = MODE1;                                 \
+                g_array_append_val (combinations, mode);                \
+            } else {                                                    \
+                mode.preferred = MM_MODEM_MODE_NONE;                    \
+                g_array_append_val (combinations, mode);                \
+            }                                                           \
+        } else {                                                        \
+            mode.allowed = MODE1;                                       \
+            mode.preferred = MM_MODEM_MODE_NONE;                        \
+            g_array_append_val (combinations, mode);                    \
+        }                                                               \
+    } while (0)
+
+    /* 2G-only, 3G-only */
+    ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
+    ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
+
+    /*
+     * This case is required because we use the same methods and operations to
+     * switch capabilities and modes. For the LTE capability there is a direct
+     * related 4G mode, and so we cannot select a '4G only' mode in this device
+     * because we wouldn't be able to know the full list of current capabilities
+     * if the device was rebooted, as we would only see LTE capability. So,
+     * handle this special case so that the LTE/4G-only mode can exclusively be
+     * selected as capability switching in this kind of devices.
+     */
+    if (!ctx->multimode || !(ctx->current_capabilities & MM_MODEM_CAPABILITY_MULTIMODE)) {
+        /* 4G-only */
+        ADD_MODE_PREFERENCE (MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
+    }
+
+    /* 2G, 3G, 4G combinations */
+    ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
+    ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
+    ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
+    ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G, MM_MODEM_MODE_4G,   MM_MODEM_MODE_NONE);
+
+    /* 5G related mode combinations are only supported when NAS SSP is supported,
+     * as there is no 5G support in NAS TP. */
+    if (ctx->nas_ssp_supported) {
+        /* Same reasoning as for the special 4G-only case above */
+        if (!ctx->multimode || !(ctx->current_capabilities & MM_MODEM_CAPABILITY_MULTIMODE)) {
+            ADD_MODE_PREFERENCE (MM_MODEM_MODE_5G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
+            ADD_MODE_PREFERENCE (MM_MODEM_MODE_4G, MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
+        }
+        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
+        ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
+        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G,   MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE);
+        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_4G,   MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE);
+        ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_4G,   MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE);
+        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G,   MM_MODEM_MODE_4G,   MM_MODEM_MODE_5G);
+    }
+
+    /* Filter out unsupported modes */
+    return mm_filter_supported_modes (all, combinations, log_object);
+}
+
+/*****************************************************************************/
 
 MMOmaSessionType
 mm_oma_session_type_from_qmi_oma_session_type (QmiOmaSessionType qmi_session_type)
diff --git a/src/mm-modem-helpers-qmi.h b/src/mm-modem-helpers-qmi.h
index b9682ab..e4eb9c7 100644
--- a/src/mm-modem-helpers-qmi.h
+++ b/src/mm-modem-helpers-qmi.h
@@ -24,6 +24,8 @@
 
 #include "mm-port.h"
 
+#define MM_MODEM_CAPABILITY_MULTIMODE (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO)
+
 /*****************************************************************************/
 /* QMI/DMS to MM translations */
 
@@ -160,16 +162,54 @@
 /* Utility to gather current capabilities from various sources */
 
 typedef struct {
+    /* Whether this is a multimode device or not */
+    gboolean multimode;
     /* NAS System Selection Preference */
     QmiNasRatModePreference nas_ssp_mode_preference_mask;
     /* NAS Technology Preference */
     QmiNasRadioTechnologyPreference nas_tp_mask;
     /* DMS Capabilities */
     MMModemCapability dms_capabilities;
-} MMQmiCapabilitiesContext;
+} MMQmiCurrentCapabilitiesContext;
 
-MMModemCapability mm_modem_capability_from_qmi_capabilities_context (MMQmiCapabilitiesContext *ctx,
-                                                                     gpointer                  log_object);
+MMModemCapability mm_current_capability_from_qmi_current_capabilities_context (MMQmiCurrentCapabilitiesContext *ctx,
+                                                                               gpointer                         log_object);
+
+/*****************************************************************************/
+/* Utility to build list of supported capabilities from various sources */
+
+typedef struct {
+    /* Whether this is a multimode device or not */
+    gboolean multimode;
+    /* NAS System Selection Preference */
+    gboolean nas_ssp_supported;
+    /* NAS Technology Preference */
+    gboolean nas_tp_supported;
+    /* DMS Capabilities */
+    MMModemCapability dms_capabilities;
+} MMQmiSupportedCapabilitiesContext;
+
+GArray *mm_supported_capabilities_from_qmi_supported_capabilities_context (MMQmiSupportedCapabilitiesContext *ctx,
+                                                                           gpointer                           log_object);
+
+/*****************************************************************************/
+/* Utility to build list of supported modes from various sources */
+
+typedef struct {
+    /* Whether this is a multimode device or not */
+    gboolean multimode;
+    /* NAS System Selection Preference */
+    gboolean nas_ssp_supported;
+    /* NAS Technology Preference */
+    gboolean nas_tp_supported;
+    /* Mask with all supported modes */
+    MMModemMode all;
+    /* Current Capabilities */
+    MMModemCapability current_capabilities;
+} MMQmiSupportedModesContext;
+
+GArray *mm_supported_modes_from_qmi_supported_modes_context (MMQmiSupportedModesContext *ctx,
+                                                             gpointer                    log_object);
 
 /*****************************************************************************/
 /* QMI unique id manipulation */
diff --git a/src/mm-modem-helpers.c b/src/mm-modem-helpers.c
index 3e22262..c969b66 100644
--- a/src/mm-modem-helpers.c
+++ b/src/mm-modem-helpers.c
@@ -13,6 +13,7 @@
  * Copyright (C) 2008 - 2009 Novell, Inc.
  * Copyright (C) 2009 - 2012 Red Hat, Inc.
  * Copyright (C) 2012 Google, Inc.
+ * Copyright (c) 2021 Qualcomm Innovation Center, Inc. All rights reserved.
  */
 
 #include <config.h>
@@ -393,7 +394,6 @@
     MMModemModeCombination all_item;
     guint i;
     GArray *filtered_combinations;
-    gboolean all_item_added = FALSE;
 
     g_return_val_if_fail (all != NULL, NULL);
     g_return_val_if_fail (all->len == 1, NULL);
@@ -416,8 +416,6 @@
              * containing all supported modes, we're already good to go. This allows us to have a
              * default with preferred != NONE (e.g. Wavecom 2G modem with allowed=CS+2G and
              * preferred=2G */
-            if (all_item.allowed == mode->allowed)
-                all_item_added = TRUE;
             g_array_append_val (filtered_combinations, *mode);
         }
     }
@@ -425,12 +423,6 @@
     if (filtered_combinations->len == 0)
         mm_obj_warn (log_object, "all supported mode combinations were filtered out");
 
-    /* Add default entry with the generic mask including all items */
-    if (!all_item_added) {
-        mm_obj_dbg (log_object, "adding an explicit item with all supported modes allowed");
-        g_array_append_val (filtered_combinations, all_item);
-    }
-
     mm_obj_dbg (log_object, "device supports %u different mode combinations",
                 filtered_combinations->len);
 
diff --git a/src/mm-plugin-manager.c b/src/mm-plugin-manager.c
index f0bdba0..8ec85a5 100644
--- a/src/mm-plugin-manager.c
+++ b/src/mm-plugin-manager.c
@@ -22,6 +22,8 @@
 #include <gmodule.h>
 #include <gio/gio.h>
 
+#include <config.h>
+
 #include <ModemManager.h>
 #include <mm-errors-types.h>
 
@@ -713,8 +715,14 @@
 #define MIN_PROBING_TIME_MSECS 2500
 
 /* Additional time to wait for other ports to appear after the last port is
- * exposed in the system. */
-#define EXTRA_PROBING_TIME_MSECS 1500
+ * exposed in the system. Longer time when not using udev, as we rely on
+ * mmcli --report-kernel-event events to report new port additions, e.g.
+ * via openwrt hotplug scripts. */
+#if defined WITH_UDEV
+# define EXTRA_PROBING_TIME_MSECS 1500
+#else
+# define EXTRA_PROBING_TIME_MSECS 3000
+#endif
 
 /* The wait time we define must always be less than the probing time */
 G_STATIC_ASSERT (MIN_WAIT_TIME_MSECS < MIN_PROBING_TIME_MSECS);
diff --git a/src/mm-shared-qmi.c b/src/mm-shared-qmi.c
index c9a9081..1447a7c 100644
--- a/src/mm-shared-qmi.c
+++ b/src/mm-shared-qmi.c
@@ -70,6 +70,7 @@
 
 typedef struct {
     /* Capabilities & modes helpers */
+    gboolean           multimode;
     MMModemCapability  current_capabilities;
     GArray            *supported_radio_interfaces;
     Feature            feature_nas_tp;
@@ -77,7 +78,6 @@
     Feature            feature_nas_ssp_extended_lte_band_preference;
     Feature            feature_nas_ssp_acquisition_order_preference;
     GArray            *feature_nas_ssp_acquisition_order_preference_array;
-    gboolean           disable_4g_only_mode;
     GArray            *supported_bands;
 
     /* Location helpers */
@@ -799,10 +799,10 @@
 } LoadCurrentCapabilitiesStep;
 
 typedef struct {
-    QmiClientNas                *nas_client;
-    QmiClientDms                *dms_client;
-    LoadCurrentCapabilitiesStep  step;
-    MMQmiCapabilitiesContext     capabilities_context;
+    QmiClientNas                    *nas_client;
+    QmiClientDms                    *dms_client;
+    LoadCurrentCapabilitiesStep      step;
+    MMQmiCurrentCapabilitiesContext  capabilities_context;
 } LoadCurrentCapabilitiesContext;
 
 MMModemCapability
@@ -1024,7 +1024,13 @@
     case LOAD_CURRENT_CAPABILITIES_STEP_LAST:
         g_assert (priv->feature_nas_tp != FEATURE_UNKNOWN);
         g_assert (priv->feature_nas_ssp != FEATURE_UNKNOWN);
-        priv->current_capabilities = mm_modem_capability_from_qmi_capabilities_context (&ctx->capabilities_context, self);
+
+        /* At this point we can already know if this is a multimode device or not */
+        if ((ctx->capabilities_context.dms_capabilities & MM_MODEM_CAPABILITY_MULTIMODE) == MM_MODEM_CAPABILITY_MULTIMODE)
+            priv->multimode = ctx->capabilities_context.multimode = TRUE;
+
+        priv->current_capabilities = mm_current_capability_from_qmi_current_capabilities_context (&ctx->capabilities_context, self);
+
         g_task_return_int (task, priv->current_capabilities);
         g_object_unref (task);
         return;
@@ -1103,12 +1109,11 @@
                                            GAsyncReadyCallback  callback,
                                            gpointer             user_data)
 {
-    GTask             *task;
-    Private           *priv;
-    MMModemCapability  mask;
-    MMModemCapability  single;
-    GArray            *supported_combinations;
-    guint              i;
+    GTask                             *task;
+    Private                           *priv;
+    GArray                            *supported_combinations;
+    guint                              i;
+    MMQmiSupportedCapabilitiesContext  ctx = { 0 };
 
     task = g_task_new (self, NULL, callback, user_data);
 
@@ -1122,53 +1127,16 @@
     }
 
     /* Build mask with all supported capabilities */
-    mask = MM_MODEM_CAPABILITY_NONE;
+    ctx.dms_capabilities = MM_MODEM_CAPABILITY_NONE;
     for (i = 0; i < priv->supported_radio_interfaces->len; i++)
-        mask |= mm_modem_capability_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self);
+        ctx.dms_capabilities |= mm_modem_capability_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self);
 
-    supported_combinations = g_array_sized_new (FALSE, FALSE, sizeof (MMModemCapability), 3);
+    ctx.nas_tp_supported = (priv->feature_nas_tp == FEATURE_SUPPORTED);
+    ctx.nas_ssp_supported = (priv->feature_nas_ssp == FEATURE_SUPPORTED);
+    ctx.multimode = priv->multimode;
 
-    /* Add all possible supported capability combinations.
-     * In order to avoid unnecessary modem reboots, we will only implement capabilities
-     * switching only when switching GSM/UMTS+CDMA/EVDO multimode devices, and only if
-     * we have support for the commands doing it.
-     */
-    if (priv->feature_nas_tp == FEATURE_SUPPORTED || priv->feature_nas_ssp == FEATURE_SUPPORTED) {
-        if (mask == (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO)) {
-            /* Multimode GSM/UMTS+CDMA/EVDO device switched to GSM/UMTS only */
-            single = MM_MODEM_CAPABILITY_GSM_UMTS;
-            g_array_append_val (supported_combinations, single);
-            /* Multimode GSM/UMTS+CDMA/EVDO device switched to CDMA/EVDO only */
-            single = MM_MODEM_CAPABILITY_CDMA_EVDO;
-            g_array_append_val (supported_combinations, single);
-        } else if (mask == (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE)) {
-            /* Multimode GSM/UMTS+CDMA/EVDO+LTE device switched to GSM/UMTS+LTE only */
-            single = MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE;
-            g_array_append_val (supported_combinations, single);
-            /* Multimode GSM/UMTS+CDMA/EVDO+LTE device switched to CDMA/EVDO+LTE only */
-            single = MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE;
-            g_array_append_val (supported_combinations, single);
-            /*
-             * Multimode GSM/UMTS+CDMA/EVDO+LTE device switched to LTE only.
-             *
-             * This case is required because we use the same methods and operations to
-             * switch capabilities and modes. For the LTE capability there is a direct
-             * related 4G mode, and so we cannot select a '4G only' mode in this device
-             * because we wouldn't be able to know the full list of current capabilities
-             * if the device was rebooted, as we would only see LTE capability. So,
-             * handle this special case so that the LTE/4G-only mode can exclusively be
-             * selected as capability switching in this kind of devices.
-             */
-            priv->disable_4g_only_mode = TRUE;
-            single = MM_MODEM_CAPABILITY_LTE;
-            g_array_append_val (supported_combinations, single);
-        }
-    }
-
-    /* Add the full mask itself */
-    single = mask;
-    g_array_append_val (supported_combinations, single);
-
+    /* Build list of supported combinations */
+    supported_combinations = mm_supported_capabilities_from_qmi_supported_capabilities_context (&ctx, self);
     g_task_return_pointer (task, supported_combinations, (GDestroyNotify) g_array_unref);
     g_object_unref (task);
 }
@@ -1718,106 +1686,38 @@
                                     GAsyncReadyCallback  callback,
                                     gpointer             user_data)
 {
-    GTask                  *task;
-    GArray                 *combinations;
-    MMModemModeCombination  mode;
-    Private                *priv;
-    MMModemMode             mask_all;
-    guint                   i;
-    GArray                 *all;
-    GArray                 *filtered;
+    GTask                      *task;
+    Private                    *priv;
+    MMQmiSupportedModesContext  ctx = { 0 };
+    guint                       i;
+    GArray                     *combinations;
 
     task = g_task_new (self, NULL, callback, user_data);
 
     priv = get_private (MM_SHARED_QMI (self));
     g_assert (priv->supported_radio_interfaces);
+    g_assert (priv->current_capabilities);
 
     /* Build all, based on the supported radio interfaces */
-    mask_all = MM_MODEM_MODE_NONE;
+    ctx.all = MM_MODEM_MODE_NONE;
     for (i = 0; i < priv->supported_radio_interfaces->len; i++)
-        mask_all |= mm_modem_mode_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self);
-    mode.allowed = mask_all;
-    mode.preferred = MM_MODEM_MODE_NONE;
-    all = g_array_sized_new (FALSE, FALSE, sizeof (MMModemModeCombination), 1);
-    g_array_append_val (all, mode);
+        ctx.all |= mm_modem_mode_from_qmi_radio_interface (g_array_index (priv->supported_radio_interfaces, QmiDmsRadioInterface, i), self);
 
-    /* If SSP and TP are not supported, ignore supported mode management */
-    if (priv->feature_nas_ssp == FEATURE_UNSUPPORTED && priv->feature_nas_tp == FEATURE_UNSUPPORTED) {
-        g_task_return_pointer (task, all, (GDestroyNotify) g_array_unref);
-        g_object_unref (task);
-        return;
-    }
+    /* Filter out those unsupported by the current capabilities */
+    if (!(priv->current_capabilities & (MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO)))
+        ctx.all &= ~(MM_MODEM_MODE_2G | MM_MODEM_MODE_3G);
+    else if (!(priv->current_capabilities & MM_MODEM_CAPABILITY_LTE))
+        ctx.all &= ~MM_MODEM_MODE_4G;
+    else if (!(priv->current_capabilities & MM_MODEM_CAPABILITY_5GNR))
+        ctx.all &= ~MM_MODEM_MODE_5G;
 
-    combinations = g_array_new (FALSE, FALSE, sizeof (MMModemModeCombination));
+    ctx.nas_ssp_supported = (priv->feature_nas_ssp == FEATURE_SUPPORTED);
+    ctx.nas_tp_supported = (priv->feature_nas_tp == FEATURE_SUPPORTED);
+    ctx.current_capabilities = priv->current_capabilities;
+    ctx.multimode = priv->multimode;
 
-#define ADD_MODE_PREFERENCE(MODE1, MODE2, MODE3, MODE4) do {            \
-        mode.allowed = MODE1;                                           \
-        if (MODE2 != MM_MODEM_MODE_NONE) {                              \
-            mode.allowed |= MODE2;                                      \
-            if (MODE3 != MM_MODEM_MODE_NONE) {                          \
-                mode.allowed |= MODE3;                                  \
-                if (MODE4 != MM_MODEM_MODE_NONE)                        \
-                    mode.allowed |= MODE4;                              \
-            }                                                           \
-            if (priv->feature_nas_ssp != FEATURE_UNSUPPORTED) {         \
-                if (MODE3 != MM_MODEM_MODE_NONE) {                      \
-                    if (MODE4 != MM_MODEM_MODE_NONE) {                  \
-                        mode.preferred = MODE4;                         \
-                        g_array_append_val (combinations, mode);        \
-                    }                                                   \
-                    mode.preferred = MODE3;                             \
-                    g_array_append_val (combinations, mode);            \
-                }                                                       \
-                mode.preferred = MODE2;                                 \
-                g_array_append_val (combinations, mode);                \
-                mode.preferred = MODE1;                                 \
-                g_array_append_val (combinations, mode);                \
-            } else {                                                    \
-                mode.preferred = MM_MODEM_MODE_NONE;                    \
-                g_array_append_val (combinations, mode);                \
-            }                                                           \
-        } else {                                                        \
-            mode.allowed = MODE1;                                       \
-            mode.preferred = MM_MODEM_MODE_NONE;                        \
-            g_array_append_val (combinations, mode);                    \
-        }                                                               \
-    } while (0)
-
-    /* 2G-only, 3G-only */
-    ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
-    ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
-
-    /* 4G-only mode is not possible in multimode GSM/UMTS+CDMA/EVDO+LTE
-     * devices. This configuration may be selected as "LTE only" capability
-     * instead. */
-    if (!priv->disable_4g_only_mode)
-        ADD_MODE_PREFERENCE (MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
-
-    /* 2G, 3G, 4G combinations */
-    ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
-    ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
-    ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_4G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
-    ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G, MM_MODEM_MODE_4G,   MM_MODEM_MODE_NONE);
-
-    /* 5G related mode combinations are only supported when NAS SSP is supported,
-     * as there is no 5G support in NAS TP. */
-    if (priv->feature_nas_ssp != FEATURE_UNSUPPORTED) {
-        ADD_MODE_PREFERENCE (MM_MODEM_MODE_5G, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
-        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
-        ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
-        ADD_MODE_PREFERENCE (MM_MODEM_MODE_4G, MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE, MM_MODEM_MODE_NONE);
-        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G,   MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE);
-        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_4G,   MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE);
-        ADD_MODE_PREFERENCE (MM_MODEM_MODE_3G, MM_MODEM_MODE_4G,   MM_MODEM_MODE_5G,   MM_MODEM_MODE_NONE);
-        ADD_MODE_PREFERENCE (MM_MODEM_MODE_2G, MM_MODEM_MODE_3G,   MM_MODEM_MODE_4G,   MM_MODEM_MODE_5G);
-    }
-
-    /* Filter out unsupported modes */
-    filtered = mm_filter_supported_modes (all, combinations, self);
-    g_array_unref (all);
-    g_array_unref (combinations);
-
-    g_task_return_pointer (task, filtered, (GDestroyNotify) g_array_unref);
+    combinations = mm_supported_modes_from_qmi_supported_modes_context (&ctx, self);
+    g_task_return_pointer (task, combinations, (GDestroyNotify) g_array_unref);
     g_object_unref (task);
 }
 
diff --git a/src/mm-sim-mbim.c b/src/mm-sim-mbim.c
index b04c35f..b0addb2 100644
--- a/src/mm-sim-mbim.c
+++ b/src/mm-sim-mbim.c
@@ -78,6 +78,24 @@
     return TRUE;
 }
 
+static void
+update_modem_unlock_retries (MMSimMbim *self,
+                             MbimPinType pin_type,
+                             guint32 remaining_attempts)
+{
+    MMBaseModem *modem = NULL;
+
+    g_object_get (G_OBJECT (self),
+                  MM_BASE_SIM_MODEM, &modem,
+                  NULL);
+    g_assert (MM_IS_BASE_MODEM (modem));
+
+    mm_broadband_modem_mbim_set_unlock_retries (MM_BROADBAND_MODEM_MBIM (modem),
+                                                mm_modem_lock_from_mbim_pin_type (pin_type),
+                                                remaining_attempts);
+    g_object_unref (modem);
+}
+
 /*****************************************************************************/
 /* Preload subscriber info */
 
@@ -786,6 +804,8 @@
                                              &pin_state,
                                              &remaining_attempts,
                                              NULL)) {
+            update_modem_unlock_retries (self, pin_type, remaining_attempts);
+
             if (!success) {
                 /* Sending PIN failed, build a better error to report */
                 if (pin_type == MBIM_PIN_TYPE_PIN1 && pin_state == MBIM_PIN_STATE_LOCKED) {
@@ -881,6 +901,8 @@
                                              &pin_state,
                                              &remaining_attempts,
                                              NULL)) {
+            update_modem_unlock_retries (self, pin_type, remaining_attempts);
+
             if (!success) {
                 /* Sending PUK failed, build a better error to report */
                 if (pin_type == MBIM_PIN_TYPE_PUK1 && pin_state == MBIM_PIN_STATE_LOCKED) {
@@ -958,12 +980,25 @@
                       GAsyncResult *res,
                       GTask *task)
 {
+    MMSimMbim *self;
     GError *error = NULL;
     MbimMessage *response;
+    MbimPinType pin_type;
+    guint32 remaining_attempts;
+
+    self = g_task_get_source_object (task);
 
     response = mbim_device_command_finish (device, res, &error);
     if (response) {
         mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);
+
+        if (mbim_message_pin_response_parse (response,
+                                             &pin_type,
+                                             NULL,
+                                             &remaining_attempts,
+                                             NULL))
+            update_modem_unlock_retries (self, pin_type, remaining_attempts);
+
         mbim_message_unref (response);
     }
 
@@ -1036,12 +1071,25 @@
                       GAsyncResult *res,
                       GTask *task)
 {
+    MMSimMbim *self;
     GError *error = NULL;
     MbimMessage *response;
+    MbimPinType pin_type;
+    guint32 remaining_attempts;
+
+    self = g_task_get_source_object (task);
 
     response = mbim_device_command_finish (device, res, &error);
     if (response) {
         mbim_message_response_get_result (response, MBIM_MESSAGE_TYPE_COMMAND_DONE, &error);
+
+        if (mbim_message_pin_response_parse (response,
+                                             &pin_type,
+                                             NULL,
+                                             &remaining_attempts,
+                                             NULL))
+            update_modem_unlock_retries (self, pin_type, remaining_attempts);
+
         mbim_message_unref (response);
     }
 
diff --git a/src/tests/test-modem-helpers-qmi.c b/src/tests/test-modem-helpers-qmi.c
index fbd6cdb..f49e1e8 100644
--- a/src/tests/test-modem-helpers-qmi.c
+++ b/src/tests/test-modem-helpers-qmi.c
@@ -11,6 +11,7 @@
  * GNU General Public License for more details:
  *
  * Copyright (C) 2012 Lanedo GmbH.
+ * Copyright (c) 2021-2022 Qualcomm Innovation Center, Inc. All rights reserved.
  */
 
 #include <glib.h>
@@ -19,28 +20,65 @@
 #include <stdlib.h>
 #include <locale.h>
 
+#include <ModemManager.h>
+#define _LIBMM_INSIDE_MM
+#include <libmm-glib.h>
+
 #include "mm-enums-types.h"
 #include "mm-modem-helpers-qmi.h"
 #include "mm-log-test.h"
 
 static void
-test_capabilities_expected (MMQmiCapabilitiesContext *ctx,
-                            MMModemCapability expected)
+test_current_capabilities_expected (MMQmiCurrentCapabilitiesContext *ctx,
+                                    MMModemCapability                expected)
 {
-    MMModemCapability built;
-    gchar *expected_str;
-    gchar *built_str;
+    MMModemCapability  built;
+    g_autofree gchar  *expected_str = NULL;
+    g_autofree gchar  *built_str = NULL;
 
-    built = mm_modem_capability_from_qmi_capabilities_context (ctx, NULL);
+    built = mm_current_capability_from_qmi_current_capabilities_context (ctx, NULL);
 
     expected_str = mm_modem_capability_build_string_from_mask (expected);
     built_str = mm_modem_capability_build_string_from_mask (built);
 
     /* compare strings, so that the error shows the string values as well */
     g_assert_cmpstr (built_str, ==, expected_str);
+}
 
-    g_free (expected_str);
-    g_free (built_str);
+static void
+test_supported_capabilities_expected (MMQmiSupportedCapabilitiesContext *ctx,
+                                      const MMModemCapability           *expected_capabilities,
+                                      guint                              n_expected_capabilities)
+{
+    g_autoptr(GArray)  built = NULL;
+    g_autofree gchar  *expected_str = NULL;
+    g_autofree gchar  *built_str = NULL;
+
+    built = mm_supported_capabilities_from_qmi_supported_capabilities_context (ctx, NULL);
+
+    expected_str = mm_common_build_capabilities_string (expected_capabilities, n_expected_capabilities);
+    built_str = mm_common_build_capabilities_string ((MMModemCapability *)built->data, built->len);
+
+    /* compare strings, so that the error shows the string values as well */
+    g_assert_cmpstr (built_str, ==, expected_str);
+}
+
+static void
+test_supported_modes_expected (MMQmiSupportedModesContext   *ctx,
+                               const MMModemModeCombination *expected_modes,
+                               guint                         n_expected_modes)
+{
+    g_autoptr(GArray)  built = NULL;
+    g_autofree gchar  *expected_str = NULL;
+    g_autofree gchar  *built_str = NULL;
+
+    built = mm_supported_modes_from_qmi_supported_modes_context (ctx, NULL);
+
+    expected_str = mm_common_build_mode_combinations_string (expected_modes, n_expected_modes);
+    built_str = mm_common_build_mode_combinations_string ((MMModemModeCombination *)built->data, built->len);
+
+    /* compare strings, so that the error shows the string values as well */
+    g_assert_cmpstr (built_str, ==, expected_str);
 }
 
 /*****************************************************************************/
@@ -57,9 +95,11 @@
 */
 
 static void
-test_uml290 (void)
+test_current_capabilities_uml290 (void)
 {
-    MMQmiCapabilitiesContext ctx;
+    MMQmiCurrentCapabilitiesContext ctx;
+
+    ctx.multimode = TRUE;
 
     /* QCDM -> CDMA/EVDO */
     ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1X |
@@ -68,7 +108,9 @@
     ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
                             MM_MODEM_CAPABILITY_CDMA_EVDO |
                             MM_MODEM_CAPABILITY_LTE);
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                         MM_MODEM_CAPABILITY_LTE));
 
     /* QCDM -> GSM/UMTS */
     ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_GSM |
@@ -77,7 +119,9 @@
     ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
                             MM_MODEM_CAPABILITY_CDMA_EVDO |
                             MM_MODEM_CAPABILITY_LTE);
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                         MM_MODEM_CAPABILITY_LTE));
 
     /* QCDM -> Automatic */
     ctx.nas_ssp_mode_preference_mask = 0;
@@ -85,10 +129,147 @@
     ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
                             MM_MODEM_CAPABILITY_CDMA_EVDO |
                             MM_MODEM_CAPABILITY_LTE);
-    test_capabilities_expected (&ctx,
-                                (MM_MODEM_CAPABILITY_GSM_UMTS |
-                                 MM_MODEM_CAPABILITY_CDMA_EVDO |
-                                 MM_MODEM_CAPABILITY_LTE));
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                         MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                         MM_MODEM_CAPABILITY_LTE));
+}
+
+static void
+test_supported_capabilities_uml290 (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE,
+        MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE,
+        MM_MODEM_CAPABILITY_LTE,
+        MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE,
+    };
+
+    ctx.multimode = TRUE;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = FALSE;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE);
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_uml290_cdma_evdo_gsm_umts_lte (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        /* we MUST not have 4G-only in multimode devices */
+        { .allowed = MM_MODEM_MODE_2G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_2G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_2G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_2G },
+    };
+
+    ctx.multimode = TRUE;
+    ctx.all = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = FALSE;
+    ctx.current_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                MM_MODEM_CAPABILITY_LTE);
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
+}
+
+static void
+test_supported_modes_uml290_cdma_evdo_lte (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        /* we MUST not have 4G-only in multimode devices */
+        { .allowed = MM_MODEM_MODE_2G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_2G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_2G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_2G },
+    };
+
+    ctx.multimode = TRUE;
+    ctx.all = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = FALSE;
+    ctx.current_capabilities = (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                MM_MODEM_CAPABILITY_LTE);
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
+}
+
+static void
+test_supported_modes_uml290_gsm_umts_lte (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        /* we MUST not have 4G-only in multimode devices */
+        { .allowed = MM_MODEM_MODE_2G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_2G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_2G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_2G },
+    };
+
+    ctx.multimode = TRUE;
+    ctx.all = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = FALSE;
+    ctx.current_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                MM_MODEM_CAPABILITY_LTE);
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
+}
+
+static void
+test_supported_modes_uml290_lte (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        /* we MUST only have 4G-only in a multimode device with only LTE capability */
+        { .allowed = MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_NONE },
+    };
+
+    ctx.multimode = TRUE;
+    ctx.all = MM_MODEM_MODE_4G;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = FALSE;
+    ctx.current_capabilities = MM_MODEM_CAPABILITY_LTE;
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
 }
 
 /*****************************************************************************/
@@ -102,19 +283,60 @@
  */
 
 static void
-test_adu960s (void)
+test_current_capabilities_adu960s (void)
 {
-    MMQmiCapabilitiesContext ctx;
+    MMQmiCurrentCapabilitiesContext ctx;
 
-    ctx.nas_ssp_mode_preference_mask = 0;
+    ctx.multimode = TRUE;
+    ctx.nas_ssp_mode_preference_mask = 0; /* Unsupported */
     ctx.nas_tp_mask = 0; /* Unsupported */
     ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
                             MM_MODEM_CAPABILITY_CDMA_EVDO |
                             MM_MODEM_CAPABILITY_LTE);
-    test_capabilities_expected (&ctx,
-                                (MM_MODEM_CAPABILITY_GSM_UMTS |
-                                 MM_MODEM_CAPABILITY_CDMA_EVDO |
-                                 MM_MODEM_CAPABILITY_LTE));
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                         MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                         MM_MODEM_CAPABILITY_LTE));
+}
+
+static void
+test_supported_capabilities_adu960s (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE,
+    };
+
+    ctx.multimode = TRUE;
+    ctx.nas_ssp_supported = FALSE;
+    ctx.nas_tp_supported = FALSE;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE);
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_adu960s (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_NONE },
+    };
+
+    ctx.multimode = TRUE;
+    ctx.all = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G;
+    ctx.nas_ssp_supported = FALSE;
+    ctx.nas_tp_supported = FALSE;
+    ctx.current_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                MM_MODEM_CAPABILITY_LTE);
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
 }
 
 /*****************************************************************************/
@@ -127,14 +349,54 @@
  */
 
 static void
-test_gobi1k_gsm (void)
+test_current_capabilities_gobi1k_gsm (void)
 {
-    MMQmiCapabilitiesContext ctx;
+    MMQmiCurrentCapabilitiesContext ctx;
 
+    ctx.multimode = FALSE;
     ctx.nas_ssp_mode_preference_mask = 0; /* Unsupported */
     ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
+}
+
+static void
+test_supported_capabilities_gobi1k_gsm (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_GSM_UMTS,
+    };
+
+    ctx.multimode = FALSE;
+    ctx.nas_ssp_supported = FALSE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.dms_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_gobi1k_gsm (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        { .allowed = MM_MODEM_MODE_2G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+    };
+
+    ctx.multimode = FALSE;
+    ctx.all = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G;
+    ctx.nas_ssp_supported = FALSE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.current_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
 }
 
 /*****************************************************************************/
@@ -147,14 +409,54 @@
  */
 
 static void
-test_gobi1k_cdma (void)
+test_current_capabilities_gobi1k_cdma (void)
 {
-    MMQmiCapabilitiesContext ctx;
+    MMQmiCurrentCapabilitiesContext ctx;
 
+    ctx.multimode = FALSE;
     ctx.nas_ssp_mode_preference_mask = 0; /* Unsupported */
     ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
+}
+
+static void
+test_supported_capabilities_gobi1k_cdma (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_CDMA_EVDO,
+    };
+
+    ctx.multimode = FALSE;
+    ctx.nas_ssp_supported = FALSE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.dms_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_gobi1k_cdma (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        { .allowed = MM_MODEM_MODE_2G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+    };
+
+    ctx.multimode = FALSE;
+    ctx.all = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G;
+    ctx.nas_ssp_supported = FALSE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.current_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
 }
 
 /*****************************************************************************/
@@ -169,27 +471,68 @@
  */
 
 static void
-test_gobi2k_gsm (void)
+test_current_capabilities_gobi2k_gsm (void)
 {
-    MMQmiCapabilitiesContext ctx;
+    MMQmiCurrentCapabilitiesContext ctx;
+
+    ctx.multimode = FALSE;
 
     /* QCDM -> Automatic */
     ctx.nas_ssp_mode_preference_mask = 0; /* Unsupported */
     ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
 
     /* QCDM -> UMTS only */
     ctx.nas_ssp_mode_preference_mask = 0; /* Unsupported */
     ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_CDMA_OR_WCDMA);
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
 
     /* QCDM -> GPRS only */
     ctx.nas_ssp_mode_preference_mask = 0; /* Unsupported */
     ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AMPS_OR_GSM);
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
+}
+
+static void
+test_supported_capabilities_gobi2k_gsm (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_GSM_UMTS,
+    };
+
+    ctx.multimode = FALSE;
+    ctx.nas_ssp_supported = FALSE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.dms_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_gobi2k_gsm (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        { .allowed = MM_MODEM_MODE_2G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+    };
+
+    ctx.multimode = FALSE;
+    ctx.all = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G;
+    ctx.nas_ssp_supported = FALSE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.current_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
 }
 
 /*****************************************************************************/
@@ -204,27 +547,68 @@
  */
 
 static void
-test_gobi2k_cdma (void)
+test_current_capabilities_gobi2k_cdma (void)
 {
-    MMQmiCapabilitiesContext ctx;
+    MMQmiCurrentCapabilitiesContext ctx;
+
+    ctx.multimode = FALSE;
 
     /* QCDM -> Automatic */
     ctx.nas_ssp_mode_preference_mask = 0; /* Unsupported */
     ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
 
     /* QCDM -> CDMA only */
     ctx.nas_ssp_mode_preference_mask = 0; /* Unsupported */
     ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP2 | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_CDMA_OR_WCDMA);
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
 
     /* QCDM -> EVDO only */
     ctx.nas_ssp_mode_preference_mask = 0; /* Unsupported */
     ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP2 | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_HDR);
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
+}
+
+static void
+test_supported_capabilities_gobi2k_cdma (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_CDMA_EVDO,
+    };
+
+    ctx.multimode = FALSE;
+    ctx.nas_ssp_supported = FALSE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.dms_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_gobi2k_cdma (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        { .allowed = MM_MODEM_MODE_2G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+    };
+
+    ctx.multimode = FALSE;
+    ctx.all = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G;
+    ctx.nas_ssp_supported = FALSE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.current_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
 }
 
 /*****************************************************************************/
@@ -241,9 +625,11 @@
  */
 
 static void
-test_gobi3k_gsm (void)
+test_current_capabilities_gobi3k_gsm (void)
 {
-    MMQmiCapabilitiesContext ctx;
+    MMQmiCurrentCapabilitiesContext ctx;
+
+    ctx.multimode = FALSE;
 
     /* QCDM -> Automatic */
     ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1X |
@@ -252,19 +638,59 @@
                                         QMI_NAS_RAT_MODE_PREFERENCE_UMTS);
     ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
 
     /* QCDM -> GSM only */
     ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_GSM);
     ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AMPS_OR_GSM);
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
 
     /* QCDM -> UMTS only */
     ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_UMTS);
     ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_CDMA_OR_WCDMA);
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_GSM_UMTS);
+}
+
+static void
+test_supported_capabilities_gobi3k_gsm (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_GSM_UMTS,
+    };
+
+    ctx.multimode = FALSE;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.dms_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_gobi3k_gsm (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        { .allowed = MM_MODEM_MODE_2G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_2G },
+    };
+
+    ctx.multimode = FALSE;
+    ctx.all = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.current_capabilities = MM_MODEM_CAPABILITY_GSM_UMTS;
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
 }
 
 /*****************************************************************************/
@@ -281,9 +707,11 @@
  */
 
 static void
-test_gobi3k_cdma (void)
+test_current_capabilities_gobi3k_cdma (void)
 {
-    MMQmiCapabilitiesContext ctx;
+    MMQmiCurrentCapabilitiesContext ctx;
+
+    ctx.multimode = FALSE;
 
     /* QCDM -> Automatic */
     ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1X |
@@ -292,19 +720,615 @@
                                         QMI_NAS_RAT_MODE_PREFERENCE_UMTS);
     ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
 
     /* QCDM -> CDMA only */
     ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1X);
     ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP2 | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_CDMA_OR_WCDMA);
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
 
     /* QCDM -> EVDO only */
     ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1XEVDO);
     ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP2 | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_HDR);
     ctx.dms_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
-    test_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_CDMA_EVDO);
+}
+
+static void
+test_supported_capabilities_gobi3k_cdma (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_CDMA_EVDO,
+    };
+
+    ctx.multimode = FALSE;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.dms_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_gobi3k_cdma (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        { .allowed = MM_MODEM_MODE_2G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_2G },
+    };
+
+    ctx.multimode = FALSE;
+    ctx.all = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.current_capabilities = MM_MODEM_CAPABILITY_CDMA_EVDO;
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
+}
+
+/*****************************************************************************/
+/* Generic with NR5G:
+ * ∘ +GCAP: +CGSM
+ * ∘ +WS46: (12,22,25,28,29,30,31)
+ * ∘ DMS GetCapa: Networks: 'cdma20001x, evdo, gsm, umts, lte, 5gnr'
+ * ∘ QMI -> Automatic = NAS TP:  Active: 'auto', duration: 'permanent'
+ *                      NAS SSP: Mode preference: 'cdma-1x, cdma-1xevdo, gsm, umts, lte, td-scdma, 5gnr'
+ * ∘ QMI -> GSM only  = NAS TP:  Active: '3gpp, amps-or-gsm', duration: 'permanent'
+ *                      NAS SSP: Mode preference: 'gsm'
+ * ∘ QMI -> UMTS only = NAS TP:  Active: '3gpp, cdma-or-wcdma', duration: 'permanent'
+ *                      NAS SSP: Mode preference: 'umts'
+ * ∘ QMI -> EVDO only = NAS TP:  Active: '3gpp2, hdr', duration: 'permanent'
+ *                      NAS SSP: Mode preference: 'cdma-1xevdo'
+ * ∘ QMI -> CDMA only = NAS TP:  Active: '3gpp2, cdma-or-wcdma', duration: 'permanent'
+ *                      NAS SSP: Mode preference: 'cdma-1x'
+ * ∘ QMI -> LTE only  = NAS TP:  Active: '3gpp, lte', duration: 'permanent'
+ *                      NAS SSP: Mode preference: 'lte'
+ * ∘ QMI -> 5GNR only = NAS TP:  Active: 'auto', duration: 'permanent'
+ *                      NAS SSP: Mode preference: '5gnr'
+ */
+
+static void
+test_current_capabilities_generic_nr5g_multimode (void)
+{
+    MMQmiCurrentCapabilitiesContext ctx;
+
+    ctx.multimode = TRUE;
+
+    /* QMI -> Automatic */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1X |
+                                        QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1XEVDO |
+                                        QMI_NAS_RAT_MODE_PREFERENCE_GSM |
+                                        QMI_NAS_RAT_MODE_PREFERENCE_UMTS |
+                                        QMI_NAS_RAT_MODE_PREFERENCE_LTE |
+                                        QMI_NAS_RAT_MODE_PREFERENCE_TD_SCDMA |
+                                        QMI_NAS_RAT_MODE_PREFERENCE_5GNR);
+    ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                         MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> GSM only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_GSM);
+    ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AMPS_OR_GSM);
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> UMTS only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_UMTS);
+    ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_CDMA_OR_WCDMA);
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> EVDO only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1XEVDO);
+    ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP2 | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_HDR);
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> CDMA only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1X);
+    ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP2 | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_CDMA_OR_WCDMA);
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> LTE only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_LTE);
+    ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_LTE);
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> 5GNR only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_5GNR);
+    ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+}
+
+static void
+test_supported_capabilities_generic_nr5g_multimode (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_LTE | MM_MODEM_CAPABILITY_5GNR,
+        MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE | MM_MODEM_CAPABILITY_5GNR,
+        MM_MODEM_CAPABILITY_LTE | MM_MODEM_CAPABILITY_5GNR,
+        MM_MODEM_CAPABILITY_GSM_UMTS | MM_MODEM_CAPABILITY_CDMA_EVDO | MM_MODEM_CAPABILITY_LTE | MM_MODEM_CAPABILITY_5GNR,
+    };
+
+    ctx.multimode = TRUE;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_generic_nr5g_multimode (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        /* we MUST not have 4G-only in multimode devices */
+        /* we MUST not have 5G-only in multimode devices */
+        /* we MUST not have 4G+5G in multimode devices */
+        { .allowed = MM_MODEM_MODE_2G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_2G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_2G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_3G },
+
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_2G },
+
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_2G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_3G },
+
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_2G },
+
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_2G },
+
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_3G },
+
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_3G },
+        { .allowed = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_2G },
+    };
+
+    ctx.multimode = TRUE;
+    ctx.all = MM_MODEM_MODE_2G | MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.current_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                MM_MODEM_CAPABILITY_LTE |
+                                MM_MODEM_CAPABILITY_5GNR);
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
+}
+
+static void
+test_current_capabilities_generic_nr5g_only (void)
+{
+    MMQmiCurrentCapabilitiesContext ctx;
+
+    ctx.multimode = FALSE;
+    ctx.nas_ssp_mode_preference_mask = QMI_NAS_RAT_MODE_PREFERENCE_5GNR;
+    ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
+    ctx.dms_capabilities = MM_MODEM_CAPABILITY_5GNR;
+    test_current_capabilities_expected (&ctx, MM_MODEM_CAPABILITY_5GNR);
+}
+
+static void
+test_supported_capabilities_generic_nr5g_only (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_5GNR,
+    };
+
+    ctx.multimode = FALSE;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.dms_capabilities = MM_MODEM_CAPABILITY_5GNR;
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_generic_nr5g_only (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        { .allowed = MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_NONE },
+    };
+
+    ctx.multimode = FALSE;
+    ctx.all = MM_MODEM_MODE_5G;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.current_capabilities = MM_MODEM_CAPABILITY_5GNR;
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
+}
+
+static void
+test_current_capabilities_generic_nr5g_lte (void)
+{
+    MMQmiCurrentCapabilitiesContext ctx;
+
+    ctx.multimode = FALSE;
+
+    /* QMI -> Automatic */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_LTE |
+                                        QMI_NAS_RAT_MODE_PREFERENCE_5GNR);
+    ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> LTE only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_LTE);
+    ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_LTE);
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> 5GNR only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_5GNR);
+    ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+}
+
+static void
+test_supported_capabilities_generic_nr5g_lte (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_LTE | MM_MODEM_CAPABILITY_5GNR,
+    };
+
+    ctx.multimode = FALSE;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_generic_nr5g_lte (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        { .allowed = MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_NONE },
+        { .allowed = MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_4G },
+    };
+
+    ctx.multimode = FALSE;
+    ctx.all = MM_MODEM_MODE_4G | MM_MODEM_MODE_5G;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.current_capabilities = (MM_MODEM_CAPABILITY_LTE |
+                                MM_MODEM_CAPABILITY_5GNR);
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
+}
+
+static void
+test_current_capabilities_generic_nr5g_lte_umts (void)
+{
+    MMQmiCurrentCapabilitiesContext ctx;
+
+    ctx.multimode = FALSE;
+
+    /* QMI -> Automatic */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_UMTS |
+                                        QMI_NAS_RAT_MODE_PREFERENCE_LTE |
+                                        QMI_NAS_RAT_MODE_PREFERENCE_5GNR);
+    ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> UMTS only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_UMTS);
+    ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_CDMA_OR_WCDMA);
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> LTE only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_LTE);
+    ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_LTE);
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> 5GNR only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_5GNR);
+    ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+}
+
+static void
+test_supported_capabilities_generic_nr5g_lte_umts (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_GSM_UMTS |MM_MODEM_CAPABILITY_LTE | MM_MODEM_CAPABILITY_5GNR,
+    };
+
+    ctx.multimode = FALSE;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_generic_nr5g_lte_umts (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+        /* we MUST have 4G-only in non-multimode devices */
+        { .allowed = MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_NONE },
+
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_3G },
+
+        /* we MUST have 5G-only in non-multimode devices */
+        { .allowed = MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_NONE },
+
+        /* we MUST have 4G+5G in non-multimode devices */
+        { .allowed = MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_4G },
+
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_3G },
+
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_3G },
+    };
+
+    ctx.multimode = FALSE;
+    ctx.all = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.current_capabilities = (MM_MODEM_CAPABILITY_GSM_UMTS |
+                                MM_MODEM_CAPABILITY_LTE |
+                                MM_MODEM_CAPABILITY_5GNR);
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
+}
+
+static void
+test_current_capabilities_generic_nr5g_lte_evdo (void)
+{
+    MMQmiCurrentCapabilitiesContext ctx;
+
+    ctx.multimode = FALSE;
+
+    /* QMI -> Automatic */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1XEVDO |
+                                        QMI_NAS_RAT_MODE_PREFERENCE_LTE |
+                                        QMI_NAS_RAT_MODE_PREFERENCE_5GNR);
+    ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> EVDO only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_CDMA_1XEVDO);
+    ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP2 | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_HDR);
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> LTE only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_LTE);
+    ctx.nas_tp_mask = (QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_3GPP | QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_LTE);
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+
+    /* QMI -> 5GNR only */
+    ctx.nas_ssp_mode_preference_mask = (QMI_NAS_RAT_MODE_PREFERENCE_5GNR);
+    ctx.nas_tp_mask = QMI_NAS_RADIO_TECHNOLOGY_PREFERENCE_AUTO;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+    test_current_capabilities_expected (&ctx,
+                                        (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                         MM_MODEM_CAPABILITY_LTE |
+                                         MM_MODEM_CAPABILITY_5GNR));
+}
+
+static void
+test_supported_capabilities_generic_nr5g_lte_evdo (void)
+{
+    MMQmiSupportedCapabilitiesContext ctx;
+    static const MMModemCapability expected_capabilities[] = {
+        MM_MODEM_CAPABILITY_CDMA_EVDO |MM_MODEM_CAPABILITY_LTE | MM_MODEM_CAPABILITY_5GNR,
+    };
+
+    ctx.multimode = FALSE;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.dms_capabilities = (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                            MM_MODEM_CAPABILITY_LTE |
+                            MM_MODEM_CAPABILITY_5GNR);
+
+    test_supported_capabilities_expected (&ctx,
+                                          expected_capabilities,
+                                          G_N_ELEMENTS (expected_capabilities));
+}
+
+static void
+test_supported_modes_generic_nr5g_lte_evdo (void)
+{
+    MMQmiSupportedModesContext ctx;
+    static const MMModemModeCombination expected_modes[] = {
+        { .allowed = MM_MODEM_MODE_3G, .preferred = MM_MODEM_MODE_NONE },
+        /* we MUST have 4G-only in non-multimode devices */
+        { .allowed = MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_NONE },
+
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G, .preferred = MM_MODEM_MODE_3G },
+
+        /* we MUST have 5G-only in non-multimode devices */
+        { .allowed = MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_NONE },
+
+        /* we MUST have 4G+5G in non-multimode devices */
+        { .allowed = MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_4G },
+
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_3G },
+
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_5G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_4G },
+        { .allowed = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G, .preferred = MM_MODEM_MODE_3G },
+    };
+
+    ctx.multimode = FALSE;
+    ctx.all = MM_MODEM_MODE_3G | MM_MODEM_MODE_4G | MM_MODEM_MODE_5G;
+    ctx.nas_ssp_supported = TRUE;
+    ctx.nas_tp_supported = TRUE;
+    ctx.current_capabilities = (MM_MODEM_CAPABILITY_CDMA_EVDO |
+                                MM_MODEM_CAPABILITY_LTE |
+                                MM_MODEM_CAPABILITY_5GNR);
+
+    test_supported_modes_expected (&ctx,
+                                   expected_modes,
+                                   G_N_ELEMENTS (expected_modes));
 }
 
 /*****************************************************************************/
@@ -315,14 +1339,60 @@
 
     g_test_init (&argc, &argv, NULL);
 
-    g_test_add_func ("/MM/QMI/Current-Capabilities/UML290",      test_uml290);
-    g_test_add_func ("/MM/QMI/Current-Capabilities/ADU960S",     test_adu960s);
-    g_test_add_func ("/MM/QMI/Current-Capabilities/Gobi1k/GSM",  test_gobi1k_gsm);
-    g_test_add_func ("/MM/QMI/Current-Capabilities/Gobi1k/CDMA", test_gobi1k_cdma);
-    g_test_add_func ("/MM/QMI/Current-Capabilities/Gobi2k/GSM",  test_gobi2k_gsm);
-    g_test_add_func ("/MM/QMI/Current-Capabilities/Gobi2k/CDMA", test_gobi2k_cdma);
-    g_test_add_func ("/MM/QMI/Current-Capabilities/Gobi3k/GSM",  test_gobi3k_gsm);
-    g_test_add_func ("/MM/QMI/Current-Capabilities/Gobi3k/CDMA", test_gobi3k_cdma);
+    g_test_add_func ("/MM/qmi/current-capabilities/UML290",                   test_current_capabilities_uml290);
+    g_test_add_func ("/MM/qmi/supported-capabilities/UML290",                 test_supported_capabilities_uml290);
+    g_test_add_func ("/MM/qmi/supported-modes/UML290/cdma-evdo-gsm-umts-lte", test_supported_modes_uml290_cdma_evdo_gsm_umts_lte);
+    g_test_add_func ("/MM/qmi/supported-modes/UML290/cdma-evdo-lte",          test_supported_modes_uml290_cdma_evdo_lte);
+    g_test_add_func ("/MM/qmi/supported-modes/UML290/gsm-umts-lte",           test_supported_modes_uml290_gsm_umts_lte);
+    g_test_add_func ("/MM/qmi/supported-modes/UML290/lte",                    test_supported_modes_uml290_lte);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/ADU960S",                  test_current_capabilities_adu960s);
+    g_test_add_func ("/MM/qmi/supported-capabilities/ADU960S",                test_supported_capabilities_adu960s);
+    g_test_add_func ("/MM/qmi/supported-modes/ADU960S",                       test_supported_modes_adu960s);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/Gobi1k/GSM",               test_current_capabilities_gobi1k_gsm);
+    g_test_add_func ("/MM/qmi/supported-capabilities/Gobi1k/GSM",             test_supported_capabilities_gobi1k_gsm);
+    g_test_add_func ("/MM/qmi/supported-modes/Gobi1k/GSM",                    test_supported_modes_gobi1k_gsm);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/Gobi1k/CDMA",              test_current_capabilities_gobi1k_cdma);
+    g_test_add_func ("/MM/qmi/supported-capabilities/Gobi1k/CDMA",            test_supported_capabilities_gobi1k_cdma);
+    g_test_add_func ("/MM/qmi/supported-modes/Gobi1k/CDMA",                   test_supported_modes_gobi1k_cdma);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/Gobi2k/GSM",               test_current_capabilities_gobi2k_gsm);
+    g_test_add_func ("/MM/qmi/supported-capabilities/Gobi2k/GSM",             test_supported_capabilities_gobi2k_gsm);
+    g_test_add_func ("/MM/qmi/supported-modes/Gobi2k/GSM",                    test_supported_modes_gobi2k_gsm);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/Gobi2k/CDMA",              test_current_capabilities_gobi2k_cdma);
+    g_test_add_func ("/MM/qmi/supported-capabilities/Gobi2k/CDMA",            test_supported_capabilities_gobi2k_cdma);
+    g_test_add_func ("/MM/qmi/supported-modes/Gobi2k/CDMA",                   test_supported_modes_gobi2k_cdma);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/Gobi3k/GSM",               test_current_capabilities_gobi3k_gsm);
+    g_test_add_func ("/MM/qmi/supported-capabilities/Gobi3k/GSM",             test_supported_capabilities_gobi3k_gsm);
+    g_test_add_func ("/MM/qmi/supported-modes/Gobi3k/GSM",                    test_supported_modes_gobi3k_gsm);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/Gobi3k/CDMA",              test_current_capabilities_gobi3k_cdma);
+    g_test_add_func ("/MM/qmi/supported-capabilities/Gobi3k/CDMA",            test_supported_capabilities_gobi3k_cdma);
+    g_test_add_func ("/MM/qmi/supported-modes/Gobi3k/CDMA",                   test_supported_modes_gobi3k_cdma);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/generic/nr5g-multimode",   test_current_capabilities_generic_nr5g_multimode);
+    g_test_add_func ("/MM/qmi/supported-capabilities/generic/nr5g-multimode", test_supported_capabilities_generic_nr5g_multimode);
+    g_test_add_func ("/MM/qmi/supported-modes/generic/nr5g-multimode",        test_supported_modes_generic_nr5g_multimode);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/generic/nr5g-only",        test_current_capabilities_generic_nr5g_only);
+    g_test_add_func ("/MM/qmi/supported-capabilities/generic/nr5g-only",      test_supported_capabilities_generic_nr5g_only);
+    g_test_add_func ("/MM/qmi/supported-modes/generic/nr5g-only",             test_supported_modes_generic_nr5g_only);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/generic/nr5g-lte",         test_current_capabilities_generic_nr5g_lte);
+    g_test_add_func ("/MM/qmi/supported-capabilities/generic/nr5g-lte",       test_supported_capabilities_generic_nr5g_lte);
+    g_test_add_func ("/MM/qmi/supported-modes/generic/nr5g-lte",              test_supported_modes_generic_nr5g_lte);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/generic/nr5g-lte-umts",    test_current_capabilities_generic_nr5g_lte_umts);
+    g_test_add_func ("/MM/qmi/supported-capabilities/generic/nr5g-lte-umts",  test_supported_capabilities_generic_nr5g_lte_umts);
+    g_test_add_func ("/MM/qmi/supported-modes/generic/nr5g-lte-umts",         test_supported_modes_generic_nr5g_lte_umts);
+
+    g_test_add_func ("/MM/qmi/current-capabilities/generic/nr5g-lte-evdo",    test_current_capabilities_generic_nr5g_lte_evdo);
+    g_test_add_func ("/MM/qmi/supported-capabilities/generic/nr5g-lte-evdo",  test_supported_capabilities_generic_nr5g_lte_evdo);
+    g_test_add_func ("/MM/qmi/supported-modes/generic/nr5g-lte-evdo",         test_supported_modes_generic_nr5g_lte_evdo);
 
     return g_test_run ();
 }
diff --git a/test/mmcli-test-sms b/test/mmcli-test-sms
index 038d777..8229d36 100755
--- a/test/mmcli-test-sms
+++ b/test/mmcli-test-sms
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 print_usage () {
     echo "usage: $0 [MODEM INDEX] [all|ucs2|gsm7|data] [NUMBER]"
diff --git a/tools/tests/test-wrapper.sh.in b/tools/tests/test-wrapper.sh.in
old mode 100644
new mode 100755
index d64ea4c..fcdb56d
--- a/tools/tests/test-wrapper.sh.in
+++ b/tools/tests/test-wrapper.sh.in
@@ -1,4 +1,4 @@
-#!/bin/bash
+#!/bin/sh
 
 # For debugging behavior of test-modemmanager-service.py, you can modify
 # this line to add --log-file option