summaryrefslogtreecommitdiff
path: root/drivers/usb/serial/cp210x.c
diff options
context:
space:
mode:
Diffstat (limited to 'drivers/usb/serial/cp210x.c')
-rw-r--r--drivers/usb/serial/cp210x.c228
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,