]> git.sur5r.net Git - u-boot/commitdiff
usb: musb: add virtual root hub control support
authorBryan Wu <bryan.wu@analog.com>
Sat, 9 Jan 2010 21:53:54 +0000 (16:53 -0500)
committerRemy Bohmer <linux@bohmer.net>
Tue, 12 Jan 2010 19:11:27 +0000 (20:11 +0100)
For MUSB devices that do not support multipoint (hubs), we have to emulate
a root hub so that we can support core operations like resetting ports.

Signed-off-by: Bryan Wu <bryan.wu@analog.com>
Signed-off-by: Cliff Cai <cliff.cai@analog.com>
Signed-off-by: Mike Frysinger <vapier@gentoo.org>
drivers/usb/musb/musb_hcd.c
drivers/usb/musb/musb_hcd.h

index adb8c38527465d769450e79d6138c378bdecae56..dd2aa7f27c1be2c231acbc857336fde6f4def0c3 100644 (file)
@@ -35,6 +35,106 @@ static struct musb_epinfo epinfo[3] = {
        {MUSB_INTR_EP, 0, 64}   /* EP2 - Interrupt IN - 64 Bytes */
 };
 
+/* --- Virtual Root Hub ---------------------------------------------------- */
+#ifdef MUSB_NO_MULTIPOINT
+static int rh_devnum;
+static u32 port_status;
+
+/* Device descriptor */
+static u8 root_hub_dev_des[] = {
+       0x12,                   /*  __u8  bLength; */
+       0x01,                   /*  __u8  bDescriptorType; Device */
+       0x00,                   /*  __u16 bcdUSB; v1.1 */
+       0x02,
+       0x09,                   /*  __u8  bDeviceClass; HUB_CLASSCODE */
+       0x00,                   /*  __u8  bDeviceSubClass; */
+       0x00,                   /*  __u8  bDeviceProtocol; */
+       0x08,                   /*  __u8  bMaxPacketSize0; 8 Bytes */
+       0x00,                   /*  __u16 idVendor; */
+       0x00,
+       0x00,                   /*  __u16 idProduct; */
+       0x00,
+       0x00,                   /*  __u16 bcdDevice; */
+       0x00,
+       0x00,                   /*  __u8  iManufacturer; */
+       0x01,                   /*  __u8  iProduct; */
+       0x00,                   /*  __u8  iSerialNumber; */
+       0x01                    /*  __u8  bNumConfigurations; */
+};
+
+/* Configuration descriptor */
+static u8 root_hub_config_des[] = {
+       0x09,                   /*  __u8  bLength; */
+       0x02,                   /*  __u8  bDescriptorType; Configuration */
+       0x19,                   /*  __u16 wTotalLength; */
+       0x00,
+       0x01,                   /*  __u8  bNumInterfaces; */
+       0x01,                   /*  __u8  bConfigurationValue; */
+       0x00,                   /*  __u8  iConfiguration; */
+       0x40,                   /*  __u8  bmAttributes;
+                                  Bit 7: Bus-powered, 6: Self-powered, 5 Remote-wakwup, 4..0: resvd */
+       0x00,                   /*  __u8  MaxPower; */
+
+       /* interface */
+       0x09,                   /*  __u8  if_bLength; */
+       0x04,                   /*  __u8  if_bDescriptorType; Interface */
+       0x00,                   /*  __u8  if_bInterfaceNumber; */
+       0x00,                   /*  __u8  if_bAlternateSetting; */
+       0x01,                   /*  __u8  if_bNumEndpoints; */
+       0x09,                   /*  __u8  if_bInterfaceClass; HUB_CLASSCODE */
+       0x00,                   /*  __u8  if_bInterfaceSubClass; */
+       0x00,                   /*  __u8  if_bInterfaceProtocol; */
+       0x00,                   /*  __u8  if_iInterface; */
+
+       /* endpoint */
+       0x07,                   /*  __u8  ep_bLength; */
+       0x05,                   /*  __u8  ep_bDescriptorType; Endpoint */
+       0x81,                   /*  __u8  ep_bEndpointAddress; IN Endpoint 1 */
+       0x03,                   /*  __u8  ep_bmAttributes; Interrupt */
+       0x00,                   /*  __u16 ep_wMaxPacketSize; ((MAX_ROOT_PORTS + 1) / 8 */
+       0x02,
+       0xff                    /*  __u8  ep_bInterval; 255 ms */
+};
+
+static unsigned char root_hub_str_index0[] = {
+       0x04,                   /*  __u8  bLength; */
+       0x03,                   /*  __u8  bDescriptorType; String-descriptor */
+       0x09,                   /*  __u8  lang ID */
+       0x04,                   /*  __u8  lang ID */
+};
+
+static unsigned char root_hub_str_index1[] = {
+       0x1c,                   /*  __u8  bLength; */
+       0x03,                   /*  __u8  bDescriptorType; String-descriptor */
+       'M',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       'U',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       'S',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       'B',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       ' ',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       'R',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       'o',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       'o',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       't',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       ' ',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       'H',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       'u',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+       'b',                    /*  __u8  Unicode */
+       0,                      /*  __u8  Unicode */
+};
+#endif
+
 /*
  * This function writes the data toggle value.
  */
@@ -411,18 +511,341 @@ static void config_hub_port(struct usb_device *dev, u8 ep)
 #endif
 }
 
+#ifdef MUSB_NO_MULTIPOINT
+
+static void musb_port_reset(int do_reset)
+{
+       u8 power = readb(&musbr->power);
+
+       if (do_reset) {
+               power &= 0xf0;
+               writeb(power | MUSB_POWER_RESET, &musbr->power);
+               port_status |= USB_PORT_STAT_RESET;
+               port_status &= ~USB_PORT_STAT_ENABLE;
+               udelay(30000);
+       } else {
+               writeb(power & ~MUSB_POWER_RESET, &musbr->power);
+
+               power = readb(&musbr->power);
+               if (power & MUSB_POWER_HSMODE)
+                       port_status |= USB_PORT_STAT_HIGH_SPEED;
+
+               port_status &= ~(USB_PORT_STAT_RESET | (USB_PORT_STAT_C_CONNECTION << 16));
+               port_status |= USB_PORT_STAT_ENABLE
+                       | (USB_PORT_STAT_C_RESET << 16)
+                       | (USB_PORT_STAT_C_ENABLE << 16);
+       }
+}
+
+/*
+ * root hub control
+ */
+static int musb_submit_rh_msg(struct usb_device *dev, unsigned long pipe,
+                             void *buffer, int transfer_len,
+                             struct devrequest *cmd)
+{
+       int leni = transfer_len;
+       int len = 0;
+       int stat = 0;
+       u32 datab[4];
+       u8 *data_buf = (u8 *) datab;
+       u16 bmRType_bReq;
+       u16 wValue;
+       u16 wIndex;
+       u16 wLength;
+       u16 int_usb;
+
+       if ((pipe & PIPE_INTERRUPT) == PIPE_INTERRUPT) {
+               debug("Root-Hub submit IRQ: NOT implemented\n");
+               return 0;
+       }
+
+       bmRType_bReq = cmd->requesttype | (cmd->request << 8);
+       wValue = swap_16(cmd->value);
+       wIndex = swap_16(cmd->index);
+       wLength = swap_16(cmd->length);
+
+       debug("--- HUB ----------------------------------------\n");
+       debug("submit rh urb, req=%x val=%#x index=%#x len=%d\n",
+           bmRType_bReq, wValue, wIndex, wLength);
+       debug("------------------------------------------------\n");
+
+       switch (bmRType_bReq) {
+       case RH_GET_STATUS:
+               debug("RH_GET_STATUS\n");
+
+               *(__u16 *) data_buf = swap_16(1);
+               len = 2;
+               break;
+
+       case RH_GET_STATUS | RH_INTERFACE:
+               debug("RH_GET_STATUS | RH_INTERFACE\n");
+
+               *(__u16 *) data_buf = swap_16(0);
+               len = 2;
+               break;
+
+       case RH_GET_STATUS | RH_ENDPOINT:
+               debug("RH_GET_STATUS | RH_ENDPOINT\n");
+
+               *(__u16 *) data_buf = swap_16(0);
+               len = 2;
+               break;
+
+       case RH_GET_STATUS | RH_CLASS:
+               debug("RH_GET_STATUS | RH_CLASS\n");
+
+               *(__u32 *) data_buf = swap_32(0);
+               len = 4;
+               break;
+
+       case RH_GET_STATUS | RH_OTHER | RH_CLASS:
+               debug("RH_GET_STATUS | RH_OTHER | RH_CLASS\n");
+
+               int_usb = readw(&musbr->intrusb);
+               if (int_usb & MUSB_INTR_CONNECT) {
+                       port_status |= USB_PORT_STAT_CONNECTION
+                               | (USB_PORT_STAT_C_CONNECTION << 16);
+                       port_status |= USB_PORT_STAT_HIGH_SPEED
+                               | USB_PORT_STAT_ENABLE;
+               }
+
+               if (port_status & USB_PORT_STAT_RESET)
+                       musb_port_reset(0);
+
+               *(__u32 *) data_buf = swap_32(port_status);
+               len = 4;
+               break;
+
+       case RH_CLEAR_FEATURE | RH_ENDPOINT:
+               debug("RH_CLEAR_FEATURE | RH_ENDPOINT\n");
+
+               switch (wValue) {
+               case RH_ENDPOINT_STALL:
+                       debug("C_HUB_ENDPOINT_STALL\n");
+                       len = 0;
+                       break;
+               }
+               port_status &= ~(1 << wValue);
+               break;
+
+       case RH_CLEAR_FEATURE | RH_CLASS:
+               debug("RH_CLEAR_FEATURE | RH_CLASS\n");
+
+               switch (wValue) {
+               case RH_C_HUB_LOCAL_POWER:
+                       debug("C_HUB_LOCAL_POWER\n");
+                       len = 0;
+                       break;
+
+               case RH_C_HUB_OVER_CURRENT:
+                       debug("C_HUB_OVER_CURRENT\n");
+                       len = 0;
+                       break;
+               }
+               port_status &= ~(1 << wValue);
+               break;
+
+       case RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS:
+               debug("RH_CLEAR_FEATURE | RH_OTHER | RH_CLASS\n");
+
+               switch (wValue) {
+               case RH_PORT_ENABLE:
+                       len = 0;
+                       break;
+
+               case RH_PORT_SUSPEND:
+                       len = 0;
+                       break;
+
+               case RH_PORT_POWER:
+                       len = 0;
+                       break;
+
+               case RH_C_PORT_CONNECTION:
+                       len = 0;
+                       break;
+
+               case RH_C_PORT_ENABLE:
+                       len = 0;
+                       break;
+
+               case RH_C_PORT_SUSPEND:
+                       len = 0;
+                       break;
+
+               case RH_C_PORT_OVER_CURRENT:
+                       len = 0;
+                       break;
+
+               case RH_C_PORT_RESET:
+                       len = 0;
+                       break;
+
+               default:
+                       debug("invalid wValue\n");
+                       stat = USB_ST_STALLED;
+               }
+
+               port_status &= ~(1 << wValue);
+               break;
+
+       case RH_SET_FEATURE | RH_OTHER | RH_CLASS:
+               debug("RH_SET_FEATURE | RH_OTHER | RH_CLASS\n");
+
+               switch (wValue) {
+               case RH_PORT_SUSPEND:
+                       len = 0;
+                       break;
+
+               case RH_PORT_RESET:
+                       musb_port_reset(1);
+                       len = 0;
+                       break;
+
+               case RH_PORT_POWER:
+                       len = 0;
+                       break;
+
+               case RH_PORT_ENABLE:
+                       len = 0;
+                       break;
+
+               default:
+                       debug("invalid wValue\n");
+                       stat = USB_ST_STALLED;
+               }
+
+               port_status |= 1 << wValue;
+               break;
+
+       case RH_SET_ADDRESS:
+               debug("RH_SET_ADDRESS\n");
+
+               rh_devnum = wValue;
+               len = 0;
+               break;
+
+       case RH_GET_DESCRIPTOR:
+               debug("RH_GET_DESCRIPTOR: %x, %d\n", wValue, wLength);
+
+               switch (wValue) {
+               case (USB_DT_DEVICE << 8):      /* device descriptor */
+                       len = min_t(unsigned int,
+                                   leni, min_t(unsigned int,
+                                               sizeof(root_hub_dev_des),
+                                               wLength));
+                       data_buf = root_hub_dev_des;
+                       break;
+
+               case (USB_DT_CONFIG << 8):      /* configuration descriptor */
+                       len = min_t(unsigned int,
+                                   leni, min_t(unsigned int,
+                                               sizeof(root_hub_config_des),
+                                               wLength));
+                       data_buf = root_hub_config_des;
+                       break;
+
+               case ((USB_DT_STRING << 8) | 0x00):     /* string 0 descriptors */
+                       len = min_t(unsigned int,
+                                   leni, min_t(unsigned int,
+                                               sizeof(root_hub_str_index0),
+                                               wLength));
+                       data_buf = root_hub_str_index0;
+                       break;
+
+               case ((USB_DT_STRING << 8) | 0x01):     /* string 1 descriptors */
+                       len = min_t(unsigned int,
+                                   leni, min_t(unsigned int,
+                                               sizeof(root_hub_str_index1),
+                                               wLength));
+                       data_buf = root_hub_str_index1;
+                       break;
+
+               default:
+                       debug("invalid wValue\n");
+                       stat = USB_ST_STALLED;
+               }
+
+               break;
+
+       case RH_GET_DESCRIPTOR | RH_CLASS:
+               debug("RH_GET_DESCRIPTOR | RH_CLASS\n");
+
+               data_buf[0] = 0x09;     /* min length; */
+               data_buf[1] = 0x29;
+               data_buf[2] = 0x1;      /* 1 port */
+               data_buf[3] = 0x01;     /* per-port power switching */
+               data_buf[3] |= 0x10;    /* no overcurrent reporting */
+
+               /* Corresponds to data_buf[4-7] */
+               data_buf[4] = 0;
+               data_buf[5] = 5;
+               data_buf[6] = 0;
+               data_buf[7] = 0x02;
+               data_buf[8] = 0xff;
+
+               len = min_t(unsigned int, leni,
+                           min_t(unsigned int, data_buf[0], wLength));
+               break;
+
+       case RH_GET_CONFIGURATION:
+               debug("RH_GET_CONFIGURATION\n");
+
+               *(__u8 *) data_buf = 0x01;
+               len = 1;
+               break;
+
+       case RH_SET_CONFIGURATION:
+               debug("RH_SET_CONFIGURATION\n");
+
+               len = 0;
+               break;
+
+       default:
+               debug("*** *** *** unsupported root hub command *** *** ***\n");
+               stat = USB_ST_STALLED;
+       }
+
+       len = min_t(int, len, leni);
+       if (buffer != data_buf)
+               memcpy(buffer, data_buf, len);
+
+       dev->act_len = len;
+       dev->status = stat;
+       debug("dev act_len %d, status %d\n", dev->act_len, dev->status);
+
+       return stat;
+}
+
+static void musb_rh_init(void)
+{
+       rh_devnum = 0;
+       port_status = 0;
+}
+
+#else
+
+static void musb_rh_init(void) {}
+
+#endif
+
 /*
  * do a control transfer
  */
 int submit_control_msg(struct usb_device *dev, unsigned long pipe, void *buffer,
                        int len, struct devrequest *setup)
 {
-#ifndef MUSB_NO_MULTIPOINT
        int devnum = usb_pipedevice(pipe);
-#endif
        u16 csr;
        u8  devspeed;
 
+#ifdef MUSB_NO_MULTIPOINT
+       /* Control message is for the HUB? */
+       if (devnum == rh_devnum)
+               return musb_submit_rh_msg(dev, pipe, buffer, len, setup);
+#endif
+
        /* select control endpoint */
        writeb(MUSB_CONTROL_EP, &musbr->index);
        csr = readw(&musbr->txcsr);
@@ -649,6 +1072,8 @@ int usb_lowlevel_init(void)
        u8  power;
        u32 timeout;
 
+       musb_rh_init();
+
        if (musb_platform_init() == -1)
                return -1;
 
index a437985dc8fdb7982283b311a06e41a6a1cc9a44..dde7d37722e123a73673f56e658f7c839ac4bc36 100644 (file)
@@ -50,6 +50,61 @@ extern unsigned char new[];
        ((readb(&musbr->power) & MUSB_POWER_HSMODE) \
                >> MUSB_POWER_HSMODE_SHIFT)
 
+#define min_t(type, x, y)      \
+       ({ type __x = (x); type __y = (y); __x < __y ? __x : __y; })
+
+/* USB HUB CONSTANTS (not OHCI-specific; see hub.h) */
+
+/* destination of request */
+#define RH_INTERFACE              0x01
+#define RH_ENDPOINT               0x02
+#define RH_OTHER                  0x03
+
+#define RH_CLASS                  0x20
+#define RH_VENDOR                 0x40
+
+/* Requests: bRequest << 8 | bmRequestType */
+#define RH_GET_STATUS          0x0080
+#define RH_CLEAR_FEATURE       0x0100
+#define RH_SET_FEATURE         0x0300
+#define RH_SET_ADDRESS         0x0500
+#define RH_GET_DESCRIPTOR      0x0680
+#define RH_SET_DESCRIPTOR      0x0700
+#define RH_GET_CONFIGURATION   0x0880
+#define RH_SET_CONFIGURATION   0x0900
+#define RH_GET_STATE           0x0280
+#define RH_GET_INTERFACE       0x0A80
+#define RH_SET_INTERFACE       0x0B00
+#define RH_SYNC_FRAME          0x0C80
+/* Our Vendor Specific Request */
+#define RH_SET_EP              0x2000
+
+/* Hub port features */
+#define RH_PORT_CONNECTION        0x00
+#define RH_PORT_ENABLE            0x01
+#define RH_PORT_SUSPEND                   0x02
+#define RH_PORT_OVER_CURRENT      0x03
+#define RH_PORT_RESET             0x04
+#define RH_PORT_POWER             0x08
+#define RH_PORT_LOW_SPEED         0x09
+
+#define RH_C_PORT_CONNECTION      0x10
+#define RH_C_PORT_ENABLE          0x11
+#define RH_C_PORT_SUSPEND         0x12
+#define RH_C_PORT_OVER_CURRENT    0x13
+#define RH_C_PORT_RESET                   0x14
+
+/* Hub features */
+#define RH_C_HUB_LOCAL_POWER      0x00
+#define RH_C_HUB_OVER_CURRENT     0x01
+
+#define RH_DEVICE_REMOTE_WAKEUP           0x00
+#define RH_ENDPOINT_STALL         0x01
+
+#define RH_ACK                    0x01
+#define RH_REQ_ERR                -1
+#define RH_NACK                           0x00
+
 /* extern functions */
 extern int musb_platform_init(void);
 extern void musb_platform_deinit(void);