diff options
Diffstat (limited to 'drivers/usb/serial/cp210x.c')
-rw-r--r-- | drivers/usb/serial/cp210x.c | 228 |
1 files changed, 220 insertions, 8 deletions
diff --git a/drivers/usb/serial/cp210x.c b/drivers/usb/serial/cp210x.c index f5143eedbc48..d0c05aa8a0d6 100644 --- a/drivers/usb/serial/cp210x.c +++ b/drivers/usb/serial/cp210x.c @@ -50,6 +50,9 @@ 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 void cp210x_process_read_urb(struct urb *urb); +static void cp210x_enable_event_mode(struct usb_serial_port *port); +static void cp210x_disable_event_mode(struct usb_serial_port *port); static const struct usb_device_id id_table[] = { { USB_DEVICE(0x045B, 0x0053) }, /* Renesas RX610 RX-Stick */ @@ -253,9 +256,21 @@ struct cp210x_serial_private { bool use_actual_rate; }; +enum cp210x_event_state { + ES_DATA, + ES_ESCAPE, + ES_LSR, + ES_LSR_DATA_0, + ES_LSR_DATA_1, + ES_MSR +}; + struct cp210x_port_private { - __u8 bInterfaceNumber; + u8 bInterfaceNumber; bool has_swapped_line_ctl; + bool event_mode; + enum cp210x_event_state event_state; + u8 lsr; }; static struct usb_serial_driver cp210x_device = { @@ -272,14 +287,18 @@ static struct usb_serial_driver cp210x_device = { .break_ctl = cp210x_break_ctl, .set_termios = cp210x_set_termios, .tx_empty = cp210x_tx_empty, + .throttle = usb_serial_generic_throttle, + .unthrottle = usb_serial_generic_unthrottle, .tiocmget = cp210x_tiocmget, .tiocmset = cp210x_tiocmset, + .get_icount = usb_serial_generic_get_icount, .attach = cp210x_attach, .disconnect = cp210x_disconnect, .release = cp210x_release, .port_probe = cp210x_port_probe, .port_remove = cp210x_port_remove, - .dtr_rts = cp210x_dtr_rts + .dtr_rts = cp210x_dtr_rts, + .process_read_urb = cp210x_process_read_urb, }; static struct usb_serial_driver * const serial_drivers[] = { @@ -401,13 +420,22 @@ struct cp210x_comm_status { */ #define PURGE_ALL 0x000f +/* CP210X_EMBED_EVENTS */ +#define CP210X_ESCCHAR 0xec + +#define CP210X_LSR_OVERRUN BIT(1) +#define CP210X_LSR_PARITY BIT(2) +#define CP210X_LSR_FRAME BIT(3) +#define CP210X_LSR_BREAK BIT(4) + + /* CP210X_GET_FLOW/CP210X_SET_FLOW read/write these 0x10 bytes */ struct cp210x_flow_ctl { __le32 ulControlHandshake; __le32 ulFlowReplace; __le32 ulXonLimit; __le32 ulXoffLimit; -} __packed; +}; /* cp210x_flow_ctl::ulControlHandshake */ #define CP210X_SERIAL_DTR_MASK GENMASK(1, 0) @@ -441,7 +469,7 @@ struct cp210x_flow_ctl { struct cp210x_pin_mode { u8 eci; u8 sci; -} __packed; +}; #define CP210X_PIN_MODE_MODEM 0 #define CP210X_PIN_MODE_GPIO BIT(0) @@ -504,7 +532,7 @@ struct cp210x_single_port_config { struct cp210x_gpio_write { u8 mask; u8 state; -} __packed; +}; /* * Helper to get interface number when we only have struct usb_serial. @@ -807,6 +835,7 @@ static int cp210x_get_line_ctl(struct usb_serial_port *port, u16 *ctl) static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port) { + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); int result; result = cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_ENABLE); @@ -818,21 +847,144 @@ static int cp210x_open(struct tty_struct *tty, struct usb_serial_port *port) /* Configure the termios structure */ cp210x_get_termios(tty, port); - /* The baud rate must be initialised on cp2104 */ - if (tty) + if (tty) { + /* The baud rate must be initialised on cp2104 */ cp210x_change_speed(tty, port, NULL); - return usb_serial_generic_open(tty, port); + if (I_INPCK(tty)) + cp210x_enable_event_mode(port); + } + + result = usb_serial_generic_open(tty, port); + if (result) + goto err_disable; + + return 0; + +err_disable: + cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_DISABLE); + port_priv->event_mode = false; + + return result; } static void cp210x_close(struct usb_serial_port *port) { + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + usb_serial_generic_close(port); /* Clear both queues; cp2108 needs this to avoid an occasional hang */ cp210x_write_u16_reg(port, CP210X_PURGE, PURGE_ALL); cp210x_write_u16_reg(port, CP210X_IFC_ENABLE, UART_DISABLE); + + /* Disabling the interface disables event-insertion mode. */ + port_priv->event_mode = false; +} + +static void cp210x_process_lsr(struct usb_serial_port *port, unsigned char lsr, char *flag) +{ + if (lsr & CP210X_LSR_BREAK) { + port->icount.brk++; + *flag = TTY_BREAK; + } else if (lsr & CP210X_LSR_PARITY) { + port->icount.parity++; + *flag = TTY_PARITY; + } else if (lsr & CP210X_LSR_FRAME) { + port->icount.frame++; + *flag = TTY_FRAME; + } + + if (lsr & CP210X_LSR_OVERRUN) { + port->icount.overrun++; + tty_insert_flip_char(&port->port, 0, TTY_OVERRUN); + } +} + +static bool cp210x_process_char(struct usb_serial_port *port, unsigned char *ch, char *flag) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + + switch (port_priv->event_state) { + case ES_DATA: + if (*ch == CP210X_ESCCHAR) { + port_priv->event_state = ES_ESCAPE; + break; + } + return false; + case ES_ESCAPE: + switch (*ch) { + case 0: + dev_dbg(&port->dev, "%s - escape char\n", __func__); + *ch = CP210X_ESCCHAR; + port_priv->event_state = ES_DATA; + return false; + case 1: + port_priv->event_state = ES_LSR_DATA_0; + break; + case 2: + port_priv->event_state = ES_LSR; + break; + case 3: + port_priv->event_state = ES_MSR; + break; + default: + dev_err(&port->dev, "malformed event 0x%02x\n", *ch); + port_priv->event_state = ES_DATA; + break; + } + break; + case ES_LSR_DATA_0: + port_priv->lsr = *ch; + port_priv->event_state = ES_LSR_DATA_1; + break; + case ES_LSR_DATA_1: + dev_dbg(&port->dev, "%s - lsr = 0x%02x, data = 0x%02x\n", + __func__, port_priv->lsr, *ch); + cp210x_process_lsr(port, port_priv->lsr, flag); + port_priv->event_state = ES_DATA; + return false; + case ES_LSR: + dev_dbg(&port->dev, "%s - lsr = 0x%02x\n", __func__, *ch); + port_priv->lsr = *ch; + cp210x_process_lsr(port, port_priv->lsr, flag); + port_priv->event_state = ES_DATA; + break; + case ES_MSR: + dev_dbg(&port->dev, "%s - msr = 0x%02x\n", __func__, *ch); + /* unimplemented */ + port_priv->event_state = ES_DATA; + break; + } + + return true; +} + +static void cp210x_process_read_urb(struct urb *urb) +{ + struct usb_serial_port *port = urb->context; + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + unsigned char *ch = urb->transfer_buffer; + char flag; + int i; + + if (!urb->actual_length) + return; + + if (port_priv->event_mode) { + for (i = 0; i < urb->actual_length; i++, ch++) { + flag = TTY_NORMAL; + + if (cp210x_process_char(port, ch, &flag)) + continue; + + tty_insert_flip_char(&port->port, *ch, flag); + } + } else { + tty_insert_flip_string(&port->port, ch, urb->actual_length); + } + tty_flip_buffer_push(&port->port); } /* @@ -915,6 +1067,7 @@ static void cp210x_get_termios_port(struct usb_serial_port *port, u32 baud; u16 bits; u32 ctl_hs; + u32 flow_repl; cp210x_read_u32_reg(port, CP210X_GET_BAUDRATE, &baud); @@ -1015,6 +1168,22 @@ static void cp210x_get_termios_port(struct usb_serial_port *port, ctl_hs = le32_to_cpu(flow_ctl.ulControlHandshake); if (ctl_hs & CP210X_SERIAL_CTS_HANDSHAKE) { dev_dbg(dev, "%s - flow control = CRTSCTS\n", __func__); + /* + * When the port is closed, the CP210x hardware disables + * auto-RTS and RTS is deasserted but it leaves auto-CTS when + * in hardware flow control mode. When re-opening the port, if + * auto-CTS is enabled on the cp210x, then auto-RTS must be + * re-enabled in the driver. + */ + flow_repl = le32_to_cpu(flow_ctl.ulFlowReplace); + flow_repl &= ~CP210X_SERIAL_RTS_MASK; + flow_repl |= CP210X_SERIAL_RTS_SHIFT(CP210X_SERIAL_RTS_FLOW_CTL); + flow_ctl.ulFlowReplace = cpu_to_le32(flow_repl); + cp210x_write_reg_block(port, + CP210X_SET_FLOW, + &flow_ctl, + sizeof(flow_ctl)); + cflag |= CRTSCTS; } else { dev_dbg(dev, "%s - flow control = NONE\n", __func__); @@ -1148,6 +1317,41 @@ static void cp210x_change_speed(struct tty_struct *tty, tty_encode_baud_rate(tty, baud, baud); } +static void cp210x_enable_event_mode(struct usb_serial_port *port) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + int ret; + + if (port_priv->event_mode) + return; + + port_priv->event_state = ES_DATA; + port_priv->event_mode = true; + + ret = cp210x_write_u16_reg(port, CP210X_EMBED_EVENTS, CP210X_ESCCHAR); + if (ret) { + dev_err(&port->dev, "failed to enable events: %d\n", ret); + port_priv->event_mode = false; + } +} + +static void cp210x_disable_event_mode(struct usb_serial_port *port) +{ + struct cp210x_port_private *port_priv = usb_get_serial_port_data(port); + int ret; + + if (!port_priv->event_mode) + return; + + ret = cp210x_write_u16_reg(port, CP210X_EMBED_EVENTS, 0); + if (ret) { + dev_err(&port->dev, "failed to disable events: %d\n", ret); + return; + } + + port_priv->event_mode = false; +} + static void cp210x_set_termios(struct tty_struct *tty, struct usb_serial_port *port, struct ktermios *old_termios) { @@ -1270,6 +1474,14 @@ static void cp210x_set_termios(struct tty_struct *tty, sizeof(flow_ctl)); } + /* + * Enable event-insertion mode only if input parity checking is + * enabled for now. + */ + if (I_INPCK(tty)) + cp210x_enable_event_mode(port); + else + cp210x_disable_event_mode(port); } static int cp210x_tiocmset(struct tty_struct *tty, |