USB: serial: mos7840: add support for MCS7810 devices
authorDonald <donald@asix.com.tw>
Thu, 19 Apr 2012 07:00:45 +0000 (15:00 +0800)
committerGreg Kroah-Hartman <gregkh@linuxfoundation.org>
Fri, 20 Apr 2012 02:08:10 +0000 (19:08 -0700)
This patch added the support of MCS7810 device for the mos7840 driver.
The MCS7810 device supports single USB2.0-to-Serial port with
a LED indicator for reflecting transmission or reception activity.

Signed-off-by: Donald Lee <donald@asix.com.tw>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
drivers/usb/serial/mos7840.c

index c526550694a06270acf60b056274ec764a75e5bc..aaef523955e0294afc126b175ee50b43a0ed8f55 100644 (file)
 #define USB_VENDOR_ID_MOSCHIP           0x9710
 #define MOSCHIP_DEVICE_ID_7840          0x7840
 #define MOSCHIP_DEVICE_ID_7820          0x7820
+#define MOSCHIP_DEVICE_ID_7810          0x7810
 /* The native component can have its vendor/device id's overridden
  * in vendor-specific implementations.  Such devices can be handled
  * by making a change here, in moschip_port_id_table, and in
 #define NUM_URBS                        16     /* URB Count */
 #define URB_TRANSFER_BUFFER_SIZE        32     /* URB Size  */
 
+/* LED on/off milliseconds*/
+#define LED_ON_MS      500
+#define LED_OFF_MS     500
+
+static int device_type;
 
 static const struct usb_device_id moschip_port_id_table[] = {
        {USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7840)},
        {USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7820)},
+       {USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7810)},
        {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_2)},
        {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_2P)},
        {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_4)},
@@ -209,6 +216,7 @@ static const struct usb_device_id moschip_port_id_table[] = {
 static const struct usb_device_id moschip_id_table_combined[] __devinitconst = {
        {USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7840)},
        {USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7820)},
+       {USB_DEVICE(USB_VENDOR_ID_MOSCHIP, MOSCHIP_DEVICE_ID_7810)},
        {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_2)},
        {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_2P)},
        {USB_DEVICE(USB_VENDOR_ID_BANDB, BANDB_DEVICE_ID_USO9ML2_4)},
@@ -261,8 +269,13 @@ struct moschip_port {
        struct urb *write_urb_pool[NUM_URBS];
        char busy[NUM_URBS];
        bool read_urb_busy;
-};
 
+       /* For device(s) with LED indicator */
+       bool has_led;
+       bool led_flag;
+       struct timer_list led_timer1;   /* Timer for LED on */
+       struct timer_list led_timer2;   /* Timer for LED off */
+};
 
 static bool debug;
 
@@ -572,6 +585,69 @@ static int mos7840_get_reg(struct moschip_port *mcs, __u16 Wval, __u16 reg,
        return ret;
 }
 
+static void mos7840_set_led_callback(struct urb *urb)
+{
+       switch (urb->status) {
+       case 0:
+               /* Success */
+               break;
+       case -ECONNRESET:
+       case -ENOENT:
+       case -ESHUTDOWN:
+               /* This urb is terminated, clean up */
+               dbg("%s - urb shutting down with status: %d", __func__,
+                       urb->status);
+               break;
+       default:
+               dbg("%s - nonzero urb status received: %d", __func__,
+                       urb->status);
+       }
+}
+
+static void mos7840_set_led_async(struct moschip_port *mcs, __u16 wval,
+                               __u16 reg)
+{
+       struct usb_device *dev = mcs->port->serial->dev;
+       struct usb_ctrlrequest *dr = mcs->dr;
+
+       dr->bRequestType = MCS_WR_RTYPE;
+       dr->bRequest = MCS_WRREQ;
+       dr->wValue = cpu_to_le16(wval);
+       dr->wIndex = cpu_to_le16(reg);
+       dr->wLength = cpu_to_le16(0);
+
+       usb_fill_control_urb(mcs->control_urb, dev, usb_sndctrlpipe(dev, 0),
+               (unsigned char *)dr, NULL, 0, mos7840_set_led_callback, NULL);
+
+       usb_submit_urb(mcs->control_urb, GFP_ATOMIC);
+}
+
+static void mos7840_set_led_sync(struct usb_serial_port *port, __u16 reg,
+                               __u16 val)
+{
+       struct usb_device *dev = port->serial->dev;
+
+       usb_control_msg(dev, usb_sndctrlpipe(dev, 0), MCS_WRREQ, MCS_WR_RTYPE,
+                       val, reg, NULL, 0, MOS_WDR_TIMEOUT);
+}
+
+static void mos7840_led_off(unsigned long arg)
+{
+       struct moschip_port *mcs = (struct moschip_port *) arg;
+
+       /* Turn off LED */
+       mos7840_set_led_async(mcs, 0x0300, MODEM_CONTROL_REGISTER);
+       mod_timer(&mcs->led_timer2,
+                               jiffies + msecs_to_jiffies(LED_OFF_MS));
+}
+
+static void mos7840_led_flag_off(unsigned long arg)
+{
+       struct moschip_port *mcs = (struct moschip_port *) arg;
+
+       mcs->led_flag = false;
+}
+
 /*****************************************************************************
  * mos7840_interrupt_callback
  *     this is the callback function for when we have received data on the
@@ -792,6 +868,14 @@ static void mos7840_bulk_in_callback(struct urb *urb)
                return;
        }
 
+       /* Turn on LED */
+       if (mos7840_port->has_led && !mos7840_port->led_flag) {
+               mos7840_port->led_flag = true;
+               mos7840_set_led_async(mos7840_port, 0x0301,
+                                       MODEM_CONTROL_REGISTER);
+               mod_timer(&mos7840_port->led_timer1,
+                               jiffies + msecs_to_jiffies(LED_ON_MS));
+       }
 
        mos7840_port->read_urb_busy = true;
        retval = usb_submit_urb(mos7840_port->read_urb, GFP_ATOMIC);
@@ -1554,6 +1638,14 @@ static int mos7840_write(struct tty_struct *tty, struct usb_serial_port *port,
        data1 = urb->transfer_buffer;
        dbg("bulkout endpoint is %d", port->bulk_out_endpointAddress);
 
+       /* Turn on LED */
+       if (mos7840_port->has_led && !mos7840_port->led_flag) {
+               mos7840_port->led_flag = true;
+               mos7840_set_led_sync(port, MODEM_CONTROL_REGISTER, 0x0301);
+               mod_timer(&mos7840_port->led_timer1,
+                               jiffies + msecs_to_jiffies(LED_ON_MS));
+       }
+
        /* send it down the pipe */
        status = usb_submit_urb(urb, GFP_ATOMIC);
 
@@ -2327,28 +2419,74 @@ static int mos7840_ioctl(struct tty_struct *tty,
        return -ENOIOCTLCMD;
 }
 
+static int mos7810_check(struct usb_serial *serial)
+{
+       int i, pass_count = 0;
+       __u16 data = 0, mcr_data = 0;
+       __u16 test_pattern = 0x55AA;
+
+       /* Store MCR setting */
+       usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+               MCS_RDREQ, MCS_RD_RTYPE, 0x0300, MODEM_CONTROL_REGISTER,
+               &mcr_data, VENDOR_READ_LENGTH, MOS_WDR_TIMEOUT);
+
+       for (i = 0; i < 16; i++) {
+               /* Send the 1-bit test pattern out to MCS7810 test pin */
+               usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0),
+                       MCS_WRREQ, MCS_WR_RTYPE,
+                       (0x0300 | (((test_pattern >> i) & 0x0001) << 1)),
+                       MODEM_CONTROL_REGISTER, NULL, 0, MOS_WDR_TIMEOUT);
+
+               /* Read the test pattern back */
+               usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+                       MCS_RDREQ, MCS_RD_RTYPE, 0, GPIO_REGISTER, &data,
+                       VENDOR_READ_LENGTH, MOS_WDR_TIMEOUT);
+
+               /* If this is a MCS7810 device, both test patterns must match */
+               if (((test_pattern >> i) ^ (~data >> 1)) & 0x0001)
+                       break;
+
+               pass_count++;
+       }
+
+       /* Restore MCR setting */
+       usb_control_msg(serial->dev, usb_sndctrlpipe(serial->dev, 0), MCS_WRREQ,
+               MCS_WR_RTYPE, 0x0300 | mcr_data, MODEM_CONTROL_REGISTER, NULL,
+               0, MOS_WDR_TIMEOUT);
+
+       if (pass_count == 16)
+               return 1;
+
+       return 0;
+}
+
 static int mos7840_calc_num_ports(struct usb_serial *serial)
 {
-       __u16 Data = 0x00;
-       int ret = 0;
+       __u16 data = 0x00;
        int mos7840_num_ports;
 
-       ret = usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
-               MCS_RDREQ, MCS_RD_RTYPE, 0, GPIO_REGISTER, &Data,
+       usb_control_msg(serial->dev, usb_rcvctrlpipe(serial->dev, 0),
+               MCS_RDREQ, MCS_RD_RTYPE, 0, GPIO_REGISTER, &data,
                VENDOR_READ_LENGTH, MOS_WDR_TIMEOUT);
 
-       if ((Data & 0x01) == 0) {
-               mos7840_num_ports = 2;
-               serial->num_bulk_in = 2;
-               serial->num_bulk_out = 2;
-               serial->num_ports = 2;
+       if (serial->dev->descriptor.idProduct == MOSCHIP_DEVICE_ID_7810 ||
+               serial->dev->descriptor.idProduct == MOSCHIP_DEVICE_ID_7820) {
+               device_type = serial->dev->descriptor.idProduct;
        } else {
-               mos7840_num_ports = 4;
-               serial->num_bulk_in = 4;
-               serial->num_bulk_out = 4;
-               serial->num_ports = 4;
+               /* For a MCS7840 device GPIO0 must be set to 1 */
+               if ((data & 0x01) == 1)
+                       device_type = MOSCHIP_DEVICE_ID_7840;
+               else if (mos7810_check(serial))
+                       device_type = MOSCHIP_DEVICE_ID_7810;
+               else
+                       device_type = MOSCHIP_DEVICE_ID_7820;
        }
 
+       mos7840_num_ports = (device_type >> 4) & 0x000F;
+       serial->num_bulk_in = mos7840_num_ports;
+       serial->num_bulk_out = mos7840_num_ports;
+       serial->num_ports = mos7840_num_ports;
+
        return mos7840_num_ports;
 }
 
@@ -2563,6 +2701,34 @@ static int mos7840_startup(struct usb_serial *serial)
                        status = -ENOMEM;
                        goto error;
                }
+
+               mos7840_port->has_led = false;
+
+               /* Initialize LED timers */
+               if (device_type == MOSCHIP_DEVICE_ID_7810) {
+                       mos7840_port->has_led = true;
+
+                       init_timer(&mos7840_port->led_timer1);
+                       mos7840_port->led_timer1.function = mos7840_led_off;
+                       mos7840_port->led_timer1.expires =
+                                       jiffies + msecs_to_jiffies(LED_ON_MS);
+                       mos7840_port->led_timer1.data =
+                                               (unsigned long)mos7840_port;
+
+                       init_timer(&mos7840_port->led_timer2);
+                       mos7840_port->led_timer2.function =
+                                               mos7840_led_flag_off;
+                       mos7840_port->led_timer2.expires =
+                                       jiffies + msecs_to_jiffies(LED_OFF_MS);
+                       mos7840_port->led_timer2.data =
+                                               (unsigned long)mos7840_port;
+
+                       mos7840_port->led_flag = false;
+
+                       /* Turn off LED */
+                       mos7840_set_led_sync(serial->port[i],
+                                               MODEM_CONTROL_REGISTER, 0x0300);
+               }
        }
        dbg ("mos7840_startup: all ports configured...........");
 
@@ -2654,6 +2820,14 @@ static void mos7840_release(struct usb_serial *serial)
                mos7840_port = mos7840_get_port_private(serial->port[i]);
                dbg("mos7840_port %d = %p", i, mos7840_port);
                if (mos7840_port) {
+                       if (mos7840_port->has_led) {
+                               /* Turn off LED */
+                               mos7840_set_led_sync(mos7840_port->port,
+                                               MODEM_CONTROL_REGISTER, 0x0300);
+
+                               del_timer_sync(&mos7840_port->led_timer1);
+                               del_timer_sync(&mos7840_port->led_timer2);
+                       }
                        kfree(mos7840_port->ctrl_buf);
                        kfree(mos7840_port->dr);
                        kfree(mos7840_port);