2009-04-09

3G Support: Qualcomm Gobi chipset - Step1: qcserial kernel module

The qcserial kernel module serves 2 functions. Its first function is to load the correct firmware into the Gobi chipset. The second purpose is to provide the serial interface to the modem device which becomes available after the firmware is successfully loaded and initialized.

The qcserial module is quite new. So new that it is part of the 2.6.30 Linux kernel, but not available as part of any previous releases. Since I wanted to use Ubuntu's regular stock kernel it was necessary to get just one file: qcserial.c

The best way is to go to http://kernel.org/ and grab the most recent kernel source code and find qcserial.c among the usb/serial kernel modules. As an alternative I pasted the sourcecode plus the makefile at the end of this post.

To install the module, copy both files, qcserial.c and Makefile to an empty directory. Build the module with make and install it with make install. The module is loaded by modprobe qcserial. After a reboot the module is loaded automatically.

If the module is loaded successfully, a serial device /dev/ttyUSB0 appears. This will be used by the firmware loader.



### BEGIN qcserial.c ##################################
/*
* Qualcomm Serial USB driver
*
* Copyright (c) 2008 QUALCOMM Incorporated.
* Copyright (c) 2009 Greg Kroah-Hartman
* 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>

#define DRIVER_VERSION "v0.4"
#define DRIVER_AUTHOR "Qualcomm Inc"
#define DRIVER_DESC "Qualcomm USB Serial driver"

#define NUM_BULK_EPS 1
#define MAX_BULK_EPS 6

static int debug;

static 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(0x05c6, 0x9221)}, /* Sony Vaio Z31 Gobi QDL Device */
{USB_DEVICE(0x05c6, 0x9222)}, /* Sony Vaio Z31 Gobi 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)
{
int retval = -ENODEV;
__u8 nintf;
__u8 ifnum;

dbg("%s", __func__);

nintf = serial->dev->actconfig->desc.bNumInterfaces;
dbg("Num Interfaces = %d", nintf);
ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber;
dbg("This Interface = %d", ifnum);

switch (nintf) {
case 1:
/* QDL mode */
if (serial->interface->num_altsetting == 2) {
struct usb_host_interface *intf;

intf = &serial->interface->altsetting[1];
if (intf->desc.bNumEndpoints == 2) {
if (usb_endpoint_is_bulk_in(&intf->endpoint[0].desc) &&
usb_endpoint_is_bulk_out(&intf->endpoint[1].desc)) {
dbg("QDL port found");
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 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,
};

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;
}

printk(KERN_INFO KBUILD_MODNAME ": " DRIVER_VERSION "\n");

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_VERSION(DRIVER_VERSION);

module_param(debug, bool, S_IRUGO | S_IWUSR);
MODULE_PARM_DESC(debug, "Debug enabled or not");
### EOF qcserial.c #########################################




### BEGIN Makefile #######################################
obj-m += qcserial.o

KDIR := /lib/modules/$(shell uname -r)
PWD := $(shell pwd)

all: default

default:
$(MAKE) -C $(KDIR)/build SUBDIRS=$(PWD) modules

clean:
$(MAKE) -C $(KDIR)/build SUBDIRS=$(PWD) clean
@rm -f modules.order
@rm -f Module.markers
@rm -f *~

install:
cp qcserial.ko $(KDIR)/kernel/drivers/usb/serial/
depmod -a

uninstall:
rm $(KDIR)/kernel/drivers/usb/serial/qcserial.ko
rmmod qcserial
### EOF Makefile ##########################################

2009-04-06

3G Support: Qualcomm Gobi chipset - Overview

One of the main reasons to get a Z series Vaio is the internal WWAN module. When I ordered the brand new Z31 model I was not aware that it included Qualcomms new Gobi chipset. Problem is that chipset is not supported with any stock kernel on any distribution. There is a kernel module called qcserial which is the correct one. Unfortunately it will only come with kernel 2.6.30 and newer. Therefore it needs to be manually compiled and installed for any current Linux distribution. Another problem is that the Gobi chipset also needs to load a provider-specific firmware before the driver can operate.

The good news is: it can be done. It is a 3-step process:
  1. compile and install qcserial kernel module
  2. compile and install gobi firmware loader by Matthew Garrett
  3. obtain and install the correct firmware
I go through these steps during my next posts.

Ubuntu 9.04 vs. Vaio VGN-Z31WN

Even though Sony's Vaio Z series is not that new, Linux support could be better. One of the first addresses to check is the "Sony Vaio Z-series Laptop" team on launchpad which can be found here. This and the corresponding mailing list provide the most current and comprehensive information on how to operate Linux on a Z series Vaio.

A lot of progress is currently being made. Thanks to the custom sony-laptop module, which you will have to compile yourself at the time of this writing, many things are working:
  • WLAN
  • Bluetooth
  • standby and hibernate
  • stamina mode with Intel graphics with approx. 4 hours running time on battery with WLAN on and medium brightness
  • Fn key combinations for volume up/down and mute
  • Fn key combinations to increase/decrease brightness
  • Eject key for the DVD burner
All that makes this notebook very usable already. Although the most important feature is to get the WWAN adapter going. I describe my efforts in the next posting.

I have not yet attempted to use the fingerprint reader yet.