diff --git a/debian/control.d/flavour-control.stub b/debian/control.d/flavour-control.stub
index 2838603..3176c8e 100644
--- a/debian/control.d/flavour-control.stub
+++ b/debian/control.d/flavour-control.stub
@@ -49,6 +49,20 @@ Description: Ubuntu supplied Linux modules for version PKGVER ALSA snapshots.
  the linux-backports-modules-alsa-FLAVOUR meta-package, which will ensure that upgrades work
  correctly, and that supporting packages are also installed.
 
+Package: linux-backports-modules-wwan-PKGVER-ABINUM-FLAVOUR
+Architecture: ARCH
+Section: SECTION_IMAGE
+Priority: optional
+Provides:
+Depends: linux-image-PKGVER-ABINUM-FLAVOUR
+Pre-Depends: dpkg (>= 1.10.24)
+Description: Ubuntu supplied Linux modules for version PKGVER on DESC.
+ This package contains WWAN modules supplied by Ubuntu for Linux PKGVER.
+ .
+ You likely do not want to install this package directly. Instead, install
+ the linux-backports-modules-wwan-FLAVOUR meta-package, which will ensure that
+ upgrades work correctly, and that supporting packages are also installed.
+
 #Package: linux-backports-modules-nouveau-PKGVER-ABINUM-FLAVOUR
 #Architecture: ARCH
 #Section: SECTION_IMAGE
diff --git a/debian/rules.d/2-binary-arch.mk b/debian/rules.d/2-binary-arch.mk
index 90f84c0..2561801 100644
--- a/debian/rules.d/2-binary-arch.mk
+++ b/debian/rules.d/2-binary-arch.mk
@@ -63,6 +63,7 @@ $(stampdir)/stamp-build-%: $(stampdir)/stamp-prepare-%
 	@echo "Building $*..."
 	cd $(builddir)/build-$*/$(CWDIR) && $(make_compat)
 	cd $(builddir)/build-$*/alsa-driver && make $(conc_level)
+	cd $(builddir)/build-$*/wwan-drivers && make $(conc_level)
 	$(kmake) $(conc_level) modules
 	@touch $@
 
@@ -71,6 +72,9 @@ install-%: cwpkgdir = $(CURDIR)/debian/linux-backports-modules-wireless-$(releas
 install-%: cwblddir = $(builddir)/build-$*/$(CWDIR)
 install-%: cwmoddir = $(cwpkgdir)/lib/modules/$(release)-$(abinum)-$*
 install-%: cwsrcdir = $(CURDIR)/updates/$(CWDIR)
+install-%: wwpkgdir = $(CURDIR)/debian/linux-backports-modules-wwan-$(release)-$(abinum)-$*
+install-%: wwmoddir = $(wwpkgdir)/lib/modules/$(release)-$(abinum)-$*
+install-%: wwsrcdir = $(CURDIR)/updates/wwan-drivers
 install-%: cspkgdir = $(CURDIR)/debian/linux-backports-modules-alsa-$(release)-$(abinum)-$*
 install-%: csmoddir = $(cspkgdir)/lib/modules/$(release)-$(abinum)-$*
 install-%: nvpkgdir = $(CURDIR)/debian/linux-backports-modules-nouveau-$(release)-$(abinum)-$*
@@ -161,6 +165,24 @@ ifeq ($(do_nouveau_package),true)
 endif
 
 	#
+	# Build the wwan-drivers packages.
+	#
+	install -d $(wwmoddir)/updates/wwan
+	find $(builddir)/build-*/wwan-drivers -type f -name '*.ko' |	       \
+	while read f; do						       \
+		install -v $$f $(wwmoddir)/updates/wwan/;		       \
+		strip --strip-debug $(wwmoddir)/updates/wwan/$$(basename $$f); \
+	done
+	$(MAKE) -C $(wwsrcdir) prefix=$(wwpkgdir) install
+	install -d $(wwpkgdir)/DEBIAN
+	for script in postinst postrm; do				       \
+		sed -e 's/@@KVER@@/$(release)-$(abinum)-$*/g'		       \
+			debian/control-scripts/$$script			       \
+			>$(wwpkgdir)/DEBIAN/$$script;			       \
+		chmod 755 $(wwpkgdir)/DEBIAN/$$script;			       \
+	done
+	
+	#
 	# The flavour specific headers package
 	#
 	install -d $(hdrdir)/include
@@ -186,6 +208,7 @@ package_list += linux-backports-modules-alsa-$(release)-$(abinum)-$*
 ifeq ($(do_nouveau_package),true)
 package_list += linux-backports-modules-nouveau-$(release)-$(abinum)-$*
 endif
+package_list += linux-backports-modules-wwan-$(release)-$(abinum)-$*
 
 binary-modules-%: install-%
 	dh_testdir
diff --git a/updates/Makefile b/updates/Makefile
index e6e25b5..7ab46a7 100644
--- a/updates/Makefile
+++ b/updates/Makefile
@@ -12,3 +12,4 @@ KBUILD_EXTMOD	:= $(src)/alsa-driver
 
 obj-y	+= wireless-staging/
 obj-y	+= thinkpad-acpi/
+obj-y	+= wwan-drivers/
diff --git a/updates/wwan-drivers/BOM b/updates/wwan-drivers/BOM
new file mode 100644
index 0000000..34a2cf5
--- /dev/null
+++ b/updates/wwan-drivers/BOM
@@ -0,0 +1,17 @@
+History of files:
+
+ * qcserial.c (v2.6.34:drivers/usb/serial)
+   + 0d4561947b8ddd5d944bdbbdc1ea1d6fd9a06041 (upstream)
+     usb serial: Add generic USB wwan support
+   + 3d7e59ad88fdb6bc50ae9b7e822d4bb5f68b68f9 (upstream)
+     USB: qcserial: Use generic USB wwan code
+   + e07896e62abbf7a741a5cd5b25ba7637bdf91ad0 (upstream)
+     USB: qcserial: Add support for Qualcomm Gobi 2000 devices
+
+ * gobi_loader-0.6 (http://www.codon.org.uk/~mjg59/gobi_loader/):
+   - http://www.codon.org.uk/~mjg59/gobi_loader/download/
+
+The qcserial driver/gobi_loader combination relies on firmware which cannot
+be redistributed (qmss.mbn, apps.mbn, UQCN.mbn). Some hints found on the
+gobi_loader page.
+
diff --git a/updates/wwan-drivers/Makefile b/updates/wwan-drivers/Makefile
new file mode 100644
index 0000000..9455e12
--- /dev/null
+++ b/updates/wwan-drivers/Makefile
@@ -0,0 +1,24 @@
+ifeq ($(KERNELRELEASE),)
+GOBI_VERSION = 0.6
+
+all:	gobi_loader
+
+gobi_loader:
+	$(MAKE) -C gobi_loader-$(GOBI_VERSION)
+
+.PHONY: install
+install:
+	$(MAKE) -C gobi_loader-$(GOBI_VERSION) prefix=$(prefix) install
+	DOCDIR=$(prefix)/usr/share/doc/$$(basename $(prefix));		\
+	install -d $$DOCDIR;						\
+	install -v gobi_loader-$(GOBI_VERSION)/README			\
+		$$DOCDIR/README.gobi_loader
+
+.PHONY: clean
+clean:
+	$(MAKE) -C gobi_loader-$(GOBI_VERSION) clean
+else
+#warning hello
+obj-m += qcserial.o
+obj-m += usb_wwan.o
+endif
diff --git a/updates/wwan-drivers/gobi_loader-0.6/60-gobi.rules b/updates/wwan-drivers/gobi_loader-0.6/60-gobi.rules
new file mode 100644
index 0000000..649a7a1
--- /dev/null
+++ b/updates/wwan-drivers/gobi_loader-0.6/60-gobi.rules
@@ -0,0 +1,33 @@
+# udev rules for firmware loading on qualcomm gobi devices
+
+ACTION=="add", SUBSYSTEM=="tty" KERNEL=="ttyUSB*" GOTO="gobi_rules"
+
+GOTO="gobi_rules_end"
+
+LABEL="gobi_rules"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9211", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="201d", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="04da", ATTRS{idProduct}=="250c", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8171", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="1410", ATTRS{idProduct}=="a008", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="0b05", ATTRS{idProduct}=="1774", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="19d2", ATTRS{idProduct}=="fff2", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="1557", ATTRS{idProduct}=="0a80", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9008", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9201", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9221", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9231", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="1f45", ATTRS{idProduct}=="0001", RUN+="gobi_loader $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="16d8", ATTRS{idProduct}=="8001", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="1199", ATTRS{idProduct}=="9000", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="03f0", ATTRS{idProduct}=="241d", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9204", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9214", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9224", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9234", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9244", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9264", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="05c6", ATTRS{idProduct}=="9274", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+ATTRS{idVendor}=="413c", ATTRS{idProduct}=="8185", RUN+="gobi_loader -2000 $env{DEVNAME} /lib/firmware/gobi"
+
+LABEL="gobi_rules_end"
diff --git a/updates/wwan-drivers/gobi_loader-0.6/Makefile b/updates/wwan-drivers/gobi_loader-0.6/Makefile
new file mode 100644
index 0000000..41cace0
--- /dev/null
+++ b/updates/wwan-drivers/gobi_loader-0.6/Makefile
@@ -0,0 +1,26 @@
+VERSION = 0.6
+
+gobi_loader: gobi_loader.c
+	gcc -Wall gobi_loader.c -o gobi_loader
+
+all: gobi_loader
+
+install: gobi_loader
+	install -D gobi_loader ${prefix}/lib/udev/gobi_loader
+	install -D 60-gobi.rules ${prefix}/lib/udev/rules.d/60-gobi.rules
+	mkdir -p ${prefix}/lib/firmware
+	-udevadm control --reload-rules
+
+uninstall:
+	-rm $(prefix)/lib/udev/gobi_loader
+	-rm $(prefix)/etc/udev/rules.d/60-gobi.rules
+
+clean:
+	-rm gobi_loader
+	-rm *~
+
+dist:
+	mkdir gobi_loader-$(VERSION)
+	cp gobi_loader.c README Makefile 60-gobi.rules gobi_loader-$(VERSION)
+	tar zcf gobi_loader-$(VERSION).tar.gz gobi_loader-$(VERSION)
+	rm -rf gobi_loader-$(VERSION)
diff --git a/updates/wwan-drivers/gobi_loader-0.6/README b/updates/wwan-drivers/gobi_loader-0.6/README
new file mode 100644
index 0000000..eef40f7
--- /dev/null
+++ b/updates/wwan-drivers/gobi_loader-0.6/README
@@ -0,0 +1,62 @@
+gobi_loader
+
+gobi_loader is a firmware loader for Qualcomm Gobi USB chipsets. These devices
+appear in an uninitialised state when power is applied and require firmware
+to be loaded before they can be used as modems. gobi_loader adds a udev rule
+that will trigger loading of the firmware and make the modem usable.
+
+Installing:
+
+make; make install
+
+You also need the qcserial driver. This is included in kernels 2.6.30 and
+later. Ensure that it has the IDs for your device. If not, add a line like
+
+{USB_DEVICE(0x1234, 0x5678)},
+
+to the id_table structure in drivers/usb/serial/qcserial.c and
+rebuilt. This device is the firmware loading device and is not usable
+as a modem. When loaded, qcserial should create a /dev/ttyUSB
+device. Check that /etc/udev/rules.d/60-gobi.rules has an entry for your device
+- if not, copy one of the existing lines and change the vendor and product IDs.
+Note that a device line is only needed for the firmware loading ID, not the
+modem ID.
+
+Now you need the modem firmware. This can be obtained from a Windows
+install - alternatively it may be possible to download from your
+vendor's site and extracted with wine. You need the amss.mbn and
+apps.mbn files corresponding to your mobile provider. For Gobi 2000
+devices you also need UQCN.mbn. As yet, I don't have a good mapping
+between devices and the appropriate firmware, so you'll need to figure
+this out yourself.  Remember that some mobile providers use CDMA and
+some use GSM - the CDMA firmware will typically be a smaller file than
+the GSM firmware (approximately 5MB for CDMA firmware, approximately
+9MB for GSM firmware). On my install, these files could be found in a
+QDLService/Packages directory.
+
+Please don't ask me for firmware. It's copyright Qualcomm and I can't
+redistribute it.
+
+Copy the appropriate firmware into /lib/firmware/gobi. Unload and
+reload the qcserial driver or reboot your machine. Assuming you
+installed the application and rules correctly, and assuming that
+qcserial and the rules file both contain your modem devices, your
+firmware will now load. The firmware loading device will now detach
+from your system and reattach with a different ID. If you had to add
+the firmware loading device to qcserial.c then you will probably also
+need to add the modem device. However, the modem device does not need
+to be added to the udev rules file.
+
+A /dev/ttyUSB device will now exist for your modem. Recent versions of
+network-manager should automatically pick it up - older versions (and
+any other modem management software) may need more assistence.
+
+Author:
+
+This code was writte by Matthew Garrett <mjg@redhat.com> and is
+released under the terms of version 2 of the GNU General Public
+License. It is based on code written by Alexander Shumakovitch and
+contains crc generation code from the Linux kernel. Gobi 2000 support
+was provided by Anssi Hannula. The code was written by examining USB
+traffic dumps under Windows - the Qualcomm drivers or firmware have
+not been reverse engineered or disassembled in any way.
\ No newline at end of file
diff --git a/updates/wwan-drivers/gobi_loader-0.6/gobi_loader.c b/updates/wwan-drivers/gobi_loader-0.6/gobi_loader.c
new file mode 100644
index 0000000..690f4ef
--- /dev/null
+++ b/updates/wwan-drivers/gobi_loader-0.6/gobi_loader.c
@@ -0,0 +1,293 @@
+/* Firmware loader for Qualcomm Gobi USB hardware */
+
+/* Copyright 2009 Red Hat <mjg@redhat.com> - heavily based on work done by
+ * Alexander Shumakovitch <shurik@gwu.edu>
+ *
+ * Gobi 2000 support provided by Anssi Hannula <anssi.hannula@iki.fi>
+ *
+ * crc-ccitt code derived from the Linux kernel, lib/crc-ccitt.c
+ *
+ * This program is free software: you can redistribute it and/or
+ * modify it under the terms of version 2 of the GNU General Public
+ * License as published by the Free Software Foundation
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program.  If not, see <http://www.gnu.org/licenses/>.
+ */
+
+#include <stdio.h>
+#include <string.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <termios.h>
+#include <unistd.h>
+#include <malloc.h>
+
+char magic1[] = {0x01, 0x51, 0x43, 0x4f, 0x4d, 0x20, 0x68, 0x69,
+		 0x67, 0x68, 0x20, 0x73, 0x70, 0x65, 0x65, 0x64, 0x20, 
+		 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x63, 0x6f, 0x6c, 0x20,
+		 0x68, 0x73, 0x74, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04,
+		 0x30, 0xff, 0xff};
+
+//char magic1[] = "QCOM high speed protocol hst\0\0\0\0\x04\x04\x30";
+
+char magic2[] = {0x25, 0x05, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+		 0x00, 0x00, 0x04, 0x00, 0x00, 0xff, 0xff};
+
+char magic3[] = {0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0xff, 0xff};
+
+char magic4[] = {0x25, 0x06, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+		 0x00, 0x00, 0x04, 0x00, 0x00, 0xff, 0xff};
+
+char magic5[] = {0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0xff, 0xff};
+
+char magic6[] = {0x25, 0x0d, 0x00, 0x00, 0x00, 0x00, 0x01, 0x00,
+		 0x00, 0x00, 0x04, 0x00, 0x00, 0xff, 0xff};
+
+char magic7[] = {0x27, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00,
+		 0x00, 0x00, 0xff, 0xff};
+
+char magic8[] = {0x29, 0xff, 0xff};
+
+/*
+ * This mysterious table is just the CRC of each possible byte. It can be
+ * computed using the standard bit-at-a-time methods. The polynomial can
+ * be seen in entry 128, 0x8408. This corresponds to x^0 + x^5 + x^12.
+ * Add the implicit x^16, and you have the standard CRC-CCITT.
+ */
+
+unsigned short const crc_ccitt_table[256] = {
+	0x0000, 0x1189, 0x2312, 0x329b, 0x4624, 0x57ad, 0x6536, 0x74bf,
+	0x8c48, 0x9dc1, 0xaf5a, 0xbed3, 0xca6c, 0xdbe5, 0xe97e, 0xf8f7,
+	0x1081, 0x0108, 0x3393, 0x221a, 0x56a5, 0x472c, 0x75b7, 0x643e,
+	0x9cc9, 0x8d40, 0xbfdb, 0xae52, 0xdaed, 0xcb64, 0xf9ff, 0xe876,
+	0x2102, 0x308b, 0x0210, 0x1399, 0x6726, 0x76af, 0x4434, 0x55bd,
+	0xad4a, 0xbcc3, 0x8e58, 0x9fd1, 0xeb6e, 0xfae7, 0xc87c, 0xd9f5,
+	0x3183, 0x200a, 0x1291, 0x0318, 0x77a7, 0x662e, 0x54b5, 0x453c,
+	0xbdcb, 0xac42, 0x9ed9, 0x8f50, 0xfbef, 0xea66, 0xd8fd, 0xc974,
+	0x4204, 0x538d, 0x6116, 0x709f, 0x0420, 0x15a9, 0x2732, 0x36bb,
+	0xce4c, 0xdfc5, 0xed5e, 0xfcd7, 0x8868, 0x99e1, 0xab7a, 0xbaf3,
+	0x5285, 0x430c, 0x7197, 0x601e, 0x14a1, 0x0528, 0x37b3, 0x263a,
+	0xdecd, 0xcf44, 0xfddf, 0xec56, 0x98e9, 0x8960, 0xbbfb, 0xaa72,
+	0x6306, 0x728f, 0x4014, 0x519d, 0x2522, 0x34ab, 0x0630, 0x17b9,
+	0xef4e, 0xfec7, 0xcc5c, 0xddd5, 0xa96a, 0xb8e3, 0x8a78, 0x9bf1,
+	0x7387, 0x620e, 0x5095, 0x411c, 0x35a3, 0x242a, 0x16b1, 0x0738,
+	0xffcf, 0xee46, 0xdcdd, 0xcd54, 0xb9eb, 0xa862, 0x9af9, 0x8b70,
+	0x8408, 0x9581, 0xa71a, 0xb693, 0xc22c, 0xd3a5, 0xe13e, 0xf0b7,
+	0x0840, 0x19c9, 0x2b52, 0x3adb, 0x4e64, 0x5fed, 0x6d76, 0x7cff,
+	0x9489, 0x8500, 0xb79b, 0xa612, 0xd2ad, 0xc324, 0xf1bf, 0xe036,
+	0x18c1, 0x0948, 0x3bd3, 0x2a5a, 0x5ee5, 0x4f6c, 0x7df7, 0x6c7e,
+	0xa50a, 0xb483, 0x8618, 0x9791, 0xe32e, 0xf2a7, 0xc03c, 0xd1b5,
+	0x2942, 0x38cb, 0x0a50, 0x1bd9, 0x6f66, 0x7eef, 0x4c74, 0x5dfd,
+	0xb58b, 0xa402, 0x9699, 0x8710, 0xf3af, 0xe226, 0xd0bd, 0xc134,
+	0x39c3, 0x284a, 0x1ad1, 0x0b58, 0x7fe7, 0x6e6e, 0x5cf5, 0x4d7c,
+	0xc60c, 0xd785, 0xe51e, 0xf497, 0x8028, 0x91a1, 0xa33a, 0xb2b3,
+	0x4a44, 0x5bcd, 0x6956, 0x78df, 0x0c60, 0x1de9, 0x2f72, 0x3efb,
+	0xd68d, 0xc704, 0xf59f, 0xe416, 0x90a9, 0x8120, 0xb3bb, 0xa232,
+	0x5ac5, 0x4b4c, 0x79d7, 0x685e, 0x1ce1, 0x0d68, 0x3ff3, 0x2e7a,
+	0xe70e, 0xf687, 0xc41c, 0xd595, 0xa12a, 0xb0a3, 0x8238, 0x93b1,
+	0x6b46, 0x7acf, 0x4854, 0x59dd, 0x2d62, 0x3ceb, 0x0e70, 0x1ff9,
+	0xf78f, 0xe606, 0xd49d, 0xc514, 0xb1ab, 0xa022, 0x92b9, 0x8330,
+	0x7bc7, 0x6a4e, 0x58d5, 0x495c, 0x3de3, 0x2c6a, 0x1ef1, 0x0f78
+};
+
+unsigned short crc_ccitt_byte(unsigned short crc, const char c)
+{
+        return (crc >> 8) ^ crc_ccitt_table[(crc ^ c) & 0xff];
+}
+
+/**
+ *	crc_ccitt - recompute the CRC for the data buffer
+ *	@crc: previous CRC value
+ *	@buffer: data pointer
+ *	@len: number of bytes in the buffer
+ */
+unsigned short crc_ccitt(short crc, char const *buffer, size_t len)
+{
+	while (len--)
+		crc = crc_ccitt_byte(crc, *buffer++);
+	return crc;
+}
+
+void usage (char **argv) {
+	printf ("usage: %s [-2000] serial_device firmware_dir\n", argv[0]);
+}
+
+int main(int argc, char **argv) {	
+	int serialfd;
+	int fwfd;
+	int len;
+	int err;
+	int gobi2000 = 0;
+	struct termios terminal_data;
+	struct stat file_data;
+	char *fwdata = malloc(1024*1024);
+
+	if (argc < 3 || argc > 4) {
+		usage(argv);
+		return -1;
+	}
+
+	if (!fwdata) {
+		fprintf(stderr, "Failed to allocate memory for firmware\n");
+		return -1;
+	}
+
+	if (argc == 4) {
+		if (!strcmp(argv[1], "-2000")) {
+			gobi2000=1;
+			magic1[33]++;
+			magic1[34]++;
+		} else {
+			usage(argv);
+		}
+	}
+
+	serialfd = open(argv[argc-2], O_RDWR);
+
+	if (serialfd == -1) {
+		perror("Failed to open serial device: ");
+		usage(argv);
+		return -1;
+	}
+
+	err = chdir(argv[argc-1]);
+	if (err) {
+		perror("Failed to change directory: ");
+		usage(argv);
+		return -1;
+	}
+
+	fwfd = open("amss.mbn", O_RDONLY);
+
+	if (fwfd == -1) {
+		perror("Failed to open firmware: ");
+		usage(argv);
+		return -1;
+	}
+
+	fstat(fwfd, &file_data);
+	*(int *)&magic2[2] = file_data.st_size - 8;
+	*(int *)&magic3[7] = file_data.st_size - 8;
+	*(short *)&magic1[sizeof(magic1)-2] =
+		~crc_ccitt(0xffff, magic1, sizeof(magic1)-2);
+	*(short *)&magic2[sizeof(magic2)-2] =
+		~crc_ccitt(0xffff, magic2, sizeof(magic2)-2);
+	*(short *)&magic3[sizeof(magic3)-2] =
+		~crc_ccitt(0xffff, magic3, sizeof(magic3)-2);
+
+	tcgetattr (serialfd, &terminal_data);
+	cfmakeraw (&terminal_data);
+	tcsetattr (serialfd, TCSANOW, &terminal_data);
+
+	write (serialfd, "\x7e", 1);
+	write (serialfd, magic1, sizeof(magic1));
+	write (serialfd, "\x7e", 1);
+
+	write (serialfd, "\x7e", 1);
+	write (serialfd, magic2, sizeof(magic2));
+	write (serialfd, "\x7e", 1);
+
+	write (serialfd, magic3, sizeof(magic3));
+
+	while (1) {
+		len = read (fwfd, fwdata, 1024*1024);
+		if (len == 1024*1024)
+			write (serialfd, fwdata, 1024*1024);
+		else {
+			write (serialfd, fwdata, len-8);
+			break;
+		}
+		write (serialfd, fwdata, 0);
+	}
+
+	fwfd = open("apps.mbn", O_RDONLY);
+
+	if (fwfd == -1) {
+		perror("Failed to open secondary firmware: ");
+		usage(argv);
+		return -1;
+	}
+
+	fstat(fwfd, &file_data);
+	*(int *)&magic4[2] = file_data.st_size;
+	*(int *)&magic5[7] = file_data.st_size;
+
+	*(short *)&magic4[sizeof(magic4)-2] =
+		~crc_ccitt(0xffff, magic4, sizeof(magic4)-2);
+	*(short *)&magic5[sizeof(magic5)-2] =
+		~crc_ccitt(0xffff, magic5, sizeof(magic5)-2);
+
+	write (serialfd, "\x7e", 1);
+	write (serialfd, magic4, sizeof(magic4));
+	write (serialfd, "\x7e", 1);
+
+	write (serialfd, magic5, sizeof(magic5));
+
+	while (1) {
+		len = read (fwfd, fwdata, 1024*1024);
+		if (len == 1024*1024)
+			write (serialfd, fwdata, 1024*1024);
+		else {
+			write (serialfd, fwdata, len);
+			break;
+		}
+		write (serialfd, fwdata, 0);
+	}
+
+	if (gobi2000) {
+		fwfd = open("UQCN.mbn", O_RDONLY);
+
+		if (fwfd == -1)
+			fwfd = open("uqcn.mbn", O_RDONLY);
+
+		if (fwfd == -1) {
+			perror("Failed to open tertiary firmware: ");
+			usage(argv);
+			return -1;
+		}
+
+		fstat(fwfd, &file_data);
+		*(int *)&magic6[2] = file_data.st_size;
+		*(int *)&magic7[7] = file_data.st_size;
+
+		*(short *)&magic6[sizeof(magic6)-2] =
+			~crc_ccitt(0xffff, magic6, sizeof(magic6)-2);
+		*(short *)&magic7[sizeof(magic7)-2] =
+			~crc_ccitt(0xffff, magic7, sizeof(magic7)-2);
+
+		write (serialfd, "\x7e", 1);
+		write (serialfd, magic6, sizeof(magic6));
+		write (serialfd, "\x7e", 1);
+
+		write (serialfd, magic7, sizeof(magic7));
+
+		while (1) {
+			len = read (fwfd, fwdata, 1024*1024);
+			if (len == 1024*1024)
+				write (serialfd, fwdata, 1024*1024);
+			else {
+				write (serialfd, fwdata, len);
+				break;
+			}
+			write (serialfd, fwdata, 0);
+		}
+	}
+
+	*(short *)&magic8[sizeof(magic8)-2] =
+		~crc_ccitt(0xffff, magic8, sizeof(magic8)-2);
+
+	write (serialfd, "\x7e", 1);
+	write (serialfd, magic8, sizeof(magic8));
+	write (serialfd, "\x7e", 1);
+
+	return 0;
+}
diff --git a/updates/wwan-drivers/qcserial.c b/updates/wwan-drivers/qcserial.c
new file mode 100644
index 0000000..04bb759
--- /dev/null
+++ b/updates/wwan-drivers/qcserial.c
@@ -0,0 +1,227 @@
+/*
+ * Qualcomm Serial USB driver
+ *
+ *	Copyright (c) 2008 QUALCOMM Incorporated.
+ *	Copyright (c) 2009 Greg Kroah-Hartman <gregkh@suse.de>
+ *	Copyright (c) 2009 Novell Inc.
+ *
+ *	This program is free software; you can redistribute it and/or
+ *	modify it under the terms of the GNU General Public License version
+ *	2 as published by the Free Software Foundation.
+ *
+ */
+
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include <linux/slab.h>
+#include "usb-wwan.h"
+
+#define DRIVER_AUTHOR "Qualcomm Inc"
+#define DRIVER_DESC "Qualcomm USB Serial driver"
+
+static int debug;
+
+static const struct usb_device_id id_table[] = {
+	{USB_DEVICE(0x05c6, 0x9211)},	/* Acer Gobi QDL device */
+	{USB_DEVICE(0x05c6, 0x9212)},	/* Acer Gobi Modem Device */
+	{USB_DEVICE(0x03f0, 0x1f1d)},	/* HP un2400 Gobi Modem Device */
+	{USB_DEVICE(0x03f0, 0x201d)},	/* HP un2400 Gobi QDL Device */
+	{USB_DEVICE(0x04da, 0x250d)},	/* Panasonic Gobi Modem device */
+	{USB_DEVICE(0x04da, 0x250c)},	/* Panasonic Gobi QDL device */
+	{USB_DEVICE(0x413c, 0x8172)},	/* Dell Gobi Modem device */
+	{USB_DEVICE(0x413c, 0x8171)},	/* Dell Gobi QDL device */
+	{USB_DEVICE(0x1410, 0xa001)},	/* Novatel Gobi Modem device */
+	{USB_DEVICE(0x1410, 0xa008)},	/* Novatel Gobi QDL device */
+	{USB_DEVICE(0x0b05, 0x1776)},	/* Asus Gobi Modem device */
+	{USB_DEVICE(0x0b05, 0x1774)},	/* Asus Gobi QDL device */
+	{USB_DEVICE(0x19d2, 0xfff3)},	/* ONDA Gobi Modem device */
+	{USB_DEVICE(0x19d2, 0xfff2)},	/* ONDA Gobi QDL device */
+	{USB_DEVICE(0x1557, 0x0a80)},	/* OQO Gobi QDL device */
+	{USB_DEVICE(0x05c6, 0x9001)},   /* Generic Gobi Modem device */
+	{USB_DEVICE(0x05c6, 0x9002)},	/* Generic Gobi Modem device */
+	{USB_DEVICE(0x05c6, 0x9202)},	/* Generic Gobi Modem device */
+	{USB_DEVICE(0x05c6, 0x9203)},	/* Generic Gobi Modem device */
+	{USB_DEVICE(0x05c6, 0x9222)},	/* Generic Gobi Modem device */
+	{USB_DEVICE(0x05c6, 0x9008)},	/* Generic Gobi QDL device */
+	{USB_DEVICE(0x05c6, 0x9201)},	/* Generic Gobi QDL device */
+	{USB_DEVICE(0x05c6, 0x9221)},	/* Generic Gobi QDL device */
+	{USB_DEVICE(0x05c6, 0x9231)},	/* Generic Gobi QDL device */
+	{USB_DEVICE(0x1f45, 0x0001)},	/* Unknown Gobi QDL device */
+	{USB_DEVICE(0x413c, 0x8185)},	/* Dell Gobi 2000 QDL device (N0218, VU936) */
+	{USB_DEVICE(0x413c, 0x8186)},	/* Dell Gobi 2000 Modem device (N0218, VU936) */
+	{USB_DEVICE(0x05c6, 0x9224)},	/* Sony Gobi 2000 QDL device (N0279, VU730) */
+	{USB_DEVICE(0x05c6, 0x9225)},	/* Sony Gobi 2000 Modem device (N0279, VU730) */
+	{USB_DEVICE(0x05c6, 0x9244)},	/* Samsung Gobi 2000 QDL device (VL176) */
+	{USB_DEVICE(0x05c6, 0x9245)},	/* Samsung Gobi 2000 Modem device (VL176) */
+	{USB_DEVICE(0x03f0, 0x241d)},	/* HP Gobi 2000 QDL device (VP412) */
+	{USB_DEVICE(0x03f0, 0x251d)},	/* HP Gobi 2000 Modem device (VP412) */
+	{USB_DEVICE(0x05c6, 0x9214)},	/* Acer Gobi 2000 QDL device (VP413) */
+	{USB_DEVICE(0x05c6, 0x9215)},	/* Acer Gobi 2000 Modem device (VP413) */
+	{USB_DEVICE(0x05c6, 0x9264)},	/* Asus Gobi 2000 QDL device (VR305) */
+	{USB_DEVICE(0x05c6, 0x9265)},	/* Asus Gobi 2000 Modem device (VR305) */
+	{USB_DEVICE(0x05c6, 0x9234)},	/* Top Global Gobi 2000 QDL device (VR306) */
+	{USB_DEVICE(0x05c6, 0x9235)},	/* Top Global Gobi 2000 Modem device (VR306) */
+	{USB_DEVICE(0x05c6, 0x9274)},	/* iRex Technologies Gobi 2000 QDL device (VR307) */
+	{USB_DEVICE(0x05c6, 0x9275)},	/* iRex Technologies Gobi 2000 Modem device (VR307) */
+	{USB_DEVICE(0x1199, 0x9000)},	/* Sierra Wireless Gobi 2000 QDL device (VT773) */
+	{USB_DEVICE(0x1199, 0x9001)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9002)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9003)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9004)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9005)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9006)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9007)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9008)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x9009)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x1199, 0x900a)},	/* Sierra Wireless Gobi 2000 Modem device (VT773) */
+	{USB_DEVICE(0x16d8, 0x8001)},	/* CMDTech Gobi 2000 QDL device (VU922) */
+	{USB_DEVICE(0x16d8, 0x8002)},	/* CMDTech Gobi 2000 Modem device (VU922) */
+	{USB_DEVICE(0x05c6, 0x9204)},	/* Gobi 2000 QDL device */
+	{USB_DEVICE(0x05c6, 0x9205)},	/* Gobi 2000 Modem device */
+	{ }				/* Terminating entry */
+};
+MODULE_DEVICE_TABLE(usb, id_table);
+
+static struct usb_driver qcdriver = {
+	.name			= "qcserial",
+	.probe			= usb_serial_probe,
+	.disconnect		= usb_serial_disconnect,
+	.id_table		= id_table,
+	.suspend		= usb_serial_suspend,
+	.resume			= usb_serial_resume,
+	.supports_autosuspend	= true,
+};
+
+static int qcprobe(struct usb_serial *serial, const struct usb_device_id *id)
+{
+	struct usb_wwan_intf_private *data;
+	struct usb_host_interface *intf = serial->interface->cur_altsetting;
+	int retval = -ENODEV;
+	__u8 nintf;
+	__u8 ifnum;
+
+	dbg("%s", __func__);
+
+	nintf = serial->dev->actconfig->desc.bNumInterfaces;
+	dbg("Num Interfaces = %d", nintf);
+	ifnum = intf->desc.bInterfaceNumber;
+	dbg("This Interface = %d", ifnum);
+
+	data = serial->private = kzalloc(sizeof(struct usb_wwan_intf_private),
+					 GFP_KERNEL);
+	if (!data)
+		return -ENOMEM;
+
+	spin_lock_init(&data->susp_lock);
+
+	switch (nintf) {
+	case 1:
+		/* QDL mode */
+		/* Gobi 2000 has a single altsetting, older ones have two */
+		if (serial->interface->num_altsetting == 2)
+			intf = &serial->interface->altsetting[1];
+		else if (serial->interface->num_altsetting > 2)
+			break;
+
+		if (intf->desc.bNumEndpoints == 2 &&
+		    usb_endpoint_is_bulk_in(&intf->endpoint[0].desc) &&
+		    usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {
+			dbg("QDL port found");
+
+			if (serial->interface->num_altsetting == 1)
+				return 0;
+
+			retval = usb_set_interface(serial->dev, ifnum, 1);
+			if (retval < 0) {
+				dev_err(&serial->dev->dev,
+					"Could not set interface, error %d\n",
+					retval);
+				retval = -ENODEV;
+			}
+			return retval;
+		}
+		break;
+
+	case 3:
+	case 4:
+		/* Composite mode */
+		if (ifnum == 2) {
+			dbg("Modem port found");
+			retval = usb_set_interface(serial->dev, ifnum, 0);
+			if (retval < 0) {
+				dev_err(&serial->dev->dev,
+					"Could not set interface, error %d\n",
+					retval);
+				retval = -ENODEV;
+			}
+			return retval;
+		}
+		break;
+
+	default:
+		dev_err(&serial->dev->dev,
+			"unknown number of interfaces: %d\n", nintf);
+		return -ENODEV;
+	}
+
+	return retval;
+}
+
+static struct usb_serial_driver qcdevice = {
+	.driver = {
+		.owner     = THIS_MODULE,
+		.name      = "qcserial",
+	},
+	.description         = "Qualcomm USB modem",
+	.id_table            = id_table,
+	.usb_driver          = &qcdriver,
+	.num_ports           = 1,
+	.probe               = qcprobe,
+	.open		     = usb_wwan_open,
+	.close		     = usb_wwan_close,
+	.write		     = usb_wwan_write,
+	.write_room	     = usb_wwan_write_room,
+	.chars_in_buffer     = usb_wwan_chars_in_buffer,
+	.attach		     = usb_wwan_startup,
+	.disconnect	     = usb_wwan_disconnect,
+	.release	     = usb_wwan_release,
+#ifdef CONFIG_PM
+	.suspend	     = usb_wwan_suspend,
+	.resume		     = usb_wwan_resume,
+#endif
+};
+
+static int __init qcinit(void)
+{
+	int retval;
+
+	retval = usb_serial_register(&qcdevice);
+	if (retval)
+		return retval;
+
+	retval = usb_register(&qcdriver);
+	if (retval) {
+		usb_serial_deregister(&qcdevice);
+		return retval;
+	}
+
+	return 0;
+}
+
+static void __exit qcexit(void)
+{
+	usb_deregister(&qcdriver);
+	usb_serial_deregister(&qcdevice);
+}
+
+module_init(qcinit);
+module_exit(qcexit);
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_LICENSE("GPL v2");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug enabled or not");
diff --git a/updates/wwan-drivers/usb-wwan.h b/updates/wwan-drivers/usb-wwan.h
new file mode 100644
index 0000000..2be298a
--- /dev/null
+++ b/updates/wwan-drivers/usb-wwan.h
@@ -0,0 +1,67 @@
+/*
+ * Definitions for USB serial mobile broadband cards
+ */
+
+#ifndef __LINUX_USB_USB_WWAN
+#define __LINUX_USB_USB_WWAN
+
+extern void usb_wwan_dtr_rts(struct usb_serial_port *port, int on);
+extern int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port);
+extern void usb_wwan_close(struct usb_serial_port *port);
+extern int usb_wwan_startup(struct usb_serial *serial);
+extern void usb_wwan_disconnect(struct usb_serial *serial);
+extern void usb_wwan_release(struct usb_serial *serial);
+extern int usb_wwan_write_room(struct tty_struct *tty);
+extern void usb_wwan_set_termios(struct tty_struct *tty,
+				 struct usb_serial_port *port,
+				 struct ktermios *old);
+extern int usb_wwan_tiocmget(struct tty_struct *tty, struct file *file);
+extern int usb_wwan_tiocmset(struct tty_struct *tty, struct file *file,
+			     unsigned int set, unsigned int clear);
+extern int usb_wwan_send_setup(struct usb_serial_port *port);
+extern int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
+			  const unsigned char *buf, int count);
+extern int usb_wwan_chars_in_buffer(struct tty_struct *tty);
+#ifdef CONFIG_PM
+extern int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message);
+extern int usb_wwan_resume(struct usb_serial *serial);
+#endif
+
+/* per port private data */
+
+#define N_IN_URB 4
+#define N_OUT_URB 4
+#define IN_BUFLEN 4096
+#define OUT_BUFLEN 4096
+
+struct usb_wwan_intf_private {
+	spinlock_t susp_lock;
+	unsigned int suspended:1;
+	int in_flight;
+	int (*send_setup) (struct usb_serial_port *port);
+	void *private;
+};
+
+struct usb_wwan_port_private {
+	/* Input endpoints and buffer for this port */
+	struct urb *in_urbs[N_IN_URB];
+	u8 *in_buffer[N_IN_URB];
+	/* Output endpoints and buffer for this port */
+	struct urb *out_urbs[N_OUT_URB];
+	u8 *out_buffer[N_OUT_URB];
+	unsigned long out_busy;	/* Bit vector of URBs in use */
+	int opened;
+	struct usb_anchor delayed;
+
+	/* Settings for the port */
+	int rts_state;		/* Handshaking pins (outputs) */
+	int dtr_state;
+	int cts_state;		/* Handshaking pins (inputs) */
+	int dsr_state;
+	int dcd_state;
+	int ri_state;
+
+	unsigned long tx_start_time[N_OUT_URB];
+};
+
+#endif /* __LINUX_USB_USB_WWAN */
diff --git a/updates/wwan-drivers/usb_wwan.c b/updates/wwan-drivers/usb_wwan.c
new file mode 100644
index 0000000..0c70b4a
--- /dev/null
+++ b/updates/wwan-drivers/usb_wwan.c
@@ -0,0 +1,665 @@
+/*
+  USB Driver layer for GSM modems
+
+  Copyright (C) 2005  Matthias Urlichs <smurf@smurf.noris.de>
+
+  This driver is free software; you can redistribute it and/or modify
+  it under the terms of Version 2 of the GNU General Public License as
+  published by the Free Software Foundation.
+
+  Portions copied from the Keyspan driver by Hugh Blemings <hugh@blemings.org>
+
+  History: see the git log.
+
+  Work sponsored by: Sigos GmbH, Germany <info@sigos.de>
+
+  This driver exists because the "normal" serial driver doesn't work too well
+  with GSM modems. Issues:
+  - data loss -- one single Receive URB is not nearly enough
+  - controlling the baud rate doesn't make sense
+*/
+
+#define DRIVER_VERSION "v0.7.2"
+#define DRIVER_AUTHOR "Matthias Urlichs <smurf@smurf.noris.de>"
+#define DRIVER_DESC "USB Driver for GSM modems"
+
+#include <linux/kernel.h>
+#include <linux/jiffies.h>
+#include <linux/errno.h>
+#include <linux/slab.h>
+#include <linux/tty.h>
+#include <linux/tty_flip.h>
+#include <linux/module.h>
+#include <linux/bitops.h>
+#include <linux/usb.h>
+#include <linux/usb/serial.h>
+#include "usb-wwan.h"
+
+static int debug;
+
+void usb_wwan_dtr_rts(struct usb_serial_port *port, int on)
+{
+	struct usb_serial *serial = port->serial;
+	struct usb_wwan_port_private *portdata;
+
+	struct usb_wwan_intf_private *intfdata;
+
+	dbg("%s", __func__);
+
+	intfdata = port->serial->private;
+
+	if (!intfdata->send_setup)
+		return;
+
+	portdata = usb_get_serial_port_data(port);
+	mutex_lock(&serial->disc_mutex);
+	portdata->rts_state = on;
+	portdata->dtr_state = on;
+	if (serial->dev)
+		intfdata->send_setup(port);
+	mutex_unlock(&serial->disc_mutex);
+}
+EXPORT_SYMBOL(usb_wwan_dtr_rts);
+
+void usb_wwan_set_termios(struct tty_struct *tty,
+			  struct usb_serial_port *port,
+			  struct ktermios *old_termios)
+{
+	struct usb_wwan_intf_private *intfdata = port->serial->private;
+
+	dbg("%s", __func__);
+
+	/* Doesn't support option setting */
+	tty_termios_copy_hw(tty->termios, old_termios);
+
+	if (intfdata->send_setup)
+		intfdata->send_setup(port);
+}
+EXPORT_SYMBOL(usb_wwan_set_termios);
+
+int usb_wwan_tiocmget(struct tty_struct *tty, struct file *file)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	unsigned int value;
+	struct usb_wwan_port_private *portdata;
+
+	portdata = usb_get_serial_port_data(port);
+
+	value = ((portdata->rts_state) ? TIOCM_RTS : 0) |
+	    ((portdata->dtr_state) ? TIOCM_DTR : 0) |
+	    ((portdata->cts_state) ? TIOCM_CTS : 0) |
+	    ((portdata->dsr_state) ? TIOCM_DSR : 0) |
+	    ((portdata->dcd_state) ? TIOCM_CAR : 0) |
+	    ((portdata->ri_state) ? TIOCM_RNG : 0);
+
+	return value;
+}
+EXPORT_SYMBOL(usb_wwan_tiocmget);
+
+int usb_wwan_tiocmset(struct tty_struct *tty, struct file *file,
+		      unsigned int set, unsigned int clear)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = port->serial->private;
+
+	if (!intfdata->send_setup)
+		return -EINVAL;
+
+	/* FIXME: what locks portdata fields ? */
+	if (set & TIOCM_RTS)
+		portdata->rts_state = 1;
+	if (set & TIOCM_DTR)
+		portdata->dtr_state = 1;
+
+	if (clear & TIOCM_RTS)
+		portdata->rts_state = 0;
+	if (clear & TIOCM_DTR)
+		portdata->dtr_state = 0;
+	return intfdata->send_setup(port);
+}
+EXPORT_SYMBOL(usb_wwan_tiocmset);
+
+/* Write */
+int usb_wwan_write(struct tty_struct *tty, struct usb_serial_port *port,
+		   const unsigned char *buf, int count)
+{
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	int i;
+	int left, todo;
+	struct urb *this_urb = NULL;	/* spurious */
+	int err;
+	unsigned long flags;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = port->serial->private;
+
+	dbg("%s: write (%d chars)", __func__, count);
+
+	i = 0;
+	left = count;
+	for (i = 0; left > 0 && i < N_OUT_URB; i++) {
+		todo = left;
+		if (todo > OUT_BUFLEN)
+			todo = OUT_BUFLEN;
+
+		this_urb = portdata->out_urbs[i];
+		if (test_and_set_bit(i, &portdata->out_busy)) {
+			if (time_before(jiffies,
+					portdata->tx_start_time[i] + 10 * HZ))
+				continue;
+			usb_unlink_urb(this_urb);
+			continue;
+		}
+		dbg("%s: endpoint %d buf %d", __func__,
+		    usb_pipeendpoint(this_urb->pipe), i);
+
+		err = usb_autopm_get_interface_async(port->serial->interface);
+		if (err < 0)
+			break;
+
+		/* send the data */
+		memcpy(this_urb->transfer_buffer, buf, todo);
+		this_urb->transfer_buffer_length = todo;
+
+		spin_lock_irqsave(&intfdata->susp_lock, flags);
+		if (intfdata->suspended) {
+			usb_anchor_urb(this_urb, &portdata->delayed);
+			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+		} else {
+			intfdata->in_flight++;
+			spin_unlock_irqrestore(&intfdata->susp_lock, flags);
+			err = usb_submit_urb(this_urb, GFP_ATOMIC);
+			if (err) {
+				dbg("usb_submit_urb %p (write bulk) failed "
+				    "(%d)", this_urb, err);
+				clear_bit(i, &portdata->out_busy);
+				spin_lock_irqsave(&intfdata->susp_lock, flags);
+				intfdata->in_flight--;
+				spin_unlock_irqrestore(&intfdata->susp_lock,
+						       flags);
+				continue;
+			}
+		}
+
+		portdata->tx_start_time[i] = jiffies;
+		buf += todo;
+		left -= todo;
+	}
+
+	count -= left;
+	dbg("%s: wrote (did %d)", __func__, count);
+	return count;
+}
+EXPORT_SYMBOL(usb_wwan_write);
+
+static void usb_wwan_indat_callback(struct urb *urb)
+{
+	int err;
+	int endpoint;
+	struct usb_serial_port *port;
+	struct tty_struct *tty;
+	unsigned char *data = urb->transfer_buffer;
+	int status = urb->status;
+
+	dbg("%s: %p", __func__, urb);
+
+	endpoint = usb_pipeendpoint(urb->pipe);
+	port = urb->context;
+
+	if (status) {
+		dbg("%s: nonzero status: %d on endpoint %02x.",
+		    __func__, status, endpoint);
+	} else {
+		tty = tty_port_tty_get(&port->port);
+		if (urb->actual_length) {
+			tty_insert_flip_string(tty, data, urb->actual_length);
+			tty_flip_buffer_push(tty);
+		} else
+			dbg("%s: empty read urb received", __func__);
+		tty_kref_put(tty);
+
+		/* Resubmit urb so we continue receiving */
+		if (status != -ESHUTDOWN) {
+			err = usb_submit_urb(urb, GFP_ATOMIC);
+			if (err && err != -EPERM)
+				printk(KERN_ERR "%s: resubmit read urb failed. "
+				       "(%d)", __func__, err);
+			else
+				usb_mark_last_busy(port->serial->dev);
+		}
+
+	}
+	return;
+}
+
+static void usb_wwan_outdat_callback(struct urb *urb)
+{
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	int i;
+
+	dbg("%s", __func__);
+
+	port = urb->context;
+	intfdata = port->serial->private;
+
+	usb_serial_port_softint(port);
+	usb_autopm_put_interface_async(port->serial->interface);
+	portdata = usb_get_serial_port_data(port);
+	spin_lock(&intfdata->susp_lock);
+	intfdata->in_flight--;
+	spin_unlock(&intfdata->susp_lock);
+
+	for (i = 0; i < N_OUT_URB; ++i) {
+		if (portdata->out_urbs[i] == urb) {
+			smp_mb__before_clear_bit();
+			clear_bit(i, &portdata->out_busy);
+			break;
+		}
+	}
+}
+
+int usb_wwan_write_room(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	int i;
+	int data_len = 0;
+	struct urb *this_urb;
+
+	portdata = usb_get_serial_port_data(port);
+
+	for (i = 0; i < N_OUT_URB; i++) {
+		this_urb = portdata->out_urbs[i];
+		if (this_urb && !test_bit(i, &portdata->out_busy))
+			data_len += OUT_BUFLEN;
+	}
+
+	dbg("%s: %d", __func__, data_len);
+	return data_len;
+}
+EXPORT_SYMBOL(usb_wwan_write_room);
+
+int usb_wwan_chars_in_buffer(struct tty_struct *tty)
+{
+	struct usb_serial_port *port = tty->driver_data;
+	struct usb_wwan_port_private *portdata;
+	int i;
+	int data_len = 0;
+	struct urb *this_urb;
+
+	portdata = usb_get_serial_port_data(port);
+
+	for (i = 0; i < N_OUT_URB; i++) {
+		this_urb = portdata->out_urbs[i];
+		/* FIXME: This locking is insufficient as this_urb may
+		   go unused during the test */
+		if (this_urb && test_bit(i, &portdata->out_busy))
+			data_len += this_urb->transfer_buffer_length;
+	}
+	dbg("%s: %d", __func__, data_len);
+	return data_len;
+}
+EXPORT_SYMBOL(usb_wwan_chars_in_buffer);
+
+int usb_wwan_open(struct tty_struct *tty, struct usb_serial_port *port)
+{
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata;
+	struct usb_serial *serial = port->serial;
+	int i, err;
+	struct urb *urb;
+
+	portdata = usb_get_serial_port_data(port);
+	intfdata = serial->private;
+
+	dbg("%s", __func__);
+
+	/* Start reading from the IN endpoint */
+	for (i = 0; i < N_IN_URB; i++) {
+		urb = portdata->in_urbs[i];
+		if (!urb)
+			continue;
+		err = usb_submit_urb(urb, GFP_KERNEL);
+		if (err) {
+			dbg("%s: submit urb %d failed (%d) %d",
+			    __func__, i, err, urb->transfer_buffer_length);
+		}
+	}
+
+	if (intfdata->send_setup)
+		intfdata->send_setup(port);
+
+	serial->interface->needs_remote_wakeup = 1;
+	spin_lock_irq(&intfdata->susp_lock);
+	portdata->opened = 1;
+	spin_unlock_irq(&intfdata->susp_lock);
+	usb_autopm_put_interface(serial->interface);
+
+	return 0;
+}
+EXPORT_SYMBOL(usb_wwan_open);
+
+void usb_wwan_close(struct usb_serial_port *port)
+{
+	int i;
+	struct usb_serial *serial = port->serial;
+	struct usb_wwan_port_private *portdata;
+	struct usb_wwan_intf_private *intfdata = port->serial->private;
+
+	dbg("%s", __func__);
+	portdata = usb_get_serial_port_data(port);
+
+	if (serial->dev) {
+		/* Stop reading/writing urbs */
+		spin_lock_irq(&intfdata->susp_lock);
+		portdata->opened = 0;
+		spin_unlock_irq(&intfdata->susp_lock);
+
+		for (i = 0; i < N_IN_URB; i++)
+			usb_kill_urb(portdata->in_urbs[i]);
+		for (i = 0; i < N_OUT_URB; i++)
+			usb_kill_urb(portdata->out_urbs[i]);
+		usb_autopm_get_interface(serial->interface);
+		serial->interface->needs_remote_wakeup = 0;
+	}
+}
+EXPORT_SYMBOL(usb_wwan_close);
+
+/* Helper functions used by usb_wwan_setup_urbs */
+static struct urb *usb_wwan_setup_urb(struct usb_serial *serial, int endpoint,
+				      int dir, void *ctx, char *buf, int len,
+				      void (*callback) (struct urb *))
+{
+	struct urb *urb;
+
+	if (endpoint == -1)
+		return NULL;	/* endpoint not needed */
+
+	urb = usb_alloc_urb(0, GFP_KERNEL);	/* No ISO */
+	if (urb == NULL) {
+		dbg("%s: alloc for endpoint %d failed.", __func__, endpoint);
+		return NULL;
+	}
+
+	/* Fill URB using supplied data. */
+	usb_fill_bulk_urb(urb, serial->dev,
+			  usb_sndbulkpipe(serial->dev, endpoint) | dir,
+			  buf, len, callback, ctx);
+
+	return urb;
+}
+
+/* Setup urbs */
+static void usb_wwan_setup_urbs(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	dbg("%s", __func__);
+
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		/* Do indat endpoints first */
+		for (j = 0; j < N_IN_URB; ++j) {
+			portdata->in_urbs[j] = usb_wwan_setup_urb(serial,
+								  port->
+								  bulk_in_endpointAddress,
+								  USB_DIR_IN,
+								  port,
+								  portdata->
+								  in_buffer[j],
+								  IN_BUFLEN,
+								  usb_wwan_indat_callback);
+		}
+
+		/* outdat endpoints */
+		for (j = 0; j < N_OUT_URB; ++j) {
+			portdata->out_urbs[j] = usb_wwan_setup_urb(serial,
+								   port->
+								   bulk_out_endpointAddress,
+								   USB_DIR_OUT,
+								   port,
+								   portdata->
+								   out_buffer
+								   [j],
+								   OUT_BUFLEN,
+								   usb_wwan_outdat_callback);
+		}
+	}
+}
+
+int usb_wwan_startup(struct usb_serial *serial)
+{
+	int i, j, err;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+	u8 *buffer;
+
+	dbg("%s", __func__);
+
+	/* Now setup per port private data */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		portdata = kzalloc(sizeof(*portdata), GFP_KERNEL);
+		if (!portdata) {
+			dbg("%s: kmalloc for usb_wwan_port_private (%d) failed!.",
+			    __func__, i);
+			return 1;
+		}
+		init_usb_anchor(&portdata->delayed);
+
+		for (j = 0; j < N_IN_URB; j++) {
+			buffer = (u8 *) __get_free_page(GFP_KERNEL);
+			if (!buffer)
+				goto bail_out_error;
+			portdata->in_buffer[j] = buffer;
+		}
+
+		for (j = 0; j < N_OUT_URB; j++) {
+			buffer = kmalloc(OUT_BUFLEN, GFP_KERNEL);
+			if (!buffer)
+				goto bail_out_error2;
+			portdata->out_buffer[j] = buffer;
+		}
+
+		usb_set_serial_port_data(port, portdata);
+
+		if (!port->interrupt_in_urb)
+			continue;
+		err = usb_submit_urb(port->interrupt_in_urb, GFP_KERNEL);
+		if (err)
+			dbg("%s: submit irq_in urb failed %d", __func__, err);
+	}
+	usb_wwan_setup_urbs(serial);
+	return 0;
+
+bail_out_error2:
+	for (j = 0; j < N_OUT_URB; j++)
+		kfree(portdata->out_buffer[j]);
+bail_out_error:
+	for (j = 0; j < N_IN_URB; j++)
+		if (portdata->in_buffer[j])
+			free_page((unsigned long)portdata->in_buffer[j]);
+	kfree(portdata);
+	return 1;
+}
+EXPORT_SYMBOL(usb_wwan_startup);
+
+static void stop_read_write_urbs(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	/* Stop reading/writing urbs */
+	for (i = 0; i < serial->num_ports; ++i) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+		for (j = 0; j < N_IN_URB; j++)
+			usb_kill_urb(portdata->in_urbs[j]);
+		for (j = 0; j < N_OUT_URB; j++)
+			usb_kill_urb(portdata->out_urbs[j]);
+	}
+}
+
+void usb_wwan_disconnect(struct usb_serial *serial)
+{
+	dbg("%s", __func__);
+
+	stop_read_write_urbs(serial);
+}
+EXPORT_SYMBOL(usb_wwan_disconnect);
+
+void usb_wwan_release(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_port_private *portdata;
+
+	dbg("%s", __func__);
+
+	/* Now free them */
+	for (i = 0; i < serial->num_ports; ++i) {
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		for (j = 0; j < N_IN_URB; j++) {
+			usb_free_urb(portdata->in_urbs[j]);
+			free_page((unsigned long)
+				  portdata->in_buffer[j]);
+			portdata->in_urbs[j] = NULL;
+		}
+		for (j = 0; j < N_OUT_URB; j++) {
+			usb_free_urb(portdata->out_urbs[j]);
+			kfree(portdata->out_buffer[j]);
+			portdata->out_urbs[j] = NULL;
+		}
+	}
+
+	/* Now free per port private data */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		kfree(usb_get_serial_port_data(port));
+	}
+}
+EXPORT_SYMBOL(usb_wwan_release);
+
+#ifdef CONFIG_PM
+int usb_wwan_suspend(struct usb_serial *serial, pm_message_t message)
+{
+	struct usb_wwan_intf_private *intfdata = serial->private;
+	int b;
+
+	dbg("%s entered", __func__);
+
+	if (message.event & PM_EVENT_AUTO) {
+		spin_lock_irq(&intfdata->susp_lock);
+		b = intfdata->in_flight;
+		spin_unlock_irq(&intfdata->susp_lock);
+
+		if (b)
+			return -EBUSY;
+	}
+
+	spin_lock_irq(&intfdata->susp_lock);
+	intfdata->suspended = 1;
+	spin_unlock_irq(&intfdata->susp_lock);
+	stop_read_write_urbs(serial);
+
+	return 0;
+}
+EXPORT_SYMBOL(usb_wwan_suspend);
+
+static void play_delayed(struct usb_serial_port *port)
+{
+	struct usb_wwan_intf_private *data;
+	struct usb_wwan_port_private *portdata;
+	struct urb *urb;
+	int err;
+
+	portdata = usb_get_serial_port_data(port);
+	data = port->serial->private;
+	while ((urb = usb_get_from_anchor(&portdata->delayed))) {
+		err = usb_submit_urb(urb, GFP_ATOMIC);
+		if (!err)
+			data->in_flight++;
+	}
+}
+
+int usb_wwan_resume(struct usb_serial *serial)
+{
+	int i, j;
+	struct usb_serial_port *port;
+	struct usb_wwan_intf_private *intfdata = serial->private;
+	struct usb_wwan_port_private *portdata;
+	struct urb *urb;
+	int err = 0;
+
+	dbg("%s entered", __func__);
+	/* get the interrupt URBs resubmitted unconditionally */
+	for (i = 0; i < serial->num_ports; i++) {
+		port = serial->port[i];
+		if (!port->interrupt_in_urb) {
+			dbg("%s: No interrupt URB for port %d", __func__, i);
+			continue;
+		}
+		err = usb_submit_urb(port->interrupt_in_urb, GFP_NOIO);
+		dbg("Submitted interrupt URB for port %d (result %d)", i, err);
+		if (err < 0) {
+			err("%s: Error %d for interrupt URB of port%d",
+			    __func__, err, i);
+			goto err_out;
+		}
+	}
+
+	for (i = 0; i < serial->num_ports; i++) {
+		/* walk all ports */
+		port = serial->port[i];
+		portdata = usb_get_serial_port_data(port);
+
+		/* skip closed ports */
+		spin_lock_irq(&intfdata->susp_lock);
+		if (!portdata->opened) {
+			spin_unlock_irq(&intfdata->susp_lock);
+			continue;
+		}
+
+		for (j = 0; j < N_IN_URB; j++) {
+			urb = portdata->in_urbs[j];
+			err = usb_submit_urb(urb, GFP_ATOMIC);
+			if (err < 0) {
+				err("%s: Error %d for bulk URB %d",
+				    __func__, err, i);
+				spin_unlock_irq(&intfdata->susp_lock);
+				goto err_out;
+			}
+		}
+		play_delayed(port);
+		spin_unlock_irq(&intfdata->susp_lock);
+	}
+	spin_lock_irq(&intfdata->susp_lock);
+	intfdata->suspended = 0;
+	spin_unlock_irq(&intfdata->susp_lock);
+err_out:
+	return err;
+}
+EXPORT_SYMBOL(usb_wwan_resume);
+#endif
+
+MODULE_AUTHOR(DRIVER_AUTHOR);
+MODULE_DESCRIPTION(DRIVER_DESC);
+MODULE_VERSION(DRIVER_VERSION);
+MODULE_LICENSE("GPL");
+
+module_param(debug, bool, S_IRUGO | S_IWUSR);
+MODULE_PARM_DESC(debug, "Debug messages");
