// SPDX-License-Identifier: GPL-2.0
/*
USB Driver for Sierra Wireless
Copyright (C) 2006, 2007, 2008 Kevin Lloyd <klloyd@sierrawireless.com>,
Copyright (C) 2008, 2009 Elina Pasheva, Matthew Safar, Rory Filer
<linux@sierrawireless.com>
IMPORTANT DISCLAIMER: This driver is not commercially supported by
Sierra Wireless. Use at your own risk.
Portions based on the option driver by Matthias Urlichs <smurf@smurf.noris.de>
Whom based his on the Keyspan driver by Hugh Blemings <hugh@blemings.org>
*/
/* Uncomment to log function calls */
/* #define DEBUG */
#define DRIVER_AUTHOR "Kevin Lloyd, Elina Pasheva, Matthew Safar, Rory Filer"
#define DRIVER_DESC "USB Driver for Sierra Wireless USB modems"
#include <linux/kernel.h>
#include <linux/jiffies.h>
#include <linux/errno.h>
#include <linux/tty.h>
#include <linux/slab.h>
#include <linux/tty_flip.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/serial.h>
#define SWIMS_USB_REQUEST_SetPower 0x00
#define SWIMS_USB_REQUEST_SetNmea 0x07
#define N_IN_URB_HM 8
#define N_OUT_URB_HM 64
#define N_IN_URB 4
#define N_OUT_URB 4
#define IN_BUFLEN 4096
#define MAX_TRANSFER (PAGE_SIZE - 512)
/* MAX_TRANSFER is chosen so that the VM is not stressed by
allocations > PAGE_SIZE and the number of packets in a page
is an integer 512 is the largest possible packet on EHCI */
static bool nmea;
struct sierra_iface_list {
const u8 *nums; /* array of interface numbers */
size_t count; /* number of elements in array */
};
struct sierra_intf_private {
spinlock_t susp_lock;
unsigned int suspended:1;
int in_flight;
unsigned int open_ports;
};
static int sierra_set_power_state(struct usb_device *udev, __u16 swiState)
{
return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
SWIMS_USB_REQUEST_SetPower, /* __u8 request */
USB_TYPE_VENDOR, /* __u8 request type */
swiState, /* __u16 value */
0, /* __u16 index */
NULL, /* void *data */
0, /* __u16 size */
USB_CTRL_SET_TIMEOUT); /* int timeout */
}
static int sierra_vsc_set_nmea(struct usb_device *udev, __u16 enable)
{
return usb_control_msg(udev, usb_sndctrlpipe(udev, 0),
SWIMS_USB_REQUEST_SetNmea, /* __u8 request */
USB_TYPE_VENDOR, /* __u8 request type */
enable, /* __u16 value */
0x0000, /* __u16 index */
NULL, /* void *data */
0, /* __u16 size */
USB_CTRL_SET_TIMEOUT); /* int timeout */
}
static int sierra_calc_num_ports(struct usb_serial *serial,
struct usb_serial_endpoints *epds)
{
int num_ports = 0;
u8 ifnum, numendpoints;
ifnum = serial->interface->cur_altsetting->desc.bInterfaceNumber;
numendpoints = serial->interface->cur_altsetting->desc.bNumEndpoints;
/* Dummy interface present on some SKUs should be ignored */
if (ifnum == 0x99)
num_ports = 0;
else if (numendpoints <= 3)
num_ports = 1;
else
num_ports = (numendpoints-1)/2;
return num_ports;
}
static bool is_listed(const u8 ifnum, const struct sierra_iface_list *list)
{
int i;
if (!list)
return false;
for (i = 0; i < list->count; i++) {
if (list->nums[i] == ifnum)
return true;
}
return false;
}
static u8 sierra_interface_num(struct usb_serial *serial)
{
return serial->interface->cur_altsetting->desc.bInterfaceNumber;
}
static int sierra_probe(struct usb_serial *serial,
const struct usb_device_id *id)
{
const struct sierra_iface_list *ignore_list;
int result = 0;
struct usb_device *udev;
u8 ifnum;
udev = serial->dev;
ifnum = sierra_interface_num(serial);
/*
* If this interface supports more than 1 alternate
* select the 2nd one
*/
if (serial->interface->num_altsetting == 2) {
dev_dbg(&udev->dev, "Selecting alt setting for interface %d\n",
ifnum);
/* We know the alternate setting is 1 for the MC8785 */
usb_set_interface(udev, ifnum, 1);
}
ignore_list = (const struct sierra_iface_list *)id->driver_info;
if (is_listed(ifnum, ignore_list)) {
dev_dbg(&serial->dev->dev, "Ignoring interface #%d\n", ifnum);
return -ENODEV;
}
return result;
}
/* interfaces with higher memory requirements */
static const u8 hi_memory_typeA_ifaces[] = { 0, 2 };
static const struct sierra_iface_list typeA_interface_list = {
.nums = hi_memory_typeA_ifaces,
.count = ARRAY_SIZE(hi_memory_typeA_ifaces),
};
static const u8 hi_memory_typeB_ifaces[] = { 3, 4, 5, 6 };
static const struct sierra_iface_list typeB_interface_list = {
.nums = hi_memory_typeB_ifaces,
.count = ARRAY_SIZE(hi_memory_typeB_ifaces),
};
/* 'ignorelist' of interfaces not served by this driver */
static const u8 direct_ip_non_serial_ifaces[] = { 7, 8, 9, 10, 11, 19, 20 };
static const struct sierra_iface_list direct_ip_interface_ignore = {
.nums = direct_ip_non_serial_ifaces,
.count = ARRAY_SIZE(direct_ip_non_serial_ifaces),
};
static const struct usb_device_id id_table[] = {
{ USB_DEVICE(0x0F3D, 0x0112) }, /* Airprime/Sierra PC 5220 */
{ USB_DEVICE(0x03F0, 0x1B1D) }, /* HP ev2200 a.k.a MC5720 */
{ USB_DEVICE(0x03F0, 0x211D) }, /* HP ev2210 a.k.a MC5725 */
{ USB_DEVICE(0x03F0, 0x1E1D) }, /* HP hs2300 a.k.a MC8775 */
{ USB_DEVICE(0x1199,