diff --git a/introspection/mm-modem-analog.xml b/introspection/mm-modem-analog.xml
new file mode 100644
index 0000000..77f7ed6
--- /dev/null
+++ b/introspection/mm-modem-analog.xml
@@ -0,0 +1,8 @@
+<?xml version="1.0" encoding="UTF-8" ?>
+
+<node name="/" xmlns:tp="http://telepathy.freedesktop.org/wiki/DbusSpec#extensions-v0">
+  <interface name="org.freedesktop.ModemManager.Modem.Analog">
+
+  </interface>
+</node>
+
diff --git a/plugins/Makefile.am b/plugins/Makefile.am
index 8192653..c83fc75 100644
--- a/plugins/Makefile.am
+++ b/plugins/Makefile.am
@@ -1,5 +1,6 @@
 pkglib_LTLIBRARIES = \
 	libmm-plugin-generic.la \
+	libmm-plugin-dialup.la \
 	libmm-plugin-moto-c.la \
 	libmm-plugin-gobi.la \
 	libmm-plugin-huawei.la \
@@ -30,6 +31,24 @@ libmm_plugin_generic_la_LDFLAGS = \
 	-module \
 	-avoid-version
 
+# Dialup/analog
+
+libmm_plugin_dialup_la_SOURCES = \
+	mm-plugin-dialup.c \
+	mm-plugin-dialup.h \
+	mm-modem-dialup.c \
+	mm-modem-dialup.h
+
+libmm_plugin_dialup_la_CPPFLAGS = \
+	$(MM_CFLAGS) \
+	$(GUDEV_CFLAGS) \
+	-I$(top_srcdir)/src
+
+libmm_plugin_dialup_la_LDFLAGS = \
+	$(GUDEV_LDFLAGS) \
+	-module \
+	-avoid-version
+
 # Motorola C-series phones
 
 libmm_plugin_moto_c_la_SOURCES = \
diff --git a/plugins/mm-modem-dialup.c b/plugins/mm-modem-dialup.c
new file mode 100644
index 0000000..c0195a7
--- /dev/null
+++ b/plugins/mm-modem-dialup.c
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include <stdlib.h>
+#include <stdio.h>
+#include <string.h>
+#include <unistd.h>
+
+#include "mm-modem-dialup.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+#include "mm-at-serial-port.h"
+
+static void modem_init (MMModem *modem_class);
+
+G_DEFINE_TYPE_EXTENDED (MMModemDialup, mm_modem_dialup, MM_TYPE_GENERIC_ANALOG, 0,
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init))
+
+
+MMModem *
+mm_modem_dialup_new (const char *device,
+                     const char *driver,
+                     const char *plugin)
+{
+    g_return_val_if_fail (device != NULL, NULL);
+    g_return_val_if_fail (driver != NULL, NULL);
+    g_return_val_if_fail (plugin != NULL, NULL);
+
+    return MM_MODEM (g_object_new (MM_TYPE_MODEM_DIALUP,
+                                   MM_MODEM_MASTER_DEVICE, device,
+                                   MM_MODEM_DRIVER, driver,
+                                   MM_MODEM_PLUGIN, plugin,
+                                   NULL));
+}
+
+static void
+modem_init (MMModem *modem_class)
+{
+}
+
+static void
+mm_modem_dialup_init (MMModemDialup *self)
+{
+}
+
+static void
+mm_modem_dialup_class_init (MMModemDialupClass *klass)
+{
+}
diff --git a/plugins/mm-modem-dialup.h b/plugins/mm-modem-dialup.h
new file mode 100644
index 0000000..4089600
--- /dev/null
+++ b/plugins/mm-modem-dialup.h
@@ -0,0 +1,43 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_MODEM_DIALUP_H
+#define MM_MODEM_DIALUP_H
+
+#include "mm-generic-analog.h"
+
+#define MM_TYPE_MODEM_DIALUP            (mm_modem_dialup_get_type ())
+#define MM_MODEM_DIALUP(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_DIALUP, MMModemDialup))
+#define MM_MODEM_DIALUP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_MODEM_DIALUP, MMModemDialupClass))
+#define MM_IS_MODEM_DIALUP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_DIALUP))
+#define MM_IS_MODEM_DIALUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_MODEM_DIALUP))
+#define MM_MODEM_DIALUP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_MODEM_DIALUP, MMModemDialupClass))
+
+typedef struct {
+    MMGenericAnalog parent;
+} MMModemDialup;
+
+typedef struct {
+    MMGenericAnalogClass parent;
+} MMModemDialupClass;
+
+GType mm_modem_dialup_get_type (void);
+
+MMModem *mm_modem_dialup_new (const char *device,
+                              const char *driver,
+                              const char *plugin_name);
+
+#endif /* MM_MODEM_DIALUP_H */
diff --git a/plugins/mm-plugin-dialup.c b/plugins/mm-plugin-dialup.c
new file mode 100644
index 0000000..6fcac21
--- /dev/null
+++ b/plugins/mm-plugin-dialup.c
@@ -0,0 +1,150 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <gmodule.h>
+#define G_UDEV_API_IS_SUBJECT_TO_CHANGE
+#include <gudev/gudev.h>
+
+#include "mm-plugin-dialup.h"
+#include "mm-modem-dialup.h"
+#include "mm-generic-analog.h"
+
+G_DEFINE_TYPE (MMPluginDialup, mm_plugin_dialup, MM_TYPE_PLUGIN_BASE)
+
+int mm_plugin_major_version = MM_PLUGIN_MAJOR_VERSION;
+int mm_plugin_minor_version = MM_PLUGIN_MINOR_VERSION;
+
+G_MODULE_EXPORT MMPlugin *
+mm_plugin_create (void)
+{
+    return MM_PLUGIN (g_object_new (MM_TYPE_PLUGIN_DIALUP,
+                                    MM_PLUGIN_BASE_NAME, "Dialup",
+                                    NULL));
+}
+
+/*****************************************************************************/
+
+static guint32
+get_level_for_capabilities (guint32 capabilities)
+{
+    /* If it can do anything at all, it's good enough for us */
+    if (capabilities != 0)
+        return 3;
+
+    return 0;
+}
+
+static void
+probe_result (MMPluginBase *base,
+              MMPluginBaseSupportsTask *task,
+              guint32 capabilities,
+              gpointer user_data)
+{
+    mm_plugin_base_supports_task_complete (task, get_level_for_capabilities (capabilities));
+}
+
+static MMPluginSupportsResult
+supports_port (MMPluginBase *base,
+               MMModem *existing,
+               MMPluginBaseSupportsTask *task)
+{
+    GUdevDevice *port;
+    guint32 cached = 0, level;
+
+    /* Can't do anything with non-serial ports */
+    port = mm_plugin_base_supports_task_get_port (task);
+    if (strcmp (g_udev_device_get_subsystem (port), "tty"))
+        return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
+
+    if (mm_plugin_base_get_cached_port_capabilities (base, port, &cached)) {
+        level = get_level_for_capabilities (cached);
+        if (level) {
+            mm_plugin_base_supports_task_complete (task, level);
+            return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS;
+        }
+        return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
+    }
+
+    /* Don't issue a reset on the same line as E0, as many modems will ignore the
+     * latter when the former is present. */
+    mm_plugin_base_supports_task_set_custom_init_command (task, "E0 V1 X4 &C1", 3, 3, TRUE);
+
+    /* Otherwise kick off a probe */
+    if (mm_plugin_base_probe_port (base, task, NULL))
+        return MM_PLUGIN_SUPPORTS_PORT_IN_PROGRESS;
+
+    return MM_PLUGIN_SUPPORTS_PORT_UNSUPPORTED;
+}
+
+static MMModem *
+grab_port (MMPluginBase *base,
+           MMModem *existing,
+           MMPluginBaseSupportsTask *task,
+           GError **error)
+{
+    GUdevDevice *port = NULL;
+    MMModem *modem = NULL;
+    const char *name, *subsys, *sysfs_path;
+
+    port = mm_plugin_base_supports_task_get_port (task);
+    g_assert (port);
+
+    subsys = g_udev_device_get_subsystem (port);
+    name = g_udev_device_get_name (port);
+
+    /* We don't check for capabilities, since there is no way for us to store them
+     * in the task when getting them from cache. Assume we're being told to grab it
+     * for a good reason. */
+
+    sysfs_path = mm_plugin_base_supports_task_get_physdev_path (task);
+    if (!existing) {
+        modem = mm_modem_dialup_new (sysfs_path,
+                                     mm_plugin_base_supports_task_get_driver (task),
+                                     mm_plugin_get_name (MM_PLUGIN (base)));
+
+        if (modem) {
+            if (!mm_modem_grab_port (modem, subsys, name, MM_PORT_TYPE_UNKNOWN, NULL, error)) {
+                g_object_unref (modem);
+                return NULL;
+            }
+        }
+    } else {
+        modem = existing;
+        if (!mm_modem_grab_port (modem, subsys, name, MM_PORT_TYPE_UNKNOWN, NULL, error))
+            return NULL;
+    }
+
+    return modem;
+}
+
+/*****************************************************************************/
+
+static void
+mm_plugin_dialup_init (MMPluginDialup *self)
+{
+    g_signal_connect (self, "probe-result", G_CALLBACK (probe_result), NULL);
+}
+
+static void
+mm_plugin_dialup_class_init (MMPluginDialupClass *klass)
+{
+    MMPluginBaseClass *pb_class = MM_PLUGIN_BASE_CLASS (klass);
+
+    pb_class->supports_port = supports_port;
+    pb_class->grab_port = grab_port;
+}
+
diff --git a/plugins/mm-plugin-dialup.h b/plugins/mm-plugin-dialup.h
new file mode 100644
index 0000000..ddff62d
--- /dev/null
+++ b/plugins/mm-plugin-dialup.h
@@ -0,0 +1,44 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_PLUGIN_DIALUP_H
+#define MM_PLUGIN_DIALUP_H
+
+#include "mm-plugin.h"
+#include "mm-plugin-base.h"
+#include "mm-generic-analog.h"
+
+#define MM_TYPE_PLUGIN_DIALUP            (mm_plugin_dialup_get_type ())
+#define MM_PLUGIN_DIALUP(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_DIALUP, MMPluginDialup))
+#define MM_PLUGIN_DIALUP_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_PLUGIN_DIALUP, MMPluginDialupClass))
+#define MM_IS_PLUGIN_DIALUP(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_PLUGIN_DIALUP))
+#define MM_IS_PLUGIN_DIALUP_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_PLUGIN_DIALUP))
+#define MM_PLUGIN_DIALUP_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_PLUGIN_DIALUP, MMPluginDialupClass))
+
+typedef struct {
+    MMPluginBase parent;
+} MMPluginDialup;
+
+typedef struct {
+    MMPluginBaseClass parent;
+} MMPluginDialupClass;
+
+GType mm_plugin_dialup_get_type (void);
+
+G_MODULE_EXPORT MMPlugin *mm_plugin_create (void);
+
+#endif /* MM_PLUGIN_DIALUP_H */
+
diff --git a/src/Makefile.am b/src/Makefile.am
index 46046ce..c35b29d 100644
--- a/src/Makefile.am
+++ b/src/Makefile.am
@@ -86,6 +86,8 @@ modem_manager_SOURCES = \
 	mm-serial-parsers.h \
 	mm-modem-base.c \
 	mm-modem-base.h \
+	mm-generic-analog.c \
+	mm-generic-analog.h \
 	mm-generic-cdma.c \
 	mm-generic-cdma.h \
 	mm-generic-gsm.c \
@@ -99,6 +101,8 @@ modem_manager_SOURCES = \
 	mm-modem-gsm-network.h \
 	mm-modem-gsm-sms.c \
 	mm-modem-gsm-sms.h \
+	mm-modem-analog.c \
+	mm-modem-analog.h \
 	mm-modem-simple.c \
 	mm-modem-simple.h \
 	mm-options.c \
@@ -131,6 +135,9 @@ mm-modem-gsm-network-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-network.xm
 mm-modem-gsm-sms-glue.h: $(top_srcdir)/introspection/mm-modem-gsm-sms.xml
 	dbus-binding-tool --prefix=mm_modem_gsm_sms --mode=glib-server --output=$@ $<
 
+mm-modem-analog-glue.h: $(top_srcdir)/introspection/mm-modem-analog.xml
+	$(AM_V_GEN) dbus-binding-tool --prefix=mm_modem_analog --mode=glib-server --output=$@ $<
+
 BUILT_SOURCES = \
 	mm-manager-glue.h \
 	mm-modem-glue.h \
@@ -138,7 +145,8 @@ BUILT_SOURCES = \
 	mm-modem-cdma-glue.h \
 	mm-modem-gsm-card-glue.h \
 	mm-modem-gsm-network-glue.h \
-	mm-modem-gsm-sms-glue.h
+	mm-modem-gsm-sms-glue.h \
+	mm-modem-analog-glue.h
 
 if WITH_LOCATION_API
 mm-modem-location-glue.h: $(top_srcdir)/introspection/mm-modem-location.xml
diff --git a/src/mm-generic-analog.c b/src/mm-generic-analog.c
new file mode 100644
index 0000000..0092310
--- /dev/null
+++ b/src/mm-generic-analog.c
@@ -0,0 +1,808 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008 - 2009 Novell, Inc.
+ * Copyright (C) 2009 - 2010 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <stdio.h>
+#include <ctype.h>
+#include <errno.h>
+#include <stdlib.h>
+
+#include "mm-generic-analog.h"
+#include "mm-modem-analog.h"
+#include "mm-modem-simple.h"
+#include "mm-at-serial-port.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+#include "mm-serial-parsers.h"
+#include "mm-modem-helpers.h"
+
+#define MM_GENERIC_ANALOG_PREV_STATE_TAG "prev-state"
+#define SS_HASH_TAG "simple-get-status"
+
+static const char *simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error);
+static gboolean simple_get_boolean_property (MMCallbackInfo *info, const char *name, GError **error);
+static void simple_state_machine (MMModem *modem, GError *error, gpointer user_data);
+
+static void modem_init (MMModem *modem_class);
+static void modem_analog_init (MMModemAnalog *analog_class);
+static void modem_simple_init (MMModemSimple *class);
+
+G_DEFINE_TYPE_EXTENDED (MMGenericAnalog, mm_generic_analog, MM_TYPE_MODEM_BASE, 0,
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM, modem_init)
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_ANALOG, modem_analog_init)
+                        G_IMPLEMENT_INTERFACE (MM_TYPE_MODEM_SIMPLE, modem_simple_init))
+
+#define MM_GENERIC_ANALOG_GET_PRIVATE(o) (G_TYPE_INSTANCE_GET_PRIVATE ((o), MM_TYPE_GENERIC_ANALOG, MMGenericAnalogPrivate))
+
+typedef struct {
+    MMCallbackInfo *simple_connect_info;
+
+    MMAtSerialPort *serial_port;
+    MMPort *data;
+} MMGenericAnalogPrivate;
+
+enum {
+    PROP_0,
+    LAST_PROP
+};
+
+MMModem *
+mm_generic_analog_new (const char *device,
+                     const char *driver,
+                     const char *plugin)
+{
+    g_return_val_if_fail (device != NULL, NULL);
+    g_return_val_if_fail (driver != NULL, NULL);
+    g_return_val_if_fail (plugin != NULL, NULL);
+
+    return MM_MODEM (g_object_new (MM_TYPE_GENERIC_ANALOG,
+                                   MM_MODEM_MASTER_DEVICE, device,
+                                   MM_MODEM_DRIVER, driver,
+                                   MM_MODEM_PLUGIN, plugin,
+                                   NULL));
+}
+
+/*****************************************************************************/
+
+static void
+connect_cleanup (MMGenericAnalog *self, GQuark error_class, guint32 error_num)
+{
+    MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (self);
+    GError *error = NULL;
+
+    if (priv->simple_connect_info && error_class) {
+        error = g_error_new_literal (error_class, error_num,
+                                     "Connection attempt terminated");
+        simple_state_machine (MM_MODEM (self), error, priv->simple_connect_info);
+        g_error_free (error);
+    }
+    priv->simple_connect_info = NULL;
+}
+
+static void
+init_done (MMAtSerialPort *port,
+           GString *response,
+           GError *error,
+           gpointer user_data)
+{
+    MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+    if (error) {
+        g_debug ("Init failed: %s", error->message);
+
+        mm_modem_set_state (MM_MODEM (info->modem),
+                            MM_MODEM_STATE_DISABLED,
+                            MM_MODEM_STATE_REASON_NONE);
+
+        info->error = g_error_copy (error);
+    }
+
+    mm_callback_info_schedule (info);
+}
+
+static void
+flash_done (MMSerialPort *port, GError *error, gpointer user_data)
+{
+    MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+    if (error) {
+        g_debug ("Flash failed: %s", error->message);
+
+        mm_modem_set_state (MM_MODEM (info->modem),
+                            MM_MODEM_STATE_DISABLED,
+                            MM_MODEM_STATE_REASON_NONE);
+
+        /* Flash failed for some reason */
+        info->error = g_error_copy (error);
+        mm_callback_info_schedule (info);
+        return;
+    }
+
+    mm_at_serial_port_queue_command (MM_AT_SERIAL_PORT (port), "E0 V1 X4 &C1", 3, init_done, user_data);
+}
+
+static void
+enable (MMModem *modem,
+        MMModemFn callback,
+        gpointer user_data)
+{
+    MMGenericAnalog *self = MM_GENERIC_ANALOG (modem);
+    MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (self);
+    MMCallbackInfo *info;
+
+    info = mm_callback_info_new (modem, callback, user_data);
+
+    g_debug ("Asked to enable generic analog modem.");
+
+    if (!mm_serial_port_open (MM_SERIAL_PORT (priv->serial_port), &info->error)) {
+        g_assert (info->error);
+        mm_callback_info_schedule (info);
+        return;
+    }
+
+    mm_modem_set_state (MM_MODEM (info->modem),
+                        MM_MODEM_STATE_ENABLING,
+                        MM_MODEM_STATE_REASON_NONE);
+
+    mm_serial_port_flash (MM_SERIAL_PORT (priv->serial_port), 100, FALSE, flash_done, info);
+}
+
+static void
+disable_set_previous_state (MMModem *modem, MMCallbackInfo *info)
+{
+    MMModemState prev_state;
+
+    /* Reset old state since the operation failed */
+    prev_state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, MM_GENERIC_ANALOG_PREV_STATE_TAG));
+    mm_modem_set_state (modem, prev_state, MM_MODEM_STATE_REASON_NONE);
+}
+
+static void
+disable_all_done (MMModem *modem, GError *error, gpointer user_data)
+{
+    MMCallbackInfo *info = user_data;
+
+    info->error = mm_modem_check_removed (modem, error);
+    if (info->error) {
+        g_debug ("Disable failed: %s", info->error->message);
+
+        if (modem)
+            disable_set_previous_state (modem, info);
+    } else {
+        MMGenericAnalog *self = MM_GENERIC_ANALOG (info->modem);
+        MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (self);
+
+        mm_serial_port_close_force (MM_SERIAL_PORT (priv->serial_port));
+        mm_modem_set_state (modem, MM_MODEM_STATE_DISABLED, MM_MODEM_STATE_REASON_NONE);
+    }
+
+    mm_callback_info_schedule (info);
+}
+
+static void
+disable_flash_done (MMSerialPort *port,
+                    GError *error,
+                    gpointer user_data)
+{
+    MMCallbackInfo *info = user_data;
+    MMGenericAnalog *self;
+
+    info->error = mm_modem_check_removed (info->modem, error);
+    if (info->error) {
+        g_debug ("Disable flash failed: %s", info->error->message);
+
+        if (info->modem)
+            disable_set_previous_state (info->modem, info);
+        mm_callback_info_schedule (info);
+        return;
+    }
+
+    self = MM_GENERIC_ANALOG (info->modem);
+
+    if (MM_GENERIC_ANALOG_GET_CLASS (self)->post_disable)
+        MM_GENERIC_ANALOG_GET_CLASS (self)->post_disable (self, disable_all_done, info);
+    else
+        disable_all_done (MM_MODEM (self), NULL, info);
+}
+
+static void
+disable (MMModem *modem,
+         MMModemFn callback,
+         gpointer user_data)
+{
+    MMGenericAnalog *self = MM_GENERIC_ANALOG (modem);
+    MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (self);
+    MMCallbackInfo *info;
+    MMModemState state;
+
+    connect_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL);
+
+    info = mm_callback_info_new (modem, callback, user_data);
+
+    /* Cache the previous state so we can reset it if the operation fails */
+    state = mm_modem_get_state (modem);
+    mm_callback_info_set_data (info,
+                               MM_GENERIC_ANALOG_PREV_STATE_TAG,
+                               GUINT_TO_POINTER (state),
+                               NULL);
+
+    mm_modem_set_state (MM_MODEM (info->modem),
+                        MM_MODEM_STATE_DISABLING,
+                        MM_MODEM_STATE_REASON_NONE);
+
+    if (mm_port_get_connected (MM_PORT (priv->serial_port)))
+        mm_serial_port_flash (MM_SERIAL_PORT (priv->serial_port), 1000, TRUE, disable_flash_done, info);
+    else
+        disable_flash_done (MM_SERIAL_PORT (priv->serial_port), NULL, info);
+}
+
+static void
+dial_done (MMAtSerialPort *port,
+           GString *response,
+           GError *error,
+           gpointer user_data)
+{
+    MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+    info->error = mm_modem_check_removed (info->modem, error);
+    if (info->error) {
+        if (info->modem) {
+            MMGenericAnalog *self = MM_GENERIC_ANALOG (info->modem);
+            MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (self);
+
+            mm_serial_port_close_force (MM_SERIAL_PORT (priv->serial_port));
+            mm_modem_set_state (info->modem, MM_MODEM_STATE_DISABLED, MM_MODEM_STATE_REASON_NONE);
+        }
+    } else {
+        MMGenericAnalog *self = MM_GENERIC_ANALOG (info->modem);
+        MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (self);
+
+        connect_cleanup (self, 0, 0);
+
+        mm_port_set_connected (priv->data, TRUE);
+        mm_modem_set_state (info->modem, MM_MODEM_STATE_CONNECTED, MM_MODEM_STATE_REASON_NONE);
+    }
+
+    mm_callback_info_schedule (info);
+}
+
+static void
+connect (MMModem *modem,
+         const char *number,
+         MMModemFn callback,
+         gpointer user_data)
+{
+    MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (modem);
+    MMCallbackInfo *info;
+    char *command = NULL;
+
+    mm_modem_set_state (modem, MM_MODEM_STATE_CONNECTING, MM_MODEM_STATE_REASON_NONE);
+
+    info = mm_callback_info_new (modem, callback, user_data);
+
+    if (priv->simple_connect_info) {
+        const char *prefix;
+        gboolean enable_speaker = simple_get_boolean_property (priv->simple_connect_info, "enable-speaker", NULL);
+        gboolean detect_dialtone = simple_get_boolean_property (priv->simple_connect_info, "detect-dialtone", NULL);
+
+        prefix = simple_get_string_property (priv->simple_connect_info, "prefix", NULL);
+        if (prefix && strlen (prefix) == 0)
+            prefix = NULL;
+
+        if (prefix)
+            command = g_strconcat ("DT", prefix,
+                                   detect_dialtone ? "W" : ",,",
+                                   number, NULL);
+
+        mm_at_serial_port_queue_command (priv->serial_port,
+                                         enable_speaker ? "M1" : "M0",
+                                         10, NULL, NULL);
+        mm_at_serial_port_queue_command (priv->serial_port,
+                                         (detect_dialtone && !prefix) ? "X4" : "X3",
+                                         10, NULL, NULL);
+    }
+
+    if (!command)
+        command = g_strconcat ("DT", number, NULL);
+
+    mm_at_serial_port_queue_command (priv->serial_port, command, 90, dial_done, info);
+    g_free (command);
+}
+
+static void
+disconnect_flash_done (MMSerialPort *port,
+                       GError *error,
+                       gpointer user_data)
+{
+    MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+
+    /* Ignore any errors here so we can transition to the disconnected state
+     * even if the modem was yanked (e.g. a HUP will make the serial port
+     * force_close itself, resulting in a failed flash). */
+
+    info->error = mm_modem_check_removed (info->modem, error);
+    g_clear_error (&info->error);
+
+    if (info->modem) {
+        MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (info->modem);
+
+        if (priv->serial_port && mm_serial_port_is_open (MM_SERIAL_PORT (priv->serial_port)))
+            mm_serial_port_close_force (MM_SERIAL_PORT (priv->serial_port));
+
+        mm_port_set_connected (MM_GENERIC_ANALOG_GET_PRIVATE (info->modem)->data, FALSE);
+        mm_modem_set_state (MM_MODEM (info->modem), MM_MODEM_STATE_DISABLED, MM_MODEM_STATE_REASON_NONE);
+    }
+
+    mm_callback_info_schedule (info);
+}
+
+static void
+disconnect (MMModem *modem,
+            MMModemFn callback,
+            gpointer user_data)
+{
+    MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (modem);
+    MMCallbackInfo *info;
+    MMModemState state;
+
+    g_return_if_fail (priv->serial_port != NULL);
+
+    g_debug ("Asked to disconnect generic analog modem.");
+
+    info = mm_callback_info_new (modem, callback, user_data);
+
+    /* Cache the previous state so we can reset it if the operation fails */
+    state = mm_modem_get_state (modem);
+    mm_callback_info_set_data (info,
+                               MM_GENERIC_ANALOG_PREV_STATE_TAG,
+                               GUINT_TO_POINTER (state),
+                               NULL);
+
+    mm_modem_set_state (modem, MM_MODEM_STATE_DISCONNECTING, MM_MODEM_STATE_REASON_NONE);
+
+    if (mm_port_get_connected (MM_PORT (priv->serial_port)))
+        mm_serial_port_flash (MM_SERIAL_PORT (priv->serial_port), 1000, TRUE, disconnect_flash_done, info);
+    else
+        disconnect_flash_done (MM_SERIAL_PORT (priv->serial_port), NULL, info);
+}
+
+static void
+get_info (MMModem *modem,
+          MMModemInfoFn callback,
+          gpointer user_data)
+{
+    MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (modem);
+    GError *error = NULL;
+
+    mm_modem_base_get_card_info (MM_MODEM_BASE (modem), priv->serial_port, error, callback, user_data);
+    g_clear_error (&error);
+}
+
+static gboolean
+owns_port (MMModem *modem, const char *subsys, const char *name)
+{
+    return !!mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name);
+}
+
+static void
+update_validity (MMGenericAnalog *self)
+{
+    MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (self);
+    gboolean new_valid = FALSE;
+
+    if (priv->serial_port && priv->data)
+        new_valid = TRUE;
+
+    mm_modem_base_set_valid (MM_MODEM_BASE (self), new_valid);
+}
+
+MMPort *
+mm_generic_analog_grab_port (MMGenericAnalog *self,
+                             const char *subsys,
+                             const char *name,
+                             MMPortType suggested_type,
+                             gpointer user_data,
+                             GError **error);
+
+MMPort *
+mm_generic_analog_grab_port (MMGenericAnalog *self,
+                             const char *subsys,
+                             const char *name,
+                             MMPortType suggested_type,
+                             gpointer user_data,
+                             GError **error)
+{
+    MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (self);
+    MMPortType ptype = MM_PORT_TYPE_PRIMARY;
+    MMPort *port;
+
+    g_return_val_if_fail (!strcmp (subsys, "tty"), FALSE);
+
+    if (priv->serial_port)
+        return NULL;
+
+    port = mm_modem_base_add_port (MM_MODEM_BASE (self), subsys, name, ptype);
+    if (!port) {
+        g_warn_if_fail (port != NULL);
+        return NULL;
+    }
+
+    if (MM_IS_AT_SERIAL_PORT (port)) {
+        g_object_set (G_OBJECT (port), MM_PORT_CARRIER_DETECT, FALSE, NULL);
+#if 0
+        /* XXX TEST */
+        g_object_set (G_OBJECT (port), MM_SERIAL_PORT_SEND_DELAY, 10000, NULL);
+#endif
+
+        mm_at_serial_port_set_response_parser (MM_AT_SERIAL_PORT (port),
+                                               mm_serial_parser_v1_parse,
+                                               mm_serial_parser_v1_new (),
+                                               mm_serial_parser_v1_destroy);
+
+        priv->serial_port = MM_AT_SERIAL_PORT (port);
+        if (!priv->data) {
+            priv->data = port;
+            g_object_notify (G_OBJECT (self), MM_MODEM_DATA_DEVICE);
+        }
+    }
+
+    update_validity (self);
+
+    return port;
+}
+
+static gboolean
+grab_port (MMModem *modem,
+           const char *subsys,
+           const char *name,
+           MMPortType suggested_type,
+           gpointer user_data,
+           GError **error)
+{
+    return !!mm_generic_analog_grab_port (MM_GENERIC_ANALOG (modem), subsys, name, suggested_type, user_data, error);
+}
+
+static void
+release_port (MMModem *modem, const char *subsys, const char *name)
+{
+    MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (modem);
+    MMPort *port;
+
+    port = mm_modem_base_get_port (MM_MODEM_BASE (modem), subsys, name);
+    if (!port)
+        return;
+
+    if (port == (MMPort *) priv->serial_port) {
+        mm_modem_base_remove_port (MM_MODEM_BASE (modem), port);
+        priv->serial_port = NULL;
+    }
+
+    if (port == priv->data) {
+        priv->data = NULL;
+        g_object_notify (G_OBJECT (modem), MM_MODEM_DATA_DEVICE);
+    }
+
+    update_validity (MM_GENERIC_ANALOG (modem));
+}
+
+/*****************************************************************************/
+/* MMModemSimple interface */
+
+typedef enum {
+    SIMPLE_STATE_BEGIN = 0,
+    SIMPLE_STATE_ENABLE,
+    SIMPLE_STATE_CONNECT,
+    SIMPLE_STATE_DONE
+} SimpleState;
+
+static const char *
+simple_get_string_property (MMCallbackInfo *info, const char *name, GError **error)
+{
+    GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties");
+    GValue *value;
+
+    value = (GValue *) g_hash_table_lookup (properties, name);
+    if (!value)
+        return NULL;
+
+    if (G_VALUE_HOLDS_STRING (value))
+        return g_value_get_string (value);
+
+    g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+                 "Invalid property type for '%s': %s (string expected)",
+                 name, G_VALUE_TYPE_NAME (value));
+
+    return NULL;
+}
+
+static gboolean
+simple_get_boolean_property (MMCallbackInfo *info, const char *name, GError **error)
+{
+    GHashTable *properties = (GHashTable *) mm_callback_info_get_data (info, "simple-connect-properties");
+    GValue *value;
+
+    value = (GValue *) g_hash_table_lookup (properties, name);
+    if (!value)
+        return FALSE;
+
+    if (G_VALUE_HOLDS_UINT (value))
+        return g_value_get_uint (value);
+
+    g_set_error (error, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL,
+                 "Invalid property type for '%s': %s (uint expected)",
+                 name, G_VALUE_TYPE_NAME (value));
+
+    return FALSE;
+}
+
+static SimpleState
+set_simple_state (MMCallbackInfo *info, SimpleState state)
+{
+    mm_callback_info_set_data (info, "simple-connect-state", GUINT_TO_POINTER (state), NULL);
+    return state;
+}
+
+static void
+simple_state_machine (MMModem *modem, GError *error, gpointer user_data)
+{
+    MMCallbackInfo *info = (MMCallbackInfo *) user_data;
+    SimpleState state = GPOINTER_TO_UINT (mm_callback_info_get_data (info, "simple-connect-state"));
+    const char *str;
+
+    info->error = mm_modem_check_removed (modem, error);
+    if (info->error)
+        goto out;
+
+    switch (state) {
+    case SIMPLE_STATE_BEGIN:
+        state = set_simple_state (info, SIMPLE_STATE_ENABLE);
+        mm_modem_enable (modem, simple_state_machine, info);
+        break;
+    case SIMPLE_STATE_ENABLE:
+        state = set_simple_state (info, SIMPLE_STATE_CONNECT);
+        mm_modem_set_state (modem, MM_MODEM_STATE_REGISTERED, MM_MODEM_STATE_REASON_NONE);
+
+        str = simple_get_string_property (info, "number", &info->error);
+        mm_modem_connect (modem, str, simple_state_machine, info);
+        break;
+    case SIMPLE_STATE_CONNECT:
+        connect_cleanup (MM_GENERIC_ANALOG (modem), 0, 0);
+        state = set_simple_state (info, SIMPLE_STATE_DONE);
+        break;
+    case SIMPLE_STATE_DONE:
+        break;
+    }
+
+ out:
+    if (info->error || state == SIMPLE_STATE_DONE) {
+        if (modem)
+            connect_cleanup (MM_GENERIC_ANALOG (modem), 0, 0);
+        mm_callback_info_schedule (info);
+    }
+}
+
+static void
+simple_connect (MMModemSimple *simple,
+                GHashTable *properties,
+                MMModemFn callback,
+                gpointer user_data)
+{
+    MMGenericAnalog *self = MM_GENERIC_ANALOG (simple);
+    MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (self);
+    MMCallbackInfo *info;
+    GError *error = NULL;
+
+    if (priv->simple_connect_info) {
+        error = g_error_new_literal (MM_MODEM_ERROR,
+                                     MM_MODEM_ERROR_OPERATION_IN_PROGRESS,
+                                     "Connection is already in progress");
+        callback (MM_MODEM (simple), error, user_data);
+        g_error_free (error);
+        return;
+    }
+
+    info = mm_callback_info_new (MM_MODEM (simple), callback, user_data);
+    priv->simple_connect_info = info;
+    mm_callback_info_set_data (info, "simple-connect-properties", 
+                               g_hash_table_ref (properties),
+                               (GDestroyNotify) g_hash_table_unref);
+
+    /* At least number must be present */
+    if (!simple_get_string_property (info, "number", &error)) {
+        if (!error)
+            error = g_error_new_literal (MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL, "Missing number property");
+    }
+
+    simple_state_machine (MM_MODEM (simple), error, info);
+}
+
+#define SS_HASH_TAG "simple-get-status"
+
+static void
+simple_free_gvalue (gpointer data)
+{
+    g_value_unset ((GValue *) data);
+    g_slice_free (GValue, data);
+}
+
+#if 0
+
+static GValue *
+simple_uint_value (guint32 i)
+{
+    GValue *val;
+
+    val = g_slice_new0 (GValue);
+    g_value_init (val, G_TYPE_UINT);
+    g_value_set_uint (val, i);
+
+    return val;
+}
+
+#endif
+
+static void
+simple_get_status_invoke (MMCallbackInfo *info)
+{
+    MMModemSimpleGetStatusFn callback = (MMModemSimpleGetStatusFn) info->callback;
+
+    callback (MM_MODEM_SIMPLE (info->modem),
+              (GHashTable *) mm_callback_info_get_data (info, SS_HASH_TAG),
+              info->error, info->user_data);
+}
+
+static void
+simple_get_status (MMModemSimple *simple,
+                   MMModemSimpleGetStatusFn callback,
+                   gpointer user_data)
+{
+    GHashTable *properties;
+    MMCallbackInfo *info;
+
+    info = mm_callback_info_new_full (MM_MODEM (simple),
+                                      simple_get_status_invoke,
+                                      G_CALLBACK (callback),
+                                      user_data);
+
+    properties = g_hash_table_new_full (g_str_hash, g_str_equal, NULL, simple_free_gvalue);
+    mm_callback_info_set_data (info, SS_HASH_TAG, properties, (GDestroyNotify) g_hash_table_unref);
+    mm_callback_info_schedule (info);
+}
+
+/*****************************************************************************/
+
+static void
+modem_valid_changed (MMGenericAnalog *self, GParamSpec *pspec, gpointer user_data)
+{
+    if (!mm_modem_get_valid (MM_MODEM (self)))
+        connect_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL);
+}
+
+static void
+modem_state_changed (MMGenericAnalog *self, GParamSpec *pspec, gpointer user_data)
+{
+    MMModemState state;
+
+    state = mm_modem_get_state (MM_MODEM (self));
+    if (state >= MM_MODEM_STATE_ENABLED) {
+    } else {
+    }
+}
+
+/*****************************************************************************/
+
+static void
+modem_init (MMModem *modem_class)
+{
+    modem_class->owns_port = owns_port;
+    modem_class->grab_port = grab_port;
+    modem_class->release_port = release_port;
+    modem_class->connect = connect;
+    modem_class->disconnect = disconnect;
+    modem_class->enable = enable;
+    modem_class->disable = disable;
+    modem_class->get_info = get_info;
+}
+
+static void
+modem_analog_init (MMModemAnalog *analog_class)
+{
+}
+
+static void
+modem_simple_init (MMModemSimple *class)
+{
+    class->connect = simple_connect;
+    class->get_status = simple_get_status;
+}
+
+static void
+mm_generic_analog_init (MMGenericAnalog *self)
+{
+    g_signal_connect (self, "notify::" MM_MODEM_VALID,
+                      G_CALLBACK (modem_valid_changed), NULL);
+    g_signal_connect (self, "notify::" MM_MODEM_STATE,
+                      G_CALLBACK (modem_state_changed), NULL);
+}
+
+static void
+set_property (GObject *object, guint prop_id,
+              const GValue *value, GParamSpec *pspec)
+{
+    switch (prop_id) {
+    case MM_MODEM_PROP_TYPE:
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+get_property (GObject *object, guint prop_id,
+              GValue *value, GParamSpec *pspec)
+{
+    MMGenericAnalogPrivate *priv = MM_GENERIC_ANALOG_GET_PRIVATE (object);
+
+    switch (prop_id) {
+    case MM_MODEM_PROP_DATA_DEVICE:
+        if (priv->data)
+            g_value_set_string (value, mm_port_get_device (priv->data));
+        else
+            g_value_set_string (value, NULL);
+        break;
+    case MM_MODEM_PROP_TYPE:
+        g_value_set_uint (value, MM_MODEM_TYPE_ANALOG);
+        break;
+    default:
+        G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec);
+        break;
+    }
+}
+
+static void
+dispose (GObject *object)
+{
+    MMGenericAnalog *self = MM_GENERIC_ANALOG (object);
+
+    connect_cleanup (self, MM_MODEM_ERROR, MM_MODEM_ERROR_GENERAL);
+
+    G_OBJECT_CLASS (mm_generic_analog_parent_class)->dispose (object);
+}
+
+static void
+mm_generic_analog_class_init (MMGenericAnalogClass *klass)
+{
+    GObjectClass *object_class = G_OBJECT_CLASS (klass);
+
+    mm_generic_analog_parent_class = g_type_class_peek_parent (klass);
+    g_type_class_add_private (object_class, sizeof (MMGenericAnalogPrivate));
+
+    /* Virtual methods */
+    object_class->set_property = set_property;
+    object_class->get_property = get_property;
+    object_class->dispose = dispose;
+
+    /* Properties */
+    g_object_class_override_property (object_class,
+                                      MM_MODEM_PROP_DATA_DEVICE,
+                                      MM_MODEM_DATA_DEVICE);
+
+    g_object_class_override_property (object_class,
+                                      MM_MODEM_PROP_TYPE,
+                                      MM_MODEM_TYPE);
+}
+
diff --git a/src/mm-generic-analog.h b/src/mm-generic-analog.h
new file mode 100644
index 0000000..48189f5
--- /dev/null
+++ b/src/mm-generic-analog.h
@@ -0,0 +1,62 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008-2010 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_GENERIC_ANALOG_H
+#define MM_GENERIC_ANALOG_H
+
+#include "mm-modem.h"
+#include "mm-modem-base.h"
+#include "mm-modem-analog.h"
+#include "mm-at-serial-port.h"
+#include "mm-qcdm-serial-port.h"
+#include "mm-callback-info.h"
+
+#define MM_TYPE_GENERIC_ANALOG            (mm_generic_analog_get_type ())
+#define MM_GENERIC_ANALOG(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_GENERIC_ANALOG, MMGenericAnalog))
+#define MM_GENERIC_ANALOG_CLASS(klass)    (G_TYPE_CHECK_CLASS_CAST ((klass),  MM_TYPE_GENERIC_ANALOG, MMGenericAnalogClass))
+#define MM_IS_GENERIC_ANALOG(obj)         (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_GENERIC_ANALOG))
+#define MM_IS_GENERIC_ANALOG_CLASS(klass) (G_TYPE_CHECK_CLASS_TYPE ((klass),  MM_TYPE_GENERIC_ANALOG))
+#define MM_GENERIC_ANALOG_GET_CLASS(obj)  (G_TYPE_INSTANCE_GET_CLASS ((obj),  MM_TYPE_GENERIC_ANALOG, MMGenericAnalogClass))
+
+typedef struct {
+    MMModemBase parent;
+} MMGenericAnalog;
+
+typedef struct {
+    MMModemBaseClass parent;
+
+    /* Called after generic enable operations, but before the modem has entered
+     * the ENABLED state.
+     */
+    void (*post_enable) (MMGenericAnalog *self,
+                         MMModemFn callback,
+                         gpointer user_data);
+
+    /* Called after generic disable operations, but before the modem has entered
+     * the DISABLED state.
+     */
+    void (*post_disable) (MMGenericAnalog *self,
+                          MMModemFn callback,
+                          gpointer user_data);
+} MMGenericAnalogClass;
+
+GType mm_generic_analog_get_type (void);
+
+MMModem *mm_generic_analog_new (const char *device,
+                                const char *driver,
+                                const char *plugin);
+
+#endif /* MM_GENERIC_ANALOG_H */
diff --git a/src/mm-manager.c b/src/mm-manager.c
index d052e1c..7e5d7dd 100644
--- a/src/mm-manager.c
+++ b/src/mm-manager.c
@@ -149,11 +149,12 @@ load_plugins (MMManager *manager)
     GDir *dir;
     const char *fname;
     MMPlugin *generic_plugin = NULL;
+    MMPlugin *analog_modem_plugin = NULL;
 
-	if (!g_module_supported ()) {
-		g_warning ("GModules are not supported on your platform!");
-		return;
-	}
+    if (!g_module_supported ()) {
+	    g_warning ("GModules are not supported on your platform!");
+	    return;
+    }
 
     dir = g_dir_open (PLUGINDIR, 0, NULL);
     if (!dir) {
@@ -175,15 +176,23 @@ load_plugins (MMManager *manager)
         if (plugin) {
             if (!strcmp (mm_plugin_get_name (plugin), MM_PLUGIN_GENERIC_NAME))
                 generic_plugin = plugin;
+            else if (!strcmp (mm_plugin_get_name (plugin), "Dialup"))
+                analog_modem_plugin = plugin;
             else
                 priv->plugins = g_slist_append (priv->plugins, plugin);
         }
     }
 
-    /* Make sure the generic plugin is last */
+    /* Make sure the generic GSM/CDMA plugin is last */
     if (generic_plugin)
         priv->plugins = g_slist_append (priv->plugins, generic_plugin);
 
+    /* Make sure the analog modem plugin is even laster */
+    if (analog_modem_plugin) {
+        g_debug ("Added analog modem plugin as the very last.");
+        priv->plugins = g_slist_append (priv->plugins, analog_modem_plugin);
+    }
+
     g_dir_close (dir);
 }
 
@@ -552,6 +561,8 @@ do_grab_port (gpointer user_data)
                 type_name = "GSM";
             else if (modem_type == MM_MODEM_TYPE_CDMA)
                 type_name = "CDMA";
+            else if (modem_type == MM_MODEM_TYPE_ANALOG)
+                type_name = "analog";
 
             device = mm_modem_get_device (modem);
             g_message ("(%s): %s modem %s claimed port %s",
@@ -647,7 +658,7 @@ supports_callback (MMPlugin *plugin,
     if (next_plugin) {
         const char *next_name = mm_plugin_get_name (next_plugin);
 
-        if (info->best_plugin && strcmp (next_name, MM_PLUGIN_GENERIC_NAME))
+        if (info->best_plugin && !strcmp (next_name, MM_PLUGIN_GENERIC_NAME))
             next_plugin = NULL;
     }
 
@@ -706,6 +717,13 @@ find_physical_device (GUdevDevice *child)
                 /* Take the first platform parent as the physical device */
                 is_platform = TRUE;
                 physdev = iter;
+
+                if (!strcmp (g_udev_device_get_name (physdev), "serial8250")) {
+                    /* Make an exception for serial8250, where the first platform
+                     * parent is the controller. The leaf is the actual device. */
+                    physdev = g_object_ref (child);
+                    g_object_unref (iter);
+                }
                 break;
             } else if (is_pci || !strcmp (subsys, "pci")) {
                 is_pci = TRUE;
@@ -783,8 +801,10 @@ device_added (MMManager *manager, GUdevDevice *device)
     physdev_subsys = g_udev_device_get_subsystem (physdev);
     if (   physdev_subsys
         && !strcmp (physdev_subsys, "platform")
+        && strncmp (g_udev_device_get_name (physdev), "serial", 6)
         && !g_udev_device_get_property_as_boolean (physdev, "ID_MM_PLATFORM_DRIVER_PROBE")) {
-        g_debug ("(%s/%s): port's parent platform driver is not whitelisted", subsys, name);
+        g_debug ("(%s/%s): port's parent platform driver '%s' is not whitelisted", subsys, name,
+                 g_udev_device_get_name (physdev));
         goto out;
     }
 
diff --git a/src/mm-modem-analog.c b/src/mm-modem-analog.c
new file mode 100644
index 0000000..503671f
--- /dev/null
+++ b/src/mm-modem-analog.c
@@ -0,0 +1,68 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008-2010 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#include <string.h>
+#include <dbus/dbus-glib.h>
+#include "mm-modem-analog.h"
+#include "mm-errors.h"
+#include "mm-callback-info.h"
+#include "mm-marshal.h"
+#include "mm-auth-provider.h"
+
+#include "mm-modem-analog-glue.h"
+
+/*****************************************************************************/
+
+static void
+mm_modem_analog_init (gpointer g_iface)
+{
+    static gboolean initialized = FALSE;
+
+    if (initialized)
+        return;
+
+    initialized = TRUE;
+}
+
+GType
+mm_modem_analog_get_type (void)
+{
+    static GType modem_type = 0;
+
+    if (!G_UNLIKELY (modem_type)) {
+        const GTypeInfo modem_info = {
+            sizeof (MMModemAnalog), /* class_size */
+            mm_modem_analog_init,   /* base_init */
+            NULL,       /* base_finalize */
+            NULL,
+            NULL,       /* class_finalize */
+            NULL,       /* class_data */
+            0,
+            0,              /* n_preallocs */
+            NULL
+        };
+
+        modem_type = g_type_register_static (G_TYPE_INTERFACE,
+                                             "MMModemAnalog",
+                                             &modem_info, 0);
+
+        g_type_interface_add_prerequisite (modem_type, MM_TYPE_MODEM);
+
+        dbus_g_object_type_install_info (modem_type, &dbus_glib_mm_modem_analog_object_info);
+    }
+
+    return modem_type;
+}
diff --git a/src/mm-modem-analog.h b/src/mm-modem-analog.h
new file mode 100644
index 0000000..a9bcdf1
--- /dev/null
+++ b/src/mm-modem-analog.h
@@ -0,0 +1,39 @@
+/* -*- Mode: C; tab-width: 4; indent-tabs-mode: nil; c-basic-offset: 4 -*- */
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details:
+ *
+ * Copyright (C) 2008-2010 Novell, Inc.
+ * Copyright (C) 2009 Red Hat, Inc.
+ */
+
+#ifndef MM_MODEM_ANALOG_H
+#define MM_MODEM_ANALOG_H
+
+#include <mm-modem.h>
+
+#define MM_TYPE_MODEM_ANALOG               (mm_modem_analog_get_type ())
+#define MM_MODEM_ANALOG(obj)               (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_MODEM_ANALOG, MMModemAnalog))
+#define MM_IS_MODEM_ANALOG(obj)            (G_TYPE_CHECK_INSTANCE_TYPE ((obj), MM_TYPE_MODEM_ANALOG))
+#define MM_MODEM_ANALOG_GET_INTERFACE(obj) (G_TYPE_INSTANCE_GET_INTERFACE ((obj), MM_TYPE_MODEM_ANALOG, MMModemAnalog))
+
+typedef struct _MMModemAnalog MMModemAnalog;
+
+struct _MMModemAnalog {
+    GTypeInterface g_iface;
+
+    /* Methods */
+
+    /* Signals */
+};
+
+GType mm_modem_analog_get_type (void);
+
+#endif  /* MM_MODEM_ANALOG_H */
diff --git a/src/mm-modem.c b/src/mm-modem.c
index 35e3b07..ecb715b 100644
--- a/src/mm-modem.c
+++ b/src/mm-modem.c
@@ -84,6 +84,8 @@ mm_modem_enable (MMModem *self,
     g_return_if_fail (MM_IS_MODEM (self));
     g_return_if_fail (callback != NULL);
 
+    g_debug ("In mm_modem_enable");
+
     state = mm_modem_get_state (self);
     if (state >= MM_MODEM_STATE_ENABLED) {
         MMCallbackInfo *info;
@@ -174,6 +176,8 @@ mm_modem_disable (MMModem *self,
     g_return_if_fail (MM_IS_MODEM (self));
     g_return_if_fail (callback != NULL);
 
+    g_debug ("In mm_modem_disable");
+
     state = mm_modem_get_state (self);
     if (state <= MM_MODEM_STATE_DISABLING) {
         MMCallbackInfo *info;
@@ -227,6 +231,8 @@ mm_modem_connect (MMModem *self,
     g_return_if_fail (callback != NULL);
     g_return_if_fail (number != NULL);
 
+    g_debug ("In mm_modem_connect");
+
     state = mm_modem_get_state (self);
     if (state >= MM_MODEM_STATE_CONNECTING) {
         MMCallbackInfo *info;
@@ -360,6 +366,8 @@ mm_modem_disconnect (MMModem *self,
     g_return_if_fail (MM_IS_MODEM (self));
     g_return_if_fail (callback != NULL);
 
+    g_debug ("In mm_modem_disconnect");
+
     state = mm_modem_get_state (self);
     if (state <= MM_MODEM_STATE_DISCONNECTING) {
         MMCallbackInfo *info;
@@ -486,6 +494,8 @@ mm_modem_get_supported_charsets (MMModem *self,
     g_return_if_fail (MM_IS_MODEM (self));
     g_return_if_fail (callback != NULL);
 
+    g_debug ("In mm_modem_get_supported_charsets");
+
     if (MM_MODEM_GET_INTERFACE (self)->get_supported_charsets)
         MM_MODEM_GET_INTERFACE (self)->get_supported_charsets (self, callback, user_data);
     else {
@@ -508,6 +518,8 @@ mm_modem_set_charset (MMModem *self,
     g_return_if_fail (MM_IS_MODEM (self));
     g_return_if_fail (callback != NULL);
 
+    g_debug ("In mm_modem_set_charset");
+
     if (MM_MODEM_GET_INTERFACE (self)->set_charset)
         MM_MODEM_GET_INTERFACE (self)->set_charset (self, charset, callback, user_data);
     else {
@@ -573,6 +585,8 @@ mm_modem_get_valid (MMModem *self)
     g_return_val_if_fail (self != NULL, FALSE);
     g_return_val_if_fail (MM_IS_MODEM (self), FALSE);
 
+    g_debug ("In mm_modem_get_valid");
+
     g_object_get (G_OBJECT (self), MM_MODEM_VALID, &valid, NULL);
     return valid;
 }
@@ -594,6 +608,8 @@ mm_modem_get_state (MMModem *self)
 {
     MMModemState state = MM_MODEM_STATE_UNKNOWN;
 
+    g_debug ("In mm_modem_get_state");
+
     g_object_get (G_OBJECT (self), MM_MODEM_STATE, &state, NULL);
     return state;
 }
@@ -641,6 +657,8 @@ mm_modem_set_state (MMModem *self,
 
     g_object_get (G_OBJECT (self), MM_MODEM_STATE, &old_state, NULL);
 
+    g_debug ("In mm_modem_set_state");
+
     if (new_state != old_state) {
         g_object_set (G_OBJECT (self), MM_MODEM_STATE, new_state, NULL);
         g_signal_emit_by_name (G_OBJECT (self), "state-changed", new_state, old_state, reason);
diff --git a/src/mm-modem.h b/src/mm-modem.h
index 6eeb4de..8bb23a0 100644
--- a/src/mm-modem.h
+++ b/src/mm-modem.h
@@ -66,6 +66,7 @@ typedef enum {
 #define MM_MODEM_TYPE_UNKNOWN  0
 #define MM_MODEM_TYPE_GSM      1
 #define MM_MODEM_TYPE_CDMA     2
+#define MM_MODEM_TYPE_ANALOG   3
 
 #define MM_MODEM_IP_METHOD_PPP    0
 #define MM_MODEM_IP_METHOD_STATIC 1
diff --git a/src/mm-plugin-base.c b/src/mm-plugin-base.c
index af91ad8..f4c0cfa 100644
--- a/src/mm-plugin-base.c
+++ b/src/mm-plugin-base.c
@@ -423,6 +423,7 @@ emit_probe_result (gpointer user_data)
         g_object_unref (task_priv->probe_port);
         task_priv->probe_port = NULL;
     }
+
     if (task_priv->qcdm_port) {
         g_object_unref (task_priv->qcdm_port);
         task_priv->qcdm_port = NULL;
@@ -592,6 +593,8 @@ real_handle_probe_response (MMPluginBase *self,
     } else if (response) {
         /* Parse the response */
 
+        task_priv->probed_caps |= MM_PLUGIN_BASE_PORT_CAP_HAYES;
+
         switch (task_priv->probe_state) {
         case PROBE_STATE_GCAP_TRY1:
         case PROBE_STATE_GCAP_TRY2:
@@ -600,7 +603,8 @@ real_handle_probe_response (MMPluginBase *self,
             /* Some modems don't respond to AT+GCAP, but often they put a
              * GCAP-style response as a line in the ATI response.
              */
-            task_priv->probed_caps = parse_gcap (response);
+            g_debug ("Got GCAP response: %s", response);
+            task_priv->probed_caps |= parse_gcap (response);
             break;
         case PROBE_STATE_CPIN:
             /* Some devices (ZTE MF628/ONDA MT503HS for example) reply to
@@ -608,13 +612,15 @@ real_handle_probe_response (MMPluginBase *self,
              * Since no known CDMA modems support AT+CPIN? we can consider the
              * device a GSM device if it returns a non-error response to AT+CPIN?.
              */
-            task_priv->probed_caps = parse_cpin (response);
+            g_debug ("Got CPIN response: %s", response);
+            task_priv->probed_caps |= parse_cpin (response);
             break;
         case PROBE_STATE_CGMM:
             /* Some models (BUSlink SCWi275u) stick stupid stuff in the CGMM
              * response but at least it allows us to identify them.
              */
-            task_priv->probed_caps = parse_cgmm (response);
+            g_debug ("Got CGMM response: %s", response);
+            task_priv->probed_caps |= parse_cgmm (response);
             break;
         default:
             break;
@@ -850,6 +856,7 @@ mm_plugin_base_probe_port (MMPluginBase *self,
     /* Open the port */
     task_priv->probe_port = serial;
     task_priv->open_id = g_idle_add (try_open, task);
+    task_priv->probed_caps = 0;
     return TRUE;
 }
 
diff --git a/src/mm-plugin-base.h b/src/mm-plugin-base.h
index a32d53b..ddfe96a 100644
--- a/src/mm-plugin-base.h
+++ b/src/mm-plugin-base.h
@@ -38,6 +38,7 @@
 #define MM_PLUGIN_BASE_PORT_CAP_IS856       0x0100 /* CDMA 3G EVDO rev 0 */
 #define MM_PLUGIN_BASE_PORT_CAP_IS856_A     0x0200 /* CDMA 3G EVDO rev A */
 #define MM_PLUGIN_BASE_PORT_CAP_QCDM        0x0400 /* QCDM-capable port */
+#define MM_PLUGIN_BASE_PORT_CAP_HAYES       0x0800 /* Responds to basic Hayes commands */
 
 #define MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK            (mm_plugin_base_supports_task_get_type ())
 #define MM_PLUGIN_BASE_SUPPORTS_TASK(obj)            (G_TYPE_CHECK_INSTANCE_CAST ((obj), MM_TYPE_PLUGIN_BASE_SUPPORTS_TASK, MMPluginBaseSupportsTask))
diff --git a/src/mm-serial-port.c b/src/mm-serial-port.c
index ed44167..2ba1f93 100644
--- a/src/mm-serial-port.c
+++ b/src/mm-serial-port.c
@@ -376,9 +376,9 @@ mm_serial_port_send_command (MMSerialPort *self,
 
     serial_debug (self, "-->", (const char *) command->data, command->len);
 
-    /* Only accept about 3 seconds of EAGAIN */
+    /* Only accept about 10 seconds of EAGAIN */
     if (priv->send_delay > 0)
-        eagain_count = 3000000 / priv->send_delay;
+        eagain_count = 10000000 / priv->send_delay;
 
     while (i < command->len) {
         p = &command->data[i];
