USB: serial: cp210x: use in-kernel types in port data
[linux-2.6-microblaze.git] / drivers / usb / serial / cp210x.c
index f5143ee..d0c05aa 100644 (file)
@@ -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,