]> git.sur5r.net Git - u-boot/blobdiff - common/usb.c
[PATCH] I2C: Add support for multiple I2C busses for RTC & DTT
[u-boot] / common / usb.c
index 4136f8d77656a6c2149ac38037b2afc88487b259..0857494b27b974c442b07d96b4f30c46d8d0c54b 100644 (file)
@@ -1,9 +1,19 @@
 /*
- * (C) Copyright 2001
- * Denis Peter, MPL AG Switzerland
  *
  * Most of this source has been derived from the Linux USB
- * project.
+ * project:
+ * (C) Copyright Linus Torvalds 1999
+ * (C) Copyright Johannes Erdfelt 1999-2001
+ * (C) Copyright Andreas Gal 1999
+ * (C) Copyright Gregory P. Smith 1999
+ * (C) Copyright Deti Fliegl 1999 (new USB architecture)
+ * (C) Copyright Randy Dunlap 2000
+ * (C) Copyright David Brownell 2000 (kernel hotplug, usb_device_id)
+ * (C) Copyright Yggdrasil Computing, Inc. 2000
+ *     (usb_device_id matching changes by Adam J. Richter)
+ *
+ * Adapted for U-Boot:
+ * (C) Copyright 2001 Denis Peter, MPL AG Switzerland
  *
  * See file CREDITS for list of people who contributed to this
  * project.
@@ -37,6 +47,7 @@
 #include <common.h>
 #include <command.h>
 #include <asm/processor.h>
+#include <linux/ctype.h>
 
 #if (CONFIG_COMMANDS & CFG_CMD_USB)
 
@@ -45,8 +56,7 @@
 #include <405gp_pci.h>
 #endif
 
-
-/* #define USB_DEBUG */
+#undef USB_DEBUG
 
 #ifdef USB_DEBUG
 #define        USB_PRINTF(fmt,args...) printf (fmt ,##args)
@@ -62,6 +72,8 @@ static int running;
 static int asynch_allowed;
 static struct devrequest setup_packet;
 
+char usb_started; /* flag for the started/stopped USB status */
+
 /**********************************************************************
  * some forward declerations...
  */
@@ -70,6 +82,7 @@ void usb_scan_devices(void);
 int usb_hub_probe(struct usb_device *dev, int ifnum);
 void usb_hub_reset(void);
 
+
 /***********************************************************************
  * wait_ms
  */
@@ -99,10 +112,12 @@ int usb_init(void)
                printf("scanning bus for devices... ");
                running=1;
                usb_scan_devices();
+               usb_started = 1;
                return 0;
        }
        else {
                printf("Error, couldn't init Lowlevel part\n");
+               usb_started = 0;
                return -1;
        }
 }
@@ -113,6 +128,7 @@ int usb_init(void)
 int usb_stop(void)
 {
        asynch_allowed=1;
+       usb_started = 0;
        usb_hub_reset();
        return usb_lowlevel_stop();
 }
@@ -157,6 +173,7 @@ int usb_control_msg(struct usb_device *dev, unsigned int pipe,
 {
        if((timeout==0)&&(!asynch_allowed)) /* request for a asynch control pipe is not allowed */
                return -1;
+
        /* set setup command */
        setup_packet.requesttype = requesttype;
        setup_packet.request = request;
@@ -268,56 +285,68 @@ int usb_set_maxpacket(struct usb_device *dev)
 int usb_parse_config(struct usb_device *dev, unsigned char *buffer, int cfgno)
 {
        struct usb_descriptor_header *head;
-       int index,ifno,epno;
-       ifno=-1;
-       epno=-1;
-
-       dev->configno=cfgno;
-       head =(struct usb_descriptor_header *)&buffer[0];
-       if(head->bDescriptorType!=USB_DT_CONFIG) {
-               printf(" ERROR: NOT USB_CONFIG_DESC %x\n",head->bDescriptorType);
+       int index, ifno, epno, curr_if_num;
+       int i;
+       unsigned char *ch;
+
+       ifno = -1;
+       epno = -1;
+       curr_if_num = -1;
+
+       dev->configno = cfgno;
+       head = (struct usb_descriptor_header *) &buffer[0];
+       if(head->bDescriptorType != USB_DT_CONFIG) {
+               printf(" ERROR: NOT USB_CONFIG_DESC %x\n", head->bDescriptorType);
                return -1;
        }
-       memcpy(&dev->config,buffer,buffer[0]);
-       dev->config.wTotalLength=swap_16(dev->config.wTotalLength);
-       dev->config.no_of_if=0;
+       memcpy(&dev->config, buffer, buffer[0]);
+       dev->config.wTotalLength = swap_16(dev->config.wTotalLength);
+       dev->config.no_of_if = 0;
 
-       index=dev->config.bLength;
+       index = dev->config.bLength;
        /* Ok the first entry must be a configuration entry, now process the others */
-       head=(struct usb_descriptor_header *)&buffer[index];
-       while(index+1 < dev->config.wTotalLength) {
+       head = (struct usb_descriptor_header *) &buffer[index];
+       while(index + 1 < dev->config.wTotalLength) {
                switch(head->bDescriptorType) {
                        case USB_DT_INTERFACE:
-                               ifno=dev->config.no_of_if;
-                               dev->config.no_of_if++; /* found an interface desc, increase numbers */
-                               memcpy(&dev->config.if_desc[ifno],&buffer[index],buffer[index]); /* copy new desc */
-                               dev->config.if_desc[ifno].no_of_ep=0;
-
+                               if(((struct usb_interface_descriptor *) &buffer[index])->
+                                       bInterfaceNumber != curr_if_num) {
+                                       /* this is a new interface, copy new desc */
+                                       ifno = dev->config.no_of_if;
+                                       dev->config.no_of_if++;
+                                       memcpy(&dev->config.if_desc[ifno],
+                                               &buffer[index], buffer[index]);
+                                       dev->config.if_desc[ifno].no_of_ep = 0;
+                                       dev->config.if_desc[ifno].num_altsetting = 1;
+                                       curr_if_num = dev->config.if_desc[ifno].bInterfaceNumber;
+                               } else {
+                                       /* found alternate setting for the interface */
+                                       dev->config.if_desc[ifno].num_altsetting++;
+                               }
                                break;
                        case USB_DT_ENDPOINT:
-                               epno=dev->config.if_desc[ifno].no_of_ep;
+                               epno = dev->config.if_desc[ifno].no_of_ep;
                                dev->config.if_desc[ifno].no_of_ep++; /* found an endpoint */
-                               memcpy(&dev->config.if_desc[ifno].ep_desc[epno],&buffer[index],buffer[index]);
-                               dev->config.if_desc[ifno].ep_desc[epno].wMaxPacketSize
-                                       =swap_16(dev->config.if_desc[ifno].ep_desc[epno].wMaxPacketSize);
-                               USB_PRINTF("if %d, ep %d\n",ifno,epno);
+                               memcpy(&dev->config.if_desc[ifno].ep_desc[epno],
+                                       &buffer[index], buffer[index]);
+                               dev->config.if_desc[ifno].ep_desc[epno].wMaxPacketSize =
+                                       swap_16(dev->config.if_desc[ifno].ep_desc[epno].wMaxPacketSize);
+                               USB_PRINTF("if %d, ep %d\n", ifno, epno);
                                break;
                        default:
-                               if(head->bLength==0)
+                               if(head->bLength == 0)
                                        return 1;
-                               USB_PRINTF("unknown Description Type : %x\n",head->bDescriptorType);
+                               USB_PRINTF("unknown Description Type : %x\n", head->bDescriptorType);
                                {
-                                       int i;
-                                       unsigned char *ch;
-                                       ch=(unsigned char *)head;
-                                       for(i=0;i<head->bLength; i++)
-                                               USB_PRINTF("%02X ",*ch++);
+                                       ch = (unsigned char *)head;
+                                       for(i = 0; i < head->bLength; i++)
+                                               USB_PRINTF("%02X ", *ch++);
                                        USB_PRINTF("\n\n\n");
                                }
                                break;
                }
-               index+=head->bLength;
-               head=(struct usb_descriptor_header *)&buffer[index];
+               index += head->bLength;
+               head = (struct usb_descriptor_header *)&buffer[index];
        }
        return 1;
 }
@@ -330,8 +359,7 @@ int usb_parse_config(struct usb_device *dev, unsigned char *buffer, int cfgno)
 int usb_clear_halt(struct usb_device *dev, int pipe)
 {
        int result;
-       unsigned short status;
-       int endp=usb_pipeendpoint(pipe)|(usb_pipein(pipe)<<7);
+       int endp = usb_pipeendpoint(pipe)|(usb_pipein(pipe)<<7);
 
        result = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
                USB_REQ_CLEAR_FEATURE, USB_RECIP_ENDPOINT, 0, endp, NULL, 0, USB_CNTL_TIMEOUT * 3);
@@ -339,15 +367,14 @@ int usb_clear_halt(struct usb_device *dev, int pipe)
        /* don't clear if failed */
        if (result < 0)
                return result;
-       result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
-               USB_REQ_GET_STATUS, USB_DIR_IN | USB_RECIP_ENDPOINT, 0, endp,
-               &status, sizeof(status), USB_CNTL_TIMEOUT * 3);
-       if (result < 0)
-               return result;
-       USB_PRINTF("usb_clear_halt: status 0x%x\n",status);
-       if (status & 1)
-               return -1;              /* still halted */
+
+       /*
+        * NOTE: we do not get status and verify reset was successful
+        * as some devices are reported to lock up upon this check..
+        */
+
        usb_endpoint_running(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe));
+
        /* toggle is reset on clear */
        usb_settoggle(dev, usb_pipeendpoint(pipe), usb_pipeout(pipe), 0);
        return 0;
@@ -423,7 +450,7 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate)
        struct usb_interface_descriptor *if_face = NULL;
        int ret, i;
 
-       for (i=0; i<dev->config.bNumInterfaces; i++) {
+       for (i = 0; i < dev->config.bNumInterfaces; i++) {
                if (dev->config.if_desc[i].bInterfaceNumber == interface) {
                        if_face = &dev->config.if_desc[i];
                        break;
@@ -433,14 +460,20 @@ int usb_set_interface(struct usb_device *dev, int interface, int alternate)
                printf("selecting invalid interface %d", interface);
                return -1;
        }
+       /*
+        * We should return now for devices with only one alternate setting.
+        * According to 9.4.10 of the Universal Serial Bus Specification Revision 2.0
+        * such devices can return with a STALL. This results in some USB sticks
+        * timeouting during initialization and then being unusable in U-Boot.
+        */
+       if (if_face->num_altsetting == 1)
+               return 0;
 
        if ((ret = usb_control_msg(dev, usb_sndctrlpipe(dev, 0),
            USB_REQ_SET_INTERFACE, USB_RECIP_INTERFACE, alternate,
            interface, NULL, 0, USB_CNTL_TIMEOUT * 5)) < 0)
                return ret;
 
-       if_face->act_altsetting = (unsigned char)alternate;
-       usb_set_maxpacket(dev);
        return 0;
 }
 
@@ -511,11 +544,74 @@ int usb_get_class_descriptor(struct usb_device *dev, int ifnum,
  */
 int usb_get_string(struct usb_device *dev, unsigned short langid, unsigned char index, void *buf, int size)
 {
-       return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
-               USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
-               (USB_DT_STRING << 8) + index, langid, buf, size, USB_CNTL_TIMEOUT);
+       int i;
+       int result;
+
+       for (i = 0; i < 3; ++i) {
+               /* some devices are flaky */
+               result = usb_control_msg(dev, usb_rcvctrlpipe(dev, 0),
+                       USB_REQ_GET_DESCRIPTOR, USB_DIR_IN,
+                       (USB_DT_STRING << 8) + index, langid, buf, size,
+                       USB_CNTL_TIMEOUT);
+
+               if (result > 0)
+                       break;
+       }
+
+       return result;
+}
+
+
+static void usb_try_string_workarounds(unsigned char *buf, int *length)
+{
+       int newlength, oldlength = *length;
+
+       for (newlength = 2; newlength + 1 < oldlength; newlength += 2)
+               if (!isprint(buf[newlength]) || buf[newlength + 1])
+                       break;
+
+       if (newlength > 2) {
+               buf[0] = newlength;
+               *length = newlength;
+       }
+}
+
+
+static int usb_string_sub(struct usb_device *dev, unsigned int langid,
+               unsigned int index, unsigned char *buf)
+{
+       int rc;
+
+       /* Try to read the string descriptor by asking for the maximum
+        * possible number of bytes */
+       rc = usb_get_string(dev, langid, index, buf, 255);
+
+       /* If that failed try to read the descriptor length, then
+        * ask for just that many bytes */
+       if (rc < 2) {
+               rc = usb_get_string(dev, langid, index, buf, 2);
+               if (rc == 2)
+                       rc = usb_get_string(dev, langid, index, buf, buf[0]);
+       }
+
+       if (rc >= 2) {
+               if (!buf[0] && !buf[1])
+                       usb_try_string_workarounds(buf, &rc);
+
+               /* There might be extra junk at the end of the descriptor */
+               if (buf[0] < rc)
+                       rc = buf[0];
+
+               rc = rc - (rc & 1); /* force a multiple of two */
+       }
+
+       if (rc < 2)
+               rc = -1;
+
+       return rc;
 }
 
+
 /********************************************************************
  * usb_string:
  * Get string index and translate it to ascii.
@@ -535,7 +631,7 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size)
 
        /* get langid for strings if it's not yet known */
        if (!dev->have_langid) {
-               err = usb_get_string(dev, 0, 0, tbuf, 4);
+               err = usb_string_sub(dev, 0, 0, tbuf);
                if (err < 0) {
                        USB_PRINTF("error getting string descriptor 0 (error=%x)\n",dev->status);
                        return -1;
@@ -550,22 +646,11 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size)
                                dev->devnum, dev->string_langid);
                }
        }
-       /* Just ask for a maximum length string and then take the length
-        * that was returned. */
-       err = usb_get_string(dev, dev->string_langid, index, tbuf, 4);
-       if (err < 0)
-               return err;
-       u=tbuf[0];
-       USB_PRINTF("Strn Len %d, index %d\n",u,index);
 
-       if (u > USB_BUFSIZ) {
-               USB_PRINTF("usb_string: failed to get string - too long: %d\n", u);
-               return -1;
-       }
-
-       err = usb_get_string(dev, dev->string_langid, index, tbuf, u);
+       err = usb_string_sub(dev, dev->string_langid, index, tbuf);
        if (err < 0)
                return err;
+
        size--;         /* leave room for trailing NULL char in output buffer */
        for (idx = 0, u = 2; u < err; u += 2) {
                if (idx >= size)
@@ -641,11 +726,66 @@ int usb_new_device(struct usb_device *dev)
        /* We still haven't set the Address yet */
        addr = dev->devnum;
        dev->devnum = 0;
+
+#undef NEW_INIT_SEQ
+#ifdef NEW_INIT_SEQ
+       /* this is a Windows scheme of initialization sequence, with double
+        * reset of the device. Some equipment is said to work only with such
+        * init sequence; this patch is based on the work by Alan Stern:
+        * http://sourceforge.net/mailarchive/forum.php?thread_id=5729457&forum_id=5398
+        */
+       int j;
+       struct usb_device_descriptor *desc;
+       int port = -1;
+       struct usb_device *parent = dev->parent;
+       unsigned short portstatus;
+
+       /* send 64-byte GET-DEVICE-DESCRIPTOR request.  Since the descriptor is
+        * only 18 bytes long, this will terminate with a short packet.  But if
+        * the maxpacket size is 8 or 16 the device may be waiting to transmit
+        * some more. */
+
+       desc = (struct usb_device_descriptor *)tmpbuf;
+       desc->bMaxPacketSize0 = 0;
+       for (j = 0; j < 3; ++j) {
+               err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, desc, 64);
+               if (err < 0) {
+                       USB_PRINTF("usb_new_device: 64 byte descr\n");
+                       break;
+               }
+       }
+       dev->descriptor.bMaxPacketSize0 = desc->bMaxPacketSize0;
+
+       /* find the port number we're at */
+       if (parent) {
+
+               for (j = 0; j < parent->maxchild; j++) {
+                       if (parent->children[j] == dev) {
+                               port = j;
+                               break;
+                       }
+               }
+               if (port < 0) {
+                       printf("usb_new_device: cannot locate device's port..\n");
+                       return 1;
+               }
+
+               /* reset the port for the second time */
+               err = hub_port_reset(dev->parent, port, &portstatus);
+               if (err < 0) {
+                       printf("\n     Couldn't reset port %i\n", port);
+                       return 1;
+               }
+       }
+#else
+       /* and this is the old and known way of initializing devices */
        err = usb_get_descriptor(dev, USB_DT_DEVICE, 0, &dev->descriptor, 8);
        if (err < 8) {
                printf("\n      USB device not responding, giving up (status=%lX)\n",dev->status);
                return 1;
        }
+#endif
+
        dev->epmaxpacketin [0] = dev->descriptor.bMaxPacketSize0;
        dev->epmaxpacketout[0] = dev->descriptor.bMaxPacketSize0;
        switch (dev->descriptor.bMaxPacketSize0) {
@@ -723,7 +863,7 @@ void usb_scan_devices(void)
        /* device 0 is always present (root hub, so let it analyze) */
        dev=usb_alloc_new_device();
        usb_new_device(dev);
-       printf("%d USB Devices found\n",dev_index);
+       printf("%d USB Device(s) found\n",dev_index);
        /* insert "driver" if possible */
 #ifdef CONFIG_USB_KEYBOARD
        drv_usb_kbd_init();
@@ -821,39 +961,15 @@ struct usb_hub_device *usb_hub_allocate(void)
 
 #define MAX_TRIES 5
 
-void usb_hub_port_connect_change(struct usb_device *dev, int port)
+static int hub_port_reset(struct usb_device *dev, int port,
+                       unsigned short *portstat)
 {
-       struct usb_device *usb;
+       int tries;
        struct usb_port_status portsts;
        unsigned short portstatus, portchange;
-       int tries;
 
-       /* Check status */
-       if (usb_get_port_status(dev, port + 1, &portsts)<0) {
-               USB_HUB_PRINTF("get_port_status failed\n");
-               return;
-       }
-
-       portstatus = swap_16(portsts.wPortStatus);
-       portchange = swap_16(portsts.wPortChange);
-       USB_HUB_PRINTF("portstatus %x, change %x, %s\n", portstatus, portchange,
-               portstatus&(1<<USB_PORT_FEAT_LOWSPEED) ? "Low Speed" : "High Speed");
-
-       /* Clear the connection change status */
-       usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_CONNECTION);
-
-       /* Disconnect any existing devices under this port */
-       if (((!(portstatus & USB_PORT_STAT_CONNECTION)) &&
-            (!(portstatus & USB_PORT_STAT_ENABLE)))|| (dev->children[port])) {
-               USB_HUB_PRINTF("usb_disconnect(&hub->children[port]);\n");
-               /* Return now if nothing is connected */
-               if (!(portstatus & USB_PORT_STAT_CONNECTION))
-                       return;
-       }
-       wait_ms(200);
-
-       /* Reset the port */
 
+       USB_HUB_PRINTF("hub_port_reset: resetting port %d...\n", port);
        for(tries=0;tries<MAX_TRIES;tries++) {
 
                usb_set_port_feature(dev, port + 1, USB_PORT_FEAT_RESET);
@@ -861,7 +977,7 @@ void usb_hub_port_connect_change(struct usb_device *dev, int port)
 
                if (usb_get_port_status(dev, port + 1, &portsts)<0) {
                        USB_HUB_PRINTF("get_port_status failed status %lX\n",dev->status);
-                       return;
+                       return -1;
                }
                portstatus = swap_16(portsts.wPortStatus);
                portchange = swap_16(portsts.wPortChange);
@@ -873,10 +989,12 @@ void usb_hub_port_connect_change(struct usb_device *dev, int port)
                        (portstatus & USB_PORT_STAT_ENABLE) ? 1 : 0);
                if ((portchange & USB_PORT_STAT_C_CONNECTION) ||
                    !(portstatus & USB_PORT_STAT_CONNECTION))
-                       return;
+                       return -1;
+
+               if (portstatus & USB_PORT_STAT_ENABLE) {
 
-               if (portstatus & USB_PORT_STAT_ENABLE)
                        break;
+               }
 
                wait_ms(200);
        }
@@ -884,10 +1002,52 @@ void usb_hub_port_connect_change(struct usb_device *dev, int port)
        if (tries==MAX_TRIES) {
                USB_HUB_PRINTF("Cannot enable port %i after %i retries, disabling port.\n", port+1, MAX_TRIES);
                USB_HUB_PRINTF("Maybe the USB cable is bad?\n");
-               return;
+               return -1;
        }
 
        usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_RESET);
+       *portstat = portstatus;
+       return 0;
+
+}
+
+
+void usb_hub_port_connect_change(struct usb_device *dev, int port)
+{
+       struct usb_device *usb;
+       struct usb_port_status portsts;
+       unsigned short portstatus, portchange;
+
+       /* Check status */
+       if (usb_get_port_status(dev, port + 1, &portsts)<0) {
+               USB_HUB_PRINTF("get_port_status failed\n");
+               return;
+       }
+
+       portstatus = swap_16(portsts.wPortStatus);
+       portchange = swap_16(portsts.wPortChange);
+       USB_HUB_PRINTF("portstatus %x, change %x, %s\n", portstatus, portchange,
+               portstatus&(1<<USB_PORT_FEAT_LOWSPEED) ? "Low Speed" : "High Speed");
+
+       /* Clear the connection change status */
+       usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_CONNECTION);
+
+       /* Disconnect any existing devices under this port */
+       if (((!(portstatus & USB_PORT_STAT_CONNECTION)) &&
+            (!(portstatus & USB_PORT_STAT_ENABLE)))|| (dev->children[port])) {
+               USB_HUB_PRINTF("usb_disconnect(&hub->children[port]);\n");
+               /* Return now if nothing is connected */
+               if (!(portstatus & USB_PORT_STAT_CONNECTION))
+                       return;
+       }
+       wait_ms(200);
+
+       /* Reset the port */
+       if (hub_port_reset(dev, port, &portstatus) < 0) {
+               printf("cannot reset port %i!?\n", port + 1);
+               return;
+       }
+
        wait_ms(200);
 
        /* Allocate a new device struct for it */