Skip to content

Commit

Permalink
Merge tag 'usb-serial-4.5-rc1' of git://git.kernel.org/pub/scm/linux/…
Browse files Browse the repository at this point in the history
…kernel/git/johan/usb-serial into usb-next

Johan writes:

USB-serial updates for v4.5-rc1

These updates add support for Moxa UPort 1100-series devices through a
new mxu11x0 driver.

The cp210x driver gains proper support for cp2108 devices by working
around a couple of firmware bugs, and generic wait-until-sent support
(e.g. for tcdrain) is also added.

Included are also some general clean ups.

Signed-off-by: Johan Hovold <johan@kernel.org>
  • Loading branch information
Greg Kroah-Hartman committed Jan 3, 2016
2 parents daf2733 + 6ff9d27 commit b40e9dc
Show file tree
Hide file tree
Showing 6 changed files with 1,180 additions and 46 deletions.
16 changes: 16 additions & 0 deletions drivers/usb/serial/Kconfig
Original file line number Diff line number Diff line change
Expand Up @@ -475,6 +475,22 @@ config USB_SERIAL_MOS7840
To compile this driver as a module, choose M here: the
module will be called mos7840. If unsure, choose N.

config USB_SERIAL_MXUPORT11
tristate "USB Moxa UPORT 11x0 Serial Driver"
---help---
Say Y here if you want to use a MOXA UPort 11x0 Serial hub.

This driver supports:

- UPort 1110 : 1 port RS-232 USB to Serial Hub.
- UPort 1130 : 1 port RS-422/485 USB to Serial Hub.
- UPort 1130I : 1 port RS-422/485 USB to Serial Hub with Isolation.
- UPort 1150 : 1 port RS-232/422/485 USB to Serial Hub.
- UPort 1150I : 1 port RS-232/422/485 USB to Serial Hub with Isolation.

To compile this driver as a module, choose M here: the
module will be called mxu11x0.

config USB_SERIAL_MXUPORT
tristate "USB Moxa UPORT Serial Driver"
---help---
Expand Down
1 change: 1 addition & 0 deletions drivers/usb/serial/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ obj-$(CONFIG_USB_SERIAL_METRO) += metro-usb.o
obj-$(CONFIG_USB_SERIAL_MOS7720) += mos7720.o
obj-$(CONFIG_USB_SERIAL_MOS7840) += mos7840.o
obj-$(CONFIG_USB_SERIAL_MXUPORT) += mxuport.o
obj-$(CONFIG_USB_SERIAL_MXUPORT11) += mxu11x0.o
obj-$(CONFIG_USB_SERIAL_NAVMAN) += navman.o
obj-$(CONFIG_USB_SERIAL_OMNINET) += omninet.o
obj-$(CONFIG_USB_SERIAL_OPTICON) += opticon.o
Expand Down
186 changes: 162 additions & 24 deletions drivers/usb/serial/cp210x.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,13 +38,14 @@ static void cp210x_change_speed(struct tty_struct *, struct usb_serial_port *,
struct ktermios *);
static void cp210x_set_termios(struct tty_struct *, struct usb_serial_port *,
struct ktermios*);
static bool cp210x_tx_empty(struct usb_serial_port *port);
static int cp210x_tiocmget(struct tty_struct *);
static int cp210x_tiocmset(struct tty_struct *, unsigned int, unsigned int);
static int cp210x_tiocmset_port(struct usb_serial_port *port,
unsigned int, unsigned int);
static void cp210x_break_ctl(struct tty_struct *, int);
static int cp210x_startup(struct usb_serial *);
static void cp210x_release(struct usb_serial *);
static int cp210x_port_probe(struct usb_serial_port *);
static int cp210x_port_remove(struct usb_serial_port *);
static void cp210x_dtr_rts(struct usb_serial_port *p, int on);

static const struct usb_device_id id_table[] = {
Expand Down Expand Up @@ -196,8 +197,9 @@ static const struct usb_device_id id_table[] = {

MODULE_DEVICE_TABLE(usb, id_table);

struct cp210x_serial_private {
struct cp210x_port_private {
__u8 bInterfaceNumber;
bool has_swapped_line_ctl;
};

static struct usb_serial_driver cp210x_device = {
Expand All @@ -213,10 +215,11 @@ static struct usb_serial_driver cp210x_device = {
.close = cp210x_close,
.break_ctl = cp210x_break_ctl,
.set_termios = cp210x_set_termios,
.tx_empty = cp210x_tx_empty,
.tiocmget = cp210x_tiocmget,
.tiocmset = cp210x_tiocmset,
.attach = cp210x_startup,
.release = cp210x_release,
.port_probe = cp210x_port_probe,
.port_remove = cp210x_port_remove,
.dtr_rts = cp210x_dtr_rts
};

Expand Down Expand Up @@ -299,6 +302,25 @@ static struct usb_serial_driver * const serial_drivers[] = {
#define CONTROL_WRITE_DTR 0x0100
#define CONTROL_WRITE_RTS 0x0200

/* CP210X_GET_COMM_STATUS returns these 0x13 bytes */
struct cp210x_comm_status {
__le32 ulErrors;
__le32 ulHoldReasons;
__le32 ulAmountInInQueue;
__le32 ulAmountInOutQueue;
u8 bEofReceived;
u8 bWaitForImmediate;
u8 bReserved;
} __packed;

/*
* CP210X_PURGE - 16 bits passed in wValue of USB request.
* SiLabs app note AN571 gives a strange description of the 4 bits:
* bit 0 or bit 2 clears the transmit queue and 1 or 3 receive.
* writing 1 to all, however, purges cp2108 well enough to avoid the hang.
*/
#define PURGE_ALL 0x000f

/*
* cp210x_get_config
* Reads from the CP210x configuration registers
Expand All @@ -310,7 +332,7 @@ static int cp210x_get_config(struct usb_serial_port *port, u8 request,
unsigned int *data, int size)
{
struct usb_serial *serial = port->serial;
struct cp210x_serial_private *spriv = usb_get_serial_data(serial);
struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
__le32 *buf;
int result, i, length;

Expand All @@ -324,7 +346,7 @@ static int cp210x_get_config(struct usb_serial_port *port, u8 request,
/* Issue the request, attempting to read 'size' bytes */
result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
request, REQTYPE_INTERFACE_TO_HOST, 0x0000,
spriv->bInterfaceNumber, buf, size,
port_priv->bInterfaceNumber, buf, size,
USB_CTRL_GET_TIMEOUT);

/* Convert data into an array of integers */
Expand Down Expand Up @@ -355,7 +377,7 @@ static int cp210x_set_config(struct usb_serial_port *port, u8 request,
unsigned int *data, int size)
{
struct usb_serial *serial = port->serial;
struct cp210x_serial_private *spriv = usb_get_serial_data(serial);
struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
__le32 *buf;
int result, i, length;

Expand All @@ -374,13 +396,13 @@ static int cp210x_set_config(struct usb_serial_port *port, u8 request,
result = usb_control_msg(serial->dev,
usb_sndctrlpipe(serial->dev, 0),
request, REQTYPE_HOST_TO_INTERFACE, 0x0000,
spriv->bInterfaceNumber, buf, size,
port_priv->bInterfaceNumber, buf, size,
USB_CTRL_SET_TIMEOUT);
} else {
result = usb_control_msg(serial->dev,
usb_sndctrlpipe(serial->dev, 0),
request, REQTYPE_HOST_TO_INTERFACE, data[0],
spriv->bInterfaceNumber, NULL, 0,
port_priv->bInterfaceNumber, NULL, 0,
USB_CTRL_SET_TIMEOUT);
}

Expand Down Expand Up @@ -409,6 +431,60 @@ static inline int cp210x_set_config_single(struct usb_serial_port *port,
return cp210x_set_config(port, request, &data, 2);
}

/*
* Detect CP2108 GET_LINE_CTL bug and activate workaround.
* Write a known good value 0x800, read it back.
* If it comes back swapped the bug is detected.
* Preserve the original register value.
*/
static int cp210x_detect_swapped_line_ctl(struct usb_serial_port *port)
{
struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
unsigned int line_ctl_save;
unsigned int line_ctl_test;
int err;

err = cp210x_get_config(port, CP210X_GET_LINE_CTL, &line_ctl_save, 2);
if (err)
return err;

line_ctl_test = 0x800;
err = cp210x_set_config(port, CP210X_SET_LINE_CTL, &line_ctl_test, 2);
if (err)
return err;

err = cp210x_get_config(port, CP210X_GET_LINE_CTL, &line_ctl_test, 2);
if (err)
return err;

if (line_ctl_test == 8) {
port_priv->has_swapped_line_ctl = true;
line_ctl_save = swab16((u16)line_ctl_save);
}

return cp210x_set_config(port, CP210X_SET_LINE_CTL, &line_ctl_save, 2);
}

/*
* Must always be called instead of cp210x_get_config(CP210X_GET_LINE_CTL)
* to workaround cp2108 bug and get correct value.
*/
static int cp210x_get_line_ctl(struct usb_serial_port *port, unsigned int *ctl)
{
struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
int err;

err = cp210x_get_config(port, CP210X_GET_LINE_CTL, ctl, 2);
if (err)
return err;

/* Workaround swapped bytes in 16-bit value from CP210X_GET_LINE_CTL */
if (port_priv->has_swapped_line_ctl)
*ctl = swab16((u16)(*ctl));

return 0;
}

/*
* cp210x_quantise_baudrate
* Quantises the baud rate as per AN205 Table 1
Expand Down Expand Up @@ -474,10 +550,62 @@ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port)

static void cp210x_close(struct usb_serial_port *port)
{
unsigned int purge_ctl;

usb_serial_generic_close(port);

/* Clear both queues; cp2108 needs this to avoid an occasional hang */
purge_ctl = PURGE_ALL;
cp210x_set_config(port, CP210X_PURGE, &purge_ctl, 2);

cp210x_set_config_single(port, CP210X_IFC_ENABLE, UART_DISABLE);
}

/*
* Read how many bytes are waiting in the TX queue.
*/
static int cp210x_get_tx_queue_byte_count(struct usb_serial_port *port,
u32 *count)
{
struct usb_serial *serial = port->serial;
struct cp210x_port_private *port_priv = usb_get_serial_port_data(port);
struct cp210x_comm_status *sts;
int result;

sts = kmalloc(sizeof(*sts), GFP_KERNEL);
if (!sts)
return -ENOMEM;

result = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
CP210X_GET_COMM_STATUS, REQTYPE_INTERFACE_TO_HOST,
0, port_priv->bInterfaceNumber, sts, sizeof(*sts),
USB_CTRL_GET_TIMEOUT);
if (result == sizeof(*sts)) {
*count = le32_to_cpu(sts->ulAmountInOutQueue);
result = 0;
} else {
dev_err(&port->dev, "failed to get comm status: %d\n", result);
if (result >= 0)
result = -EPROTO;
}

kfree(sts);

return result;
}

static bool cp210x_tx_empty(struct usb_serial_port *port)
{
int err;
u32 count;

err = cp210x_get_tx_queue_byte_count(port, &count);
if (err)
return true;

return !count;
}

/*
* cp210x_get_termios
* Reads the baud rate, data bits, parity, stop bits and flow control mode
Expand Down Expand Up @@ -519,7 +647,7 @@ static void cp210x_get_termios_port(struct usb_serial_port *port,

cflag = *cflagp;

cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
cp210x_get_line_ctl(port, &bits);
cflag &= ~CSIZE;
switch (bits & BITS_DATA_MASK) {
case BITS_DATA_5:
Expand Down Expand Up @@ -687,7 +815,7 @@ static void cp210x_set_termios(struct tty_struct *tty,

/* If the number of data bits is to be updated */
if ((cflag & CSIZE) != (old_cflag & CSIZE)) {
cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
cp210x_get_line_ctl(port, &bits);
bits &= ~BITS_DATA_MASK;
switch (cflag & CSIZE) {
case CS5:
Expand Down Expand Up @@ -721,7 +849,7 @@ static void cp210x_set_termios(struct tty_struct *tty,

if ((cflag & (PARENB|PARODD|CMSPAR)) !=
(old_cflag & (PARENB|PARODD|CMSPAR))) {
cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
cp210x_get_line_ctl(port, &bits);
bits &= ~BITS_PARITY_MASK;
if (cflag & PARENB) {
if (cflag & CMSPAR) {
Expand All @@ -747,7 +875,7 @@ static void cp210x_set_termios(struct tty_struct *tty,
}

if ((cflag & CSTOPB) != (old_cflag & CSTOPB)) {
cp210x_get_config(port, CP210X_GET_LINE_CTL, &bits, 2);
cp210x_get_line_ctl(port, &bits);
bits &= ~BITS_STOP_MASK;
if (cflag & CSTOPB) {
bits |= BITS_STOP_2;
Expand Down Expand Up @@ -862,29 +990,39 @@ static void cp210x_break_ctl(struct tty_struct *tty, int break_state)
cp210x_set_config(port, CP210X_SET_BREAK, &state, 2);
}

static int cp210x_startup(struct usb_serial *serial)
static int cp210x_port_probe(struct usb_serial_port *port)
{
struct usb_serial *serial = port->serial;
struct usb_host_interface *cur_altsetting;
struct cp210x_serial_private *spriv;
struct cp210x_port_private *port_priv;
int ret;

spriv = kzalloc(sizeof(*spriv), GFP_KERNEL);
if (!spriv)
port_priv = kzalloc(sizeof(*port_priv), GFP_KERNEL);
if (!port_priv)
return -ENOMEM;

cur_altsetting = serial->interface->cur_altsetting;
spriv->bInterfaceNumber = cur_altsetting->desc.bInterfaceNumber;
port_priv->bInterfaceNumber = cur_altsetting->desc.bInterfaceNumber;

usb_set_serial_port_data(port, port_priv);

usb_set_serial_data(serial, spriv);
ret = cp210x_detect_swapped_line_ctl(port);
if (ret) {
kfree(port_priv);
return ret;
}

return 0;
}

static void cp210x_release(struct usb_serial *serial)
static int cp210x_port_remove(struct usb_serial_port *port)
{
struct cp210x_serial_private *spriv;
struct cp210x_port_private *port_priv;

port_priv = usb_get_serial_port_data(port);
kfree(port_priv);

spriv = usb_get_serial_data(serial);
kfree(spriv);
return 0;
}

module_usb_serial_driver(serial_drivers, id_table);
Expand Down
Loading

0 comments on commit b40e9dc

Please sign in to comment.