USB: serial: cp210x: modem-control fixes
Subsystems (2)
Johan Hovold
maintainercover USB: serial: cp210x: modem-control fixes
This series fixes the modem-control handling and its interaction with hardware flow control in the cp210x driver.
Johan
Johan Hovold (7):
USB: serial: cp210x: suppress modem-control errors
USB: serial: cp210x: fix modem-control handling
USB: serial: cp210x: drop shift macros
USB: serial: cp210x: clean up flow-control debug message
USB: serial: cp210x: clean up printk zero padding
USB: serial: cp210x: fix RTS handling
USB: serial: cp210x: clean up auto-RTS handling
drivers/usb/serial/cp210x.c | 113 ++++++++++++++++++++++++++++-------- 1 file changed, 89 insertions(+), 24 deletions(-)
Signature · 1 line
2.26.2
Johan Hovold
maintainerPATCH 1/7 USB: serial: cp210x: suppress modem-control errors
The CP210X_SET_MHS request cannot be used to control RTS when hardware flow control (auto-RTS) is enabled and instead returns an error which is currently logged as:
cp210x ttyUSB0: failed set request 0x7 status: -32
when opening and closing a port (and on TIOCMSET requests).
Add a crtscts flag to keep track of the hardware flow-control setting and use it to suppress any request to change RTS when auto-RTS is enabled.
Note that RTS is still deasserted when disabling the UART as part of close.
--- drivers/usb/serial/cp210x.c | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-)
drivers/usb/serial/cp210x.c +30 -3
@@ -21,6 +21,7 @@#include <linux/usb/serial.h> #include <linux/gpio/driver.h> #include <linux/bitops.h> +#include <linux/mutex.h> #define DRIVER_DESC "Silicon Labs CP210x RS232 serial adaptor driver"
@@ -264,7 +265,10 @@ struct cp210x_port_private {u8 bInterfaceNumber;bool event_mode;enum cp210x_event_state event_state; -u8 lsr; +u8 lsr; + +struct mutex mutex; +bool crtscts; };static struct usb_serial_driver cp210x_device = {
@@ -1117,6 +1121,7 @@ static bool cp210x_termios_change(const struct ktermios *a, const struct ktermiostatic void cp210x_set_flow_control (struct tty_struct *tty,struct usb_serial_port *port,struct ktermios *old_termios) { +struct cp210x_port_private *port_priv =usb_get_serial_port_data (port);struct cp210x_special_chars chars;struct cp210x_flow_ctl flow_ctl;u32 flow_repl;
@@ -1143,10 +1148,12 @@ static void cp210x_set_flow_control(struct tty_struct *tty,return ; } +mutex_lock (&port_priv->mutex); + ret =cp210x_read_reg_block (port,CP210X_GET_FLOW , &flow_ctl,sizeof (flow_ctl));if (ret) -return ; + goto out_unlock; ctl_hs =le32_to_cpu (flow_ctl.ulControlHandshake); flow_repl =le32_to_cpu (flow_ctl.ulFlowReplace);
@@ -1161,10 +1168,12 @@ static void cp210x_set_flow_control(struct tty_struct *tty, ctl_hs |=CP210X_SERIAL_CTS_HANDSHAKE ; flow_repl &= ~CP210X_SERIAL_RTS_MASK ; flow_repl |=CP210X_SERIAL_RTS_SHIFT (CP210X_SERIAL_RTS_FLOW_CTL ); + port_priv->crtscts = true; }else { ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE ; flow_repl &= ~CP210X_SERIAL_RTS_MASK ; flow_repl |=CP210X_SERIAL_RTS_SHIFT (CP210X_SERIAL_RTS_ACTIVE ); + port_priv->crtscts = false; }if (I_IXOFF (tty))
@@ -1188,6 +1197,8 @@ static void cp210x_set_flow_control(struct tty_struct *tty,cp210x_write_reg_block (port,CP210X_SET_FLOW , &flow_ctl,sizeof (flow_ctl)); +out_unlock: +mutex_unlock (&port_priv->mutex); } static void cp210x_set_termios(struct tty_struct *tty,
@@ -1272,7 +1283,9 @@ static int cp210x_tiocmset(struct tty_struct *tty,static int cp210x_tiocmset_port (struct usb_serial_port *port,unsigned int set,unsigned int clear) { +struct cp210x_port_private *port_priv =usb_get_serial_port_data (port);u16 control =0 ; +int ret;if (set &TIOCM_RTS ) { control |=CONTROL_RTS ;
@@ -1291,9 +1304,22 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port, control |=CONTROL_WRITE_DTR ; } +mutex_lock (&port_priv->mutex); + +/* +* SET_MHS cannot be used to control RTS when auto-RTS is enabled. +* Note that RTS is still deasserted when disabling the UART on close. +*/ +if (port_priv->crtscts) + control &= ~CONTROL_WRITE_RTS ; +dev_dbg (&port->dev,"%s - control = 0x%.4x\n" , __func__, control); -return cp210x_write_u16_reg (port,CP210X_SET_MHS , control); + ret =cp210x_write_u16_reg (port,CP210X_SET_MHS , control); + +mutex_unlock (&port_priv->mutex); + +return ret; }static void cp210x_dtr_rts (struct usb_serial_port *port,int on)
@@ -1770,6 +1796,7 @@ static int cp210x_port_probe(struct usb_serial_port *port)return -ENOMEM ; port_priv->bInterfaceNumber =cp210x_interface_num (serial); +mutex_init (&port_priv->mutex);usb_set_serial_port_data (port, port_priv);
Signature · 1 line
2.26.2
Johan Hovold
maintainerPATCH 2/7 USB: serial: cp210x: fix modem-control handling
In reply to Johan HovoldThe vendor request used to set the flow-control settings also sets the state of the modem-control lines.
Add state variables to keep track of the modem-control lines to avoid always asserting the lines whenever the flow-control settings are updated.
This specifically also avoids asserting DTR/RTS when opening a port with the line speed set to B0.
--- drivers/usb/serial/cp210x.c | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-)
drivers/usb/serial/cp210x.c +16 -4
@@ -269,6 +269,8 @@ struct cp210x_port_private {struct mutex mutex;bool crtscts; +bool dtr; +bool rts; };static struct usb_serial_driver cp210x_device = {
@@ -1162,7 +1164,10 @@ static void cp210x_set_flow_control(struct tty_struct *tty, ctl_hs &= ~CP210X_SERIAL_DCD_HANDSHAKE ; ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY ; ctl_hs &= ~CP210X_SERIAL_DTR_MASK ; - ctl_hs |=CP210X_SERIAL_DTR_SHIFT (CP210X_SERIAL_DTR_ACTIVE ); +if (port_priv->dtr) + ctl_hs |=CP210X_SERIAL_DTR_SHIFT (CP210X_SERIAL_DTR_ACTIVE ); +else + ctl_hs |=CP210X_SERIAL_DTR_SHIFT (CP210X_SERIAL_DTR_INACTIVE );if (C_CRTSCTS (tty)) { ctl_hs |=CP210X_SERIAL_CTS_HANDSHAKE ;
@@ -1172,7 +1177,10 @@ static void cp210x_set_flow_control(struct tty_struct *tty, }else { ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE ; flow_repl &= ~CP210X_SERIAL_RTS_MASK ; - flow_repl |=CP210X_SERIAL_RTS_SHIFT (CP210X_SERIAL_RTS_ACTIVE ); +if (port_priv->rts) + flow_repl |=CP210X_SERIAL_RTS_SHIFT (CP210X_SERIAL_RTS_ACTIVE ); +else + flow_repl |=CP210X_SERIAL_RTS_SHIFT (CP210X_SERIAL_RTS_INACTIVE ); port_priv->crtscts = false; }
@@ -1287,25 +1295,29 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port,u16 control =0 ;int ret; +mutex_lock (&port_priv->mutex); +if (set &TIOCM_RTS ) { + port_priv->rts = true; control |=CONTROL_RTS ; control |=CONTROL_WRITE_RTS ; }if (set &TIOCM_DTR ) { + port_priv->dtr = true; control |=CONTROL_DTR ; control |=CONTROL_WRITE_DTR ; }if (clear &TIOCM_RTS ) { + port_priv->rts = false; control &= ~CONTROL_RTS ; control |=CONTROL_WRITE_RTS ; }if (clear &TIOCM_DTR ) { + port_priv->dtr = false; control &= ~CONTROL_DTR ; control |=CONTROL_WRITE_DTR ; } -mutex_lock (&port_priv->mutex); -/* * SET_MHS cannot be used to control RTS when auto-RTS is enabled. * Note that RTS is still deasserted when disabling the UART on close.
Signature · 1 line
2.26.2
Johan Hovold
maintainerPATCH 3/7 USB: serial: cp210x: drop shift macros
In reply to Johan HovoldDrop the macros used to shift the flow-control settings to make the code more readable for consistency with the other requests.
--- drivers/usb/serial/cp210x.c | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-)
drivers/usb/serial/cp210x.c +11 -17
@@ -449,17 +449,14 @@ struct cp210x_flow_ctl {/* cp210x_flow_ctl::ulControlHandshake */ #define CP210X_SERIAL_DTR_MASK GENMASK(1, 0) -#define CP210X_SERIAL_DTR_SHIFT (_mode) (_mode) +#define CP210X_SERIAL_DTR_INACTIVE (0 << 0) +#define CP210X_SERIAL_DTR_ACTIVE (1 << 0) +#define CP210X_SERIAL_DTR_FLOW_CTL (2 << 0)#define CP210X_SERIAL_CTS_HANDSHAKE BIT(3)#define CP210X_SERIAL_DSR_HANDSHAKE BIT(4)#define CP210X_SERIAL_DCD_HANDSHAKE BIT(5)#define CP210X_SERIAL_DSR_SENSITIVITY BIT(6) -/* values for cp210x_flow_ctl::ulControlHandshake::CP210X_SERIAL_DTR_MASK */ -#define CP210X_SERIAL_DTR_INACTIVE 0 -#define CP210X_SERIAL_DTR_ACTIVE 1 -#define CP210X_SERIAL_DTR_FLOW_CTL 2 -/* cp210x_flow_ctl::ulFlowReplace */ #define CP210X_SERIAL_AUTO_TRANSMIT BIT(0)#define CP210X_SERIAL_AUTO_RECEIVE BIT(1)
@@ -467,14 +464,11 @@ struct cp210x_flow_ctl {#define CP210X_SERIAL_NULL_STRIPPING BIT(3)#define CP210X_SERIAL_BREAK_CHAR BIT(4)#define CP210X_SERIAL_RTS_MASK GENMASK(7, 6) -#define CP210X_SERIAL_RTS_SHIFT (_mode) (_mode << 6) +#define CP210X_SERIAL_RTS_INACTIVE (0 << 6) +#define CP210X_SERIAL_RTS_ACTIVE (1 << 6) +#define CP210X_SERIAL_RTS_FLOW_CTL (2 << 6)#define CP210X_SERIAL_XOFF_CONTINUE BIT(31) -/* values for cp210x_flow_ctl::ulFlowReplace::CP210X_SERIAL_RTS_MASK */ -#define CP210X_SERIAL_RTS_INACTIVE 0 -#define CP210X_SERIAL_RTS_ACTIVE 1 -#define CP210X_SERIAL_RTS_FLOW_CTL 2 -/* CP210X_VENDOR_SPECIFIC, CP210X_GET_DEVICEMODE call reads these 0x2 bytes. */ struct cp210x_pin_mode {u8 eci;
@@ -1165,22 +1159,22 @@ static void cp210x_set_flow_control(struct tty_struct *tty, ctl_hs &= ~CP210X_SERIAL_DSR_SENSITIVITY ; ctl_hs &= ~CP210X_SERIAL_DTR_MASK ;if (port_priv->dtr) - ctl_hs |=CP210X_SERIAL_DTR_SHIFT (CP210X_SERIAL_DTR_ACTIVE ); + ctl_hs |=CP210X_SERIAL_DTR_ACTIVE ;else - ctl_hs |=CP210X_SERIAL_DTR_SHIFT (CP210X_SERIAL_DTR_INACTIVE ); + ctl_hs |=CP210X_SERIAL_DTR_INACTIVE ;if (C_CRTSCTS (tty)) { ctl_hs |=CP210X_SERIAL_CTS_HANDSHAKE ; flow_repl &= ~CP210X_SERIAL_RTS_MASK ; - flow_repl |=CP210X_SERIAL_RTS_SHIFT (CP210X_SERIAL_RTS_FLOW_CTL ); + flow_repl |=CP210X_SERIAL_RTS_FLOW_CTL ; port_priv->crtscts = true; }else { ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE ; flow_repl &= ~CP210X_SERIAL_RTS_MASK ;if (port_priv->rts) - flow_repl |=CP210X_SERIAL_RTS_SHIFT (CP210X_SERIAL_RTS_ACTIVE ); + flow_repl |=CP210X_SERIAL_RTS_ACTIVE ;else - flow_repl |=CP210X_SERIAL_RTS_SHIFT (CP210X_SERIAL_RTS_INACTIVE ); + flow_repl |=CP210X_SERIAL_RTS_INACTIVE ; port_priv->crtscts = false; }
Signature · 1 line
2.26.2
Johan Hovold
maintainerPATCH 4/7 USB: serial: cp210x: clean up flow-control debug message
In reply to Johan HovoldShorten the flow-control debug message by abbreviating the field names and reducing the value width to two characters. The latter improves readability since all but the least significant byte will almost always be zero anyway.
--- drivers/usb/serial/cp210x.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/usb/serial/cp210x.c +2 -2
@@ -1191,8 +1191,8 @@ static void cp210x_set_flow_control(struct tty_struct *tty, flow_ctl.ulXonLimit =cpu_to_le32 (128 ); flow_ctl.ulXoffLimit =cpu_to_le32 (128 ); -dev_dbg (&port->dev,"%s - ulControlHandshake=0x%08x, ulFlowReplace=0x%08x\n" , - __func__, ctl_hs, flow_repl); +dev_dbg (&port->dev,"%s - ctrl = 0x%02x, flow = 0x%02x\n" , __func__, + ctl_hs, flow_repl); flow_ctl.ulControlHandshake =cpu_to_le32 (ctl_hs); flow_ctl.ulFlowReplace =cpu_to_le32 (flow_repl);
Signature · 1 line
2.26.2
Johan Hovold
maintainerPATCH 5/7 USB: serial: cp210x: clean up printk zero padding
In reply to Johan HovoldUse the 0-flag and a field width to specify zero-padding consistently in printk messages.
--- drivers/usb/serial/cp210x.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-)
drivers/usb/serial/cp210x.c +2 -2
@@ -1319,7 +1319,7 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port,if (port_priv->crtscts) control &= ~CONTROL_WRITE_RTS ; -dev_dbg (&port->dev,"%s - control = 0x%.4x\n" , __func__, control); +dev_dbg (&port->dev,"%s - control = 0x%04x\n" , __func__, control); ret =cp210x_write_u16_reg (port,CP210X_SET_MHS , control);
@@ -1353,7 +1353,7 @@ static int cp210x_tiocmget(struct tty_struct *tty) |((control &CONTROL_RING )?TIOCM_RI :0 ) |((control &CONTROL_DCD ) ?TIOCM_CD :0 ); -dev_dbg (&port->dev,"%s - control = 0x%.2x\n" , __func__, control); +dev_dbg (&port->dev,"%s - control = 0x%02x\n" , __func__, control);return result; }
Signature · 1 line
2.26.2
Johan Hovold
maintainerPATCH 6/7 USB: serial: cp210x: fix RTS handling
In reply to Johan HovoldClearing TIOCM_RTS should always deassert RTS and setting the same bit should enable auto-RTS if hardware flow control is enabled.
This allows user space to throttle input directly at the source also when hardware-assisted flow control is enabled and makes dtr_rts() always deassert both lines during close (when HUPCL is set).
--- drivers/usb/serial/cp210x.c | 47 +++++++++++++++++++++++++++++++------ 1 file changed, 40 insertions(+), 7 deletions(-)
drivers/usb/serial/cp210x.c +40 -7
@@ -1166,7 +1166,10 @@ static void cp210x_set_flow_control(struct tty_struct *tty,if (C_CRTSCTS (tty)) { ctl_hs |=CP210X_SERIAL_CTS_HANDSHAKE ; flow_repl &= ~CP210X_SERIAL_RTS_MASK ; - flow_repl |=CP210X_SERIAL_RTS_FLOW_CTL ; +if (port_priv->rts) + flow_repl |=CP210X_SERIAL_RTS_FLOW_CTL ; +else + flow_repl |=CP210X_SERIAL_RTS_INACTIVE ; port_priv->crtscts = true; }else { ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE ;
@@ -1286,6 +1289,8 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port, unsigned int set, unsigned int clear) {struct cp210x_port_private *port_priv =usb_get_serial_port_data (port); +struct cp210x_flow_ctl flow_ctl; +u32 ctl_hs, flow_repl;u16 control =0 ;int ret;
@@ -1313,16 +1318,44 @@ static int cp210x_tiocmset_port(struct usb_serial_port *port, }/* -* SET_MHS cannot be used to control RTS when auto-RTS is enabled. -* Note that RTS is still deasserted when disabling the UART on close. +* Use SET_FLOW to set DTR and enable/disable auto-RTS when hardware +* flow control is enabled. */ -if (port_priv->crtscts) - control &= ~CONTROL_WRITE_RTS ; +if (port_priv->crtscts && control &CONTROL_WRITE_RTS ) { + ret =cp210x_read_reg_block (port,CP210X_GET_FLOW , &flow_ctl, +sizeof (flow_ctl)); +if (ret) + goto out_unlock; -dev_dbg (&port->dev,"%s - control = 0x%04x\n" , __func__, control); + ctl_hs =le32_to_cpu (flow_ctl.ulControlHandshake); + flow_repl =le32_to_cpu (flow_ctl.ulFlowReplace); - ret =cp210x_write_u16_reg (port,CP210X_SET_MHS , control); + ctl_hs &= ~CP210X_SERIAL_DTR_MASK ; +if (port_priv->dtr) + ctl_hs |=CP210X_SERIAL_DTR_ACTIVE ; +else + ctl_hs |=CP210X_SERIAL_DTR_INACTIVE ; + flow_repl &= ~CP210X_SERIAL_RTS_MASK ; +if (port_priv->rts) + flow_repl |=CP210X_SERIAL_RTS_FLOW_CTL ; +else + flow_repl |=CP210X_SERIAL_RTS_INACTIVE ; + + flow_ctl.ulControlHandshake =cpu_to_le32 (ctl_hs); + flow_ctl.ulFlowReplace =cpu_to_le32 (flow_repl); + +dev_dbg (&port->dev,"%s - ctrl = 0x%02x, flow = 0x%02x\n" , + __func__, ctl_hs, flow_repl); + + ret =cp210x_write_reg_block (port,CP210X_SET_FLOW , &flow_ctl, +sizeof (flow_ctl)); + }else { +dev_dbg (&port->dev,"%s - control = 0x%04x\n" , __func__, control); + + ret =cp210x_write_u16_reg (port,CP210X_SET_MHS , control); + } +out_unlock:mutex_unlock (&port_priv->mutex);return ret;
Signature · 1 line
2.26.2
Johan Hovold
maintainerPATCH 7/7 USB: serial: cp210x: clean up auto-RTS handling
In reply to Johan HovoldClear the RTS bits of the flow-control request before determining the new value when updating the settings.
--- drivers/usb/serial/cp210x.c | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-)
drivers/usb/serial/cp210x.c +1 -2
@@ -1163,9 +1163,9 @@ static void cp210x_set_flow_control(struct tty_struct *tty,else ctl_hs |=CP210X_SERIAL_DTR_INACTIVE ; + flow_repl &= ~CP210X_SERIAL_RTS_MASK ;if (C_CRTSCTS (tty)) { ctl_hs |=CP210X_SERIAL_CTS_HANDSHAKE ; - flow_repl &= ~CP210X_SERIAL_RTS_MASK ;if (port_priv->rts) flow_repl |=CP210X_SERIAL_RTS_FLOW_CTL ;else
@@ -1173,7 +1173,6 @@ static void cp210x_set_flow_control(struct tty_struct *tty, port_priv->crtscts = true; }else { ctl_hs &= ~CP210X_SERIAL_CTS_HANDSHAKE ; - flow_repl &= ~CP210X_SERIAL_RTS_MASK ;if (port_priv->rts) flow_repl |=CP210X_SERIAL_RTS_ACTIVE ;else
Signature · 1 line
2.26.2