From 0420f7a34f02038c4bfcf7a5305c8b9c1f481129 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Wed, 19 Mar 2014 09:17:35 +0100 Subject: USB: i.MX chipidea: Implement OTG support for the poor For situations when we don't know the desired mode for the OTG port we register a otg device which has a mode parameter to specifiy the desired mode on the command line. Signed-off-by: Sascha Hauer --- drivers/usb/imx/chipidea-imx.c | 101 +++++++++++++++++++++++++++++++++++++---- include/usb/chipidea-imx.h | 1 + 2 files changed, 93 insertions(+), 9 deletions(-) diff --git a/drivers/usb/imx/chipidea-imx.c b/drivers/usb/imx/chipidea-imx.c index 9b6829b8f5..62feae8eac 100644 --- a/drivers/usb/imx/chipidea-imx.c +++ b/drivers/usb/imx/chipidea-imx.c @@ -20,6 +20,7 @@ #include #include #include +#include #include #include #include @@ -31,9 +32,12 @@ struct imx_chipidea { void __iomem *base; struct ehci_data data; unsigned long flags; - enum imx_usb_mode mode; + uint32_t mode; int portno; enum usb_phy_interface phymode; + struct param_d *param_mode; + int role_registered; + struct regulator *vbus; }; static int imx_chipidea_port_init(void *drvdata) @@ -99,6 +103,19 @@ static int imx_chipidea_probe_dt(struct imx_chipidea *ci) case USB_DR_MODE_PERIPHERAL: ci->mode = IMX_USB_MODE_DEVICE; break; + case USB_DR_MODE_OTG: + ci->mode = IMX_USB_MODE_OTG; + break; + case USB_DR_MODE_UNKNOWN: + /* + * No dr_mode specified. This means it can either be OTG + * for port 0 or host mode for the other host-only ports. + */ + if (ci->portno == 0) + ci->mode = IMX_USB_MODE_OTG; + else + ci->mode = IMX_USB_MODE_HOST; + break; } ci->phymode = of_usb_get_phy_mode(ci->dev->device_node, NULL); @@ -129,6 +146,72 @@ static int imx_chipidea_probe_dt(struct imx_chipidea *ci) return 0; } +static int ci_register_role(struct imx_chipidea *ci) +{ + if (ci->role_registered) + return -EBUSY; + + if (ci->mode == IMX_USB_MODE_HOST) { + if (IS_ENABLED(CONFIG_USB_EHCI)) { + ci->role_registered = 1; + return ehci_register(ci->dev, &ci->data); + } else { + dev_err(ci->dev, "Host support not available\n"); + return -ENODEV; + } + } + + if (ci->mode == IMX_USB_MODE_DEVICE) { + if (IS_ENABLED(CONFIG_USB_GADGET_DRIVER_ARC)) { + ci->role_registered = 1; + return ci_udc_register(ci->dev, ci->base); + } else { + dev_err(ci->dev, "USB device support not available\n"); + return -ENODEV; + } + } + + return 0; +} + +static int ci_set_mode(struct param_d *param, void *priv) +{ + struct imx_chipidea *ci = priv; + + if (ci->role_registered) + return -EBUSY; + + return ci_register_role(ci); +} + +static const char *ci_mode_names[] = { + "host", "peripheral", "otg" +}; + +static struct device_d imx_otg_device = { + .name = "otg", + .id = DEVICE_ID_SINGLE, +}; + +static int ci_register_otg_device(struct imx_chipidea *ci) +{ + int ret; + + if (imx_otg_device.parent) + return -EBUSY; + + imx_otg_device.parent = ci->dev; + + ret = register_device(&imx_otg_device); + if (ret) + return ret; + + ci->param_mode = dev_add_param_enum(&imx_otg_device, "mode", + ci_set_mode, NULL, &ci->mode, + ci_mode_names, ARRAY_SIZE(ci_mode_names), ci); + return 0; +} + static int imx_chipidea_probe(struct device_d *dev) { struct imxusb_platformdata *pdata = dev->platform_data; @@ -154,6 +237,10 @@ static int imx_chipidea_probe(struct device_d *dev) ci->mode = pdata->mode; } + ci->vbus = regulator_get(dev, "vbus"); + + regulator_enable(ci->vbus); + base = dev_request_mem_region(dev, 0); if (!base) return -ENODEV; @@ -178,14 +265,10 @@ static int imx_chipidea_probe(struct device_d *dev) ci->data.hcor = base + 0x140; ci->data.flags = EHCI_HAS_TT; - if (ci->mode == IMX_USB_MODE_HOST && IS_ENABLED(CONFIG_USB_EHCI)) { - ret = ehci_register(dev, &ci->data); - } else if (ci->mode == IMX_USB_MODE_DEVICE && IS_ENABLED(CONFIG_USB_GADGET_DRIVER_ARC)) { - ret = ci_udc_register(dev, base); - } else { - dev_err(dev, "No supported role\n"); - ret = -ENODEV; - } + if (ci->mode == IMX_USB_MODE_OTG) + ret = ci_register_otg_device(ci); + else + ret = ci_register_role(ci); return ret; }; diff --git a/include/usb/chipidea-imx.h b/include/usb/chipidea-imx.h index 487217cb7c..09e19af091 100644 --- a/include/usb/chipidea-imx.h +++ b/include/usb/chipidea-imx.h @@ -37,6 +37,7 @@ enum imx_usb_mode { IMX_USB_MODE_HOST, IMX_USB_MODE_DEVICE, + IMX_USB_MODE_OTG, }; struct imxusb_platformdata { -- cgit v1.2.3 From 9fc0d34763d4e3cdaa9ed05210bfdc23286b65a3 Mon Sep 17 00:00:00 2001 From: Antony Pavlov Date: Wed, 21 May 2014 12:54:42 +0400 Subject: commands: usb: add tree view capability This patch adds U-Boot 'usb tree' command functionality to barebox. Here is an example output: 1 ID 0000:0000 | u-boot EHCI Host Controller | +-2 ID 05e3:0606 | USB2.0 Hub | +-3 ID 10c4:ea60 | Silicon Labs CP2102 USB to UART Bridge Contr P-00-00669 | +-4 ID 05e3:0606 | | USB2.0 Hub | | | +-5 ID 05e3:0608 | | | USB2.0 Hub | | | | | +-6 ID 0d8c:000c | | C-Media USB Headphone Set | | | +-7 ID 0d8c:000c | C-Media USB Headphone Set | +-8 ID 0846:1040 NETGEAR NETGEAR FA120 Adapter The tree view is enabled with 'usb -t' Signed-off-by: Antony Pavlov Signed-off-by: Sascha Hauer --- commands/usb.c | 105 +++++++++++++++++++++++++++++++++++++++++++++++-- drivers/usb/core/usb.c | 2 +- include/usb/usb.h | 3 ++ 3 files changed, 106 insertions(+), 4 deletions(-) diff --git a/commands/usb.c b/commands/usb.c index 073c79c719..c158852ca9 100644 --- a/commands/usb.c +++ b/commands/usb.c @@ -22,21 +22,118 @@ #include #include +/* shows the device tree recursively */ +static void usb_show_tree_graph(struct usb_device *dev, char *pre) +{ + int i, index; + int has_child, last_child; + + index = strlen(pre); + printf(" %s", pre); + /* check if the device has connected children */ + has_child = 0; + + for (i = 0; i < dev->maxchild; i++) { + if (dev->children[i] != NULL) + has_child = 1; + } + + /* check if we are the last one */ + last_child = 1; + + if (dev->parent) { + for (i = 0; i < dev->parent->maxchild; i++) { + if (dev->parent->children[i]) + last_child = 0; + + if (dev->parent->children[i] == dev) + last_child = 1; + } /* for all children of the parent */ + printf("\b+-"); + + /* correct last child */ + if (last_child) + pre[index - 1] = ' '; + } else { + /* if not root hub */ + printf(" "); + } + + printf("%d ", dev->devnum); + + pre[index++] = ' '; + pre[index++] = has_child ? '|' : ' '; + pre[index] = 0; + + printf("ID %04x:%04x\n", dev->descriptor->idVendor, dev->descriptor->idProduct); + + if (strlen(dev->mf) || strlen(dev->prod) || strlen(dev->serial)) + printf(" %s %s %s %s\n", pre, dev->mf, dev->prod, dev->serial); + + printf(" %s\n", pre); + + if (dev->maxchild > 0) { + for (i = 0; i < dev->maxchild; i++) { + if (dev->children[i] != NULL) { + usb_show_tree_graph(dev->children[i], pre); + pre[index] = 0; + } + } + } +} + +/* main routine for the tree command */ +static void usb_show_tree(struct usb_device *dev) +{ + char preamble[32]; + + memset(preamble, 0, 32); + usb_show_tree_graph(dev, &preamble[0]); +} + +static void usb_show_devices(bool tree) +{ + struct usb_device *dev; + + list_for_each_entry(dev, &usb_device_list, list) { + if (tree) { + if (dev->parent == NULL) + usb_show_tree(dev); + } else { + printf("Bus %03d Device %03d: ID %04x:%04x %s\n", + dev->host->busnum, dev->devnum, + dev->descriptor->idVendor, + dev->descriptor->idProduct, + dev->prod); + } + } +} + static int do_usb(int argc, char *argv[]) { int opt; - int force = 0; + int force = 0, tree = 0, show = 0; - while ((opt = getopt(argc, argv, "f")) > 0) { + while ((opt = getopt(argc, argv, "fts")) > 0) { switch (opt) { case 'f': force = 1; break; + case 't': + tree = 1; + show = 1; + break; + case 's': + show = 1; + break; } } usb_rescan(force); + if (show) + usb_show_devices(tree); + return 0; } @@ -45,12 +142,14 @@ BAREBOX_CMD_HELP_TEXT("Scan for USB devices.") BAREBOX_CMD_HELP_TEXT("") BAREBOX_CMD_HELP_TEXT("Options:") BAREBOX_CMD_HELP_OPT("-f", "force rescan") +BAREBOX_CMD_HELP_OPT("-s", "show devices") +BAREBOX_CMD_HELP_OPT("-t", "show USB tree") BAREBOX_CMD_HELP_END BAREBOX_CMD_START(usb) .cmd = do_usb, BAREBOX_CMD_DESC("(re-)detect USB devices") - BAREBOX_CMD_OPTS("[-f]") + BAREBOX_CMD_OPTS("[-fts]") BAREBOX_CMD_GROUP(CMD_GRP_HWMANIP) BAREBOX_CMD_HELP(cmd_usb_help) BAREBOX_CMD_COMPLETE(empty_complete) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index f5724322ce..710f611995 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -70,7 +70,7 @@ static int hub_port_reset(struct usb_device *dev, int port, unsigned short *portstat); static LIST_HEAD(host_list); -static LIST_HEAD(usb_device_list); +LIST_HEAD(usb_device_list); static void print_usb_device(struct usb_device *dev) { diff --git a/include/usb/usb.h b/include/usb/usb.h index ff5242b8af..34edbae0a5 100644 --- a/include/usb/usb.h +++ b/include/usb/usb.h @@ -473,4 +473,7 @@ enum usb_phy_interface { USBPHY_INTERFACE_MODE_SERIAL, USBPHY_INTERFACE_MODE_HSIC, }; + +extern struct list_head usb_device_list; + #endif /*_USB_H_ */ -- cgit v1.2.3 From 7c8c7e5ff344ffb4df21853101731f7d939a2fdd Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 10 Jul 2014 12:26:43 +0200 Subject: USB: host: simplify usb_new_device usb_new_device tries to find the port number it is attached to by iterating over the parents children and using the loop counter index as port number. Instead, just use the portnr field in struct usb_device. Signed-off-by: Sascha Hauer --- drivers/usb/core/usb.c | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 710f611995..4f5c0edded 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -297,7 +297,6 @@ static int usb_new_device(struct usb_device *dev) int tmp; void *buf; struct usb_device_descriptor *desc; - int port = -1; struct usb_device *parent = dev->parent; unsigned short portstatus; char str[16]; @@ -338,24 +337,10 @@ static int usb_new_device(struct usb_device *dev) /* find the port number we're at */ if (parent) { - int j; - - for (j = 0; j < parent->maxchild; j++) { - if (parent->children[j] == dev) { - port = j; - break; - } - } - if (port < 0) { - printf("%s: cannot locate device's port.\n", __func__); - err = -ENODEV; - goto err_out; - } - /* reset the port for the second time */ - err = hub_port_reset(dev->parent, port, &portstatus); + err = hub_port_reset(dev->parent, dev->portnr - 1, &portstatus); if (err < 0) { - printf("\n Couldn't reset port %i\n", port); + printf("\n Couldn't reset port %i\n", dev->portnr); goto err_out; } } @@ -433,7 +418,7 @@ static int usb_new_device(struct usb_device *dev) dev->serial, sizeof(dev->serial)); if (parent) { - sprintf(dev->dev.name, "%s-%d", parent->dev.name, port); + sprintf(dev->dev.name, "%s-%d", parent->dev.name, dev->portnr - 1); } else { sprintf(dev->dev.name, "usb%d", dev->host->busnum); } @@ -1108,6 +1093,8 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port) dev->children[port] = usb; usb->parent = dev; + usb->portnr = port + 1; + /* Run it through the hoops (find a driver, etc) */ if (usb_new_device(usb)) { /* Woops, disable the port */ -- cgit v1.2.3 From 16e1d3899cfab2d1d5883372b0c5563dd5697b4b Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 10 Jul 2014 13:48:41 +0200 Subject: USB: host: hub: Turn into a driver Since we have a driver model we can make the hub driver a real driver model driver. Put it into a new file to separate the hub from the USB core stuff. Signed-off-by: Sascha Hauer --- drivers/usb/core/Makefile | 3 +- drivers/usb/core/hub.c | 434 ++++++++++++++++++++++++++++++++++++++++++++++ drivers/usb/core/hub.h | 7 + drivers/usb/core/usb.c | 424 ++------------------------------------------ drivers/usb/core/usb.h | 7 + 5 files changed, 460 insertions(+), 415 deletions(-) create mode 100644 drivers/usb/core/hub.c create mode 100644 drivers/usb/core/hub.h create mode 100644 drivers/usb/core/usb.h diff --git a/drivers/usb/core/Makefile b/drivers/usb/core/Makefile index a74f141db4..dc7e635b0e 100644 --- a/drivers/usb/core/Makefile +++ b/drivers/usb/core/Makefile @@ -1,3 +1,2 @@ - -obj-$(CONFIG_USB_HOST) += usb.o +obj-$(CONFIG_USB_HOST) += usb.o hub.o obj-$(CONFIG_OFDEVICE) += of.o diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c new file mode 100644 index 0000000000..0f43955862 --- /dev/null +++ b/drivers/usb/core/hub.c @@ -0,0 +1,434 @@ +/* + * hub.c - USB hub support + * + * Copyright (c) 2011 Sascha Hauer , Pengutronix + * + * See file CREDITS for list of people who contributed to this + * project. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + */ + +#include +#include +#include +#include +#include +#include +#include + +#include "usb.h" +#include "hub.h" + +#undef USB_HUB_DEBUG + +#ifdef USB_HUB_DEBUG +#define USB_HUB_PRINTF(fmt, args...) printf(fmt , ##args) +#else +#define USB_HUB_PRINTF(fmt, args...) +#endif + +#define USB_BUFSIZ 512 + +static int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB, + USB_DT_HUB << 8, 0, data, size, USB_CNTL_TIMEOUT); +} + +static int usb_clear_port_feature(struct usb_device *dev, int port, int feature) +{ + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, + port, NULL, 0, USB_CNTL_TIMEOUT); +} + +static int usb_set_port_feature(struct usb_device *dev, int port, int feature) +{ + return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, feature, + port, NULL, 0, USB_CNTL_TIMEOUT); +} + +static int usb_get_hub_status(struct usb_device *dev, void *data) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB, 0, 0, + data, sizeof(struct usb_hub_status), USB_CNTL_TIMEOUT); +} + +static int usb_get_port_status(struct usb_device *dev, int port, void *data) +{ + return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), + USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port, + data, sizeof(struct usb_hub_status), USB_CNTL_TIMEOUT); +} + + +static void usb_hub_power_on(struct usb_hub_device *hub) +{ + int i; + struct usb_device *dev; + + dev = hub->pusb_dev; + /* Enable power to the ports */ + USB_HUB_PRINTF("enabling power on all ports\n"); + for (i = 0; i < dev->maxchild; i++) { + usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER); + USB_HUB_PRINTF("port %d returns %lX\n", i + 1, dev->status); + } + /* power on is encoded in 2ms increments -> times 2 for the actual delay */ + mdelay(hub->desc.bPwrOn2PwrGood*2); +} + +#define MAX_TRIES 5 + +static inline char *portspeed(int portstatus) +{ + if (portstatus & (1 << USB_PORT_FEAT_HIGHSPEED)) + return "480 Mb/s"; + else if (portstatus & (1 << USB_PORT_FEAT_LOWSPEED)) + return "1.5 Mb/s"; + else + return "12 Mb/s"; +} + +int hub_port_reset(struct usb_device *dev, int port, + unsigned short *portstat) +{ + int tries; + struct usb_port_status portsts; + unsigned short portstatus, portchange; + + 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); + wait_ms(200); + + if (usb_get_port_status(dev, port + 1, &portsts) < 0) { + USB_HUB_PRINTF("get_port_status failed status %lX\n", + dev->status); + return -1; + } + portstatus = le16_to_cpu(portsts.wPortStatus); + portchange = le16_to_cpu(portsts.wPortChange); + + USB_HUB_PRINTF("portstatus %x, change %x, %s\n", + portstatus, portchange, + portspeed(portstatus)); + + USB_HUB_PRINTF("STAT_C_CONNECTION = %d STAT_CONNECTION = %d" \ + " USB_PORT_STAT_ENABLE %d\n", + (portchange & USB_PORT_STAT_C_CONNECTION) ? 1 : 0, + (portstatus & USB_PORT_STAT_CONNECTION) ? 1 : 0, + (portstatus & USB_PORT_STAT_ENABLE) ? 1 : 0); + + if ((portchange & USB_PORT_STAT_C_CONNECTION) || + !(portstatus & USB_PORT_STAT_CONNECTION)) + return -1; + + if (portstatus & USB_PORT_STAT_ENABLE) + break; + + wait_ms(200); + } + + 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 -1; + } + + usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_RESET); + *portstat = portstatus; + return 0; +} + + +static 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 = le16_to_cpu(portsts.wPortStatus); + portchange = le16_to_cpu(portsts.wPortChange); + USB_HUB_PRINTF("portstatus %x, change %x, %s\n", + portstatus, portchange, portspeed(portstatus)); + + /* 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 */ + usb = usb_alloc_new_device(); + usb->host = dev->host; + + if (portstatus & USB_PORT_STAT_HIGH_SPEED) + usb->speed = USB_SPEED_HIGH; + else if (portstatus & USB_PORT_STAT_LOW_SPEED) + usb->speed = USB_SPEED_LOW; + else + usb->speed = USB_SPEED_FULL; + + dev->children[port] = usb; + usb->parent = dev; + usb->portnr = port + 1; + + /* Run it through the hoops (find a driver, etc) */ + if (usb_new_device(usb)) { + /* Woops, disable the port */ + USB_HUB_PRINTF("hub: disabling port %d\n", port + 1); + usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_ENABLE); + } +} + +static int usb_hub_configure(struct usb_device *dev) +{ + unsigned char buffer[USB_BUFSIZ], *bitmap; + struct usb_hub_descriptor *descriptor; + struct usb_hub_status *hubsts; + int i; + struct usb_hub_device *hub; + + hub = xzalloc(sizeof (*hub)); + dev->hub = hub; + + hub->pusb_dev = dev; + /* Get the the hub descriptor */ + if (usb_get_hub_descriptor(dev, buffer, 4) < 0) { + USB_HUB_PRINTF("%s: failed to get hub " \ + "descriptor, giving up %lX\n", __func__, dev->status); + return -1; + } + descriptor = (struct usb_hub_descriptor *)buffer; + + /* silence compiler warning if USB_BUFSIZ is > 256 [= sizeof(char)] */ + i = descriptor->bLength; + if (i > USB_BUFSIZ) { + USB_HUB_PRINTF("%s: failed to get hub " \ + "descriptor - too long: %d\n", __func__, + descriptor->bLength); + return -1; + } + + if (usb_get_hub_descriptor(dev, buffer, descriptor->bLength) < 0) { + USB_HUB_PRINTF("%s: failed to get hub " \ + "descriptor 2nd giving up %lX\n", __func__, dev->status); + return -1; + } + memcpy((unsigned char *)&hub->desc, buffer, descriptor->bLength); + /* adjust 16bit values */ + hub->desc.wHubCharacteristics = + le16_to_cpu(descriptor->wHubCharacteristics); + /* set the bitmap */ + bitmap = (unsigned char *)&hub->desc.DeviceRemovable[0]; + /* devices not removable by default */ + memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8); + bitmap = (unsigned char *)&hub->desc.PortPowerCtrlMask[0]; + memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8); /* PowerMask = 1B */ + + for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++) + hub->desc.DeviceRemovable[i] = descriptor->DeviceRemovable[i]; + + for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++) + hub->desc.DeviceRemovable[i] = descriptor->PortPowerCtrlMask[i]; + + dev->maxchild = descriptor->bNbrPorts; + USB_HUB_PRINTF("%d ports detected\n", dev->maxchild); + + switch (hub->desc.wHubCharacteristics & HUB_CHAR_LPSM) { + case 0x00: + USB_HUB_PRINTF("ganged power switching\n"); + break; + case 0x01: + USB_HUB_PRINTF("individual port power switching\n"); + break; + case 0x02: + case 0x03: + USB_HUB_PRINTF("unknown reserved power switching mode\n"); + break; + } + + if (hub->desc.wHubCharacteristics & HUB_CHAR_COMPOUND) + USB_HUB_PRINTF("part of a compound device\n"); + else + USB_HUB_PRINTF("standalone hub\n"); + + switch (hub->desc.wHubCharacteristics & HUB_CHAR_OCPM) { + case 0x00: + USB_HUB_PRINTF("global over-current protection\n"); + break; + case 0x08: + USB_HUB_PRINTF("individual port over-current protection\n"); + break; + case 0x10: + case 0x18: + USB_HUB_PRINTF("no over-current protection\n"); + break; + } + + USB_HUB_PRINTF("power on to power good time: %dms\n", + descriptor->bPwrOn2PwrGood * 2); + USB_HUB_PRINTF("hub controller current requirement: %dmA\n", + descriptor->bHubContrCurrent); + + for (i = 0; i < dev->maxchild; i++) + USB_HUB_PRINTF("port %d is%s removable\n", i + 1, + hub->desc.DeviceRemovable[(i + 1) / 8] & \ + (1 << ((i + 1) % 8)) ? " not" : ""); + + if (sizeof(struct usb_hub_status) > USB_BUFSIZ) { + USB_HUB_PRINTF("%s: failed to get Status - " \ + "too long: %d\n", __func__, descriptor->bLength); + return -1; + } + + if (usb_get_hub_status(dev, buffer) < 0) { + USB_HUB_PRINTF("%s: failed to get Status %lX\n", __func__, + dev->status); + return -1; + } + + hubsts = (struct usb_hub_status *)buffer; + USB_HUB_PRINTF("get_hub_status returned status %X, change %X\n", + le16_to_cpu(hubsts->wHubStatus), + le16_to_cpu(hubsts->wHubChange)); + USB_HUB_PRINTF("local power source is %s\n", + (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_LOCAL_POWER) ? \ + "lost (inactive)" : "good"); + USB_HUB_PRINTF("%sover-current condition exists\n", + (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_OVERCURRENT) ? \ + "" : "no "); + usb_hub_power_on(hub); + + for (i = 0; i < dev->maxchild; i++) { + struct usb_port_status portsts; + unsigned short portstatus, portchange; + + if (usb_get_port_status(dev, i + 1, &portsts) < 0) { + USB_HUB_PRINTF("get_port_status failed\n"); + continue; + } + + portstatus = le16_to_cpu(portsts.wPortStatus); + portchange = le16_to_cpu(portsts.wPortChange); + USB_HUB_PRINTF("Port %d Status %X Change %X\n", + i + 1, portstatus, portchange); + + if (portchange & USB_PORT_STAT_C_CONNECTION) { + USB_HUB_PRINTF("port %d connection change\n", i + 1); + usb_hub_port_connect_change(dev, i); + } + if (portchange & USB_PORT_STAT_C_ENABLE) { + USB_HUB_PRINTF("port %d enable change, status %x\n", + i + 1, portstatus); + usb_clear_port_feature(dev, i + 1, + USB_PORT_FEAT_C_ENABLE); + + /* EM interference sometimes causes bad shielded USB + * devices to be shutdown by the hub, this hack enables + * them again. Works at least with mouse driver */ + if (!(portstatus & USB_PORT_STAT_ENABLE) && + (portstatus & USB_PORT_STAT_CONNECTION) && + ((dev->children[i]))) { + USB_HUB_PRINTF("already running port %i " \ + "disabled by hub (EMI?), " \ + "re-enabling...\n", i + 1); + usb_hub_port_connect_change(dev, i); + } + } + if (portstatus & USB_PORT_STAT_SUSPEND) { + USB_HUB_PRINTF("port %d suspend change\n", i + 1); + usb_clear_port_feature(dev, i + 1, + USB_PORT_FEAT_SUSPEND); + } + + if (portchange & USB_PORT_STAT_C_OVERCURRENT) { + USB_HUB_PRINTF("port %d over-current change\n", i + 1); + usb_clear_port_feature(dev, i + 1, + USB_PORT_FEAT_C_OVER_CURRENT); + usb_hub_power_on(hub); + } + + if (portchange & USB_PORT_STAT_C_RESET) { + USB_HUB_PRINTF("port %d reset change\n", i + 1); + usb_clear_port_feature(dev, i + 1, + USB_PORT_FEAT_C_RESET); + } + } /* end for i all ports */ + + return 0; +} + +static int usb_hub_probe(struct usb_device *usbdev, + const struct usb_device_id *id) +{ + return usb_hub_configure(usbdev); +} + +static void usb_hub_disconnect(struct usb_device *usbdev) +{ + free(usbdev->hub); +} + +/* Table with supported devices, most specific first. */ +static struct usb_device_id usb_hubage_usb_ids[] = { + { + .match_flags = USB_DEVICE_ID_MATCH_INT_CLASS, + .bInterfaceClass = USB_CLASS_HUB, + }, + { } +}; + + +/*********************************************************************** + * USB Storage driver initialization and registration + ***********************************************************************/ + +static struct usb_driver usb_hubage_driver = { + .name = "usb-hub", + .id_table = usb_hubage_usb_ids, + .probe = usb_hub_probe, + .disconnect = usb_hub_disconnect, +}; + +static int __init usb_hub_init(void) +{ + return usb_driver_register(&usb_hubage_driver); +} +device_initcall(usb_hub_init); diff --git a/drivers/usb/core/hub.h b/drivers/usb/core/hub.h new file mode 100644 index 0000000000..fcf1f0c18a --- /dev/null +++ b/drivers/usb/core/hub.h @@ -0,0 +1,7 @@ +#ifndef __CORE_HUB_H +#define __CORE_HUB_H + +int hub_port_reset(struct usb_device *dev, int port, + unsigned short *portstat); + +#endif /* __CORE_HUB_H */ diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 4f5c0edded..c289bbbc7d 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -52,6 +52,9 @@ #include +#include "usb.h" +#include "hub.h" + /* #define USB_DEBUG */ #ifdef USB_DEBUG @@ -65,10 +68,6 @@ static int dev_index; static int asynch_allowed; -static int usb_hub_probe(struct usb_device *dev, int ifnum); -static int hub_port_reset(struct usb_device *dev, int port, - unsigned short *portstat); - static LIST_HEAD(host_list); LIST_HEAD(usb_device_list); @@ -291,7 +290,7 @@ static int usb_get_descriptor(struct usb_device *dev, unsigned char type, * * Returns 0 for success, != 0 for error. */ -static int usb_new_device(struct usb_device *dev) +int usb_new_device(struct usb_device *dev) { int addr, err; int tmp; @@ -427,13 +426,14 @@ static int usb_new_device(struct usb_device *dev) if (dev->host->hw_dev) dev->dev.parent = dev->host->hw_dev; - register_device(&dev->dev); - - /* now prode if the device is a hub */ - usb_hub_probe(dev, 0); - print_usb_device(dev); + err = register_device(&dev->dev); + if (err) { + printf("Failed to register device: %s\n", strerror(-err)); + return err; + } + dev_add_param_int_ro(&dev->dev, "iManufacturer", dev->descriptor->iManufacturer, "%d"); dev_add_param_int_ro(&dev->dev, "iProduct", @@ -457,7 +457,7 @@ err_out: return err; } -static struct usb_device *usb_alloc_new_device(void) +struct usb_device *usb_alloc_new_device(void) { struct usb_device *usbdev = xzalloc(sizeof (*usbdev)); @@ -911,408 +911,6 @@ int usb_string(struct usb_device *dev, int index, char *buf, size_t size) return err; } -/**************************************************************************** - * HUB "Driver" - * Probes device for being a hub and configurate it - */ - -#undef USB_HUB_DEBUG - -#ifdef USB_HUB_DEBUG -#define USB_HUB_PRINTF(fmt, args...) printf(fmt , ##args) -#else -#define USB_HUB_PRINTF(fmt, args...) -#endif - -static int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size) -{ - return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB, - USB_DT_HUB << 8, 0, data, size, USB_CNTL_TIMEOUT); -} - -static int usb_clear_port_feature(struct usb_device *dev, int port, int feature) -{ - return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, - port, NULL, 0, USB_CNTL_TIMEOUT); -} - -static int usb_set_port_feature(struct usb_device *dev, int port, int feature) -{ - return usb_control_msg(dev, usb_sndctrlpipe(dev, 0), - USB_REQ_SET_FEATURE, USB_RT_PORT, feature, - port, NULL, 0, USB_CNTL_TIMEOUT); -} - -static int usb_get_hub_status(struct usb_device *dev, void *data) -{ - return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB, 0, 0, - data, sizeof(struct usb_hub_status), USB_CNTL_TIMEOUT); -} - -static int usb_get_port_status(struct usb_device *dev, int port, void *data) -{ - return usb_control_msg(dev, usb_rcvctrlpipe(dev, 0), - USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, port, - data, sizeof(struct usb_hub_status), USB_CNTL_TIMEOUT); -} - - -static void usb_hub_power_on(struct usb_hub_device *hub) -{ - int i; - struct usb_device *dev; - - dev = hub->pusb_dev; - /* Enable power to the ports */ - USB_HUB_PRINTF("enabling power on all ports\n"); - for (i = 0; i < dev->maxchild; i++) { - usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER); - USB_HUB_PRINTF("port %d returns %lX\n", i + 1, dev->status); - } - /* power on is encoded in 2ms increments -> times 2 for the actual delay */ - mdelay(hub->desc.bPwrOn2PwrGood*2); -} - -#define MAX_TRIES 5 - -static inline char *portspeed(int portstatus) -{ - if (portstatus & (1 << USB_PORT_FEAT_HIGHSPEED)) - return "480 Mb/s"; - else if (portstatus & (1 << USB_PORT_FEAT_LOWSPEED)) - return "1.5 Mb/s"; - else - return "12 Mb/s"; -} - -static int hub_port_reset(struct usb_device *dev, int port, - unsigned short *portstat) -{ - int tries; - struct usb_port_status portsts; - unsigned short portstatus, portchange; - - 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); - wait_ms(200); - - if (usb_get_port_status(dev, port + 1, &portsts) < 0) { - USB_HUB_PRINTF("get_port_status failed status %lX\n", - dev->status); - return -1; - } - portstatus = le16_to_cpu(portsts.wPortStatus); - portchange = le16_to_cpu(portsts.wPortChange); - - USB_HUB_PRINTF("portstatus %x, change %x, %s\n", - portstatus, portchange, - portspeed(portstatus)); - - USB_HUB_PRINTF("STAT_C_CONNECTION = %d STAT_CONNECTION = %d" \ - " USB_PORT_STAT_ENABLE %d\n", - (portchange & USB_PORT_STAT_C_CONNECTION) ? 1 : 0, - (portstatus & USB_PORT_STAT_CONNECTION) ? 1 : 0, - (portstatus & USB_PORT_STAT_ENABLE) ? 1 : 0); - - if ((portchange & USB_PORT_STAT_C_CONNECTION) || - !(portstatus & USB_PORT_STAT_CONNECTION)) - return -1; - - if (portstatus & USB_PORT_STAT_ENABLE) - break; - - wait_ms(200); - } - - 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 -1; - } - - usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_C_RESET); - *portstat = portstatus; - return 0; -} - - -static 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 = le16_to_cpu(portsts.wPortStatus); - portchange = le16_to_cpu(portsts.wPortChange); - USB_HUB_PRINTF("portstatus %x, change %x, %s\n", - portstatus, portchange, portspeed(portstatus)); - - /* 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 */ - usb = usb_alloc_new_device(); - usb->host = dev->host; - - if (portstatus & USB_PORT_STAT_HIGH_SPEED) - usb->speed = USB_SPEED_HIGH; - else if (portstatus & USB_PORT_STAT_LOW_SPEED) - usb->speed = USB_SPEED_LOW; - else - usb->speed = USB_SPEED_FULL; - - dev->children[port] = usb; - usb->parent = dev; - usb->portnr = port + 1; - - /* Run it through the hoops (find a driver, etc) */ - if (usb_new_device(usb)) { - /* Woops, disable the port */ - USB_HUB_PRINTF("hub: disabling port %d\n", port + 1); - usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_ENABLE); - } -} - - -static int usb_hub_configure(struct usb_device *dev) -{ - unsigned char buffer[USB_BUFSIZ], *bitmap; - struct usb_hub_descriptor *descriptor; - struct usb_hub_status *hubsts; - int i; - struct usb_hub_device *hub; - - hub = xzalloc(sizeof (*hub)); - dev->hub = hub; - - hub->pusb_dev = dev; - /* Get the the hub descriptor */ - if (usb_get_hub_descriptor(dev, buffer, 4) < 0) { - USB_HUB_PRINTF("%s: failed to get hub " \ - "descriptor, giving up %lX\n", __func__, dev->status); - return -1; - } - descriptor = (struct usb_hub_descriptor *)buffer; - - /* silence compiler warning if USB_BUFSIZ is > 256 [= sizeof(char)] */ - i = descriptor->bLength; - if (i > USB_BUFSIZ) { - USB_HUB_PRINTF("%s: failed to get hub " \ - "descriptor - too long: %d\n", __func__, - descriptor->bLength); - return -1; - } - - if (usb_get_hub_descriptor(dev, buffer, descriptor->bLength) < 0) { - USB_HUB_PRINTF("%s: failed to get hub " \ - "descriptor 2nd giving up %lX\n", __func__, dev->status); - return -1; - } - memcpy((unsigned char *)&hub->desc, buffer, descriptor->bLength); - /* adjust 16bit values */ - hub->desc.wHubCharacteristics = - le16_to_cpu(descriptor->wHubCharacteristics); - /* set the bitmap */ - bitmap = (unsigned char *)&hub->desc.DeviceRemovable[0]; - /* devices not removable by default */ - memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8); - bitmap = (unsigned char *)&hub->desc.PortPowerCtrlMask[0]; - memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8); /* PowerMask = 1B */ - - for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++) - hub->desc.DeviceRemovable[i] = descriptor->DeviceRemovable[i]; - - for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++) - hub->desc.DeviceRemovable[i] = descriptor->PortPowerCtrlMask[i]; - - dev->maxchild = descriptor->bNbrPorts; - USB_HUB_PRINTF("%d ports detected\n", dev->maxchild); - - switch (hub->desc.wHubCharacteristics & HUB_CHAR_LPSM) { - case 0x00: - USB_HUB_PRINTF("ganged power switching\n"); - break; - case 0x01: - USB_HUB_PRINTF("individual port power switching\n"); - break; - case 0x02: - case 0x03: - USB_HUB_PRINTF("unknown reserved power switching mode\n"); - break; - } - - if (hub->desc.wHubCharacteristics & HUB_CHAR_COMPOUND) - USB_HUB_PRINTF("part of a compound device\n"); - else - USB_HUB_PRINTF("standalone hub\n"); - - switch (hub->desc.wHubCharacteristics & HUB_CHAR_OCPM) { - case 0x00: - USB_HUB_PRINTF("global over-current protection\n"); - break; - case 0x08: - USB_HUB_PRINTF("individual port over-current protection\n"); - break; - case 0x10: - case 0x18: - USB_HUB_PRINTF("no over-current protection\n"); - break; - } - - USB_HUB_PRINTF("power on to power good time: %dms\n", - descriptor->bPwrOn2PwrGood * 2); - USB_HUB_PRINTF("hub controller current requirement: %dmA\n", - descriptor->bHubContrCurrent); - - for (i = 0; i < dev->maxchild; i++) - USB_HUB_PRINTF("port %d is%s removable\n", i + 1, - hub->desc.DeviceRemovable[(i + 1) / 8] & \ - (1 << ((i + 1) % 8)) ? " not" : ""); - - if (sizeof(struct usb_hub_status) > USB_BUFSIZ) { - USB_HUB_PRINTF("%s: failed to get Status - " \ - "too long: %d\n", __func__, descriptor->bLength); - return -1; - } - - if (usb_get_hub_status(dev, buffer) < 0) { - USB_HUB_PRINTF("%s: failed to get Status %lX\n", __func__, - dev->status); - return -1; - } - - hubsts = (struct usb_hub_status *)buffer; - USB_HUB_PRINTF("get_hub_status returned status %X, change %X\n", - le16_to_cpu(hubsts->wHubStatus), - le16_to_cpu(hubsts->wHubChange)); - USB_HUB_PRINTF("local power source is %s\n", - (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_LOCAL_POWER) ? \ - "lost (inactive)" : "good"); - USB_HUB_PRINTF("%sover-current condition exists\n", - (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_OVERCURRENT) ? \ - "" : "no "); - usb_hub_power_on(hub); - - for (i = 0; i < dev->maxchild; i++) { - struct usb_port_status portsts; - unsigned short portstatus, portchange; - - if (usb_get_port_status(dev, i + 1, &portsts) < 0) { - USB_HUB_PRINTF("get_port_status failed\n"); - continue; - } - - portstatus = le16_to_cpu(portsts.wPortStatus); - portchange = le16_to_cpu(portsts.wPortChange); - USB_HUB_PRINTF("Port %d Status %X Change %X\n", - i + 1, portstatus, portchange); - - if (portchange & USB_PORT_STAT_C_CONNECTION) { - USB_HUB_PRINTF("port %d connection change\n", i + 1); - usb_hub_port_connect_change(dev, i); - } - if (portchange & USB_PORT_STAT_C_ENABLE) { - USB_HUB_PRINTF("port %d enable change, status %x\n", - i + 1, portstatus); - usb_clear_port_feature(dev, i + 1, - USB_PORT_FEAT_C_ENABLE); - - /* EM interference sometimes causes bad shielded USB - * devices to be shutdown by the hub, this hack enables - * them again. Works at least with mouse driver */ - if (!(portstatus & USB_PORT_STAT_ENABLE) && - (portstatus & USB_PORT_STAT_CONNECTION) && - ((dev->children[i]))) { - USB_HUB_PRINTF("already running port %i " \ - "disabled by hub (EMI?), " \ - "re-enabling...\n", i + 1); - usb_hub_port_connect_change(dev, i); - } - } - if (portstatus & USB_PORT_STAT_SUSPEND) { - USB_HUB_PRINTF("port %d suspend change\n", i + 1); - usb_clear_port_feature(dev, i + 1, - USB_PORT_FEAT_SUSPEND); - } - - if (portchange & USB_PORT_STAT_C_OVERCURRENT) { - USB_HUB_PRINTF("port %d over-current change\n", i + 1); - usb_clear_port_feature(dev, i + 1, - USB_PORT_FEAT_C_OVER_CURRENT); - usb_hub_power_on(hub); - } - - if (portchange & USB_PORT_STAT_C_RESET) { - USB_HUB_PRINTF("port %d reset change\n", i + 1); - usb_clear_port_feature(dev, i + 1, - USB_PORT_FEAT_C_RESET); - } - } /* end for i all ports */ - - return 0; -} - -static int usb_hub_probe(struct usb_device *dev, int ifnum) -{ - struct usb_interface *iface; - struct usb_endpoint_descriptor *ep; - int ret; - - iface = &dev->config.interface[ifnum]; - /* Is it a hub? */ - if (iface->desc.bInterfaceClass != USB_CLASS_HUB) - return 0; - /* Some hubs have a subclass of 1, which AFAICT according to the */ - /* specs is not defined, but it works */ - if ((iface->desc.bInterfaceSubClass != 0) && - (iface->desc.bInterfaceSubClass != 1)) - return 0; - /* Multiple endpoints? What kind of mutant ninja-hub is this? */ - if (iface->desc.bNumEndpoints != 1) - return 0; - ep = &iface->ep_desc[0]; - /* Output endpoint? Curiousier and curiousier.. */ - if (!(ep->bEndpointAddress & USB_DIR_IN)) - return 0; - /* If it's not an interrupt endpoint, we'd better punt! */ - if ((ep->bmAttributes & 3) != 3) - return 0; - /* We found a hub */ - USB_HUB_PRINTF("USB hub found\n"); - ret = usb_hub_configure(dev); - return ret; -} - int usb_driver_register(struct usb_driver *drv) { drv->driver.name = drv->name; diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h new file mode 100644 index 0000000000..3e79407d78 --- /dev/null +++ b/drivers/usb/core/usb.h @@ -0,0 +1,7 @@ +#ifndef __CORE_USB_H +#define __CORE_USB_H + +struct usb_device *usb_alloc_new_device(void); +int usb_new_device(struct usb_device *dev); + +#endif /* __CORE_USB_H */ -- cgit v1.2.3 From 2b21f4c822c0e04618c4b46e295dbabbfe6e0602 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 10 Jul 2014 13:55:20 +0200 Subject: USB: host: fixup USB device hierarchy Make all USB devices children to the hub device they are attached to. Signed-off-by: Sascha Hauer --- drivers/usb/core/hub.c | 1 + drivers/usb/core/usb.c | 3 +-- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 0f43955862..26c2ca05ab 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -196,6 +196,7 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port) /* Allocate a new device struct for it */ usb = usb_alloc_new_device(); + usb->dev.parent = &dev->dev; usb->host = dev->host; if (portstatus & USB_PORT_STAT_HIGH_SPEED) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index c289bbbc7d..7c69e102ad 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -424,8 +424,6 @@ int usb_new_device(struct usb_device *dev) dev->dev.id = DEVICE_ID_SINGLE; - if (dev->host->hw_dev) - dev->dev.parent = dev->host->hw_dev; print_usb_device(dev); err = register_device(&dev->dev); @@ -500,6 +498,7 @@ int usb_host_detect(struct usb_host *host, int force) return ret; dev = usb_alloc_new_device(); + dev->dev.parent = host->hw_dev; dev->host = host; usb_new_device(dev); -- cgit v1.2.3 From a7a3d9e1ae9283ee7c1aaba9a6b0ca80ebf77801 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 10 Jul 2014 15:03:22 +0200 Subject: USB: host: hub: Use dev_dbg Now that a hub is a driver use dev_dbg rather than plain printf. Signed-off-by: Sascha Hauer --- drivers/usb/core/hub.c | 88 +++++++++++++++++++++++--------------------------- 1 file changed, 40 insertions(+), 48 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 26c2ca05ab..6d5e3b97f8 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -28,14 +28,6 @@ #include "usb.h" #include "hub.h" -#undef USB_HUB_DEBUG - -#ifdef USB_HUB_DEBUG -#define USB_HUB_PRINTF(fmt, args...) printf(fmt , ##args) -#else -#define USB_HUB_PRINTF(fmt, args...) -#endif - #define USB_BUFSIZ 512 static int usb_get_hub_descriptor(struct usb_device *dev, void *data, int size) @@ -81,10 +73,10 @@ static void usb_hub_power_on(struct usb_hub_device *hub) dev = hub->pusb_dev; /* Enable power to the ports */ - USB_HUB_PRINTF("enabling power on all ports\n"); + dev_dbg(&dev->dev, "enabling power on all ports\n"); for (i = 0; i < dev->maxchild; i++) { usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER); - USB_HUB_PRINTF("port %d returns %lX\n", i + 1, dev->status); + dev_dbg(&dev->dev, "port %d returns %lX\n", i + 1, dev->status); } /* power on is encoded in 2ms increments -> times 2 for the actual delay */ mdelay(hub->desc.bPwrOn2PwrGood*2); @@ -109,25 +101,25 @@ int hub_port_reset(struct usb_device *dev, int port, struct usb_port_status portsts; unsigned short portstatus, portchange; - USB_HUB_PRINTF("hub_port_reset: resetting port %d...\n", port); + dev_dbg(&dev->dev, "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); wait_ms(200); if (usb_get_port_status(dev, port + 1, &portsts) < 0) { - USB_HUB_PRINTF("get_port_status failed status %lX\n", + dev_dbg(&dev->dev, "get_port_status failed status %lX\n", dev->status); return -1; } portstatus = le16_to_cpu(portsts.wPortStatus); portchange = le16_to_cpu(portsts.wPortChange); - USB_HUB_PRINTF("portstatus %x, change %x, %s\n", + dev_dbg(&dev->dev, "portstatus %x, change %x, %s\n", portstatus, portchange, portspeed(portstatus)); - USB_HUB_PRINTF("STAT_C_CONNECTION = %d STAT_CONNECTION = %d" \ + dev_dbg(&dev->dev, "STAT_C_CONNECTION = %d STAT_CONNECTION = %d" \ " USB_PORT_STAT_ENABLE %d\n", (portchange & USB_PORT_STAT_C_CONNECTION) ? 1 : 0, (portstatus & USB_PORT_STAT_CONNECTION) ? 1 : 0, @@ -144,9 +136,9 @@ int hub_port_reset(struct usb_device *dev, int port, } if (tries == MAX_TRIES) { - USB_HUB_PRINTF("Cannot enable port %i after %i retries, " \ + dev_dbg(&dev->dev, "Cannot enable port %i after %i retries, " \ "disabling port.\n", port + 1, MAX_TRIES); - USB_HUB_PRINTF("Maybe the USB cable is bad?\n"); + dev_dbg(&dev->dev, "Maybe the USB cable is bad?\n"); return -1; } @@ -164,13 +156,13 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port) /* Check status */ if (usb_get_port_status(dev, port + 1, &portsts) < 0) { - USB_HUB_PRINTF("get_port_status failed\n"); + dev_dbg(&dev->dev, "get_port_status failed\n"); return; } portstatus = le16_to_cpu(portsts.wPortStatus); portchange = le16_to_cpu(portsts.wPortChange); - USB_HUB_PRINTF("portstatus %x, change %x, %s\n", + dev_dbg(&dev->dev, "portstatus %x, change %x, %s\n", portstatus, portchange, portspeed(portstatus)); /* Clear the connection change status */ @@ -179,7 +171,7 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port) /* 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"); + dev_dbg(&dev->dev, "usb_disconnect(&hub->children[port]);\n"); /* Return now if nothing is connected */ if (!(portstatus & USB_PORT_STAT_CONNECTION)) return; @@ -213,7 +205,7 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port) /* Run it through the hoops (find a driver, etc) */ if (usb_new_device(usb)) { /* Woops, disable the port */ - USB_HUB_PRINTF("hub: disabling port %d\n", port + 1); + dev_dbg(&dev->dev, "hub: disabling port %d\n", port + 1); usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_ENABLE); } } @@ -232,7 +224,7 @@ static int usb_hub_configure(struct usb_device *dev) hub->pusb_dev = dev; /* Get the the hub descriptor */ if (usb_get_hub_descriptor(dev, buffer, 4) < 0) { - USB_HUB_PRINTF("%s: failed to get hub " \ + dev_dbg(&dev->dev, "%s: failed to get hub " \ "descriptor, giving up %lX\n", __func__, dev->status); return -1; } @@ -241,14 +233,14 @@ static int usb_hub_configure(struct usb_device *dev) /* silence compiler warning if USB_BUFSIZ is > 256 [= sizeof(char)] */ i = descriptor->bLength; if (i > USB_BUFSIZ) { - USB_HUB_PRINTF("%s: failed to get hub " \ + dev_dbg(&dev->dev, "%s: failed to get hub " \ "descriptor - too long: %d\n", __func__, descriptor->bLength); return -1; } if (usb_get_hub_descriptor(dev, buffer, descriptor->bLength) < 0) { - USB_HUB_PRINTF("%s: failed to get hub " \ + dev_dbg(&dev->dev, "%s: failed to get hub " \ "descriptor 2nd giving up %lX\n", __func__, dev->status); return -1; } @@ -270,69 +262,69 @@ static int usb_hub_configure(struct usb_device *dev) hub->desc.DeviceRemovable[i] = descriptor->PortPowerCtrlMask[i]; dev->maxchild = descriptor->bNbrPorts; - USB_HUB_PRINTF("%d ports detected\n", dev->maxchild); + dev_dbg(&dev->dev, "%d ports detected\n", dev->maxchild); switch (hub->desc.wHubCharacteristics & HUB_CHAR_LPSM) { case 0x00: - USB_HUB_PRINTF("ganged power switching\n"); + dev_dbg(&dev->dev, "ganged power switching\n"); break; case 0x01: - USB_HUB_PRINTF("individual port power switching\n"); + dev_dbg(&dev->dev, "individual port power switching\n"); break; case 0x02: case 0x03: - USB_HUB_PRINTF("unknown reserved power switching mode\n"); + dev_dbg(&dev->dev, "unknown reserved power switching mode\n"); break; } if (hub->desc.wHubCharacteristics & HUB_CHAR_COMPOUND) - USB_HUB_PRINTF("part of a compound device\n"); + dev_dbg(&dev->dev, "part of a compound device\n"); else - USB_HUB_PRINTF("standalone hub\n"); + dev_dbg(&dev->dev, "standalone hub\n"); switch (hub->desc.wHubCharacteristics & HUB_CHAR_OCPM) { case 0x00: - USB_HUB_PRINTF("global over-current protection\n"); + dev_dbg(&dev->dev, "global over-current protection\n"); break; case 0x08: - USB_HUB_PRINTF("individual port over-current protection\n"); + dev_dbg(&dev->dev, "individual port over-current protection\n"); break; case 0x10: case 0x18: - USB_HUB_PRINTF("no over-current protection\n"); + dev_dbg(&dev->dev, "no over-current protection\n"); break; } - USB_HUB_PRINTF("power on to power good time: %dms\n", + dev_dbg(&dev->dev, "power on to power good time: %dms\n", descriptor->bPwrOn2PwrGood * 2); - USB_HUB_PRINTF("hub controller current requirement: %dmA\n", + dev_dbg(&dev->dev, "hub controller current requirement: %dmA\n", descriptor->bHubContrCurrent); for (i = 0; i < dev->maxchild; i++) - USB_HUB_PRINTF("port %d is%s removable\n", i + 1, + dev_dbg(&dev->dev, "port %d is%s removable\n", i + 1, hub->desc.DeviceRemovable[(i + 1) / 8] & \ (1 << ((i + 1) % 8)) ? " not" : ""); if (sizeof(struct usb_hub_status) > USB_BUFSIZ) { - USB_HUB_PRINTF("%s: failed to get Status - " \ + dev_dbg(&dev->dev, "%s: failed to get Status - " \ "too long: %d\n", __func__, descriptor->bLength); return -1; } if (usb_get_hub_status(dev, buffer) < 0) { - USB_HUB_PRINTF("%s: failed to get Status %lX\n", __func__, + dev_dbg(&dev->dev, "%s: failed to get Status %lX\n", __func__, dev->status); return -1; } hubsts = (struct usb_hub_status *)buffer; - USB_HUB_PRINTF("get_hub_status returned status %X, change %X\n", + dev_dbg(&dev->dev, "get_hub_status returned status %X, change %X\n", le16_to_cpu(hubsts->wHubStatus), le16_to_cpu(hubsts->wHubChange)); - USB_HUB_PRINTF("local power source is %s\n", + dev_dbg(&dev->dev, "local power source is %s\n", (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_LOCAL_POWER) ? \ "lost (inactive)" : "good"); - USB_HUB_PRINTF("%sover-current condition exists\n", + dev_dbg(&dev->dev, "%sover-current condition exists\n", (le16_to_cpu(hubsts->wHubStatus) & HUB_STATUS_OVERCURRENT) ? \ "" : "no "); usb_hub_power_on(hub); @@ -342,21 +334,21 @@ static int usb_hub_configure(struct usb_device *dev) unsigned short portstatus, portchange; if (usb_get_port_status(dev, i + 1, &portsts) < 0) { - USB_HUB_PRINTF("get_port_status failed\n"); + dev_dbg(&dev->dev, "get_port_status failed\n"); continue; } portstatus = le16_to_cpu(portsts.wPortStatus); portchange = le16_to_cpu(portsts.wPortChange); - USB_HUB_PRINTF("Port %d Status %X Change %X\n", + dev_dbg(&dev->dev, "Port %d Status %X Change %X\n", i + 1, portstatus, portchange); if (portchange & USB_PORT_STAT_C_CONNECTION) { - USB_HUB_PRINTF("port %d connection change\n", i + 1); + dev_dbg(&dev->dev, "port %d connection change\n", i + 1); usb_hub_port_connect_change(dev, i); } if (portchange & USB_PORT_STAT_C_ENABLE) { - USB_HUB_PRINTF("port %d enable change, status %x\n", + dev_dbg(&dev->dev, "port %d enable change, status %x\n", i + 1, portstatus); usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_ENABLE); @@ -367,27 +359,27 @@ static int usb_hub_configure(struct usb_device *dev) if (!(portstatus & USB_PORT_STAT_ENABLE) && (portstatus & USB_PORT_STAT_CONNECTION) && ((dev->children[i]))) { - USB_HUB_PRINTF("already running port %i " \ + dev_dbg(&dev->dev, "already running port %i " \ "disabled by hub (EMI?), " \ "re-enabling...\n", i + 1); usb_hub_port_connect_change(dev, i); } } if (portstatus & USB_PORT_STAT_SUSPEND) { - USB_HUB_PRINTF("port %d suspend change\n", i + 1); + dev_dbg(&dev->dev, "port %d suspend change\n", i + 1); usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_SUSPEND); } if (portchange & USB_PORT_STAT_C_OVERCURRENT) { - USB_HUB_PRINTF("port %d over-current change\n", i + 1); + dev_dbg(&dev->dev, "port %d over-current change\n", i + 1); usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_OVER_CURRENT); usb_hub_power_on(hub); } if (portchange & USB_PORT_STAT_C_RESET) { - USB_HUB_PRINTF("port %d reset change\n", i + 1); + dev_dbg(&dev->dev, "port %d reset change\n", i + 1); usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_C_RESET); } -- cgit v1.2.3 From 87b9bea20e922a46f661d69f7b5ae900159914f1 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 10 Jul 2014 15:04:35 +0200 Subject: USB: host: hub: Use usb_hub_power_on from U-Boot U-Boots power good delay function is more conservative than ours. Use it to be on the safe side. The U-Boot guys have discussed a lot about it, let's hope they got it right. Signed-off-by: Sascha Hauer --- drivers/usb/core/hub.c | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 6d5e3b97f8..9c87037888 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -70,16 +70,33 @@ static void usb_hub_power_on(struct usb_hub_device *hub) { int i; struct usb_device *dev; + unsigned pgood_delay = hub->desc.bPwrOn2PwrGood * 2; dev = hub->pusb_dev; + + /* + * Enable power to the ports: + * Here we Power-cycle the ports: aka, + * turning them off and turning on again. + */ + for (i = 0; i < dev->maxchild; i++) { + usb_clear_port_feature(dev, i + 1, USB_PORT_FEAT_POWER); + dev_dbg(&dev->dev, "port %d returns %lX\n", i + 1, dev->status); + } + + /* Wait at least 2 * bPwrOn2PwrGood for PP to change */ + mdelay(pgood_delay); + /* Enable power to the ports */ dev_dbg(&dev->dev, "enabling power on all ports\n"); + for (i = 0; i < dev->maxchild; i++) { usb_set_port_feature(dev, i + 1, USB_PORT_FEAT_POWER); dev_dbg(&dev->dev, "port %d returns %lX\n", i + 1, dev->status); } + /* power on is encoded in 2ms increments -> times 2 for the actual delay */ - mdelay(hub->desc.bPwrOn2PwrGood*2); + mdelay(pgood_delay + 1000); } #define MAX_TRIES 5 -- cgit v1.2.3 From 9d5b3cbe511ec68c92b38307603490ef303ec4f8 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 10 Jul 2014 15:12:49 +0200 Subject: USB: host: factor out port configuration to separate function Signed-off-by: Sascha Hauer --- drivers/usb/core/hub.c | 125 +++++++++++++++++++++++++++++-------------------- 1 file changed, 74 insertions(+), 51 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 9c87037888..689a79c774 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -227,6 +227,65 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port) } } +static int usb_hub_configure_port(struct usb_device *dev, int port) +{ + struct usb_port_status portsts; + unsigned short portstatus, portchange; + + if (usb_get_port_status(dev, port + 1, &portsts) < 0) { + dev_dbg(&dev->dev, "get_port_status failed\n"); + return -EIO; + } + + portstatus = le16_to_cpu(portsts.wPortStatus); + portchange = le16_to_cpu(portsts.wPortChange); + dev_dbg(&dev->dev, "Port %d Status %X Change %X\n", + port + 1, portstatus, portchange); + + if (portchange & USB_PORT_STAT_C_CONNECTION) { + dev_dbg(&dev->dev, "port %d connection change\n", port + 1); + usb_hub_port_connect_change(dev, port); + } + if (portchange & USB_PORT_STAT_C_ENABLE) { + dev_dbg(&dev->dev, "port %d enable change, status %x\n", + port + 1, portstatus); + usb_clear_port_feature(dev, port + 1, + USB_PORT_FEAT_C_ENABLE); + + /* EM interference sometimes causes bad shielded USB + * devices to be shutdown by the hub, this hack enables + * them again. Works at least with mouse driver */ + if (!(portstatus & USB_PORT_STAT_ENABLE) && + (portstatus & USB_PORT_STAT_CONNECTION) && + ((dev->children[port]))) { + dev_dbg(&dev->dev, "already running port %i " \ + "disabled by hub (EMI?), " \ + "re-enabling...\n", port + 1); + usb_hub_port_connect_change(dev, port); + } + } + if (portstatus & USB_PORT_STAT_SUSPEND) { + dev_dbg(&dev->dev, "port %d suspend change\n", port + 1); + usb_clear_port_feature(dev, port + 1, + USB_PORT_FEAT_SUSPEND); + } + + if (portchange & USB_PORT_STAT_C_OVERCURRENT) { + dev_dbg(&dev->dev, "port %d over-current change\n", port + 1); + usb_clear_port_feature(dev, port + 1, + USB_PORT_FEAT_C_OVER_CURRENT); + usb_hub_power_on(dev->hub); + } + + if (portchange & USB_PORT_STAT_C_RESET) { + dev_dbg(&dev->dev, "port %d reset change\n", port + 1); + usb_clear_port_feature(dev, port + 1, + USB_PORT_FEAT_C_RESET); + } + + return 0; +} + static int usb_hub_configure(struct usb_device *dev) { unsigned char buffer[USB_BUFSIZ], *bitmap; @@ -346,61 +405,23 @@ static int usb_hub_configure(struct usb_device *dev) "" : "no "); usb_hub_power_on(hub); - for (i = 0; i < dev->maxchild; i++) { - struct usb_port_status portsts; - unsigned short portstatus, portchange; - - if (usb_get_port_status(dev, i + 1, &portsts) < 0) { - dev_dbg(&dev->dev, "get_port_status failed\n"); - continue; - } + for (i = 0; i < dev->maxchild; i++) + usb_hub_configure_port(dev, i); - portstatus = le16_to_cpu(portsts.wPortStatus); - portchange = le16_to_cpu(portsts.wPortChange); - dev_dbg(&dev->dev, "Port %d Status %X Change %X\n", - i + 1, portstatus, portchange); + return 0; +} - if (portchange & USB_PORT_STAT_C_CONNECTION) { - dev_dbg(&dev->dev, "port %d connection change\n", i + 1); - usb_hub_port_connect_change(dev, i); - } - if (portchange & USB_PORT_STAT_C_ENABLE) { - dev_dbg(&dev->dev, "port %d enable change, status %x\n", - i + 1, portstatus); - usb_clear_port_feature(dev, i + 1, - USB_PORT_FEAT_C_ENABLE); - - /* EM interference sometimes causes bad shielded USB - * devices to be shutdown by the hub, this hack enables - * them again. Works at least with mouse driver */ - if (!(portstatus & USB_PORT_STAT_ENABLE) && - (portstatus & USB_PORT_STAT_CONNECTION) && - ((dev->children[i]))) { - dev_dbg(&dev->dev, "already running port %i " \ - "disabled by hub (EMI?), " \ - "re-enabling...\n", i + 1); - usb_hub_port_connect_change(dev, i); - } - } - if (portstatus & USB_PORT_STAT_SUSPEND) { - dev_dbg(&dev->dev, "port %d suspend change\n", i + 1); - usb_clear_port_feature(dev, i + 1, - USB_PORT_FEAT_SUSPEND); - } +static int usb_hub_detect(struct device_d *dev) +{ + struct usb_device *usbdev = container_of(dev, struct usb_device, dev); + int i; - if (portchange & USB_PORT_STAT_C_OVERCURRENT) { - dev_dbg(&dev->dev, "port %d over-current change\n", i + 1); - usb_clear_port_feature(dev, i + 1, - USB_PORT_FEAT_C_OVER_CURRENT); - usb_hub_power_on(hub); - } + usb_hub_configure(usbdev); - if (portchange & USB_PORT_STAT_C_RESET) { - dev_dbg(&dev->dev, "port %d reset change\n", i + 1); - usb_clear_port_feature(dev, i + 1, - USB_PORT_FEAT_C_RESET); - } - } /* end for i all ports */ + for (i = 0; i < usbdev->maxchild; i++) { + if (usbdev->children[i]) + device_detect(&usbdev->children[i]->dev); + } return 0; } @@ -408,6 +429,8 @@ static int usb_hub_configure(struct usb_device *dev) static int usb_hub_probe(struct usb_device *usbdev, const struct usb_device_id *id) { + usbdev->dev.detect = usb_hub_detect; + return usb_hub_configure(usbdev); } -- cgit v1.2.3 From c7d3ec47f9699bb210799593ebb277168259b8c5 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 10 Jul 2014 15:17:50 +0200 Subject: USB: host: hub: only configure hub once We had a single function which configures the hub and scans the ports. Split this up and configure the hub only once and scan the ports during detect() time. This allows to plug in additional devices into a hub while continuing to use the already existing devices. Signed-off-by: Sascha Hauer --- drivers/usb/core/hub.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 689a79c774..39cadb55ff 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -405,6 +405,13 @@ static int usb_hub_configure(struct usb_device *dev) "" : "no "); usb_hub_power_on(hub); + return 0; +} + +static int usb_hub_configure_ports(struct usb_device *dev) +{ + int i; + for (i = 0; i < dev->maxchild; i++) usb_hub_configure_port(dev, i); @@ -416,7 +423,7 @@ static int usb_hub_detect(struct device_d *dev) struct usb_device *usbdev = container_of(dev, struct usb_device, dev); int i; - usb_hub_configure(usbdev); + usb_hub_configure_ports(usbdev); for (i = 0; i < usbdev->maxchild; i++) { if (usbdev->children[i]) -- cgit v1.2.3 From 4bff0a0553b739427834ef7669621f08ee84dec0 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 15 Jul 2014 15:19:53 +0200 Subject: USB: host: implement usb_remove_device Signed-off-by: Sascha Hauer --- drivers/usb/core/hub.c | 1 + drivers/usb/core/usb.c | 19 +++++++++++++++++++ drivers/usb/core/usb.h | 1 + 3 files changed, 21 insertions(+) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 39cadb55ff..144442a4ca 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -189,6 +189,7 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port) if (((!(portstatus & USB_PORT_STAT_CONNECTION)) && (!(portstatus & USB_PORT_STAT_ENABLE))) || (dev->children[port])) { dev_dbg(&dev->dev, "usb_disconnect(&hub->children[port]);\n"); + usb_remove_device(dev->children[port]); /* Return now if nothing is connected */ if (!(portstatus & USB_PORT_STAT_CONNECTION)) return; diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 7c69e102ad..faf509ec9d 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -455,6 +455,25 @@ err_out: return err; } +void usb_remove_device(struct usb_device *usbdev) +{ + int i, ret; + + for (i = 0; i < usbdev->maxchild; i++) { + if (usbdev->children[i]) + usb_remove_device(usbdev->children[i]); + } + + dev_info(&usbdev->dev, "removing\n"); + + ret = unregister_device(&usbdev->dev); + if (ret) + dev_err(&usbdev->dev, "failed to unregister\n"); + + usbdev->parent->children[usbdev->portnr - 1] = NULL; + free(usbdev); +} + struct usb_device *usb_alloc_new_device(void) { struct usb_device *usbdev = xzalloc(sizeof (*usbdev)); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index 3e79407d78..a0c05506dd 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -3,5 +3,6 @@ struct usb_device *usb_alloc_new_device(void); int usb_new_device(struct usb_device *dev); +void usb_remove_device(struct usb_device *dev); #endif /* __CORE_USB_H */ -- cgit v1.2.3 From 99b79e4a456b6bfee07b385cca722c5da925f28b Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Tue, 15 Jul 2014 15:20:49 +0200 Subject: USB: host: detect port change only once in usb_hub_configure_port Otherwise a device may be registered/unregistered multiple times during scanning the hub ports. Signed-off-by: Sascha Hauer --- drivers/usb/core/hub.c | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 144442a4ca..f90a927eef 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -226,12 +226,14 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port) dev_dbg(&dev->dev, "hub: disabling port %d\n", port + 1); usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_ENABLE); } + device_detect(&usb->dev); } static int usb_hub_configure_port(struct usb_device *dev, int port) { struct usb_port_status portsts; unsigned short portstatus, portchange; + int connect_change = 0; if (usb_get_port_status(dev, port + 1, &portsts) < 0) { dev_dbg(&dev->dev, "get_port_status failed\n"); @@ -245,7 +247,7 @@ static int usb_hub_configure_port(struct usb_device *dev, int port) if (portchange & USB_PORT_STAT_C_CONNECTION) { dev_dbg(&dev->dev, "port %d connection change\n", port + 1); - usb_hub_port_connect_change(dev, port); + connect_change = 1; } if (portchange & USB_PORT_STAT_C_ENABLE) { dev_dbg(&dev->dev, "port %d enable change, status %x\n", @@ -262,9 +264,13 @@ static int usb_hub_configure_port(struct usb_device *dev, int port) dev_dbg(&dev->dev, "already running port %i " \ "disabled by hub (EMI?), " \ "re-enabling...\n", port + 1); - usb_hub_port_connect_change(dev, port); + connect_change = 1; } } + + if (connect_change) + usb_hub_port_connect_change(dev, port); + if (portstatus & USB_PORT_STAT_SUSPEND) { dev_dbg(&dev->dev, "port %d suspend change\n", port + 1); usb_clear_port_feature(dev, port + 1, -- cgit v1.2.3 From 81a2205228646a9abf3548c197d415ef9167a1c5 Mon Sep 17 00:00:00 2001 From: Sascha Hauer Date: Thu, 10 Jul 2014 15:10:09 +0200 Subject: USB: host: drop force rescan We can now detect changes in the USB device hierarchy properly, so the 'force' option to the usb command is no longer necessary. We just scan the busses each time the usb command is called. Signed-off-by: Sascha Hauer --- commands/usb.c | 9 +++------ drivers/usb/core/usb.c | 39 ++++++++++++--------------------------- drivers/usb/host/ehci-hcd.c | 2 +- include/usb/usb.h | 6 +++--- 4 files changed, 19 insertions(+), 37 deletions(-) diff --git a/commands/usb.c b/commands/usb.c index c158852ca9..a37d50327f 100644 --- a/commands/usb.c +++ b/commands/usb.c @@ -112,13 +112,10 @@ static void usb_show_devices(bool tree) static int do_usb(int argc, char *argv[]) { int opt; - int force = 0, tree = 0, show = 0; + int tree = 0, show = 0; - while ((opt = getopt(argc, argv, "fts")) > 0) { + while ((opt = getopt(argc, argv, "ts")) > 0) { switch (opt) { - case 'f': - force = 1; - break; case 't': tree = 1; show = 1; @@ -129,7 +126,7 @@ static int do_usb(int argc, char *argv[]) } } - usb_rescan(force); + usb_rescan(); if (show) usb_show_devices(tree); diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index faf509ec9d..fdf9d94a52 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -492,50 +492,35 @@ struct usb_device *usb_alloc_new_device(void) return usbdev; } -int usb_host_detect(struct usb_host *host, int force) +int usb_host_detect(struct usb_host *host) { - struct usb_device *dev, *tmp; int ret; - if (host->scanned && !force) - return -EBUSY; - - list_for_each_entry_safe(dev, tmp, &usb_device_list, list) { - if (dev->host != host) - continue; + if (!host->root_dev) { + ret = host->init(host); + if (ret) + return ret; - list_del(&dev->list); - unregister_device(&dev->dev); - free(dev->hub); - dma_free(dev->setup_packet); - dma_free(dev->descriptor); - free(dev); + host->root_dev = usb_alloc_new_device(); + host->root_dev->dev.parent = host->hw_dev; + host->root_dev->host = host; + usb_new_device(host->root_dev); } - ret = host->init(host); - if (ret) - return ret; - - dev = usb_alloc_new_device(); - dev->dev.parent = host->hw_dev; - dev->host = host; - usb_new_device(dev); - - host->scanned = 1; + device_detect(&host->root_dev->dev); return 0; } -void usb_rescan(int force) +void usb_rescan(void) { struct usb_host *host; int ret; pr_info("USB: scanning bus for devices...\n"); - dev_index = 0; list_for_each_entry(host, &host_list, list) { - ret = usb_host_detect(host, force); + ret = usb_host_detect(host); if (ret) continue; } diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index d30c3aa1d1..9e30deb419 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -871,7 +871,7 @@ static int ehci_detect(struct device_d *dev) { struct ehci_priv *ehci = dev->priv; - return usb_host_detect(&ehci->host, 0); + return usb_host_detect(&ehci->host); } int ehci_register(struct device_d *dev, struct ehci_data *data) diff --git a/include/usb/usb.h b/include/usb/usb.h index 34edbae0a5..41f92c2df0 100644 --- a/include/usb/usb.h +++ b/include/usb/usb.h @@ -150,12 +150,12 @@ struct usb_host { struct device_d *hw_dev; int busnum; - int scanned; + struct usb_device *root_dev; }; int usb_register_host(struct usb_host *); -int usb_host_detect(struct usb_host *host, int force); +int usb_host_detect(struct usb_host *host); int usb_set_protocol(struct usb_device *dev, int ifnum, int protocol); int usb_set_idle(struct usb_device *dev, int ifnum, int duration, @@ -185,7 +185,7 @@ int usb_clear_halt(struct usb_device *dev, int pipe); int usb_string(struct usb_device *dev, int index, char *buf, size_t size); int usb_set_interface(struct usb_device *dev, int interface, int alternate); -void usb_rescan(int force); +void usb_rescan(void); /* big endian -> little endian conversion */ /* some CPUs are already little endian e.g. the ARM920T */ -- cgit v1.2.3 From bfea72da82fe7f324dd4504576dff7b76173dc79 Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Wed, 23 Jul 2014 15:51:42 +0200 Subject: USB: import ch11.h from Linux Linux has an extra include for chapter 11 (Hub Specification) of USB 2.0 spec. We already have a ch9.h, so import the one from Linux. Barebox specific changes will be patched up later. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- include/usb/ch11.h | 277 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 277 insertions(+) create mode 100644 include/usb/ch11.h diff --git a/include/usb/ch11.h b/include/usb/ch11.h new file mode 100644 index 0000000000..331499d597 --- /dev/null +++ b/include/usb/ch11.h @@ -0,0 +1,277 @@ +/* + * This file holds Hub protocol constants and data structures that are + * defined in chapter 11 (Hub Specification) of the USB 2.0 specification. + * + * It is used/shared between the USB core, the HCDs and couple of other USB + * drivers. + */ + +#ifndef __LINUX_CH11_H +#define __LINUX_CH11_H + +#include /* __u8 etc */ + +/* This is arbitrary. + * From USB 2.0 spec Table 11-13, offset 7, a hub can + * have up to 255 ports. The most yet reported is 10. + * + * Current Wireless USB host hardware (Intel i1480 for example) allows + * up to 22 devices to connect. Upcoming hardware might raise that + * limit. Because the arrays need to add a bit for hub status data, we + * use 31, so plus one evens out to four bytes. + */ +#define USB_MAXCHILDREN 31 + +/* + * Hub request types + */ + +#define USB_RT_HUB (USB_TYPE_CLASS | USB_RECIP_DEVICE) +#define USB_RT_PORT (USB_TYPE_CLASS | USB_RECIP_OTHER) + +/* + * Hub class requests + * See USB 2.0 spec Table 11-16 + */ +#define HUB_CLEAR_TT_BUFFER 8 +#define HUB_RESET_TT 9 +#define HUB_GET_TT_STATE 10 +#define HUB_STOP_TT 11 + +/* + * Hub class additional requests defined by USB 3.0 spec + * See USB 3.0 spec Table 10-6 + */ +#define HUB_SET_DEPTH 12 +#define HUB_GET_PORT_ERR_COUNT 13 + +/* + * Hub Class feature numbers + * See USB 2.0 spec Table 11-17 + */ +#define C_HUB_LOCAL_POWER 0 +#define C_HUB_OVER_CURRENT 1 + +/* + * Port feature numbers + * See USB 2.0 spec Table 11-17 + */ +#define USB_PORT_FEAT_CONNECTION 0 +#define USB_PORT_FEAT_ENABLE 1 +#define USB_PORT_FEAT_SUSPEND 2 /* L2 suspend */ +#define USB_PORT_FEAT_OVER_CURRENT 3 +#define USB_PORT_FEAT_RESET 4 +#define USB_PORT_FEAT_L1 5 /* L1 suspend */ +#define USB_PORT_FEAT_POWER 8 +#define USB_PORT_FEAT_LOWSPEED 9 /* Should never be used */ +#define USB_PORT_FEAT_C_CONNECTION 16 +#define USB_PORT_FEAT_C_ENABLE 17 +#define USB_PORT_FEAT_C_SUSPEND 18 +#define USB_PORT_FEAT_C_OVER_CURRENT 19 +#define USB_PORT_FEAT_C_RESET 20 +#define USB_PORT_FEAT_TEST 21 +#define USB_PORT_FEAT_INDICATOR 22 +#define USB_PORT_FEAT_C_PORT_L1 23 + +/* + * Port feature selectors added by USB 3.0 spec. + * See USB 3.0 spec Table 10-7 + */ +#define USB_PORT_FEAT_LINK_STATE 5 +#define USB_PORT_FEAT_U1_TIMEOUT 23 +#define USB_PORT_FEAT_U2_TIMEOUT 24 +#define USB_PORT_FEAT_C_PORT_LINK_STATE 25 +#define USB_PORT_FEAT_C_PORT_CONFIG_ERROR 26 +#define USB_PORT_FEAT_REMOTE_WAKE_MASK 27 +#define USB_PORT_FEAT_BH_PORT_RESET 28 +#define USB_PORT_FEAT_C_BH_PORT_RESET 29 +#define USB_PORT_FEAT_FORCE_LINKPM_ACCEPT 30 + +#define USB_PORT_LPM_TIMEOUT(p) (((p) & 0xff) << 8) + +/* USB 3.0 hub remote wake mask bits, see table 10-14 */ +#define USB_PORT_FEAT_REMOTE_WAKE_CONNECT (1 << 8) +#define USB_PORT_FEAT_REMOTE_WAKE_DISCONNECT (1 << 9) +#define USB_PORT_FEAT_REMOTE_WAKE_OVER_CURRENT (1 << 10) + +/* + * Hub Status and Hub Change results + * See USB 2.0 spec Table 11-19 and Table 11-20 + */ +struct usb_port_status { + __le16 wPortStatus; + __le16 wPortChange; +} __attribute__ ((packed)); + +/* + * wPortStatus bit field + * See USB 2.0 spec Table 11-21 + */ +#define USB_PORT_STAT_CONNECTION 0x0001 +#define USB_PORT_STAT_ENABLE 0x0002 +#define USB_PORT_STAT_SUSPEND 0x0004 +#define USB_PORT_STAT_OVERCURRENT 0x0008 +#define USB_PORT_STAT_RESET 0x0010 +#define USB_PORT_STAT_L1 0x0020 +/* bits 6 to 7 are reserved */ +#define USB_PORT_STAT_POWER 0x0100 +#define USB_PORT_STAT_LOW_SPEED 0x0200 +#define USB_PORT_STAT_HIGH_SPEED 0x0400 +#define USB_PORT_STAT_TEST 0x0800 +#define USB_PORT_STAT_INDICATOR 0x1000 +/* bits 13 to 15 are reserved */ + +/* + * Additions to wPortStatus bit field from USB 3.0 + * See USB 3.0 spec Table 10-10 + */ +#define USB_PORT_STAT_LINK_STATE 0x01e0 +#define USB_SS_PORT_STAT_POWER 0x0200 +#define USB_SS_PORT_STAT_SPEED 0x1c00 +#define USB_PORT_STAT_SPEED_5GBPS 0x0000 +/* Valid only if port is enabled */ +/* Bits that are the same from USB 2.0 */ +#define USB_SS_PORT_STAT_MASK (USB_PORT_STAT_CONNECTION | \ + USB_PORT_STAT_ENABLE | \ + USB_PORT_STAT_OVERCURRENT | \ + USB_PORT_STAT_RESET) + +/* + * Definitions for PORT_LINK_STATE values + * (bits 5-8) in wPortStatus + */ +#define USB_SS_PORT_LS_U0 0x0000 +#define USB_SS_PORT_LS_U1 0x0020 +#define USB_SS_PORT_LS_U2 0x0040 +#define USB_SS_PORT_LS_U3 0x0060 +#define USB_SS_PORT_LS_SS_DISABLED 0x0080 +#define USB_SS_PORT_LS_RX_DETECT 0x00a0 +#define USB_SS_PORT_LS_SS_INACTIVE 0x00c0 +#define USB_SS_PORT_LS_POLLING 0x00e0 +#define USB_SS_PORT_LS_RECOVERY 0x0100 +#define USB_SS_PORT_LS_HOT_RESET 0x0120 +#define USB_SS_PORT_LS_COMP_MOD 0x0140 +#define USB_SS_PORT_LS_LOOPBACK 0x0160 + +/* + * wPortChange bit field + * See USB 2.0 spec Table 11-22 and USB 2.0 LPM ECN Table-4.10 + * Bits 0 to 5 shown, bits 6 to 15 are reserved + */ +#define USB_PORT_STAT_C_CONNECTION 0x0001 +#define USB_PORT_STAT_C_ENABLE 0x0002 +#define USB_PORT_STAT_C_SUSPEND 0x0004 +#define USB_PORT_STAT_C_OVERCURRENT 0x0008 +#define USB_PORT_STAT_C_RESET 0x0010 +#define USB_PORT_STAT_C_L1 0x0020 +/* + * USB 3.0 wPortChange bit fields + * See USB 3.0 spec Table 10-11 + */ +#define USB_PORT_STAT_C_BH_RESET 0x0020 +#define USB_PORT_STAT_C_LINK_STATE 0x0040 +#define USB_PORT_STAT_C_CONFIG_ERROR 0x0080 + +/* + * wHubCharacteristics (masks) + * See USB 2.0 spec Table 11-13, offset 3 + */ +#define HUB_CHAR_LPSM 0x0003 /* Logical Power Switching Mode mask */ +#define HUB_CHAR_COMMON_LPSM 0x0000 /* All ports power control at once */ +#define HUB_CHAR_INDV_PORT_LPSM 0x0001 /* per-port power control */ +#define HUB_CHAR_NO_LPSM 0x0002 /* no power switching */ + +#define HUB_CHAR_COMPOUND 0x0004 /* hub is part of a compound device */ + +#define HUB_CHAR_OCPM 0x0018 /* Over-Current Protection Mode mask */ +#define HUB_CHAR_COMMON_OCPM 0x0000 /* All ports Over-Current reporting */ +#define HUB_CHAR_INDV_PORT_OCPM 0x0008 /* per-port Over-current reporting */ +#define HUB_CHAR_NO_OCPM 0x0010 /* No Over-current Protection support */ + +#define HUB_CHAR_TTTT 0x0060 /* TT Think Time mask */ +#define HUB_CHAR_PORTIND 0x0080 /* per-port indicators (LEDs) */ + +struct usb_hub_status { + __le16 wHubStatus; + __le16 wHubChange; +} __attribute__ ((packed)); + +/* + * Hub Status & Hub Change bit masks + * See USB 2.0 spec Table 11-19 and Table 11-20 + * Bits 0 and 1 for wHubStatus and wHubChange + * Bits 2 to 15 are reserved for both + */ +#define HUB_STATUS_LOCAL_POWER 0x0001 +#define HUB_STATUS_OVERCURRENT 0x0002 +#define HUB_CHANGE_LOCAL_POWER 0x0001 +#define HUB_CHANGE_OVERCURRENT 0x0002 + + +/* + * Hub descriptor + * See USB 2.0 spec Table 11-13 + */ + +#define USB_DT_HUB (USB_TYPE_CLASS | 0x09) +#define USB_DT_SS_HUB (USB_TYPE_CLASS | 0x0a) +#define USB_DT_HUB_NONVAR_SIZE 7 +#define USB_DT_SS_HUB_SIZE 12 + +/* + * Hub Device descriptor + * USB Hub class device protocols + */ + +#define USB_HUB_PR_FS 0 /* Full speed hub */ +#define USB_HUB_PR_HS_NO_TT 0 /* Hi-speed hub without TT */ +#define USB_HUB_PR_HS_SINGLE_TT 1 /* Hi-speed hub with single TT */ +#define USB_HUB_PR_HS_MULTI_TT 2 /* Hi-speed hub with multiple TT */ +#define USB_HUB_PR_SS 3 /* Super speed hub */ + +struct usb_hub_descriptor { + __u8 bDescLength; + __u8 bDescriptorType; + __u8 bNbrPorts; + __le16 wHubCharacteristics; + __u8 bPwrOn2PwrGood; + __u8 bHubContrCurrent; + + /* 2.0 and 3.0 hubs differ here */ + union { + struct { + /* add 1 bit for hub status change; round to bytes */ + __u8 DeviceRemovable[(USB_MAXCHILDREN + 1 + 7) / 8]; + __u8 PortPwrCtrlMask[(USB_MAXCHILDREN + 1 + 7) / 8]; + } __attribute__ ((packed)) hs; + + struct { + __u8 bHubHdrDecLat; + __le16 wHubDelay; + __le16 DeviceRemovable; + } __attribute__ ((packed)) ss; + } u; +} __attribute__ ((packed)); + +/* port indicator status selectors, tables 11-7 and 11-25 */ +#define HUB_LED_AUTO 0 +#define HUB_LED_AMBER 1 +#define HUB_LED_GREEN 2 +#define HUB_LED_OFF 3 + +enum hub_led_mode { + INDICATOR_AUTO = 0, + INDICATOR_CYCLE, + /* software blinks for attention: software, hardware, reserved */ + INDICATOR_GREEN_BLINK, INDICATOR_GREEN_BLINK_OFF, + INDICATOR_AMBER_BLINK, INDICATOR_AMBER_BLINK_OFF, + INDICATOR_ALT_BLINK, INDICATOR_ALT_BLINK_OFF +} __attribute__ ((packed)); + +/* Transaction Translator Think Times, in bits */ +#define HUB_TTTT_8_BITS 0x00 +#define HUB_TTTT_16_BITS 0x20 +#define HUB_TTTT_24_BITS 0x40 +#define HUB_TTTT_32_BITS 0x60 + +#endif /* __LINUX_CH11_H */ -- cgit v1.2.3 From 73476de0e04f6dd8d136295471bcab5fe6894a2a Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Wed, 23 Jul 2014 15:51:43 +0200 Subject: USB: reduce USB_MAXCHILDREN on imported ch11.h Barebox supports up to 8 USB devices attached on a Hub, Linux does 31. Reduce the USB_MAXCHILDREN define in ch11.h to the Barebox one and put a comment above. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- include/usb/ch11.h | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/include/usb/ch11.h b/include/usb/ch11.h index 331499d597..924a9ea64c 100644 --- a/include/usb/ch11.h +++ b/include/usb/ch11.h @@ -19,8 +19,10 @@ * up to 22 devices to connect. Upcoming hardware might raise that * limit. Because the arrays need to add a bit for hub status data, we * use 31, so plus one evens out to four bytes. + * + * Reduced to 8 max children for Barebox. */ -#define USB_MAXCHILDREN 31 +#define USB_MAXCHILDREN 8 /* * Hub request types -- cgit v1.2.3 From f4e7910195cd31bb8099ee0aef84efe8fb4ca25f Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Wed, 23 Jul 2014 15:51:44 +0200 Subject: USB: fixup usb_hub_descriptor length name All other descriptors have their length field named bLength, except imported usb_hub_descriptor from Linux uses bDescLength. Adjust the name to match the others. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- include/usb/ch11.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/usb/ch11.h b/include/usb/ch11.h index 924a9ea64c..93f891aea7 100644 --- a/include/usb/ch11.h +++ b/include/usb/ch11.h @@ -232,7 +232,7 @@ struct usb_hub_status { #define USB_HUB_PR_SS 3 /* Super speed hub */ struct usb_hub_descriptor { - __u8 bDescLength; + __u8 bLength; __u8 bDescriptorType; __u8 bNbrPorts; __le16 wHubCharacteristics; -- cgit v1.2.3 From 186f164af06628e5357e78ac3d3e2309c777fe05 Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Wed, 23 Jul 2014 15:51:45 +0200 Subject: USB: fix PowerPowerCtrlMask assignment usb_hub_configure() gets the hub descriptor and copies its values over to a local descriptor. While copying PortPowerCtrlMask it erroneously overwrites DeviceRemovable due to a copy-and-paste error. Fix it up. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/core/hub.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index f90a927eef..7553bcdd5e 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -342,7 +342,7 @@ static int usb_hub_configure(struct usb_device *dev) hub->desc.DeviceRemovable[i] = descriptor->DeviceRemovable[i]; for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++) - hub->desc.DeviceRemovable[i] = descriptor->PortPowerCtrlMask[i]; + hub->desc.PortPowerCtrlMask[i] = descriptor->PortPowerCtrlMask[i]; dev->maxchild = descriptor->bNbrPorts; dev_dbg(&dev->dev, "%d ports detected\n", dev->maxchild); -- cgit v1.2.3 From 60ebedf1e7dcf768478483c5d572040de7525c65 Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Wed, 23 Jul 2014 15:51:46 +0200 Subject: USB: Move FooRequest defines and add class requests {Device,Interface,Endpoint}Requests are currently defined in private ehci.h but are also useful for other drivers than ehci-hcd. Move them to usb/usb_defs.h and also add some more class requests. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/host/ehci.h | 16 ---------------- include/usb/usb_defs.h | 26 ++++++++++++++++++++++++++ 2 files changed, 26 insertions(+), 16 deletions(-) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 5d899cc2ec..b1e768b493 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -22,22 +22,6 @@ #define CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS 16 #endif -/* (shifted) direction/type/recipient from the USB 2.0 spec, table 9.2 */ -#define DeviceRequest \ - ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) << 8) - -#define DeviceOutRequest \ - ((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE) << 8) - -#define InterfaceRequest \ - ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) - -#define EndpointRequest \ - ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) - -#define EndpointOutRequest \ - ((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) - /* * Register Space. */ diff --git a/include/usb/usb_defs.h b/include/usb/usb_defs.h index ace20e430d..461f7dbf4e 100644 --- a/include/usb/usb_defs.h +++ b/include/usb/usb_defs.h @@ -238,4 +238,30 @@ #define HUB_CHANGE_LOCAL_POWER 0x0001 #define HUB_CHANGE_OVERCURRENT 0x0002 +/* (shifted) direction/type/recipient from the USB 2.0 spec, table 9.2 */ +#define DeviceRequest \ + ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) << 8) + +#define DeviceOutRequest \ + ((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_DEVICE) << 8) + +#define InterfaceRequest \ + ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) + +#define EndpointRequest \ + ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) + +#define EndpointOutRequest \ + ((USB_DIR_OUT | USB_TYPE_STANDARD | USB_RECIP_INTERFACE) << 8) + +/* class requests from the USB 2.0 hub spec, table 11-15 */ +/* GetBusState and SetHubDescriptor are optional, omitted */ +#define ClearHubFeature (0x2000 | USB_REQ_CLEAR_FEATURE) +#define ClearPortFeature (0x2300 | USB_REQ_CLEAR_FEATURE) +#define GetHubDescriptor (0xa000 | USB_REQ_GET_DESCRIPTOR) +#define GetHubStatus (0xa000 | USB_REQ_GET_STATUS) +#define GetPortStatus (0xa300 | USB_REQ_GET_STATUS) +#define SetHubFeature (0x2000 | USB_REQ_SET_FEATURE) +#define SetPortFeature (0x2300 | USB_REQ_SET_FEATURE) + #endif /*_USB_DEFS_H_ */ -- cgit v1.2.3 From 3c91719f14b744cf28e35d8a7d908dea3ff2d7c7 Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Wed, 23 Jul 2014 15:51:47 +0200 Subject: USB: EHCI: reuse ch9.h config and interface descriptors Now that usb_config_descriptor and usb_interface_descriptor are clean of any additional fields, drop the duplication in EHCI code and use the default ones. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/host/ehci-hcd.c | 4 ++-- drivers/usb/host/ehci.h | 25 ------------------------- 2 files changed, 2 insertions(+), 27 deletions(-) diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 9e30deb419..7857576738 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -57,8 +57,8 @@ struct ehci_priv { static struct descriptor { struct usb_hub_descriptor hub; struct usb_device_descriptor device; - struct usb_linux_config_descriptor config; - struct usb_linux_interface_descriptor interface; + struct usb_config_descriptor config; + struct usb_interface_descriptor interface; struct usb_endpoint_descriptor endpoint; } __attribute__ ((packed)) descriptor = { { diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index b1e768b493..8cc477aa4f 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -68,31 +68,6 @@ struct ehci_hcor { #define USBMODE_CM_HC (3 << 0) /* host controller mode */ #define USBMODE_CM_IDLE (0 << 0) /* idle state */ -/* Interface descriptor */ -struct usb_linux_interface_descriptor { - unsigned char bLength; - unsigned char bDescriptorType; - unsigned char bInterfaceNumber; - unsigned char bAlternateSetting; - unsigned char bNumEndpoints; - unsigned char bInterfaceClass; - unsigned char bInterfaceSubClass; - unsigned char bInterfaceProtocol; - unsigned char iInterface; -} __attribute__ ((packed)); - -/* Configuration descriptor information.. */ -struct usb_linux_config_descriptor { - unsigned char bLength; - unsigned char bDescriptorType; - unsigned short wTotalLength; - unsigned char bNumInterfaces; - unsigned char bConfigurationValue; - unsigned char iConfiguration; - unsigned char bmAttributes; - unsigned char MaxPower; -} __attribute__ ((packed)); - #if defined CONFIG_EHCI_DESC_BIG_ENDIAN #define ehci_readl(x) (*((volatile u32 *)(x))) #define ehci_writel(a, b) (*((volatile u32 *)(a)) = ((volatile u32)b)) -- cgit v1.2.3 From 756c95b7644748b334130332e2c4e56cbb217406 Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Wed, 23 Jul 2014 15:51:48 +0200 Subject: USB: EHCI: make use of defines for descriptors Now that we have a full set of defines for each descriptor field, make use of it for the EHCI HCD descriptors. This fixes endianess issues for .device.bcdUSB, .device.bcdDevice, .config.wTotalLength, and .endpoint.wMaxPacketSize. Also, .endpoint.bInterval was set to 0 instead of 255 due to a copy-and-paste error while assigning u8[] to usb_endpoint_descriptor. This also is a preparation for including ch11.h later, which has a modified usb_hub_descriptor for USB 3.0. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/host/ehci-hcd.c | 104 ++++++++++++++++++++++---------------------- 1 file changed, 52 insertions(+), 52 deletions(-) diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 7857576738..a3063f988d 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -61,62 +61,62 @@ static struct descriptor { struct usb_interface_descriptor interface; struct usb_endpoint_descriptor endpoint; } __attribute__ ((packed)) descriptor = { - { - 0x8, /* bDescLength */ - 0x29, /* bDescriptorType: hub descriptor */ - 2, /* bNrPorts -- runtime modified */ - 0, /* wHubCharacteristics */ - 10, /* bPwrOn2PwrGood */ - 0, /* bHubCntrCurrent */ - {}, /* Device removable */ - {} /* at most 7 ports! XXX */ + .hub = { + .bLength = USB_DT_HUB_NONVAR_SIZE + + ((USB_MAXCHILDREN + 1 + 7) / 8), + .bDescriptorType = USB_DT_HUB, + .bNbrPorts = 2, /* runtime modified */ + .wHubCharacteristics = 0, + .bPwrOn2PwrGood = 10, + .bHubContrCurrent = 0, + .DeviceRemovable = {}, + .PortPowerCtrlMask = {} }, - { - 0x12, /* bLength */ - 1, /* bDescriptorType: UDESC_DEVICE */ - 0x0002, /* bcdUSB: v2.0 */ - 9, /* bDeviceClass: UDCLASS_HUB */ - 0, /* bDeviceSubClass: UDSUBCLASS_HUB */ - 1, /* bDeviceProtocol: UDPROTO_HSHUBSTT */ - 64, /* bMaxPacketSize: 64 bytes */ - 0x0000, /* idVendor */ - 0x0000, /* idProduct */ - 0x0001, /* bcdDevice */ - 1, /* iManufacturer */ - 2, /* iProduct */ - 0, /* iSerialNumber */ - 1 /* bNumConfigurations: 1 */ + .device = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0002), /* v2.0 */ + .bDeviceClass = USB_CLASS_HUB, + .bDeviceSubClass = 0, + .bDeviceProtocol = 1, /* bDeviceProtocol: UDPROTO_HSHUBSTT */ + .bMaxPacketSize0 = 64, + .idVendor = 0x0000, + .idProduct = 0x0000, + .bcdDevice = __constant_cpu_to_le16(0x0001), + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 0, + .bNumConfigurations = 1 }, - { - 0x9, - 2, /* bDescriptorType: UDESC_CONFIG */ - cpu_to_le16(0x19), - 1, /* bNumInterface */ - 1, /* bConfigurationValue */ - 0, /* iConfiguration */ - 0x40, /* bmAttributes: UC_SELF_POWER */ - 0 /* bMaxPower */ + .config = { + .bLength = USB_DT_CONFIG_SIZE, + .bDescriptorType = USB_DT_CONFIG, + .wTotalLength = __constant_cpu_to_le16(USB_DT_CONFIG_SIZE + + USB_DT_INTERFACE_SIZE + USB_DT_ENDPOINT_SIZE), + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 0 }, - { - 0x9, /* bLength */ - 4, /* bDescriptorType: UDESC_INTERFACE */ - 0, /* bInterfaceNumber */ - 0, /* bAlternateSetting */ - 1, /* bNumEndpoints */ - 9, /* bInterfaceClass: UICLASS_HUB */ - 0, /* bInterfaceSubClass: UISUBCLASS_HUB */ - 0, /* bInterfaceProtocol: UIPROTO_HSHUBSTT */ - 0 /* iInterface */ + .interface = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HUB, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, /* bInterfaceProtocol: UIPROTO_HSHUBSTT */ + .iInterface = 0 }, - { - 0x7, /* bLength */ - 5, /* bDescriptorType: UDESC_ENDPOINT */ - 0x81, /* bEndpointAddress: - * UE_DIR_IN | EHCI_INTR_ENDPT - */ - 3, /* bmAttributes: UE_INTERRUPT */ - 8, 0, /* wMaxPacketSize */ - 255 /* bInterval */ + .endpoint = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x81, /* UE_DIR_IN | EHCI_INTR_ENDPT */ + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16((USB_MAXCHILDREN + 1 + 7) / 8), + .bInterval = 255 }, }; -- cgit v1.2.3 From f261bce3a699fd11f7f2d0bbbde26ec26ed1d55f Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Wed, 23 Jul 2014 15:51:49 +0200 Subject: USB: EHCI: use descriptor length fields Instead of hard-coding descriptor length, pick it from the corresponding descriptor. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/host/ehci-hcd.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index a3063f988d..ab1bc0331b 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -503,12 +503,12 @@ ehci_submit_root(struct usb_device *dev, unsigned long pipe, void *buffer, case USB_DT_DEVICE: dev_dbg(ehci->dev, "USB_DT_DEVICE request\n"); srcptr = &descriptor.device; - srclen = 0x12; + srclen = descriptor.device.bLength; break; case USB_DT_CONFIG: dev_dbg(ehci->dev, "USB_DT_CONFIG config\n"); srcptr = &descriptor.config; - srclen = 0x19; + srclen = le16_to_cpu(descriptor.config.wTotalLength); break; case USB_DT_STRING: dev_dbg(ehci->dev, "USB_DT_STRING config\n"); @@ -543,7 +543,7 @@ ehci_submit_root(struct usb_device *dev, unsigned long pipe, void *buffer, case USB_DT_HUB: dev_dbg(ehci->dev, "USB_DT_HUB config\n"); srcptr = &descriptor.hub; - srclen = 0x8; + srclen = descriptor.hub.bLength; break; default: dev_dbg(ehci->dev, "unknown value %x\n", le16_to_cpu(req->value)); -- cgit v1.2.3 From 6931e9007c7f189c0a66d4a66d2aa1a936a4d385 Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Wed, 23 Jul 2014 15:51:50 +0200 Subject: USB: Use descriptors from ch11.h Use the descriptors from ch11.h instead of duplicating them in usb.h. usb_hub_descriptor now contains a union .u to differentiate HS hub descriptor from SS hub descriptor. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/core/hub.c | 10 +++++----- drivers/usb/host/ehci-hcd.c | 4 ++-- include/usb/usb.h | 27 +-------------------------- 3 files changed, 8 insertions(+), 33 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 7553bcdd5e..c1f743cbed 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -332,17 +332,17 @@ static int usb_hub_configure(struct usb_device *dev) hub->desc.wHubCharacteristics = le16_to_cpu(descriptor->wHubCharacteristics); /* set the bitmap */ - bitmap = (unsigned char *)&hub->desc.DeviceRemovable[0]; + bitmap = (unsigned char *)&hub->desc.u.hs.DeviceRemovable[0]; /* devices not removable by default */ memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8); - bitmap = (unsigned char *)&hub->desc.PortPowerCtrlMask[0]; + bitmap = (unsigned char *)&hub->desc.u.hs.PortPwrCtrlMask[0]; memset(bitmap, 0xff, (USB_MAXCHILDREN+1+7)/8); /* PowerMask = 1B */ for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++) - hub->desc.DeviceRemovable[i] = descriptor->DeviceRemovable[i]; + hub->desc.u.hs.DeviceRemovable[i] = descriptor->u.hs.DeviceRemovable[i]; for (i = 0; i < ((hub->desc.bNbrPorts + 1 + 7)/8); i++) - hub->desc.PortPowerCtrlMask[i] = descriptor->PortPowerCtrlMask[i]; + hub->desc.u.hs.PortPwrCtrlMask[i] = descriptor->u.hs.PortPwrCtrlMask[i]; dev->maxchild = descriptor->bNbrPorts; dev_dbg(&dev->dev, "%d ports detected\n", dev->maxchild); @@ -385,7 +385,7 @@ static int usb_hub_configure(struct usb_device *dev) for (i = 0; i < dev->maxchild; i++) dev_dbg(&dev->dev, "port %d is%s removable\n", i + 1, - hub->desc.DeviceRemovable[(i + 1) / 8] & \ + hub->desc.u.hs.DeviceRemovable[(i + 1) / 8] & \ (1 << ((i + 1) % 8)) ? " not" : ""); if (sizeof(struct usb_hub_status) > USB_BUFSIZ) { diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index ab1bc0331b..a76e06bd56 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -69,8 +69,8 @@ static struct descriptor { .wHubCharacteristics = 0, .bPwrOn2PwrGood = 10, .bHubContrCurrent = 0, - .DeviceRemovable = {}, - .PortPowerCtrlMask = {} + .u.hs.DeviceRemovable = {}, + .u.hs.PortPwrCtrlMask = {} }, .device = { .bLength = USB_DT_DEVICE_SIZE, diff --git a/include/usb/usb.h b/include/usb/usb.h index 41f92c2df0..82acf20b12 100644 --- a/include/usb/usb.h +++ b/include/usb/usb.h @@ -24,6 +24,7 @@ #include #include +#include #include #include @@ -311,32 +312,6 @@ void usb_rescan(void); /************************************************************************* * Hub Stuff */ -struct usb_port_status { - unsigned short wPortStatus; - unsigned short wPortChange; -} __attribute__ ((packed)); - -struct usb_hub_status { - unsigned short wHubStatus; - unsigned short wHubChange; -} __attribute__ ((packed)); - - -/* Hub descriptor */ -struct usb_hub_descriptor { - unsigned char bLength; - unsigned char bDescriptorType; - unsigned char bNbrPorts; - unsigned short wHubCharacteristics; - unsigned char bPwrOn2PwrGood; - unsigned char bHubContrCurrent; - unsigned char DeviceRemovable[(USB_MAXCHILDREN+1+7)/8]; - unsigned char PortPowerCtrlMask[(USB_MAXCHILDREN+1+7)/8]; - /* DeviceRemovable and PortPwrCtrlMask want to be variable-length - bitmaps that hold max 255 entries. (bit0 is ignored) */ -} __attribute__ ((packed)); - - struct usb_hub_device { struct usb_device *pusb_dev; struct usb_hub_descriptor desc; -- cgit v1.2.3 From 7d643b416dd2cdcd39307b4d51afb5e1da8a65c1 Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Wed, 23 Jul 2014 15:51:51 +0200 Subject: USB: remove redundant defines from usb_defs.h We imported a bunch of defines with ch9.h and ch11.h that are now duplicated in usb_defs.h. Get rid of each duplicate in usb_defs.h and prefer the ones from ch9.h and ch11.h. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- include/usb/usb_defs.h | 117 ------------------------------------------------- 1 file changed, 117 deletions(-) diff --git a/include/usb/usb_defs.h b/include/usb/usb_defs.h index 461f7dbf4e..8e32379c63 100644 --- a/include/usb/usb_defs.h +++ b/include/usb/usb_defs.h @@ -24,17 +24,6 @@ /* USB constants */ -/* Device and/or Interface Class codes */ -#define USB_CLASS_PER_INTERFACE 0 /* for DeviceClass */ -#define USB_CLASS_AUDIO 1 -#define USB_CLASS_COMM 2 -#define USB_CLASS_HID 3 -#define USB_CLASS_PRINTER 7 -#define USB_CLASS_MASS_STORAGE 8 -#define USB_CLASS_HUB 9 -#define USB_CLASS_DATA 10 -#define USB_CLASS_VENDOR_SPEC 0xff - /* some HID sub classes */ #define USB_SUB_HID_NONE 0 #define USB_SUB_HID_BOOT 1 @@ -60,53 +49,14 @@ #define US_PR_CBI 0 /* Control/Bulk/Interrupt */ #define US_PR_BULK 0x50 /* bulk only */ -/* USB types */ -#define USB_TYPE_STANDARD (0x00 << 5) -#define USB_TYPE_CLASS (0x01 << 5) -#define USB_TYPE_VENDOR (0x02 << 5) -#define USB_TYPE_RESERVED (0x03 << 5) - -/* USB recipients */ -#define USB_RECIP_DEVICE 0x00 -#define USB_RECIP_INTERFACE 0x01 -#define USB_RECIP_ENDPOINT 0x02 -#define USB_RECIP_OTHER 0x03 - -/* USB directions */ -#define USB_DIR_OUT 0 -#define USB_DIR_IN 0x80 - /* Descriptor types */ -#define USB_DT_DEVICE 0x01 -#define USB_DT_CONFIG 0x02 -#define USB_DT_STRING 0x03 -#define USB_DT_INTERFACE 0x04 -#define USB_DT_ENDPOINT 0x05 - #define USB_DT_HID (USB_TYPE_CLASS | 0x01) #define USB_DT_REPORT (USB_TYPE_CLASS | 0x02) #define USB_DT_PHYSICAL (USB_TYPE_CLASS | 0x03) -#define USB_DT_HUB (USB_TYPE_CLASS | 0x09) /* Descriptor sizes per descriptor type */ -#define USB_DT_DEVICE_SIZE 18 -#define USB_DT_CONFIG_SIZE 9 -#define USB_DT_INTERFACE_SIZE 9 -#define USB_DT_ENDPOINT_SIZE 7 -#define USB_DT_ENDPOINT_AUDIO_SIZE 9 /* Audio extension */ -#define USB_DT_HUB_NONVAR_SIZE 7 #define USB_DT_HID_SIZE 9 -/* Endpoints */ -#define USB_ENDPOINT_NUMBER_MASK 0x0f /* in bEndpointAddress */ -#define USB_ENDPOINT_DIR_MASK 0x80 - -#define USB_ENDPOINT_XFERTYPE_MASK 0x03 /* in bmAttributes */ -#define USB_ENDPOINT_XFER_CONTROL 0 -#define USB_ENDPOINT_XFER_ISOC 1 -#define USB_ENDPOINT_XFER_BULK 2 -#define USB_ENDPOINT_XFER_INT 3 - /* USB Packet IDs (PIDs) */ #define USB_PID_UNDEF_0 0xf0 #define USB_PID_OUT 0xe1 @@ -125,19 +75,6 @@ #define USB_PID_STALL 0x1e #define USB_PID_UNDEF_F 0x0f -/* Standard requests */ -#define USB_REQ_GET_STATUS 0x00 -#define USB_REQ_CLEAR_FEATURE 0x01 -#define USB_REQ_SET_FEATURE 0x03 -#define USB_REQ_SET_ADDRESS 0x05 -#define USB_REQ_GET_DESCRIPTOR 0x06 -#define USB_REQ_SET_DESCRIPTOR 0x07 -#define USB_REQ_GET_CONFIGURATION 0x08 -#define USB_REQ_SET_CONFIGURATION 0x09 -#define USB_REQ_GET_INTERFACE 0x0A -#define USB_REQ_SET_INTERFACE 0x0B -#define USB_REQ_SYNCH_FRAME 0x0C - /* HID requests */ #define USB_REQ_GET_REPORT 0x01 #define USB_REQ_GET_IDLE 0x02 @@ -175,69 +112,15 @@ * Hub defines */ -/* - * Hub request types - */ - -#define USB_RT_HUB (USB_TYPE_CLASS | USB_RECIP_DEVICE) -#define USB_RT_PORT (USB_TYPE_CLASS | USB_RECIP_OTHER) - -/* - * Hub Class feature numbers - */ -#define C_HUB_LOCAL_POWER 0 -#define C_HUB_OVER_CURRENT 1 - /* * Port feature numbers */ -#define USB_PORT_FEAT_CONNECTION 0 -#define USB_PORT_FEAT_ENABLE 1 -#define USB_PORT_FEAT_SUSPEND 2 -#define USB_PORT_FEAT_OVER_CURRENT 3 -#define USB_PORT_FEAT_RESET 4 -#define USB_PORT_FEAT_POWER 8 -#define USB_PORT_FEAT_LOWSPEED 9 #define USB_PORT_FEAT_HIGHSPEED 10 -#define USB_PORT_FEAT_C_CONNECTION 16 -#define USB_PORT_FEAT_C_ENABLE 17 -#define USB_PORT_FEAT_C_SUSPEND 18 -#define USB_PORT_FEAT_C_OVER_CURRENT 19 -#define USB_PORT_FEAT_C_RESET 20 /* wPortStatus bits */ -#define USB_PORT_STAT_CONNECTION 0x0001 -#define USB_PORT_STAT_ENABLE 0x0002 -#define USB_PORT_STAT_SUSPEND 0x0004 -#define USB_PORT_STAT_OVERCURRENT 0x0008 -#define USB_PORT_STAT_RESET 0x0010 -#define USB_PORT_STAT_POWER 0x0100 -#define USB_PORT_STAT_LOW_SPEED 0x0200 -#define USB_PORT_STAT_HIGH_SPEED 0x0400 /* support for EHCI */ #define USB_PORT_STAT_SPEED \ (USB_PORT_STAT_LOW_SPEED | USB_PORT_STAT_HIGH_SPEED) -/* wPortChange bits */ -#define USB_PORT_STAT_C_CONNECTION 0x0001 -#define USB_PORT_STAT_C_ENABLE 0x0002 -#define USB_PORT_STAT_C_SUSPEND 0x0004 -#define USB_PORT_STAT_C_OVERCURRENT 0x0008 -#define USB_PORT_STAT_C_RESET 0x0010 - -/* wHubCharacteristics (masks) */ -#define HUB_CHAR_LPSM 0x0003 -#define HUB_CHAR_COMPOUND 0x0004 -#define HUB_CHAR_OCPM 0x0018 - -/* - *Hub Status & Hub Change bit masks - */ -#define HUB_STATUS_LOCAL_POWER 0x0001 -#define HUB_STATUS_OVERCURRENT 0x0002 - -#define HUB_CHANGE_LOCAL_POWER 0x0001 -#define HUB_CHANGE_OVERCURRENT 0x0002 - /* (shifted) direction/type/recipient from the USB 2.0 spec, table 9.2 */ #define DeviceRequest \ ((USB_DIR_IN | USB_TYPE_STANDARD | USB_RECIP_DEVICE) << 8) -- cgit v1.2.3 From 422cd55ca880786a013244dd61ca7a7b978e7d22 Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Sat, 26 Jul 2014 17:24:39 +0200 Subject: USB: Fix stale usb devices in usb_device_list New usb devices are added to a list of usb devices, but when removing the corresponding usb_device it was not removed from that list. Fix it by deleting it properly from the usb_device_list. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/core/usb.c | 1 + 1 file changed, 1 insertion(+) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index fdf9d94a52..201b84e0ee 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -471,6 +471,7 @@ void usb_remove_device(struct usb_device *usbdev) dev_err(&usbdev->dev, "failed to unregister\n"); usbdev->parent->children[usbdev->portnr - 1] = NULL; + list_del(&usbdev->list); free(usbdev); } -- cgit v1.2.3 From 8938752dd883b03003701264fcb2bf96522e728f Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Sat, 26 Jul 2014 17:24:40 +0200 Subject: USB: Count detected USB devices independent of dev_index Adding a usb device increases dev_index every time a new device is allocated. Removing a usb device does not decrease again, of course. As we print the number of detected usb devices after each usb bus scan based on dev_index, we cannot trust dev_index here. Keep track of the correct number of (currently) detected usb devices by introducing an independent dev_count that gets increased on detect and decreased on remove. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/core/usb.c | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 201b84e0ee..7307a6f103 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -65,6 +65,7 @@ #define USB_BUFSIZ 512 +static int dev_count; static int dev_index; static int asynch_allowed; @@ -447,6 +448,7 @@ int usb_new_device(struct usb_device *dev) dev_add_param_int_ro(&dev->dev, "idProduct", le16_to_cpu(dev->descriptor->idProduct), "%04x"); list_add_tail(&dev->list, &usb_device_list); + dev_count++; err = 0; @@ -473,6 +475,7 @@ void usb_remove_device(struct usb_device *usbdev) usbdev->parent->children[usbdev->portnr - 1] = NULL; list_del(&usbdev->list); free(usbdev); + dev_count--; } struct usb_device *usb_alloc_new_device(void) @@ -526,7 +529,7 @@ void usb_rescan(void) continue; } - pr_info("%d USB Device(s) found\n", dev_index); + pr_info("%d USB Device(s) found\n", dev_count); } /* -- cgit v1.2.3 From 418add62c1391ae9195ec1c145906332732dc93c Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Sat, 26 Jul 2014 17:24:41 +0200 Subject: USB: improve error paths and tear-down USB core isn't too strict about allocation/deallocation and add/remove sequences of usb devices. Especially, error paths and device tear-down are kind of broken and cause hangs on failing usb device detect. This patch improves the situation by introducing a usb_free_device() that tears down allocated resources by usb_alloc_new_device(). usb_remove_device() now only deals with resources that have been added by usb_new_device(). Also, error handling is fixed or improved to ensure that no devices are unregistered that have not been previously registered. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/core/hub.c | 19 ++++++++++++------- drivers/usb/core/usb.c | 48 ++++++++++++++++++++++++++++-------------------- drivers/usb/core/usb.h | 1 + 3 files changed, 41 insertions(+), 27 deletions(-) diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index c1f743cbed..d42b47cbe0 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -186,19 +186,21 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port) 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])) { - dev_dbg(&dev->dev, "usb_disconnect(&hub->children[port]);\n"); + if (dev->children[port] && !(portstatus & USB_PORT_STAT_CONNECTION)) { + dev_dbg(&dev->dev, "disconnect detected on port %d\n", port + 1); usb_remove_device(dev->children[port]); - /* Return now if nothing is connected */ - if (!(portstatus & USB_PORT_STAT_CONNECTION)) - return; + return; } + + /* Remove disabled but connected devices */ + if (dev->children[port] && !(portstatus & USB_PORT_STAT_ENABLE)) + usb_remove_device(dev->children[port]); + wait_ms(200); /* Reset the port */ if (hub_port_reset(dev, port, &portstatus) < 0) { - printf("cannot reset port %i!?\n", port + 1); + dev_warn(&dev->dev, "cannot reset port %i!?\n", port + 1); return; } @@ -225,7 +227,10 @@ static void usb_hub_port_connect_change(struct usb_device *dev, int port) /* Woops, disable the port */ dev_dbg(&dev->dev, "hub: disabling port %d\n", port + 1); usb_clear_port_feature(dev, port + 1, USB_PORT_FEAT_ENABLE); + usb_free_device(usb); + return; } + device_detect(&usb->dev); } diff --git a/drivers/usb/core/usb.c b/drivers/usb/core/usb.c index 7307a6f103..dff29361a8 100644 --- a/drivers/usb/core/usb.c +++ b/drivers/usb/core/usb.c @@ -430,7 +430,7 @@ int usb_new_device(struct usb_device *dev) err = register_device(&dev->dev); if (err) { printf("Failed to register device: %s\n", strerror(-err)); - return err; + goto err_out; } dev_add_param_int_ro(&dev->dev, "iManufacturer", @@ -457,42 +457,45 @@ err_out: return err; } +void usb_free_device(struct usb_device *usbdev) +{ + dma_free(usbdev->descriptor); + dma_free(usbdev->setup_packet); + free(usbdev); +} + void usb_remove_device(struct usb_device *usbdev) { - int i, ret; + int i; - for (i = 0; i < usbdev->maxchild; i++) { - if (usbdev->children[i]) - usb_remove_device(usbdev->children[i]); - } + if (!usbdev) + return; - dev_info(&usbdev->dev, "removing\n"); + for (i = 0; i < usbdev->maxchild; i++) + usb_remove_device(usbdev->children[i]); + if (usbdev->parent && usbdev->portnr) + usbdev->parent->children[usbdev->portnr - 1] = NULL; + list_del(&usbdev->list); + dev_count--; - ret = unregister_device(&usbdev->dev); - if (ret) + if (unregister_device(&usbdev->dev)) dev_err(&usbdev->dev, "failed to unregister\n"); + else + dev_info(&usbdev->dev, "removed\n"); - usbdev->parent->children[usbdev->portnr - 1] = NULL; - list_del(&usbdev->list); - free(usbdev); - dev_count--; + usb_free_device(usbdev); } struct usb_device *usb_alloc_new_device(void) { struct usb_device *usbdev = xzalloc(sizeof (*usbdev)); - if (!usbdev) - return NULL; - - usbdev->devnum = dev_index + 1; + usbdev->devnum = ++dev_index; usbdev->maxchild = 0; usbdev->dev.bus = &usb_bus_type; usbdev->setup_packet = dma_alloc(sizeof(*usbdev->setup_packet)); usbdev->descriptor = dma_alloc(sizeof(*usbdev->descriptor)); - dev_index++; - return usbdev; } @@ -508,7 +511,12 @@ int usb_host_detect(struct usb_host *host) host->root_dev = usb_alloc_new_device(); host->root_dev->dev.parent = host->hw_dev; host->root_dev->host = host; - usb_new_device(host->root_dev); + + ret = usb_new_device(host->root_dev); + if (ret) { + usb_free_device(host->root_dev); + return ret; + } } device_detect(&host->root_dev->dev); diff --git a/drivers/usb/core/usb.h b/drivers/usb/core/usb.h index a0c05506dd..a5bb255121 100644 --- a/drivers/usb/core/usb.h +++ b/drivers/usb/core/usb.h @@ -2,6 +2,7 @@ #define __CORE_USB_H struct usb_device *usb_alloc_new_device(void); +void usb_free_device(struct usb_device *dev); int usb_new_device(struct usb_device *dev); void usb_remove_device(struct usb_device *dev); -- cgit v1.2.3 From 343ccff163789949f2c683dcea819ceb68dbcf02 Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Sat, 26 Jul 2014 17:24:42 +0200 Subject: USB: EHCI: use min3 from Linux EHCI HCD has a private version of min3() determining the smallest number out of 3. We already have min()/max() imported from Linux, also get min3()/max3() and use it instead of EHCI's private one. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/host/ehci-hcd.c | 12 +----------- include/linux/kernel.h | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 11 deletions(-) diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index a76e06bd56..c0ea8d013a 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -436,16 +436,6 @@ fail: return -1; } -static inline int min3(int a, int b, int c) -{ - - if (b < a) - a = b; - if (c < a) - a = c; - return a; -} - #ifdef CONFIG_MACH_EFIKA_MX_SMARTBOOK #include /* @@ -717,7 +707,7 @@ ehci_submit_root(struct usb_device *dev, unsigned long pipe, void *buffer, } wait_ms(1); - len = min3(srclen, le16_to_cpu(req->length), length); + len = min3(srclen, (int)le16_to_cpu(req->length), length); if (srcptr != NULL && len > 0) memcpy(buffer, srcptr, len); else diff --git a/include/linux/kernel.h b/include/linux/kernel.h index 4322f01580..f588d3b003 100644 --- a/include/linux/kernel.h +++ b/include/linux/kernel.h @@ -21,6 +21,24 @@ (void) (&_max1 == &_max2); \ _max1 > _max2 ? _max1 : _max2; }) +#define min3(x, y, z) ({ \ + typeof(x) _min1 = (x); \ + typeof(y) _min2 = (y); \ + typeof(z) _min3 = (z); \ + (void) (&_min1 == &_min2); \ + (void) (&_min1 == &_min3); \ + _min1 < _min2 ? (_min1 < _min3 ? _min1 : _min3) : \ + (_min2 < _min3 ? _min2 : _min3); }) + +#define max3(x, y, z) ({ \ + typeof(x) _max1 = (x); \ + typeof(y) _max2 = (y); \ + typeof(z) _max3 = (z); \ + (void) (&_max1 == &_max2); \ + (void) (&_max1 == &_max3); \ + _max1 > _max2 ? (_max1 > _max3 ? _max1 : _max3) : \ + (_max2 > _max3 ? _max2 : _max3); }) + /** * clamp - return a value clamped to a given range with strict typechecking * @val: current value -- cgit v1.2.3 From 7df5350362a21cf74c1e87542a33345b100aeb6f Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Sat, 26 Jul 2014 17:24:43 +0200 Subject: include: import {lower,upper}_32_bits helpers This imports {lower,upper}_32_bits defines to allow to get upper and lower 32b part of a 64b number. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- include/common.h | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/include/common.h b/include/common.h index e7ab5feeac..9919fa4f5d 100644 --- a/include/common.h +++ b/include/common.h @@ -281,6 +281,22 @@ void barebox_set_hostname(const char *); } \ ) +/** + * upper_32_bits - return bits 32-63 of a number + * @n: the number we're accessing + * + * A basic shift-right of a 64- or 32-bit quantity. Use this to suppress + * the "right shift count >= width of type" warning when that quantity is + * 32-bits. + */ +#define upper_32_bits(n) ((u32)(((n) >> 16) >> 16)) + +/** + * lower_32_bits - return bits 0-31 of a number + * @n: the number we're accessing + */ +#define lower_32_bits(n) ((u32)(n)) + #define abs(x) ({ \ long __x = (x); \ (__x < 0) ? -__x : __x; \ -- cgit v1.2.3 From 237bad9cc7b42d98c065dd6e20425b441248358d Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Sat, 26 Jul 2014 17:24:44 +0200 Subject: USB: host: add xHCI HCD, Hub, and platform driver This adds support for xHCI USB 3.0 host controllers found on various SoCs and PCI devices. Currently, the driver only supports the virtual USB 2.0 ports of the host controller, so if you plan to use USB 3.0 devices, put a USB 2.0 cable in between. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/host/Kconfig | 11 + drivers/usb/host/Makefile | 1 + drivers/usb/host/xhci-hcd.c | 1509 +++++++++++++++++++++++++++++++++++++++++++ drivers/usb/host/xhci-hub.c | 647 +++++++++++++++++++ drivers/usb/host/xhci.h | 1279 ++++++++++++++++++++++++++++++++++++ include/usb/xhci.h | 33 + 6 files changed, 3480 insertions(+) create mode 100644 drivers/usb/host/xhci-hcd.c create mode 100644 drivers/usb/host/xhci-hub.c create mode 100644 drivers/usb/host/xhci.h create mode 100644 include/usb/xhci.h diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 5a3ce40705..8c64a3b99c 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -22,3 +22,14 @@ config USB_OHCI_AT91 bool "AT91 OHCI driver" endif + +config USB_XHCI + bool "xHCI driver" + help + The eXtensible Host Controller Interface (xHCI) is standard for + USB 3.0 "SuperSpeed" host controller hardware. xHCI specification + defines support for all USB device speeds from USB 3.0 down to + USB 1.1 without the need for companion controllers. + + This driver currently only supports virtual USB 2.0 ports, if you + plan to use USB 3.0 devices, use a USB 2.0 cable in between. diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 156fc7fed9..a5c009ebd6 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -3,3 +3,4 @@ obj-$(CONFIG_USB_EHCI_OMAP) += ehci-omap.o obj-$(CONFIG_USB_EHCI_ATMEL) += ehci-atmel.o obj-$(CONFIG_USB_OHCI) += ohci-hcd.o obj-$(CONFIG_USB_OHCI_AT91) += ohci-at91.o +obj-$(CONFIG_USB_XHCI) += xhci-hcd.o xhci-hub.o diff --git a/drivers/usb/host/xhci-hcd.c b/drivers/usb/host/xhci-hcd.c new file mode 100644 index 0000000000..9253419750 --- /dev/null +++ b/drivers/usb/host/xhci-hcd.c @@ -0,0 +1,1509 @@ +/* + * xHCI HCD driver + * + * Sebastian Hesselbarth + * + * Some code borrowed from the Linux xHCI driver + * Author: Sarah Sharp + * Copyright (C) 2008 Intel Corp. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ +//#define DEBUG +#include +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" + +/* + * xHCI ring handling + */ + +static int xhci_ring_is_last_trb(struct xhci_ring *ring, union xhci_trb *trb) +{ + if (ring->type == TYPE_EVENT) + return trb == &ring->trbs[NUM_EVENT_TRBS]; + else + return TRB_TYPE_LINK(le32_to_cpu(trb->link.control)); +} + +static int xhci_ring_increment(struct xhci_ring *ring, bool enqueue) +{ + union xhci_trb **queue = (enqueue) ? &ring->enqueue : &ring->dequeue; + + (*queue)++; + + if (!xhci_ring_is_last_trb(ring, *queue)) + return 0; + + if (ring->type == TYPE_EVENT) { + *queue = &ring->trbs[0]; + ring->cycle_state ^= 1; + } else { + u32 ctrl = le32_to_cpu((*queue)->link.control); + void *p = (void *)(dma_addr_t) + le64_to_cpu((*queue)->link.segment_ptr); + + ctrl = (ctrl & ~TRB_CYCLE) | ring->cycle_state; + (*queue)->link.control = cpu_to_le32(ctrl); + + if (enqueue) + ring->enqueue = p; + else + ring->dequeue = p; + + if (ctrl & LINK_TOGGLE) + ring->cycle_state ^= 1; + } + + return 0; +} + +static int xhci_ring_issue_trb(struct xhci_ring *ring, union xhci_trb *trb) +{ + union xhci_trb *enq = ring->enqueue; + int i; + + /* Pass TRB to hardware */ + trb->generic.field[3] &= ~TRB_CYCLE; + trb->generic.field[3] |= ring->cycle_state; + for (i = 0; i < 4; i++) + enq->generic.field[i] = cpu_to_le32(trb->generic.field[i]); + + xhci_ring_increment(ring, 1); + + return 0; +} + +static void xhci_ring_init(struct xhci_ring *ring, int num_trbs, + enum xhci_ring_type type) +{ + ring->type = type; + ring->cycle_state = 1; + ring->num_trbs = num_trbs; + ring->enqueue = ring->dequeue = &ring->trbs[0]; + + /* Event ring is not linked */ + if (type == TYPE_EVENT) + return; + + ring->trbs[num_trbs-1].link.segment_ptr = + cpu_to_le64((dma_addr_t)&ring->trbs[0]); + ring->trbs[num_trbs-1].link.control = + cpu_to_le32(TRB_TYPE(TRB_LINK) | LINK_TOGGLE); +} + +static struct xhci_ring *xhci_get_endpoint_ring(struct xhci_hcd *xhci) +{ + struct xhci_ring *ring; + + if (list_empty(&xhci->rings_list)) { + dev_err(xhci->dev, "no more endpoint rings available\n"); + return NULL; + } + + ring = list_last_entry(&xhci->rings_list, struct xhci_ring, list); + list_del_init(&ring->list); + + return ring; +} + +static void xhci_put_endpoint_ring(struct xhci_hcd *xhci, struct xhci_ring *ring) +{ + if (!ring) + return; + + memset(ring->trbs, 0, ring->num_trbs * sizeof(union xhci_trb)); + list_add_tail(&ring->list, &xhci->rings_list); +} + +/* + * xhci_get_endpoint_index - Used for passing endpoint bitmasks between the + * core and HCDs. Find the index for an endpoint given its descriptor. + * Use the return value to right shift 1 for the bitmask. + * + * Index = (epnum * 2) + direction - 1, + * where direction = 0 for OUT, 1 for IN. + * For control endpoints, the IN index is used (OUT index is unused), so + * index = (epnum * 2) + direction - 1 = (epnum * 2) + 1 - 1 = (epnum * 2) + */ +static unsigned int xhci_get_endpoint_index(u8 epaddress, u8 epattributes) +{ + u8 epnum = epaddress & USB_ENDPOINT_NUMBER_MASK; + u8 xfer = epattributes & USB_ENDPOINT_XFERTYPE_MASK; + unsigned int index; + + if (xfer == USB_ENDPOINT_XFER_CONTROL) + index = (unsigned int)(epnum * 2); + else + index = (unsigned int)(epnum * 2) + + ((epaddress & USB_DIR_IN) ? 1 : 0) - 1; + + return index; +} + +static u8 xhci_get_endpoint_type(u8 epaddress, u8 epattributes) +{ + int in = epaddress & USB_ENDPOINT_DIR_MASK; + u8 xfer = epattributes & USB_ENDPOINT_XFERTYPE_MASK; + u8 type; + + switch (xfer) { + case USB_ENDPOINT_XFER_CONTROL: + type = CTRL_EP; + break; + case USB_ENDPOINT_XFER_ISOC: + type = (in) ? ISOC_IN_EP : ISOC_OUT_EP; + break; + case USB_ENDPOINT_XFER_BULK: + type = (in) ? BULK_IN_EP : BULK_OUT_EP; + break; + case USB_ENDPOINT_XFER_INT: + type = (in) ? INT_IN_EP : INT_OUT_EP; + break; + } + + return type; +} + +/* + * Convert interval expressed as 2^(bInterval - 1) == interval into + * straight exponent value 2^n == interval. + * + */ +static u32 xhci_parse_exponent_interval(struct usb_device *udev, + struct usb_endpoint_descriptor *ep) +{ + u32 interval; + + interval = clamp_val(ep->bInterval, 1, 16) - 1; + /* + * Full speed isoc endpoints specify interval in frames, + * not microframes. We are using microframes everywhere, + * so adjust accordingly. + */ + if (udev->speed == USB_SPEED_FULL) + interval += 3; /* 1 frame = 2^3 uframes */ + + return interval; +} + +/* + * Convert bInterval expressed in microframes (in 1-255 range) to exponent of + * microframes, rounded down to nearest power of 2. + */ +static u32 xhci_microframes_to_exponent(struct usb_device *udev, + struct usb_endpoint_descriptor *ep, u32 desc_interval, + u32 min_exponent, u32 max_exponent) +{ + u32 interval; + + interval = fls(desc_interval) - 1; + return clamp_val(interval, min_exponent, max_exponent); +} + +static inline u32 xhci_parse_microframe_interval(struct usb_device *udev, + struct usb_endpoint_descriptor *ep) +{ + if (ep->bInterval == 0) + return 0; + return xhci_microframes_to_exponent(udev, ep, ep->bInterval, 0, 15); +} + + +static inline u32 xhci_parse_frame_interval(struct usb_device *udev, + struct usb_endpoint_descriptor *ep) +{ + return xhci_microframes_to_exponent(udev, ep, ep->bInterval * 8, 3, 10); +} + +static u32 xhci_get_endpoint_interval(struct usb_device *udev, + struct usb_endpoint_descriptor *ep) +{ + u8 type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + u32 interval = 0; + + switch (udev->speed) { + case USB_SPEED_HIGH: + /* Max NAK rate */ + if (type == USB_ENDPOINT_XFER_CONTROL || + type == USB_ENDPOINT_XFER_BULK) { + interval = xhci_parse_microframe_interval(udev, ep); + break; + } + /* Fall through - SS and HS isoc/int have same decoding */ + case USB_SPEED_SUPER: + if (type == USB_ENDPOINT_XFER_ISOC || + type == USB_ENDPOINT_XFER_INT) + interval = xhci_parse_exponent_interval(udev, ep); + break; + case USB_SPEED_FULL: + if (type == USB_ENDPOINT_XFER_ISOC) { + interval = xhci_parse_exponent_interval(udev, ep); + break; + } + /* + * Fall through for interrupt endpoint interval decoding + * since it uses the same rules as low speed interrupt + * endpoints. + */ + case USB_SPEED_LOW: + if (type == USB_ENDPOINT_XFER_ISOC || + type == USB_ENDPOINT_XFER_INT) + interval = xhci_parse_frame_interval(udev, ep); + break; + } + + return interval; +} + +/* The "Mult" field in the endpoint context is only set for SuperSpeed isoc eps. + * High speed endpoint descriptors can define "the number of additional + * transaction opportunities per microframe", but that goes in the Max Burst + * endpoint context field. + */ +static u32 xhci_get_endpoint_mult(struct usb_device *udev, + struct usb_endpoint_descriptor *ep) +{ + u8 type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + + if (udev->speed != USB_SPEED_SUPER || type != USB_ENDPOINT_XFER_ISOC) + return 0; + /* FIXME: return ss_ep_comp_descriptor.bmAttributes */ + return 0; +} + +/* Return the maximum endpoint service interval time (ESIT) payload. + * Basically, this is the maxpacket size, multiplied by the burst size + * and mult size. + */ +static u32 xhci_get_max_esit_payload(struct usb_device *udev, + struct usb_endpoint_descriptor *ep) +{ + u8 type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + int max_burst; + int max_packet; + u16 mps; + + /* Only applies for interrupt or isochronous endpoints */ + if (type != USB_ENDPOINT_XFER_INT && type != USB_ENDPOINT_XFER_ISOC) + return 0; + + /* FIXME: return ss_ep_comp_descriptor.wBytesPerInterval */ + if (udev->speed == USB_SPEED_SUPER) + return 0; + + mps = le16_to_cpu(ep->wMaxPacketSize); + max_packet = GET_MAX_PACKET(mps); + max_burst = (mps & 0x1800) >> 11; + /* A 0 in max burst means 1 transfer per ESIT */ + return max_packet * (max_burst + 1); +} + +int xhci_handshake(void __iomem *p, u32 mask, u32 done, int usec) +{ + u32 result; + u64 start; + + start = get_time_ns(); + + while (1) { + result = readl(p) & mask; + if (result == done) + return 0; + if (is_timeout(start, usec * USECOND)) + return -ETIMEDOUT; + } +} + +int xhci_issue_command(struct xhci_hcd *xhci, union xhci_trb *trb) +{ + int ret; + + ret = xhci_ring_issue_trb(&xhci->cmd_ring, trb); + if (ret) + return ret; + + /* Ring the bell */ + writel(DB_VALUE_HOST, &xhci->dba->doorbell[0]); + readl(&xhci->dba->doorbell[0]); + + return 0; +} + +static void xhci_set_event_dequeue(struct xhci_hcd *xhci) +{ + u64 reg64; + + reg64 = xhci_read_64(&xhci->ir_set->erst_dequeue); + reg64 &= ERST_PTR_MASK; + /* + * Don't clear the EHB bit (which is RW1C) because + * there might be more events to service. + */ + reg64 &= ~ERST_EHB; + reg64 |= (dma_addr_t)xhci->event_ring.dequeue & + ~(dma_addr_t)ERST_PTR_MASK; + + /* Update HC event ring dequeue pointer */ + xhci_write_64(reg64, &xhci->ir_set->erst_dequeue); +} + +int xhci_wait_for_event(struct xhci_hcd *xhci, u8 type, union xhci_trb *trb) +{ + while (true) { + union xhci_trb *deq = xhci->event_ring.dequeue; + u8 event_type; + int i, ret; + + ret = xhci_handshake(&deq->event_cmd.flags, + cpu_to_le32(TRB_CYCLE), + cpu_to_le32(xhci->event_ring.cycle_state), + XHCI_CMD_DEFAULT_TIMEOUT / USECOND); + if (ret) { + dev_err(xhci->dev, "Timeout while waiting for event\n"); + return ret; + } + + for (i = 0; i < 4; i++) + trb->generic.field[i] = + le32_to_cpu(deq->generic.field[i]); + + xhci_set_event_dequeue(xhci); + xhci_ring_increment(&xhci->event_ring, 0); + + event_type = TRB_FIELD_TO_TYPE(trb->event_cmd.flags); + + switch (event_type) { + case TRB_PORT_STATUS: + dev_dbg(xhci->dev, "Event PortStatusChange %u\n", + GET_PORT_ID(trb->generic.field[0])); + break; + case TRB_TRANSFER: + dev_dbg(xhci->dev, "Event Transfer %u\n", + GET_COMP_CODE(trb->event_cmd.status)); + ret = -GET_COMP_CODE(trb->event_cmd.status); + if (ret == -COMP_SUCCESS) + ret = 0; + break; + case TRB_COMPLETION: + dev_dbg(xhci->dev, "Event CommandCompletion %u\n", + GET_COMP_CODE(trb->event_cmd.status)); + ret = -GET_COMP_CODE(trb->event_cmd.status); + if (ret == -COMP_SUCCESS) + ret = 0; + break; + default: + dev_err(xhci->dev, "unhandled event %u (%02x) [%08x %08x %08x %08x]\n", + event_type, event_type, + trb->generic.field[0], trb->generic.field[1], + trb->generic.field[2], trb->generic.field[3]); + } + + if (event_type == type) + return ret; + } + return -ENOSYS; +} + +static struct xhci_virtual_device *xhci_find_virtdev(struct xhci_hcd *xhci, + struct usb_device *udev) +{ + struct xhci_virtual_device *vdev; + + list_for_each_entry(vdev, &xhci->vdev_list, list) + if (vdev->udev == udev) + return vdev; + + return NULL; +} + +static struct xhci_virtual_device *xhci_alloc_virtdev(struct xhci_hcd *xhci, + struct usb_device *udev) +{ + struct xhci_virtual_device *vdev; + size_t sz_ctx, sz_ictx, sz_dctx; + void *p; + + vdev = xzalloc(sizeof(*vdev)); + vdev->udev = udev; + list_add_tail(&vdev->list, &xhci->vdev_list); + + sz_ctx = HCC_64BYTE_CONTEXT(xhci->hcc_params) ? 2048 : 1024; + /* Device Context: 64B aligned */ + sz_dctx = ALIGN(sz_ctx, 64); + /* Input Control Context: 64B aligned */ + sz_ictx = ALIGN(sz_ctx + HCC_CTX_SIZE(xhci->hcc_params), 64); + + vdev->dma_size = sz_ictx + sz_dctx; + p = vdev->dma = dma_alloc_coherent(vdev->dma_size); + memset(vdev->dma, 0, vdev->dma_size); + + vdev->out_ctx = p; p += sz_dctx; + vdev->in_ctx = p; p += sz_ictx; + + return vdev; +} + +static void xhci_free_virtdev(struct xhci_virtual_device *vdev) +{ + struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + int i; + + for (i = 0; i < USB_MAXENDPOINTS; i++) + if (vdev->ep[i]) + xhci_put_endpoint_ring(xhci, vdev->ep[i]); + + list_del(&vdev->list); + dma_free_coherent(vdev->dma, vdev->dma_size); + free(vdev); +} + +static int xhci_virtdev_issue_transfer(struct xhci_virtual_device *vdev, + u8 ep, union xhci_trb *trb, bool ringbell) +{ + struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + struct xhci_ring *ring = vdev->ep[ep]; + int ret; + + ret = xhci_ring_issue_trb(ring, trb); + if (ret || !ringbell) + return ret; + + /* Ring the bell */ + writel(DB_VALUE(ep, 0), &xhci->dba->doorbell[vdev->slot_id]); + readl(&xhci->dba->doorbell[vdev->slot_id]); + + return 0; +} + +static void xhci_virtdev_zero_in_ctx(struct xhci_virtual_device *vdev) +{ + int i; + + /* When a device's add flag and drop flag are zero, any subsequent + * configure endpoint command will leave that endpoint's state + * untouched. Make sure we don't leave any old state in the input + * endpoint contexts. + */ + vdev->in_ctx->icc.drop_flags = 0; + vdev->in_ctx->icc.add_flags = 0; + vdev->in_ctx->slot.dev_info &= cpu_to_le32(~LAST_CTX_MASK); + /* Endpoint 0 is always valid */ + vdev->in_ctx->slot.dev_info |= cpu_to_le32(LAST_CTX(1)); + for (i = 1; i < 31; i++) { + vdev->in_ctx->ep[i].ep_info = 0; + vdev->in_ctx->ep[i].ep_info2 = 0; + vdev->in_ctx->ep[i].deq = 0; + vdev->in_ctx->ep[i].tx_info = 0; + } +} + +static int xhci_virtdev_disable_slot(struct xhci_virtual_device *vdev) +{ + struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + union xhci_trb trb; + int ret; + + /* Issue Disable Slot Command */ + memset(&trb, 0, sizeof(union xhci_trb)); + trb.event_cmd.flags = TRB_TYPE(TRB_DISABLE_SLOT) | + SLOT_ID_FOR_TRB(vdev->slot_id); + xhci_print_trb(xhci, &trb, "Request DisableSlot"); + xhci_issue_command(xhci, &trb); + ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); + xhci_print_trb(xhci, &trb, "Response DisableSlot"); + + /* Clear Device Context Base Address Array */ + xhci->dcbaa[vdev->slot_id] = 0; + + return ret; +} + +static int xhci_virtdev_enable_slot(struct xhci_virtual_device *vdev) +{ + struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + union xhci_trb trb; + int slot_id; + int ret; + + /* Issue Enable Slot Command */ + memset(&trb, 0, sizeof(union xhci_trb)); + trb.event_cmd.flags = TRB_TYPE(TRB_ENABLE_SLOT); + xhci_print_trb(xhci, &trb, "Request EnableSlot"); + xhci_issue_command(xhci, &trb); + ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); + xhci_print_trb(xhci, &trb, "Response EnableSlot"); + if (ret) + return ret; + + slot_id = TRB_TO_SLOT_ID(trb.event_cmd.flags); + if (slot_id == 0) { + dev_err(xhci->dev, "EnableSlot returned reserved slot ID 0\n"); + return -EINVAL; + } + + vdev->slot_id = slot_id; + + return 0; +} + +int xhci_virtdev_reset(struct xhci_virtual_device *vdev) +{ + struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + union xhci_trb trb; + int ret; + + /* If device is not setup, there is no point in resetting it */ + if (GET_SLOT_STATE(le32_to_cpu(vdev->out_ctx->slot.dev_state)) == + SLOT_STATE_DISABLED) + return 0; + + memset(&trb, 0, sizeof(union xhci_trb)); + trb.event_cmd.flags = TRB_TYPE(TRB_RESET_DEV) | + SLOT_ID_FOR_TRB(vdev->slot_id); + xhci_print_trb(xhci, &trb, "Request Reset"); + xhci_issue_command(xhci, &trb); + ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); + xhci_print_trb(xhci, &trb, "Response Reset"); + + /* + * The Reset Device command can't fail, according to the 0.95/0.96 spec, + * unless we tried to reset a slot ID that wasn't enabled, + * or the device wasn't in the addressed or configured state. + */ + switch (GET_COMP_CODE(trb.event_cmd.status)) { + case COMP_CMD_ABORT: + case COMP_CMD_STOP: + dev_warn(xhci->dev, "Timeout waiting for reset device command\n"); + ret = -ETIMEDOUT; + break; + case COMP_EBADSLT: /* 0.95 completion code for bad slot ID */ + case COMP_CTX_STATE: /* 0.96 completion code for same thing */ + /* Don't treat this as an error. May change my mind later. */ + ret = 0; + case COMP_SUCCESS: + break; + default: + ret = -EINVAL; + } + + return ret; +} + +/* + * Once a hub descriptor is fetched for a device, we need to update the xHC's + * internal data structures for the device. + */ +static int xhci_virtdev_update_hub_device(struct xhci_virtual_device *vdev, + void *buffer, int length) +{ + struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + struct usb_hub_descriptor *desc = buffer; + union xhci_trb trb; + u32 dev_info, dev_info2, tt_info; + u8 maxchild; + u16 hubchar; + int ret; + + /* Need at least first byte of wHubCharacteristics */ + if (length < 4) + return 0; + /* Skip already configured hub device */ + if (vdev->out_ctx->slot.dev_info & DEV_HUB) + return 0; + + maxchild = desc->bNbrPorts; + hubchar = le16_to_cpu(desc->wHubCharacteristics); + + /* update slot context */ + memcpy(&vdev->in_ctx->slot, &vdev->out_ctx->slot, + sizeof(struct xhci_slot_ctx)); + vdev->in_ctx->icc.add_flags |= cpu_to_le32(SLOT_FLAG); + vdev->in_ctx->icc.drop_flags = 0; + vdev->in_ctx->slot.dev_state = 0; + dev_info = le32_to_cpu(vdev->in_ctx->slot.dev_info); + dev_info2 = le32_to_cpu(vdev->in_ctx->slot.dev_info2); + tt_info = le32_to_cpu(vdev->in_ctx->slot.tt_info); + + dev_info |= DEV_HUB; + /* HS Multi-TT in bDeviceProtocol */ + if (vdev->udev->speed == USB_SPEED_HIGH && + vdev->udev->descriptor->bDeviceProtocol == USB_HUB_PR_HS_MULTI_TT) + dev_info |= DEV_MTT; + if (xhci->hci_version > 0x95) { + dev_info2 |= XHCI_MAX_PORTS(maxchild); + /* Set TT think time - convert from ns to FS bit times. + * 0 = 8 FS bit times, 1 = 16 FS bit times, + * 2 = 24 FS bit times, 3 = 32 FS bit times. + * + * xHCI 1.0: this field shall be 0 if the device is not a + * High-speed hub. + */ + if (xhci->hci_version < 0x100 || + vdev->udev->speed == USB_SPEED_HIGH) { + u32 think_time = (hubchar & HUB_CHAR_TTTT) >> 5; + tt_info |= TT_THINK_TIME(think_time); + } + } + vdev->in_ctx->slot.dev_info = cpu_to_le32(dev_info); + vdev->in_ctx->slot.dev_info2 = cpu_to_le32(dev_info2); + vdev->in_ctx->slot.tt_info = cpu_to_le32(tt_info); + + /* Issue Configure Endpoint or Evaluate Context Command */ + memset(&trb, 0, sizeof(union xhci_trb)); + xhci_write_64((dma_addr_t)vdev->in_ctx, &trb.event_cmd.cmd_trb); + trb.event_cmd.flags = SLOT_ID_FOR_TRB(vdev->slot_id); + if (xhci->hci_version > 0x95) + trb.event_cmd.flags |= TRB_TYPE(TRB_CONFIG_EP); + else + trb.event_cmd.flags |= TRB_TYPE(TRB_EVAL_CONTEXT); + xhci_print_trb(xhci, &trb, "Request ConfigureEndpoint"); + xhci_issue_command(xhci, &trb); + ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); + xhci_print_trb(xhci, &trb, "Response ConfigureEndpoint"); + xhci_virtdev_zero_in_ctx(vdev); + + return ret; +} + +static int xhci_virtdev_update_hub_status(struct xhci_virtual_device *vhub, + int port) +{ + struct xhci_hcd *xhci = to_xhci_hcd(vhub->udev->host); + struct usb_device *udev = vhub->udev->children[port - 1]; + struct xhci_virtual_device *vdev; + + if (!udev) + return 0; + + /* Check if we have a virtual device for it */ + vdev = xhci_find_virtdev(xhci, udev); + if (vdev) + xhci_virtdev_detach(vdev); + + return 0; +} + +static int xhci_virtdev_configure(struct xhci_virtual_device *vdev, int config) +{ + struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + struct usb_device *udev = vdev->udev; + union xhci_trb trb; + u32 add_flags = 0, last_ctx; + int i, j; + int ret; + + for (i = 0; i < udev->config.no_of_if; i++) { + struct usb_interface *intf = &udev->config.interface[i]; + + for (j = 0; j < intf->no_of_ep; j++) { + struct usb_endpoint_descriptor *ep = &intf->ep_desc[j]; + u8 type = ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK; + u8 eptype = xhci_get_endpoint_type(ep->bEndpointAddress, + ep->bmAttributes); + u8 epi = xhci_get_endpoint_index(ep->bEndpointAddress, + ep->bmAttributes); + struct xhci_ep_ctx *ctx = &vdev->in_ctx->ep[epi]; + u32 mps, interval, mult, esit, max_packet, max_burst; + u32 ep_info, ep_info2, tx_info; + + vdev->ep[epi] = xhci_get_endpoint_ring(xhci); + if (!vdev->ep[epi]) + return -ENOMEM; + /* FIXME: set correct ring type */ + xhci_ring_init(vdev->ep[epi], NUM_TRANSFER_TRBS, + TYPE_BULK); + add_flags |= BIT(epi+1); + + mps = le16_to_cpu(ep->wMaxPacketSize); + interval = xhci_get_endpoint_interval(vdev->udev, ep); + mult = xhci_get_endpoint_mult(vdev->udev, ep); + esit = xhci_get_max_esit_payload(vdev->udev, ep); + max_packet = GET_MAX_PACKET(mps); + max_burst = 0; + + ep_info = EP_INTERVAL(interval) | EP_MULT(mult); + ep_info2 = EP_TYPE(eptype); + if (type == USB_ENDPOINT_XFER_ISOC) + ep_info2 |= ERROR_COUNT(0); + else + ep_info2 |= ERROR_COUNT(3); + + switch (udev->speed) { + case USB_SPEED_SUPER: + /* FIXME: max_burst = ss_ep_comp.bMaxBurst */ + max_burst = 0; + break; + case USB_SPEED_HIGH: + /* Some devices get this wrong */ + if (type == USB_ENDPOINT_XFER_BULK) + max_packet = 512; + if (type == USB_ENDPOINT_XFER_ISOC || + type == USB_ENDPOINT_XFER_INT) + max_burst = (mps & 0x1800) >> 11; + break; + case USB_SPEED_FULL: + case USB_SPEED_LOW: + break; + } + ep_info2 |= MAX_PACKET(max_packet) | MAX_BURST(max_burst); + + tx_info = MAX_ESIT_PAYLOAD_FOR_EP(esit); + switch (type) { + case USB_ENDPOINT_XFER_CONTROL: + tx_info |= AVG_TRB_LENGTH_FOR_EP(8); + break; + case USB_ENDPOINT_XFER_ISOC: + case USB_ENDPOINT_XFER_BULK: + tx_info |= AVG_TRB_LENGTH_FOR_EP(3 * 1024); + break; + case USB_ENDPOINT_XFER_INT: + tx_info |= AVG_TRB_LENGTH_FOR_EP(1 * 1024); + break; + } + + ctx->ep_info = cpu_to_le32(ep_info); + ctx->ep_info2 = cpu_to_le32(ep_info2); + ctx->tx_info = cpu_to_le32(tx_info); + ctx->deq = + cpu_to_le64((dma_addr_t)vdev->ep[epi]->enqueue | + vdev->ep[epi]->cycle_state); + } + } + + last_ctx = fls(add_flags) - 1; + + /* See section 4.6.6 - A0 = 1; A1 = D0 = D1 = 0 */ + vdev->in_ctx->icc.add_flags = cpu_to_le32(add_flags); + vdev->in_ctx->icc.add_flags |= cpu_to_le32(SLOT_FLAG); + vdev->in_ctx->icc.add_flags &= cpu_to_le32(~EP0_FLAG); + vdev->in_ctx->icc.drop_flags &= cpu_to_le32(~(SLOT_FLAG | EP0_FLAG)); + + /* Don't issue the command if there's no endpoints to update. */ + if (vdev->in_ctx->icc.add_flags == cpu_to_le32(SLOT_FLAG) && + vdev->in_ctx->icc.drop_flags == 0) + return 0; + + vdev->in_ctx->slot.dev_info &= cpu_to_le32(~LAST_CTX_MASK); + vdev->in_ctx->slot.dev_info |= cpu_to_le32(LAST_CTX(last_ctx)); + + /* Issue Configure Endpoint Command */ + memset(&trb, 0, sizeof(union xhci_trb)); + xhci_write_64((dma_addr_t)vdev->in_ctx, &trb.event_cmd.cmd_trb); + trb.event_cmd.flags = TRB_TYPE(TRB_CONFIG_EP) | + SLOT_ID_FOR_TRB(vdev->slot_id); + xhci_print_trb(xhci, &trb, "Request ConfigureEndpoint"); + xhci_issue_command(xhci, &trb); + ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); + xhci_print_trb(xhci, &trb, "Response ConfigureEndpoint"); + xhci_virtdev_zero_in_ctx(vdev); + + return ret; +} + +static int xhci_virtdev_deconfigure(struct xhci_virtual_device *vdev) +{ + struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + union xhci_trb trb; + int ret; + + /* Issue Deconfigure Endpoint Command */ + memset(&trb, 0, sizeof(union xhci_trb)); + xhci_write_64((dma_addr_t)vdev->in_ctx, &trb.event_cmd.cmd_trb); + trb.event_cmd.flags = TRB_TYPE(TRB_CONFIG_EP) | TRB_DC | + SLOT_ID_FOR_TRB(vdev->slot_id); + xhci_print_trb(xhci, &trb, "Request DeconfigureEndpoint"); + xhci_issue_command(xhci, &trb); + ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); + xhci_print_trb(xhci, &trb, "Response DeconfigureEndpoint"); + xhci_virtdev_zero_in_ctx(vdev); + + return ret; +} + +static int xhci_virtdev_init(struct xhci_virtual_device *vdev) +{ + struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + struct usb_device *top_dev; + int max_packets; + u32 route = 0, dev_info, dev_info2, tt_info, ep_info2, tx_info; + bool on_hs_hub = false; + int hs_slot_id = 0; + + /* + * Find the root hub port this device is under, also determine SlotID + * of possible external HS hub a LS/FS device could be connected to. + */ + for (top_dev = vdev->udev; top_dev->parent && top_dev->parent->parent; + top_dev = top_dev->parent) { + if (top_dev->parent->descriptor->bDeviceClass == USB_CLASS_HUB) + route = (route << 4) | (top_dev->portnr & 0xf); + if (top_dev->parent->descriptor->bDeviceClass == USB_CLASS_HUB && + top_dev->parent->speed != USB_SPEED_LOW && + top_dev->parent->speed != USB_SPEED_FULL) { + on_hs_hub |= true; + if (!hs_slot_id) { + struct xhci_virtual_device *vhub = + xhci_find_virtdev(xhci, top_dev->parent); + hs_slot_id = vhub->slot_id; + } + } + } + + /* 4.3.3 3) Initalize Input Slot Context */ + dev_info = LAST_CTX(1); + switch (vdev->udev->speed) { + case USB_SPEED_SUPER: + dev_info |= SLOT_SPEED_SS; + max_packets = 512; + break; + case USB_SPEED_HIGH: + dev_info |= SLOT_SPEED_HS; + max_packets = 64; + break; + case USB_SPEED_FULL: + dev_info |= SLOT_SPEED_FS; + max_packets = 64; + break; + case USB_SPEED_LOW: + dev_info |= SLOT_SPEED_LS; + max_packets = 8; + break; + default: + max_packets = 0; + break; + } + dev_info |= route; + dev_info2 = ROOT_HUB_PORT(top_dev->portnr); + tt_info = 0; + + /* Is this a LS/FS device under an external HS hub? */ + if (on_hs_hub && (vdev->udev->speed == USB_SPEED_FULL || + vdev->udev->speed == USB_SPEED_LOW)) { + dev_info |= DEV_MTT; + tt_info |= (top_dev->portnr << 8) | hs_slot_id; + } + + vdev->in_ctx->slot.dev_info = cpu_to_le32(dev_info); + vdev->in_ctx->slot.dev_info2 = cpu_to_le32(dev_info2); + vdev->in_ctx->slot.tt_info = cpu_to_le32(tt_info); + + /* 4.3.3 4) Initalize Transfer Ring */ + vdev->ep[0] = xhci_get_endpoint_ring(xhci); + if (!vdev->ep[0]) + return -ENOMEM; + xhci_ring_init(vdev->ep[0], NUM_TRANSFER_TRBS, TYPE_CTRL); + + /* 4.3.3 5) Initialize Input Control Endpoint 0 Context */ + ep_info2 = EP_TYPE(CTRL_EP) | MAX_BURST(0) | ERROR_COUNT(3); + ep_info2 |= MAX_PACKET(max_packets); + tx_info = AVG_TRB_LENGTH_FOR_EP(8); + vdev->in_ctx->ep[0].ep_info2 = cpu_to_le32(ep_info2); + vdev->in_ctx->ep[0].tx_info = cpu_to_le32(tx_info); + vdev->in_ctx->ep[0].deq = cpu_to_le64((dma_addr_t)vdev->ep[0]->enqueue | + vdev->ep[0]->cycle_state); + + /* 4.3.3 6+7) Initalize and Set Device Context Base Address Array */ + xhci->dcbaa[vdev->slot_id] = cpu_to_le64((dma_addr_t)vdev->out_ctx); + + return 0; +} + +static int xhci_virtdev_setup(struct xhci_virtual_device *vdev, + enum xhci_setup_dev setup) +{ + struct xhci_hcd *xhci = to_xhci_hcd(vdev->udev->host); + union xhci_trb trb; + int ret; + + /* + * If this is the first Set Address since device + * plug-in then initialize Slot Context + */ + if (!vdev->in_ctx->slot.dev_info) + xhci_virtdev_init(vdev); + else { + /* Otherwise, update Control Ring Dequeue pointer */ + vdev->in_ctx->ep[0].deq = + cpu_to_le64((dma_addr_t)vdev->ep[0]->enqueue | + vdev->ep[0]->cycle_state); + /* + * FS devices have MaxPacketSize0 of 8 or 64, we start + * with 64. If assumtion was wrong, fix it up here. + */ + if (vdev->udev->speed == USB_SPEED_FULL && + vdev->udev->maxpacketsize == PACKET_SIZE_8) { + u32 info = le32_to_cpu(vdev->in_ctx->ep[0].ep_info2); + info &= ~MAX_PACKET_MASK; + info |= MAX_PACKET(8); + vdev->in_ctx->ep[0].ep_info2 = cpu_to_le32(info); + } + } + + vdev->in_ctx->icc.add_flags = cpu_to_le32(SLOT_FLAG | EP0_FLAG); + vdev->in_ctx->icc.drop_flags = 0; + + /* Issue Address Device Command */ + memset(&trb, 0, sizeof(union xhci_trb)); + xhci_write_64((dma_addr_t)vdev->in_ctx, &trb.event_cmd.cmd_trb); + trb.event_cmd.flags = TRB_TYPE(TRB_ADDR_DEV) | + SLOT_ID_FOR_TRB(vdev->slot_id); + if (setup == SETUP_CONTEXT_ONLY) + trb.event_cmd.flags |= TRB_BSR; + xhci_print_trb(xhci, &trb, "Request AddressDevice"); + xhci_issue_command(xhci, &trb); + ret = xhci_wait_for_event(xhci, TRB_COMPLETION, &trb); + xhci_print_trb(xhci, &trb, "Response AddressDevice"); + xhci_virtdev_zero_in_ctx(vdev); + + return ret; +} + +static int xhci_virtdev_set_address(struct xhci_virtual_device *vdev) +{ + return xhci_virtdev_setup(vdev, SETUP_CONTEXT_ADDRESS); +} + +static int xhci_virtdev_enable(struct xhci_virtual_device *vdev) +{ + return xhci_virtdev_setup(vdev, SETUP_CONTEXT_ONLY); +} + +static int xhci_virtdev_attach(struct xhci_hcd *xhci, struct usb_device *udev) +{ + struct xhci_virtual_device *vdev; + int ret; + + vdev = xhci_alloc_virtdev(xhci, udev); + if (IS_ERR(vdev)) + return PTR_ERR(vdev); + + ret = xhci_virtdev_enable_slot(vdev); + if (ret) + return ret; + + return xhci_virtdev_enable(vdev); +} + +int xhci_virtdev_detach(struct xhci_virtual_device *vdev) +{ + xhci_virtdev_deconfigure(vdev); + xhci_virtdev_disable_slot(vdev); + xhci_free_virtdev(vdev); + + return 0; +} + +static int xhci_submit_normal(struct usb_device *udev, unsigned long pipe, + void *buffer, int length) +{ + struct usb_host *host = udev->host; + struct xhci_hcd *xhci = to_xhci_hcd(host); + struct xhci_virtual_device *vdev; + union xhci_trb trb; + u8 epaddr = (usb_pipein(pipe) ? USB_DIR_IN : USB_DIR_OUT) | + usb_pipeendpoint(pipe); + u8 epi = xhci_get_endpoint_index(epaddr, usb_pipetype(pipe)); + int ret; + + vdev = xhci_find_virtdev(xhci, udev); + if (!vdev) + return -ENODEV; + + dev_dbg(xhci->dev, "%s udev %p vdev %p slot %u state %u epi %u in_ctx %p out_ctx %p\n", + __func__, udev, vdev, vdev->slot_id, + GET_SLOT_STATE(le32_to_cpu(vdev->out_ctx->slot.dev_state)), epi, + vdev->in_ctx, vdev->out_ctx); + + /* Normal TRB */ + memset(&trb, 0, sizeof(union xhci_trb)); + trb.event_cmd.cmd_trb = cpu_to_le64((dma_addr_t)buffer); + /* FIXME: TD remainder */ + trb.event_cmd.status = TRB_LEN(length) | TRB_INTR_TARGET(0); + trb.event_cmd.flags = TRB_TYPE(TRB_NORMAL) | TRB_IOC; + if (usb_pipein(pipe)) + trb.event_cmd.flags |= TRB_ISP; + xhci_print_trb(xhci, &trb, "Request Normal"); + xhci_virtdev_issue_transfer(vdev, epi, &trb, true); + ret = xhci_wait_for_event(xhci, TRB_TRANSFER, &trb); + xhci_print_trb(xhci, &trb, "Response Normal"); + + switch (ret) { + case -COMP_SHORT_TX: + udev->status = 0; + udev->act_len = length - EVENT_TRB_LEN(trb.event_cmd.status); + return 0; + case 0: + udev->status = 0; + udev->act_len = 0; + return 0; + case -ETIMEDOUT: + udev->status = USB_ST_CRC_ERR; + return -1; + default: + return -1; + } +} + +static int xhci_submit_control(struct usb_device *udev, unsigned long pipe, + void *buffer, int length, struct devrequest *req) +{ + struct usb_host *host = udev->host; + struct xhci_hcd *xhci = to_xhci_hcd(host); + struct xhci_virtual_device *vdev; + union xhci_trb trb; + u16 typeReq = (req->requesttype << 8) | req->request; + int ret; + + dev_dbg(xhci->dev, "%s req %u (%#x), type %u (%#x), value %u (%#x), index %u (%#x), length %u (%#x)\n", + __func__, req->request, req->request, + req->requesttype, req->requesttype, + le16_to_cpu(req->value), le16_to_cpu(req->value), + le16_to_cpu(req->index), le16_to_cpu(req->index), + le16_to_cpu(req->length), le16_to_cpu(req->length)); + + vdev = xhci_find_virtdev(xhci, udev); + if (!vdev) { + ret = xhci_virtdev_attach(xhci, udev); + if (ret) + return ret; + vdev = xhci_find_virtdev(xhci, udev); + } + if (!vdev) + return -ENODEV; + + dev_dbg(xhci->dev, "%s udev %p vdev %p slot %u state %u epi %u in_ctx %p out_ctx %p\n", + __func__, udev, vdev, vdev->slot_id, + GET_SLOT_STATE(le32_to_cpu(vdev->out_ctx->slot.dev_state)), 0, + vdev->in_ctx, vdev->out_ctx); + + if (req->request == USB_REQ_SET_ADDRESS) + return xhci_virtdev_set_address(vdev); + if (req->request == USB_REQ_SET_CONFIGURATION) { + ret = xhci_virtdev_configure(vdev, le16_to_cpu(req->value)); + if (ret) + return ret; + } + + /* Setup TRB */ + memset(&trb, 0, sizeof(union xhci_trb)); + trb.generic.field[0] = le16_to_cpu(req->value) << 16 | + req->request << 8 | req->requesttype; + trb.generic.field[1] = le16_to_cpu(req->length) << 16 | + le16_to_cpu(req->index); + trb.event_cmd.status = TRB_LEN(8) | TRB_INTR_TARGET(0); + trb.event_cmd.flags = TRB_TYPE(TRB_SETUP) | TRB_IDT; + if (xhci->hci_version == 0x100 && length > 0) { + if (req->requesttype & USB_DIR_IN) + trb.event_cmd.flags |= TRB_TX_TYPE(TRB_DATA_IN); + else + trb.event_cmd.flags |= TRB_TX_TYPE(TRB_DATA_OUT); + } + xhci_print_trb(xhci, &trb, "Request Setup "); + xhci_virtdev_issue_transfer(vdev, 0, &trb, false); + + /* Data TRB */ + if (length > 0) { + memset(&trb, 0, sizeof(union xhci_trb)); + trb.event_cmd.cmd_trb = cpu_to_le64((dma_addr_t)buffer); + /* FIXME: TD remainder */ + trb.event_cmd.status = TRB_LEN(length) | TRB_INTR_TARGET(0); + trb.event_cmd.flags = TRB_TYPE(TRB_DATA) | TRB_IOC; + if (req->requesttype & USB_DIR_IN) + trb.event_cmd.flags |= TRB_ISP | TRB_DIR_IN; + xhci_print_trb(xhci, &trb, "Request Data "); + xhci_virtdev_issue_transfer(vdev, 0, &trb, false); + } + + /* Status TRB */ + memset(&trb, 0, sizeof(union xhci_trb)); + trb.event_cmd.status = TRB_INTR_TARGET(0); + if (length > 0 && req->requesttype & USB_DIR_IN) + trb.event_cmd.flags = 0; + else + trb.event_cmd.flags = TRB_DIR_IN; + trb.event_cmd.flags |= TRB_TYPE(TRB_STATUS) | TRB_IOC; + xhci_print_trb(xhci, &trb, "Request Status"); + xhci_virtdev_issue_transfer(vdev, 0, &trb, true); + + if (length > 0 && req->requesttype & USB_DIR_IN) { + ret = xhci_wait_for_event(xhci, TRB_TRANSFER, &trb); + xhci_print_trb(xhci, &trb, "Response Data "); + if (ret == -COMP_SHORT_TX) + length -= EVENT_TRB_LEN(trb.event_cmd.status); + else if (ret < 0) + return ret; + } + + ret = xhci_wait_for_event(xhci, TRB_TRANSFER, &trb); + xhci_print_trb(xhci, &trb, "Response Status"); + if (ret < 0) + return ret; + + /* + * usb core doesn't notify us about device events on + * external Hubs, track it ourselves. + */ + if (typeReq == GetHubDescriptor) + xhci_virtdev_update_hub_device(vdev, buffer, length); + if (typeReq == ClearPortFeature && + cpu_to_le16(req->value) == USB_PORT_FEAT_C_CONNECTION) + xhci_virtdev_update_hub_status(vdev, le16_to_cpu(req->index)); + + return length; +} + +/* + * xHCI host controller driver + */ + +static void xhci_dma_alloc(struct xhci_hcd *xhci) +{ + size_t sz_sp, sz_spa, sz_dca, sz_cmd, sz_evt, sz_erst, sz_ep; + u64 reg64; + void *p; + int i, num_ep; + + /* Scratchpad buffers: PAGE_SIZE aligned */ + sz_sp = ALIGN(xhci->num_sp * xhci->page_size, xhci->page_size); + /* Device Context Array: 64B aligned */ + sz_dca = ALIGN(xhci->max_slots * sizeof(u64), 64); + /* Command Ring: 64B aligned */ + sz_cmd = ALIGN(NUM_COMMAND_TRBS * sizeof(union xhci_trb), 64); + /* Event Ring: 64B aligned */ + sz_evt = NUM_EVENT_SEGM * + ALIGN(NUM_EVENT_TRBS * sizeof(union xhci_trb), 64); + /* Event Ring Segment Table: 64B aligned */ + sz_erst = ALIGN(NUM_EVENT_SEGM * sizeof(struct xhci_erst_entry), 64); + /* Scratchpad Buffer Array: 64B aligned */ + sz_spa = ALIGN(xhci->num_sp * sizeof(u64), 64); + + xhci->dma_size = sz_sp + sz_spa + sz_dca + sz_cmd + sz_evt + sz_erst; + + /* + * Endpoint Transfer Ring: 16B aligned + * + * We allocate up to MAX_EP_RINGS from the rest of the PAGE + * for virtual devices to pick-up (and return) for endpoint trbs. + */ + sz_ep = ALIGN(NUM_TRANSFER_TRBS * sizeof(union xhci_trb), 16); + + num_ep = PAGE_ALIGN(xhci->dma_size) - + MIN_EP_RINGS * sz_ep - xhci->dma_size; + num_ep /= sz_ep; + num_ep = max(MAX_EP_RINGS, MIN_EP_RINGS + num_ep); + xhci->dma_size += num_ep * sz_ep; + + p = xhci->dma = dma_alloc_coherent(xhci->dma_size); + memset(xhci->dma, 0, xhci->dma_size); + + xhci->sp = p; p += sz_sp; + xhci->dcbaa = p; p += sz_dca; + xhci->cmd_ring.trbs = p; p += sz_cmd; + xhci->event_ring.trbs = p; p += sz_evt; + xhci->event_erst = p; p += sz_erst; + xhci->sp_array = p; p += sz_spa; + + xhci->rings = xzalloc(num_ep * sizeof(*xhci->rings)); + for (i = 0; i < num_ep; i++) { + xhci->rings[i].trbs = p; + p += sz_ep; + xhci_put_endpoint_ring(xhci, &xhci->rings[i]); + } + + /* Setup Scratchpad Buffer Array and Base Address in Device Context */ + reg64 = cpu_to_le64((dma_addr_t)xhci->sp); + for (i = 0; i < xhci->num_sp; i++, reg64 += xhci->page_size) + xhci->sp_array[i] = cpu_to_le64(reg64); + if (xhci->num_sp) + xhci->dcbaa[0] = cpu_to_le64((dma_addr_t)xhci->sp_array); + + /* Setup Event Ring Segment Table and Event Ring */ + reg64 = (dma_addr_t)&xhci->event_ring.trbs[0]; + xhci->event_erst[0].seg_addr = cpu_to_le64(reg64); + xhci->event_erst[0].seg_size = cpu_to_le32(NUM_EVENT_TRBS); + xhci_ring_init(&xhci->event_ring, NUM_EVENT_TRBS, TYPE_EVENT); + + /* Setup Command Ring */ + xhci_ring_init(&xhci->cmd_ring, NUM_COMMAND_TRBS, TYPE_COMMAND); +} + +static int xhci_halt(struct xhci_hcd *xhci) +{ + u32 reg = readl(&xhci->op_regs->status); + u32 mask = ~XHCI_IRQS; + + if (!(reg & STS_HALT)) + mask &= ~CMD_RUN; + + /* disable any IRQs and begin halting process */ + reg = readl(&xhci->op_regs->command); + reg &= mask; + writel(reg, &xhci->op_regs->command); + + return xhci_handshake(&xhci->op_regs->status, + STS_HALT, STS_HALT, XHCI_MAX_HALT_USEC); +} + +static int xhci_reset(struct xhci_hcd *xhci) +{ + u32 reg; + int ret; + + reg = readl(&xhci->op_regs->command); + reg |= CMD_RESET; + writel(reg, &xhci->op_regs->command); + + ret = xhci_handshake(&xhci->op_regs->command, + CMD_RESET, 0, 10 * SECOND / USECOND); + if (ret) { + dev_err(xhci->dev, "failed to reset\n"); + return ret; + } + + return 0; +} + +static int xhci_start(struct xhci_hcd *xhci) +{ + u32 reg; + int ret, i; + + reg = readl(&xhci->op_regs->command); + reg |= CMD_RUN; + writel(reg, &xhci->op_regs->command); + + ret = xhci_handshake(&xhci->op_regs->status, + STS_HALT, 0, XHCI_MAX_HALT_USEC); + if (ret) { + dev_err(xhci->dev, "failed to start\n"); + return ret; + } + + /* Ensure ports are powered-off */ + for (i = 0; i < xhci->num_usb_ports; i++) + xhci_hub_port_power(xhci, i, false); + + return 0; +} + +static int xhci_init(struct usb_host *host) +{ + struct xhci_hcd *xhci = to_xhci_hcd(host); + u32 reg; + u64 reg64; + int i, tmp, ret; + + ret = xhci_halt(xhci); + if (ret) + return ret; + + ret = xhci_reset(xhci); + if (ret) + return ret; + + tmp = readl(&xhci->op_regs->page_size); + for (i = 0; i < 16; i++) { + if ((0x1 & tmp) != 0) + break; + tmp >>= 1; + } + if (i < 16) + tmp = (1 << (i+12)); + else + dev_warn(xhci->dev, "unsupported page size %d\n", tmp); + /* Use 4K pages, since that's common and the minimum the HC supports */ + xhci->page_shift = 12; + xhci->page_size = 1 << xhci->page_shift; + + xhci->rootdev = 0; + xhci->num_sp = HCS_MAX_SCRATCHPAD(xhci->hcs_params2); + xhci->max_slots = HCS_MAX_SLOTS(xhci->hcs_params1); + xhci_dma_alloc(xhci); + + ret = xhci_hub_setup_ports(xhci); + if (ret) + return ret; + + /* + * Program the Max Device Slots Enabled (MaxSlotsEn) field in the + * CONFIG register (5.4.7) with the max number of slots HC can handle. + */ + reg = readl(&xhci->op_regs->config_reg); + reg |= (xhci->max_slots & HCS_SLOTS_MASK); + writel(reg, &xhci->op_regs->config_reg); + + /* + * Program the Device Context Base Address Array Pointer (DCBAAP) + * register (5.4.6) with a 64-bit address pointing to where the + * Device Context Base Address Array is located. + */ + xhci_write_64((dma_addr_t)xhci->dcbaa, &xhci->op_regs->dcbaa_ptr); + + /* + * Define the Command Ring Dequeue Pointer by programming the + * Command Ring Control Register (5.4.5) with a 64-bit address + * pointing to the starting address of the first TRB of the Command + * Ring. + */ + reg64 = xhci_read_64(&xhci->op_regs->cmd_ring); + reg64 = (reg64 & (u64)CMD_RING_RSVD_BITS) | + ((dma_addr_t)&xhci->cmd_ring.trbs[0] & + ~(dma_addr_t)CMD_RING_RSVD_BITS) | + xhci->cmd_ring.cycle_state; + xhci_write_64(reg64, &xhci->op_regs->cmd_ring); + + reg = readl(&xhci->cap_regs->db_off) & DBOFF_MASK; + xhci->dba = (void __iomem *)xhci->cap_regs + reg; + xhci->ir_set = &xhci->run_regs->ir_set[0]; + + reg64 = (dma_addr_t)&xhci->event_ring.trbs[0] & + ~(dma_addr_t)CMD_RING_RSVD_BITS; + xhci->event_erst[i].seg_addr = cpu_to_le64(reg64); + xhci->event_erst[i].seg_size = cpu_to_le32(NUM_EVENT_TRBS); + reg = readl(&xhci->ir_set->erst_size) & ~ERST_SIZE_MASK; + writel(reg | NUM_EVENT_SEGM, &xhci->ir_set->erst_size); + xhci_set_event_dequeue(xhci); + + reg64 = xhci_read_64(&xhci->ir_set->erst_base); + reg64 &= ERST_PTR_MASK; + reg64 |= (dma_addr_t)xhci->event_erst & + ~(dma_addr_t)CMD_RING_RSVD_BITS; + xhci_write_64(reg64, &xhci->ir_set->erst_base); + + /* + * Write the USBCMD (5.4.1) to turn the host controller ON via + * setting the Run/Stop (R/S) bit to ‘1’. This operation allows the + * xHC to begin accepting doorbell references. + */ + + return xhci_start(xhci); + + /* + * At this point, the host controller is up and running and the Root + * Hub ports (5.4.8) will begin reporting device connects, etc., + * and system software may begin enumerating devices. + * System software may follow the procedures described in section 4.3, + * to enumerate attached devices. + * + * USB2 (LS/FS/HS) devices require the port reset process to advance + * the port to the Enabled state. Once USB2 ports are Enabled, the port + * is active with SOFs occurring on the port, but the Pipe Schedules + * have not yet been enabled. + * + * SS ports automatically advance to the Enabled state if a successful + * device attach is detected. + */ +} + +static int xhci_submit_bulk_msg(struct usb_device *dev, unsigned long pipe, + void *buffer, int length, int timeout) +{ + return xhci_submit_normal(dev, pipe, buffer, length); +} + +static int xhci_submit_control_msg(struct usb_device *dev, unsigned long pipe, + void *buffer, int length, struct devrequest *setup, int timeout) +{ + struct xhci_hcd *xhci = to_xhci_hcd(dev->host); + + /* Catch Root Hub requests */ + if (usb_pipedevice(pipe) == xhci->rootdev) { + if (xhci->rootdev == 0) + dev->speed = USB_SPEED_HIGH; + return xhci_hub_control(dev, pipe, buffer, length, setup); + } + + return xhci_submit_control(dev, pipe, buffer, length, setup); +} + +static int xhci_submit_int_msg(struct usb_device *dev, unsigned long pipe, + void *buffer, int length, int interval) +{ + struct xhci_hcd *xhci = to_xhci_hcd(dev->host); + + dev_err(xhci->dev, "Interrupt messages not supported\n"); + + return -ENOTSUPP; +} + +static int xhci_detect(struct device_d *dev) +{ + struct xhci_hcd *xhci = dev->priv; + + return usb_host_detect(&xhci->host); +} + +int xhci_register(struct device_d *dev, struct xhci_data *data) +{ + struct usb_host *host; + struct xhci_hcd *xhci; + + xhci = xzalloc(sizeof(*xhci)); + host = &xhci->host; + INIT_LIST_HEAD(&xhci->vdev_list); + xhci->dev = dev; + xhci->cap_regs = data->regs; + xhci->op_regs = (void __iomem *)xhci->cap_regs + + HC_LENGTH(readl(&xhci->cap_regs->hc_capbase)); + xhci->run_regs = (void __iomem *)xhci->cap_regs + + (readl(&xhci->cap_regs->run_regs_off) & RTSOFF_MASK); + /* Cache read-only capability registers */ + xhci->hcs_params1 = readl(&xhci->cap_regs->hcs_params1); + xhci->hcs_params2 = readl(&xhci->cap_regs->hcs_params2); + xhci->hcs_params3 = readl(&xhci->cap_regs->hcs_params3); + xhci->hcc_capbase = readl(&xhci->cap_regs->hc_capbase); + xhci->hci_version = HC_VERSION(xhci->hcc_capbase); + xhci->hcc_params = readl(&xhci->cap_regs->hcc_params); + + host->hw_dev = dev; + host->init = xhci_init; + host->submit_int_msg = xhci_submit_int_msg; + host->submit_control_msg = xhci_submit_control_msg; + host->submit_bulk_msg = xhci_submit_bulk_msg; + + dev->priv = xhci; + dev->detect = xhci_detect; + + usb_register_host(host); + + dev_info(dev, "USB xHCI %x.%02x\n", + xhci->hci_version >> 8, xhci->hci_version & 0xff); + + return 0; +} + +/* + * xHCI platform driver + */ + +static int xhci_probe(struct device_d *dev) +{ + struct xhci_data data = {}; + + data.regs = dev_request_mem_region(dev, 0); + + return xhci_register(dev, &data); +} + +static void xhci_remove(struct device_d *dev) +{ + struct xhci_hcd *xhci = dev->priv; + xhci_halt(xhci); +} + +static struct driver_d xhci_driver = { + .name = "xHCI", + .probe = xhci_probe, + .remove = xhci_remove, +}; +device_platform_driver(xhci_driver); diff --git a/drivers/usb/host/xhci-hub.c b/drivers/usb/host/xhci-hub.c new file mode 100644 index 0000000000..bf952570f8 --- /dev/null +++ b/drivers/usb/host/xhci-hub.c @@ -0,0 +1,647 @@ +/* + * xHCI USB 3.0 Root Hub + * + * Sebastian Hesselbarth + * + * This currently does not support any SuperSpeed capabilities. + * + * Some code borrowed from the Linux xHCI driver + * Author: Sarah Sharp + * Copyright (C) 2008 Intel Corp. + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ +//#define DEBUG +#include +#include +#include +#include +#include +#include +#include + +#include "xhci.h" + +static const struct usb_root_hub_info usb_rh_info = { + .hub = { + .bLength = USB_DT_HUB_NONVAR_SIZE + + ((USB_MAXCHILDREN + 1 + 7) / 8), + .bDescriptorType = USB_DT_HUB, + .bNbrPorts = 0, /* runtime modified */ + .wHubCharacteristics = 0, + .bPwrOn2PwrGood = 10, + .bHubContrCurrent = 0, + .u.hs.DeviceRemovable = {}, + .u.hs.PortPwrCtrlMask = {} + }, + .device = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = __constant_cpu_to_le16(0x0002), /* v2.0 */ + .bDeviceClass = USB_CLASS_HUB, + .bDeviceSubClass = 0, + .bDeviceProtocol = USB_HUB_PR_HS_MULTI_TT, + .bMaxPacketSize0 = 64, + .idVendor = 0x0000, + .idProduct = 0x0000, + .bcdDevice = __constant_cpu_to_le16(0x0001), + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 0, + .bNumConfigurations = 1 + }, + .config = { + .bLength = USB_DT_CONFIG_SIZE, + .bDescriptorType = USB_DT_CONFIG, + .wTotalLength = __constant_cpu_to_le16(USB_DT_CONFIG_SIZE + + USB_DT_INTERFACE_SIZE + USB_DT_ENDPOINT_SIZE), + .bNumInterfaces = 1, + .bConfigurationValue = 1, + .iConfiguration = 0, + .bmAttributes = USB_CONFIG_ATT_SELFPOWER, + .bMaxPower = 0 + }, + .interface = { + .bLength = USB_DT_INTERFACE_SIZE, + .bDescriptorType = USB_DT_INTERFACE, + .bInterfaceNumber = 0, + .bAlternateSetting = 0, + .bNumEndpoints = 1, + .bInterfaceClass = USB_CLASS_HUB, + .bInterfaceSubClass = 0, + .bInterfaceProtocol = 0, + .iInterface = 0 + }, + .endpoint = { + .bLength = USB_DT_ENDPOINT_SIZE, + .bDescriptorType = USB_DT_ENDPOINT, + .bEndpointAddress = 0x81, /* UE_DIR_IN | EHCI_INTR_ENDPT */ + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = __constant_cpu_to_le16((USB_MAXCHILDREN + 1 + 7) / 8), + .bInterval = 255 + } +}; + +static void xhci_setup_common_hub_descriptor(struct xhci_hcd *xhci, + struct usb_hub_descriptor *desc, int ports) +{ + u16 val; + + /* xhci section 5.4.9 says 20ms max */ + desc->bPwrOn2PwrGood = 10; + desc->bHubContrCurrent = 0; + desc->bNbrPorts = xhci->num_usb_ports; + + val = 0; + /* Bits 1:0 - support per-port power switching, or power always on */ + if (HCC_PPC(xhci->hcc_params)) + val |= HUB_CHAR_INDV_PORT_LPSM; + else + val |= HUB_CHAR_NO_LPSM; + /* Bit 2 - root hubs are not part of a compound device */ + /* Bits 4:3 - individual port over current protection */ + val |= HUB_CHAR_INDV_PORT_OCPM; + /* Bits 6:5 - no TTs in root ports */ + /* Bit 7 - no port indicators */ + desc->wHubCharacteristics = cpu_to_le16(val); +} + +static void xhci_setup_usb2_hub_descriptor(struct xhci_hcd *xhci) +{ + struct usb_hub_descriptor *desc = &xhci->usb_info.hub; + __u8 port_removable[(USB_MAXCHILDREN + 1 + 7) / 8]; + int ports; + u32 portsc; + u16 val; + int i; + + ports = xhci->num_usb_ports; + xhci_setup_common_hub_descriptor(xhci, desc, ports); + desc->bDescriptorType = USB_DT_HUB; + val = 1 + (ports / 8); + desc->bLength = USB_DT_HUB_NONVAR_SIZE + 2 * val; + + /* The Device Removable bits are reported on a byte granularity. + * If the port doesn't exist within that byte, the bit is set to 0. + */ + memset(port_removable, 0, sizeof(port_removable)); + for (i = 0; i < ports; i++) { + portsc = readl(xhci->usb_ports[i]); + /* If a device is removable, PORTSC reports a 0, same as in the + * hub descriptor DeviceRemovable bits. + */ + if (portsc & PORT_DEV_REMOVE) + /* This math is hairy because bit 0 of DeviceRemovable + * is reserved, and bit 1 is for port 1, etc. + */ + port_removable[(i + 1) / 8] |= 1 << ((i + 1) % 8); + } + + /* ch11.h defines a hub descriptor that has room for USB_MAXCHILDREN + * ports on it. The USB 2.0 specification says that there are two + * variable length fields at the end of the hub descriptor: + * DeviceRemovable and PortPwrCtrlMask. But since we can have less than + * USB_MAXCHILDREN ports, we may need to use the DeviceRemovable array + * to set PortPwrCtrlMask bits. PortPwrCtrlMask must always be set to + * 0xFF, so we initialize the both arrays (DeviceRemovable and + * PortPwrCtrlMask) to 0xFF. Then we set the DeviceRemovable for each + * set of ports that actually exist. + */ + memset(desc->u.hs.DeviceRemovable, 0xff, + sizeof(desc->u.hs.DeviceRemovable)); + memset(desc->u.hs.PortPwrCtrlMask, 0xff, + sizeof(desc->u.hs.PortPwrCtrlMask)); + + for (i = 0; i < (ports + 1 + 7) / 8; i++) + memset(&desc->u.hs.DeviceRemovable[i], port_removable[i], + sizeof(__u8)); +} + +/* FIXME: usb core does not know about USB_SPEED_SUPER at all */ +static __maybe_unused void xhci_setup_usb3_hub_descriptor(struct xhci_hcd *xhci) +{ + struct usb_hub_descriptor *desc = &xhci->usb_info.hub; + int ports; + u16 port_removable; + u32 portsc; + int i; + + ports = xhci->num_usb_ports; + xhci_setup_common_hub_descriptor(xhci, desc, ports); + desc->bDescriptorType = USB_DT_SS_HUB; + desc->bLength = USB_DT_SS_HUB_SIZE; + /* + * header decode latency should be zero for roothubs, + * see section 4.23.5.2. + */ + desc->u.ss.bHubHdrDecLat = 0; + desc->u.ss.wHubDelay = 0; + port_removable = 0; + /* bit 0 is reserved, bit 1 is for port 1, etc. */ + for (i = 0; i < ports; i++) { + portsc = readl(xhci->usb_ports[i]); + if (portsc & PORT_DEV_REMOVE) + port_removable |= 1 << (i + 1); + } + desc->u.ss.DeviceRemovable = cpu_to_le16(port_removable); +} + +static void xhci_add_in_port(struct xhci_hcd *xhci, unsigned int num_ports, + __le32 __iomem *addr, u8 major_revision, int max_caps) +{ + u32 reg, port_offset, port_count; + int i; + + if (major_revision > 0x03) { + dev_warn(xhci->dev, "Ignoring unknown port speed, Ext Cap %p, rev %02x\n", + addr, major_revision); + return; + } + + /* Port offset and count in the third dword, see section 7.2 */ + reg = readl(addr + 2); + port_offset = XHCI_EXT_PORT_OFF(reg); + port_count = XHCI_EXT_PORT_COUNT(reg); + + /* Port count includes the current port offset */ + if (port_offset == 0 || (port_offset + port_count - 1) > num_ports) + /* WTF? "Valid values are ‘1’ to MaxPorts" */ + return; + + /* cache usb2 port capabilities */ + if (major_revision < 0x03 && xhci->num_ext_caps < max_caps) + xhci->ext_caps[xhci->num_ext_caps++] = reg; + + port_offset--; + for (i = port_offset; i < (port_offset + port_count); i++) { + /* Duplicate entry. Ignore the port if the revisions differ. */ + if (xhci->port_array[i] != 0) { + dev_warn(xhci->dev, "Duplicate port entry, Ext Cap %p, port %u\n", + addr, i); + dev_warn(xhci->dev, "Port was marked as USB %u, duplicated as USB %u\n", + xhci->port_array[i], major_revision); + /* + * Only adjust the roothub port counts if we haven't + * found a similar duplicate. + */ + if (xhci->port_array[i] != major_revision && + xhci->port_array[i] != DUPLICATE_ENTRY) { + xhci->num_usb_ports--; + xhci->port_array[i] = DUPLICATE_ENTRY; + } + continue; + } + xhci->port_array[i] = major_revision; + xhci->num_usb_ports++; + } +} + +int xhci_hub_setup_ports(struct xhci_hcd *xhci) +{ + u32 offset, tmp_offset; + __le32 __iomem *addr, *tmp_addr; + unsigned int num_ports; + int i, cap_count = 0; + + offset = HCC_EXT_CAPS(xhci->hcc_params); + if (offset == 0) { + dev_err(xhci->dev, "No Extended Capability Registers\n"); + return -ENODEV; + } + + addr = &xhci->cap_regs->hc_capbase + offset; + + /* count extended protocol capability entries for later caching */ + tmp_addr = addr; + tmp_offset = offset; + do { + u32 cap_id = readl(tmp_addr); + + if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) + cap_count++; + + tmp_offset = XHCI_EXT_CAPS_NEXT(cap_id); + tmp_addr += tmp_offset; + } while (tmp_offset); + + num_ports = HCS_MAX_PORTS(xhci->hcs_params1); + xhci->port_array = xzalloc(num_ports * sizeof(*xhci->port_array)); + xhci->ext_caps = xzalloc(cap_count * sizeof(*xhci->ext_caps)); + + while (1) { + u32 cap_id = readl(addr); + + if (XHCI_EXT_CAPS_ID(cap_id) == XHCI_EXT_CAPS_PROTOCOL) + xhci_add_in_port(xhci, num_ports, addr, + (u8)XHCI_EXT_PORT_MAJOR(cap_id), + cap_count); + offset = XHCI_EXT_CAPS_NEXT(cap_id); + if (!offset || xhci->num_usb_ports == num_ports) + break; + addr += offset; + } + + if (xhci->num_usb_ports == 0) { + dev_err(xhci->dev, "No ports on the roothubs?\n"); + return -ENODEV; + } + + xhci->usb_ports = xzalloc(num_ports * sizeof(*xhci->usb_ports)); + for (i = 0; i < num_ports; i++) + xhci->usb_ports[i] = &xhci->op_regs->port_status_base + + NUM_PORT_REGS * i; + memcpy(&xhci->usb_info, &usb_rh_info, sizeof(usb_rh_info)); + xhci_setup_usb2_hub_descriptor(xhci); + + return 0; +} + +/* + * These bits are Read Only (RO) and should be saved and written to the + * registers: 0, 3, 10:13, 30 + * connect status, over-current status, port speed, and device removable. + * connect status and port speed are also sticky - meaning they're in + * the AUX well and they aren't changed by a hot, warm, or cold reset. + */ +#define XHCI_PORT_RO (PORT_CONNECT | PORT_OC | DEV_SPEED_MASK | \ + PORT_DEV_REMOVE) +/* + * These bits are RW; writing a 0 clears the bit, writing a 1 sets the bit: + * bits 5:8, 9, 14:15, 25:27 + * link state, port power, port indicator state, "wake on" enable state + */ +#define XHCI_PORT_RWS (PORT_PLS_MASK | PORT_POWER | PORT_LED_MASK | \ + PORT_WKCONN_E | PORT_WKDISC_E | PORT_WKOC_E) +/* + * These bits are RW; writing a 1 sets the bit, writing a 0 has no effect: + * bit 4 (port reset) + */ +#define XHCI_PORT_RW1S (PORT_RESET) +/* + * These bits are RW; writing a 1 clears the bit, writing a 0 has no effect: + * bits 1, 17, 18, 19, 20, 21, 22, 23 + * port enable/disable, and + * change bits: connect, PED, warm port reset changed (reserved 0 for USB 2.0), + * over-current, reset, link state, and L1 change + */ +#define XHCI_PORT_RW1CS (PORT_PE | PORT_CSC | PORT_PEC | PORT_WRC | \ + PORT_OCC | PORT_RC | PORT_PLC | PORT_CEC) +/* + * Bit 16 is RW, and writing a '1' to it causes the link state control to be + * latched in + */ +#define XHCI_PORT_RW (PORT_LINK_STROBE) +/* + * These bits are Reserved Zero (RsvdZ) and zero should be written to them: + * bits 2, 24, 28:31 + */ +#define XHCI_PORT_RZ (BIT(2) | BIT(24) | (0xf<<28)) + +/* + * Given a port state, this function returns a value that would result in the + * port being in the same state, if the value was written to the port status + * control register. + * Save Read Only (RO) bits and save read/write bits where + * writing a 0 clears the bit and writing a 1 sets the bit (RWS). + * For all other types (RW1S, RW1CS, RW, and RZ), writing a '0' has no effect. + */ +static u32 inline xhci_port_state_to_neutral(u32 state) +{ + /* Save read-only status and port state */ + return (state & XHCI_PORT_RO) | (state & XHCI_PORT_RWS); +} + +static int xhci_hub_finish_port_detach(struct xhci_hcd *xhci, int port) +{ + struct xhci_virtual_device *vdev, *temp; + union xhci_trb trb; + int ret; + + ret = xhci_wait_for_event(xhci, TRB_PORT_STATUS, &trb); + if (ret) + return ret; + + /* Tear-down any attached virtual devices */ + list_for_each_entry_safe(vdev, temp, &xhci->vdev_list, list) + if (vdev->udev && vdev->udev->portnr == port) + xhci_virtdev_detach(vdev); + + return 0; +} + +static int xhci_hub_finish_port_reset(struct xhci_hcd *xhci, int port) +{ + struct xhci_virtual_device *vdev; + union xhci_trb trb; + int ret; + + ret = xhci_wait_for_event(xhci, TRB_PORT_STATUS, &trb); + if (ret) + return ret; + + /* Reset any attached virtual devices */ + list_for_each_entry(vdev, &xhci->vdev_list, list) + if (vdev->udev && vdev->udev->portnr == port) + xhci_virtdev_reset(vdev); + + return 0; +} + +void xhci_hub_port_power(struct xhci_hcd *xhci, int port, + bool enable) +{ + u32 reg = readl(xhci->usb_ports[port]); + + reg = xhci_port_state_to_neutral(reg); + if (enable) + reg |= PORT_POWER; + else + reg &= ~PORT_POWER; + writel(reg, xhci->usb_ports[port]); +} + +static __maybe_unused int xhci_hub_port_warm_reset(struct xhci_hcd *xhci, int port) +{ + void __iomem *portsc = xhci->usb_ports[port]; + u32 reg; + + reg = xhci_port_state_to_neutral(readl(portsc)); + writel(reg | PORT_WR, portsc); + return xhci_handshake(portsc, PORT_RESET, 0, 10 * SECOND/USECOND); +} + +int xhci_hub_control(struct usb_device *dev, unsigned long pipe, + void *buffer, int length, struct devrequest *req) +{ + struct usb_host *host = dev->host; + struct xhci_hcd *xhci = to_xhci_hcd(host); + struct usb_root_hub_info *info; + __le32 __iomem **port_array; + int max_ports; + void *srcptr = NULL; + u8 tmpbuf[4]; + u16 typeReq; + int len, port, srclen = 0; + u32 reg; + + dev_dbg(xhci->dev, "%s req %u (%#x), type %u (%#x), value %u (%#x), index %u (%#x), length %u (%#x)\n", + __func__, req->request, req->request, + req->requesttype, req->requesttype, + le16_to_cpu(req->value), le16_to_cpu(req->value), + le16_to_cpu(req->index), le16_to_cpu(req->index), + le16_to_cpu(req->length), le16_to_cpu(req->length)); + + info = &xhci->usb_info; + port_array = xhci->usb_ports; + max_ports = xhci->num_usb_ports; + + typeReq = (req->requesttype << 8) | req->request; + switch (typeReq) { + case DeviceRequest | USB_REQ_GET_DESCRIPTOR: + dev_dbg(xhci->dev, "GetDeviceDescriptor %u\n", + le16_to_cpu(req->value) >> 8); + + switch (le16_to_cpu(req->value) >> 8) { + case USB_DT_DEVICE: + srcptr = &info->device; + srclen = info->device.bLength; + break; + case USB_DT_CONFIG: + srcptr = &info->config; + srclen = le16_to_cpu(info->config.wTotalLength); + break; + case USB_DT_STRING: + switch (le16_to_cpu(req->value) & 0xff) { + case 0: /* Language */ + srcptr = "\4\3\1\0"; + srclen = 4; + break; + case 1: /* Vendor: "barebox" */ + srcptr = "\20\3b\0a\0r\0e\0b\0o\0x\0"; + srclen = 16; + break; + case 2: /* Product: "USB 3.0 Root Hub" */ + srcptr = "\42\3U\0S\0B\0 \0\63\0.\0\60\0 \0R\0o\0o\0t\0 \0H\0u\0b"; + srclen = 34; + break; + default: + dev_warn(xhci->dev, "unknown string descriptor %x\n", + le16_to_cpu(req->value) >> 8); + goto unknown; + } + break; + } + break; + case DeviceOutRequest | USB_REQ_SET_CONFIGURATION: + dev_dbg(xhci->dev, "SetDeviceConfiguration\n"); + /* Nothing to do */ + break; + case DeviceOutRequest | USB_REQ_SET_ADDRESS: + dev_dbg(xhci->dev, "SetDeviceAddress %u\n", + le16_to_cpu(req->value)); + + xhci->rootdev = le16_to_cpu(req->value); + break; + case GetHubDescriptor: + dev_dbg(xhci->dev, "GetHubDescriptor %u\n", + le16_to_cpu(req->value) >> 8); + + switch (le16_to_cpu(req->value) >> 8) { + case USB_DT_HUB: + srcptr = &info->hub; + srclen = info->hub.bLength; + break; + default: + dev_warn(xhci->dev, "unknown descriptor %x\n", + le16_to_cpu(req->value) >> 8); + goto unknown; + } + break; + case GetHubStatus: + dev_dbg(xhci->dev, "GetHubStatus\n"); + + /* No power source, over-current reported per port */ + tmpbuf[0] = 0x00; + tmpbuf[1] = 0x00; + srcptr = tmpbuf; + srclen = 2; + break; + case GetPortStatus: + dev_dbg(xhci->dev, "GetPortStatus %u\n", + le16_to_cpu(req->index)); + + memset(tmpbuf, 0, 4); + + port = le16_to_cpu(req->index); + if (!port || port > max_ports) + goto unknown; + port--; + + /* read PORTSC register */ + reg = readl(port_array[port]); + + if (reg & PORT_CONNECT) { + tmpbuf[0] |= USB_PORT_STAT_CONNECTION; + if (DEV_LOWSPEED(reg)) + tmpbuf[1] |= USB_PORT_STAT_LOW_SPEED >> 8; + else if (DEV_HIGHSPEED(reg)) + tmpbuf[1] |= USB_PORT_STAT_HIGH_SPEED >> 8; + } + if (reg & PORT_PE) + tmpbuf[0] |= USB_PORT_STAT_ENABLE; + if (reg & PORT_OC) + tmpbuf[0] |= USB_PORT_STAT_OVERCURRENT; + if (reg & PORT_RESET) + tmpbuf[0] |= USB_PORT_STAT_RESET; + /* USB 2.0 only */ + if ((reg & PORT_PLS_MASK) == XDEV_U3 && reg & PORT_POWER) + tmpbuf[0] |= USB_PORT_STAT_SUSPEND; + /* USB 2.0 only */ + if (reg & PORT_POWER) + tmpbuf[1] |= USB_PORT_STAT_POWER >> 8; + if (reg & PORT_CSC) + tmpbuf[2] |= USB_PORT_STAT_C_CONNECTION; + if (reg & PORT_PEC) + tmpbuf[2] |= USB_PORT_STAT_C_ENABLE; + if (reg & PORT_OCC) + tmpbuf[2] |= USB_PORT_STAT_C_OVERCURRENT; + if (reg & PORT_RC) + tmpbuf[2] |= USB_PORT_STAT_C_RESET; + srcptr = tmpbuf; + srclen = 4; + break; + case ClearPortFeature: + dev_dbg(xhci->dev, "ClearPortFeature %u %u\n", + le16_to_cpu(req->index), le16_to_cpu(req->value)); + + port = le16_to_cpu(req->index); + if (!port || port > max_ports) + goto unknown; + port--; + + reg = xhci_port_state_to_neutral(readl(port_array[port])); + + switch (le16_to_cpu(req->value)) { + case USB_PORT_FEAT_ENABLE: + reg &= ~PORT_PE; + break; + case USB_PORT_FEAT_POWER: + reg &= ~PORT_POWER; + break; + case USB_PORT_FEAT_C_CONNECTION: + reg |= PORT_CSC; + break; + case USB_PORT_FEAT_C_ENABLE: + reg |= PORT_PEC; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + reg |= PORT_OCC; + break; + case USB_PORT_FEAT_C_RESET: + reg |= PORT_RC; + break; + default: + dev_warn(xhci->dev, "unknown feature %u\n", + le16_to_cpu(req->value)); + goto unknown; + } + writel(reg, port_array[port]); + readl(port_array[port]); + + if ((reg & PORT_CONNECT) == 0 && + le16_to_cpu(req->value) == USB_PORT_FEAT_C_CONNECTION) + xhci_hub_finish_port_detach(xhci, port + 1); + + break; + case SetPortFeature: + dev_dbg(xhci->dev, "SetPortFeature %u %u\n", + le16_to_cpu(req->index), le16_to_cpu(req->value)); + + port = le16_to_cpu(req->index); + if (!port || port > max_ports) + goto unknown; + port--; + + reg = xhci_port_state_to_neutral(readl(port_array[port])); + + switch (le16_to_cpu(req->value)) { + case USB_PORT_FEAT_POWER: + reg |= PORT_POWER; + break; + case USB_PORT_FEAT_RESET: + reg |= PORT_RESET; + break; + default: + dev_warn(xhci->dev, "unknown feature %u\n", + le16_to_cpu(req->value)); + goto unknown; + } + writel(reg, port_array[port]); + readl(port_array[port]); + + if (le16_to_cpu(req->value) == USB_PORT_FEAT_RESET) + xhci_hub_finish_port_reset(xhci, port + 1); + + break; + default: + dev_warn(xhci->dev, "unknown root hub request %u (%#x) type %u (%#x)\n", + req->request, req->request, + req->requesttype, req->requesttype); + goto unknown; + } + + len = min3(srclen, (int)le16_to_cpu(req->length), length); + if (srcptr && len) + memcpy(buffer, srcptr, len); + dev->act_len = len; + dev->status = 0; + + return 0; + +unknown: + dev->act_len = 0; + dev->status = USB_ST_STALLED; + return -ENOTSUPP; +} diff --git a/drivers/usb/host/xhci.h b/drivers/usb/host/xhci.h new file mode 100644 index 0000000000..078f881183 --- /dev/null +++ b/drivers/usb/host/xhci.h @@ -0,0 +1,1279 @@ +/* + * xHCI USB 3.0 Specification + * + * Sebastian Hesselbarth + * + * Some code borrowed from the Linux xHCI driver + * Author: Sarah Sharp + * Copyright (C) 2008 Intel Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __XHCI_H +#define __XHCI_H + +#define NUM_COMMAND_TRBS 8 +#define NUM_TRANSFER_TRBS 8 +#define NUM_EVENT_SEGM 1 /* only one supported */ +#define NUM_EVENT_TRBS 16 /* minimum 16 TRBS */ +#define MIN_EP_RINGS 3 /* Control + Bulk In/Out */ +#define MAX_EP_RINGS (MIN_EP_RINGS * USB_MAXCHILDREN) + +/* Up to 16 ms to halt an HC */ +#define XHCI_MAX_HALT_USEC (16 * 1000) + +/* Command and Status registers offset from the Operational Registers address */ +#define XHCI_CMD_OFFSET 0x00 +#define XHCI_STS_OFFSET 0x04 +/* HCCPARAMS offset from PCI base address */ +#define XHCI_HCC_PARAMS_OFFSET 0x10 +/* xHCI PCI Configuration Registers */ +#define XHCI_SBRN_OFFSET 0x60 + +/* Max number of USB devices for any host controller - limit in section 6.1 */ +#define MAX_HC_SLOTS 256 +/* Section 5.3.3 - MaxPorts */ +#define MAX_HC_PORTS 127 + +/* + * xHCI register interface. + * This corresponds to the eXtensible Host Controller Interface (xHCI) + * Revision 0.95 specification + */ + +/** + * struct xhci_cap_regs - xHCI Host Controller Capability Registers. + * @hc_capbase: length of the capabilities register and HC version number + * @hcs_params1: HCSPARAMS1 - Structural Parameters 1 + * @hcs_params2: HCSPARAMS2 - Structural Parameters 2 + * @hcs_params3: HCSPARAMS3 - Structural Parameters 3 + * @hcc_params: HCCPARAMS - Capability Parameters + * @db_off: DBOFF - Doorbell array offset + * @run_regs_off: RTSOFF - Runtime register space offset + */ +struct xhci_cap_regs { + __le32 hc_capbase; + __le32 hcs_params1; + __le32 hcs_params2; + __le32 hcs_params3; + __le32 hcc_params; + __le32 db_off; + __le32 run_regs_off; + /* Reserved up to (CAPLENGTH - 0x1C) */ +}; + +/* hc_capbase bitmasks */ +/* bits 7:0 - how long is the Capabilities register */ +#define HC_LENGTH(p) ((p) & 0x00ff) +/* bits 31:16 */ +#define HC_VERSION(p) (((p) >> 16) & 0xffff) + +/* HCSPARAMS1 - hcs_params1 - bitmasks */ +/* bits 0:7, Max Device Slots */ +#define HCS_MAX_SLOTS(p) (((p) >> 0) & 0xff) +#define HCS_SLOTS_MASK 0xff +/* bits 8:18, Max Interrupters */ +#define HCS_MAX_INTRS(p) (((p) >> 8) & 0x7ff) +/* bits 24:31, Max Ports - max value is 0x7F = 127 ports */ +#define HCS_MAX_PORTS(p) (((p) >> 24) & 0x7f) + +/* HCSPARAMS2 - hcs_params2 - bitmasks */ +/* bits 0:3, frames or uframes that SW needs to queue transactions + * ahead of the HW to meet periodic deadlines */ +#define HCS_IST(p) (((p) >> 0) & 0xf) +/* bits 4:7, max number of Event Ring segments */ +#define HCS_ERST_MAX(p) (((p) >> 4) & 0xf) +/* bit 26 Scratchpad restore - for save/restore HW state - not used yet */ +/* bits 27:31 number of Scratchpad buffers SW must allocate for the HW */ +#define HCS_MAX_SCRATCHPAD(p) (((p) >> 27) & 0x1f) + +/* HCSPARAMS3 - hcs_params3 - bitmasks */ +/* bits 0:7, Max U1 to U0 latency for the roothub ports */ +#define HCS_U1_LATENCY(p) (((p) >> 0) & 0xff) +/* bits 16:31, Max U2 to U0 latency for the roothub ports */ +#define HCS_U2_LATENCY(p) (((p) >> 16) & 0xffff) + +/* HCCPARAMS - hcc_params - bitmasks */ +/* true: HC can use 64-bit address pointers */ +#define HCC_64BIT_ADDR(p) ((p) & BIT(0)) +/* true: HC can do bandwidth negotiation */ +#define HCC_BANDWIDTH_NEG(p) ((p) & BIT(1)) +/* true: HC uses 64-byte Device Context structures + * FIXME 64-byte context structures aren't supported yet. + */ +#define HCC_64BYTE_CONTEXT(p) ((p) & BIT(2)) +#define HCC_CTX_SIZE(p) (HCC_64BYTE_CONTEXT(p) ? 64 : 32) +/* true: HC has port power switches */ +#define HCC_PPC(p) ((p) & BIT(3)) +/* true: HC has port indicators */ +#define HCC_INDICATOR(p) ((p) & BIT(4)) +/* true: HC has Light HC Reset Capability */ +#define HCC_LIGHT_RESET(p) ((p) & BIT(5)) +/* true: HC supports latency tolerance messaging */ +#define HCC_LTC(p) ((p) & BIT(6)) +/* true: no secondary Stream ID Support */ +#define HCC_NSS(p) ((p) & BIT(7)) +/* Max size for Primary Stream Arrays - 2^(n+1), where n is bits 12:15 */ +#define HCC_MAX_PSA(p) (1 << ((((p) >> 12) & 0xf) + 1)) +/* Extended Capabilities pointer from PCI base - section 5.3.6 */ +#define HCC_EXT_CAPS(p) (((p) >> 16) & 0xffff) + +/* db_off bitmask - bits 0:1 reserved */ +#define DBOFF_MASK (~0x3) + +/* run_regs_off bitmask - bits 0:4 reserved */ +#define RTSOFF_MASK (~0x1f) + +/* Number of registers per port */ +#define NUM_PORT_REGS 4 + +#define PORTSC 0 +#define PORTPMSC 1 +#define PORTLI 2 +#define PORTHLPMC 3 + +/** + * struct xhci_op_regs - xHCI Host Controller Operational Registers. + * @command: USBCMD - xHC command register + * @status: USBSTS - xHC status register + * @page_size: This indicates the page size that the host controller + * supports. If bit n is set, the HC supports a page size + * of 2^(n+12), up to a 128MB page size. + * 4K is the minimum page size. + * @cmd_ring: CRP - 64-bit Command Ring Pointer + * @dcbaa_ptr: DCBAAP - 64-bit Device Context Base Address Array Pointer + * @config_reg: CONFIG - Configure Register + * @port_status_base: PORTSCn - base address for Port Status and Control + * Each port has a Port Status and Control register, + * followed by a Port Power Management Status and Control + * register, a Port Link Info register, and a reserved + * register. + * @port_power_base: PORTPMSCn - base address for + * Port Power Management Status and Control + * @port_link_base: PORTLIn - base address for Port Link Info (current + * Link PM state and control) for USB 2.1 and USB 3.0 + * devices. + */ +struct xhci_op_regs { + __le32 command; + __le32 status; + __le32 page_size; + __le32 reserved1; + __le32 reserved2; + __le32 dev_notification; + __le64 cmd_ring; + /* rsvd: offset 0x20-2F */ + __le32 reserved3[4]; + __le64 dcbaa_ptr; + __le32 config_reg; + /* rsvd: offset 0x3C-3FF */ + __le32 reserved4[241]; + /* port 1 registers, which serve as a base address for other ports */ + __le32 port_status_base; + __le32 port_power_base; + __le32 port_link_base; + __le32 reserved5; + /* registers for ports 2-255 */ + __le32 reserved6[NUM_PORT_REGS*254]; +}; + +/* USBCMD - USB command - command bitmasks */ +/* start/stop HC execution - do not write unless HC is halted*/ +#define CMD_RUN BIT(0) +/* Reset HC - resets internal HC state machine and all registers (except + * PCI config regs). HC does NOT drive a USB reset on the downstream ports. + * The xHCI driver must reinitialize the xHC after setting this bit. + */ +#define CMD_RESET BIT(1) +/* Event Interrupt Enable - a '1' allows interrupts from the host controller */ +#define CMD_EIE BIT(2) +/* Host System Error Interrupt Enable - get out-of-band signal for HC errors */ +#define CMD_HSEIE BIT(3) +/* bits 4:6 are reserved (and should be preserved on writes). */ +/* light reset (port status stays unchanged) - reset completed when this is 0 */ +#define CMD_LRESET BIT(7) +/* host controller save/restore state. */ +#define CMD_CSS BIT(8) +#define CMD_CRS BIT(9) +/* Enable Wrap Event - '1' means xHC generates an event when MFINDEX wraps. */ +#define CMD_EWE BIT(10) +/* MFINDEX power management - '1' means xHC can stop MFINDEX counter if all root + * hubs are in U3 (selective suspend), disconnect, disabled, or powered-off. + * '0' means the xHC can power it off if all ports are in the disconnect, + * disabled, or powered-off state. + */ +#define CMD_PM_INDEX BIT(11) +/* bits 12:31 are reserved (and should be preserved on writes). */ +#define XHCI_IRQS (CMD_EIE | CMD_HSEIE | CMD_EWE) + +/* IMAN - Interrupt Management Register */ +#define IMAN_IE BIT(1) +#define IMAN_IP BIT(0) + +/* USBSTS - USB status - status bitmasks */ +/* HC not running - set to 1 when run/stop bit is cleared. */ +#define STS_HALT BIT(0) +/* serious error, e.g. PCI parity error. The HC will clear the run/stop bit. */ +#define STS_FATAL BIT(2) +/* event interrupt - clear this prior to clearing any IP flags in IR set*/ +#define STS_EINT BIT(3) +/* port change detect */ +#define STS_PORT BIT(4) +/* bits 5:7 reserved and zeroed */ +/* save state status - '1' means xHC is saving state */ +#define STS_SAVE BIT(8) +/* restore state status - '1' means xHC is restoring state */ +#define STS_RESTORE BIT(9) +/* true: save or restore error */ +#define STS_SRE BIT(10) +/* true: Controller Not Ready to accept doorbell or op reg writes after reset */ +#define STS_CNR BIT(11) +/* true: internal Host Controller Error - SW needs to reset and reinitialize */ +#define STS_HCE BIT(12) +/* bits 13:31 reserved and should be preserved */ + +/* + * DNCTRL - Device Notification Control Register - dev_notification bitmasks + * Generate a device notification event when the HC sees a transaction with a + * notification type that matches a bit set in this bit field. + */ +#define DEV_NOTE_MASK (0xffff) +#define ENABLE_DEV_NOTE(x) BIT(x) +/* Most of the device notification types should only be used for debug. + * SW does need to pay attention to function wake notifications. + */ +#define DEV_NOTE_FWAKE ENABLE_DEV_NOTE(1) + +/* CRCR - Command Ring Control Register - cmd_ring bitmasks */ +/* bit 0 is the command ring cycle state */ +/* stop ring operation after completion of the currently executing command */ +#define CMD_RING_PAUSE BIT(1) +/* stop ring immediately - abort the currently executing command */ +#define CMD_RING_ABORT BIT(2) +/* true: command ring is running */ +#define CMD_RING_RUNNING BIT(3) +/* bits 4:5 reserved and should be preserved */ +/* Command Ring pointer - bit mask for the lower 32 bits. */ +#define CMD_RING_RSVD_BITS (0x3f) + +/* CONFIG - Configure Register - config_reg bitmasks */ +/* bits 0:7 - maximum number of device slots enabled (NumSlotsEn) */ +#define MAX_DEVS(p) ((p) & 0xff) +/* bits 8:31 - reserved and should be preserved */ + +/* PORTSC - Port Status and Control Register - port_status_base bitmasks */ +/* true: device connected */ +#define PORT_CONNECT BIT(0) +/* true: port enabled */ +#define PORT_PE BIT(1) +/* bit 2 reserved and zeroed */ +/* true: port has an over-current condition */ +#define PORT_OC BIT(3) +/* true: port reset signaling asserted */ +#define PORT_RESET BIT(4) +/* Port Link State - bits 5:8 + * A read gives the current link PM state of the port, + * a write with Link State Write Strobe set sets the link state. + */ +#define PORT_PLS_MASK (0xf << 5) +#define XDEV_U0 (0x0 << 5) +#define XDEV_U2 (0x2 << 5) +#define XDEV_U3 (0x3 << 5) +#define XDEV_RESUME (0xf << 5) +/* true: port has power (see HCC_PPC) */ +#define PORT_POWER BIT(9) +/* bits 10:13 indicate device speed: + * 0 - undefined speed - port hasn't be initialized by a reset yet + * 1 - full speed + * 2 - low speed + * 3 - high speed + * 4 - super speed + * 5-15 reserved + */ +#define DEV_SPEED_MASK (0xf << 10) +#define XDEV_FS (0x1 << 10) +#define XDEV_LS (0x2 << 10) +#define XDEV_HS (0x3 << 10) +#define XDEV_SS (0x4 << 10) +#define DEV_UNDEFSPEED(p) (((p) & DEV_SPEED_MASK) == (0x0<<10)) +#define DEV_FULLSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_FS) +#define DEV_LOWSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_LS) +#define DEV_HIGHSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_HS) +#define DEV_SUPERSPEED(p) (((p) & DEV_SPEED_MASK) == XDEV_SS) +/* Bits 20:23 in the Slot Context are the speed for the device */ +#define SLOT_SPEED_FS (XDEV_FS << 10) +#define SLOT_SPEED_LS (XDEV_LS << 10) +#define SLOT_SPEED_HS (XDEV_HS << 10) +#define SLOT_SPEED_SS (XDEV_SS << 10) +/* Port Indicator Control */ +#define PORT_LED_OFF (0 << 14) +#define PORT_LED_AMBER (1 << 14) +#define PORT_LED_GREEN (2 << 14) +#define PORT_LED_MASK (3 << 14) +/* Port Link State Write Strobe - set this when changing link state */ +#define PORT_LINK_STROBE BIT(16) +/* true: connect status change */ +#define PORT_CSC BIT(17) +/* true: port enable change */ +#define PORT_PEC BIT(18) +/* true: warm reset for a USB 3.0 device is done. A "hot" reset puts the port + * into an enabled state, and the device into the default state. A "warm" reset + * also resets the link, forcing the device through the link training sequence. + * SW can also look at the Port Reset register to see when warm reset is done. + */ +#define PORT_WRC BIT(19) +/* true: over-current change */ +#define PORT_OCC BIT(20) +/* true: reset change - 1 to 0 transition of PORT_RESET */ +#define PORT_RC BIT(21) +/* port link status change - set on some port link state transitions: + * Transition Reason + * ------------------------------------------------------------------------------ + * - U3 to Resume Wakeup signaling from a device + * - Resume to Recovery to U0 USB 3.0 device resume + * - Resume to U0 USB 2.0 device resume + * - U3 to Recovery to U0 Software resume of USB 3.0 device complete + * - U3 to U0 Software resume of USB 2.0 device complete + * - U2 to U0 L1 resume of USB 2.1 device complete + * - U0 to U0 (???) L1 entry rejection by USB 2.1 device + * - U0 to disabled L1 entry error with USB 2.1 device + * - Any state to inactive Error on USB 3.0 port + */ +#define PORT_PLC BIT(22) +/* port configure error change - port failed to configure its link partner */ +#define PORT_CEC BIT(23) +/* Cold Attach Status - xHC can set this bit to report device attached during + * Sx state. Warm port reset should be perfomed to clear this bit and move port + * to connected state. + */ +#define PORT_CAS BIT(24) +/* wake on connect (enable) */ +#define PORT_WKCONN_E BIT(25) +/* wake on disconnect (enable) */ +#define PORT_WKDISC_E BIT(26) +/* wake on over-current (enable) */ +#define PORT_WKOC_E BIT(27) +/* bits 28:29 reserved */ +/* true: device is removable - for USB 3.0 roothub emulation */ +#define PORT_DEV_REMOVE BIT(30) +/* Initiate a warm port reset - complete when PORT_WRC is '1' */ +#define PORT_WR BIT(31) + +/* We mark duplicate entries with -1 */ +#define DUPLICATE_ENTRY ((u8)(-1)) + +/* Port Power Management Status and Control - port_power_base bitmasks */ +/* Inactivity timer value for transitions into U1, in microseconds. + * Timeout can be up to 127us. 0xFF means an infinite timeout. + */ +#define PORT_U1_TIMEOUT(p) ((p) & 0xff) +#define PORT_U1_TIMEOUT_MASK 0xff +/* Inactivity timer value for transitions into U2 */ +#define PORT_U2_TIMEOUT(p) (((p) & 0xff) << 8) +#define PORT_U2_TIMEOUT_MASK (0xff << 8) +/* Bits 24:31 for port testing */ + +/* USB2 Protocol PORTSPMSC */ +#define PORT_L1S_MASK 0x7 +#define PORT_L1S_SUCCESS 0x1 +#define PORT_RWE BIT(3) +#define PORT_HIRD(p) (((p) & 0xf) << 4) +#define PORT_HIRD_MASK (0xf << 4) +#define PORT_L1DS_MASK (0xff << 8) +#define PORT_L1DS(p) (((p) & 0xff) << 8) +#define PORT_HLE BIT(16) + +/* USB2 Protocol PORTHLPMC */ +#define PORT_HIRDM(p) ((p) & 3) +#define PORT_L1_TIMEOUT(p) (((p) & 0xff) << 2) +#define PORT_BESLD(p) (((p) & 0xf) << 10) + +/* use 512 microseconds as USB2 LPM L1 default timeout. */ +#define XHCI_L1_TIMEOUT 512 + +/* Set default HIRD/BESL value to 4 (350/400us) for USB2 L1 LPM resume latency. + * Safe to use with mixed HIRD and BESL systems (host and device) and is used + * by other operating systems. + * + * XHCI 1.0 errata 8/14/12 Table 13 notes: + * "Software should choose xHC BESL/BESLD field values that do not violate a + * device's resume latency requirements, + * e.g. not program values > '4' if BLC = '1' and a HIRD device is attached, + * or not program values < '4' if BLC = '0' and a BESL device is attached. + */ +#define XHCI_DEFAULT_BESL 4 + +/** + * struct xhci_intr_reg - Interrupt Register Set + * @irq_pending: IMAN - Interrupt Management Register. Used to enable + * interrupts and check for pending interrupts. + * @irq_control: IMOD - Interrupt Moderation Register. + * Used to throttle interrupts. + * @erst_size: Number of segments in the Event Ring Segment Table (ERST). + * @erst_base: ERST base address. + * @erst_dequeue: Event ring dequeue pointer. + * + * Each interrupter (defined by a MSI-X vector) has an event ring and an Event + * Ring Segment Table (ERST) associated with it. The event ring is comprised of + * multiple segments of the same size. The HC places events on the ring and + * "updates the Cycle bit in the TRBs to indicate to software the current + * position of the Enqueue Pointer." The HCD (Linux) processes those events and + * updates the dequeue pointer. + */ +struct xhci_intr_reg { + __le32 irq_pending; + __le32 irq_control; + __le32 erst_size; + __le32 rsvd; + __le64 erst_base; + __le64 erst_dequeue; +}; + +/* irq_pending bitmasks */ +#define ER_IRQ_PENDING(p) ((p) & 0x1) +/* bits 2:31 need to be preserved */ +/* THIS IS BUGGY - FIXME - IP IS WRITE 1 TO CLEAR */ +#define ER_IRQ_CLEAR(p) ((p) & 0xfffffffe) +#define ER_IRQ_ENABLE(p) ((ER_IRQ_CLEAR(p)) | 0x2) +#define ER_IRQ_DISABLE(p) ((ER_IRQ_CLEAR(p)) & ~(0x2)) + +/* irq_control bitmasks */ +/* Minimum interval between interrupts (in 250ns intervals). The interval + * between interrupts will be longer if there are no events on the event ring. + * Default is 4000 (1 ms). + */ +#define ER_IRQ_INTERVAL_MASK 0xffff +/* Counter used to count down the time to the next interrupt - HW use only */ +#define ER_IRQ_COUNTER_MASK (0xffff << 16) + +/* erst_size bitmasks */ +/* Preserve bits 16:31 of erst_size */ +#define ERST_SIZE_MASK (0xffff << 16) + +/* erst_dequeue bitmasks */ +/* Dequeue ERST Segment Index (DESI) - Segment number (or alias) + * where the current dequeue pointer lies. This is an optional HW hint. + */ +#define ERST_DESI_MASK 0x7 +/* Event Handler Busy (EHB) - is the event ring scheduled to be serviced by + * a work queue (or delayed service routine)? + */ +#define ERST_EHB BIT(3) +#define ERST_PTR_MASK 0xf + +/** + * struct xhci_run_regs + * @microframe_index: MFINDEX - current microframe number + * + * Section 5.5 Host Controller Runtime Registers: + * "Software should read and write these registers using only Dword (32 bit) + * or larger accesses" + */ +struct xhci_run_regs { + __le32 microframe_index; + __le32 rsvd[7]; + struct xhci_intr_reg ir_set[128]; +}; + +/** + * struct doorbell_array + * + * Bits 0 - 7: Endpoint target + * Bits 8 - 15: RsvdZ + * Bits 16 - 31: Stream ID + * + * Section 5.6 + */ +struct xhci_doorbell_array { + __le32 doorbell[256]; +}; + +#define DB_VALUE(ep, stream) ((((ep) + 1) & 0xff) | ((stream) << 16)) +#define DB_VALUE_HOST 0x00000000 + +/** + * struct xhci_slot_ctx + * @dev_info: Route string, device speed, hub info, and last valid endpoint + * @dev_info2: Max exit latency for device number, root hub port number + * @tt_info: tt_info is used to construct split transaction tokens + * @dev_state: slot state and device address + * + * Slot Context - section 6.2.1.1. This assumes the HC uses 32-byte context + * structures. If the HC uses 64-byte contexts, there is an additional 32 bytes + * reserved at the end of the slot context for HC internal use. + */ +struct xhci_slot_ctx { + __le32 dev_info; + __le32 dev_info2; + __le32 tt_info; + __le32 dev_state; + /* offset 0x10 to 0x1f reserved for HC internal use */ + __le32 reserved[4]; +}; + +/* dev_info bitmasks */ +/* Route String - 0:19 */ +#define ROUTE_STRING_MASK 0xfffff +/* Device speed - values defined by PORTSC Device Speed field - 20:23 */ +#define DEV_SPEED (0xf << 20) +/* bit 24 reserved */ +/* Is this LS/FS device connected through a HS hub? - bit 25 */ +#define DEV_MTT BIT(25) +/* Set if the device is a hub - bit 26 */ +#define DEV_HUB BIT(26) +/* Index of the last valid endpoint context in this device context - 27:31 */ +#define LAST_CTX_MASK (0x1f << 27) +#define LAST_CTX(p) ((p) << 27) +#define LAST_CTX_TO_EP_NUM(p) (((p) >> 27) - 1) +#define SLOT_FLAG BIT(0) +#define EP0_FLAG BIT(1) + +/* dev_info2 bitmasks */ +/* Max Exit Latency (ms) - worst case time to wake up all links in dev path */ +#define MAX_EXIT 0xffff +/* Root hub port number that is needed to access the USB device */ +#define ROOT_HUB_PORT(p) (((p) & 0xff) << 16) +#define DEVINFO_TO_ROOT_HUB_PORT(p) (((p) >> 16) & 0xff) +/* Maximum number of ports under a hub device */ +#define XHCI_MAX_PORTS(p) (((p) & 0xff) << 24) + +/* tt_info bitmasks */ +/* + * TT Hub Slot ID - for low or full speed devices attached to a high-speed hub + * The Slot ID of the hub that isolates the high speed signaling from + * this low or full-speed device. '0' if attached to root hub port. + */ +#define TT_SLOT 0xff +/* + * The number of the downstream facing port of the high-speed hub + * '0' if the device is not low or full speed. + */ +#define TT_PORT (0xff << 8) +#define TT_THINK_TIME(p) (((p) & 0x3) << 16) + +/* dev_state bitmasks */ +/* USB device address - assigned by the HC */ +#define DEV_ADDR_MASK 0xff +/* bits 8:26 reserved */ +/* Slot state */ +#define SLOT_STATE (0x1f << 27) +#define GET_SLOT_STATE(p) (((p) & (0x1f << 27)) >> 27) + +#define SLOT_STATE_DISABLED 0x0 +#define SLOT_STATE_ENABLED SLOT_STATE_DISABLED +#define SLOT_STATE_DEFAULT 0x1 +#define SLOT_STATE_ADDRESSED 0x2 +#define SLOT_STATE_CONFIGURED 0x3 + +/** + * struct xhci_ep_ctx + * @ep_info: endpoint state, streams, mult, and interval information. + * @ep_info2: information on endpoint type, max packet size, max burst size, + * error count, and whether the HC will force an event for all + * transactions. + * @deq: 64-bit ring dequeue pointer address. If the endpoint only + * defines one stream, this points to the endpoint transfer ring. + * Otherwise, it points to a stream context array, which has a + * ring pointer for each flow. + * @tx_info: Average TRB lengths for the endpoint ring and + * max payload within an Endpoint Service Interval Time (ESIT). + * + * Endpoint Context - section 6.2.1.2. This assumes the HC uses 32-byte context + * structures. If the HC uses 64-byte contexts, there is an additional 32 bytes + * reserved at the end of the endpoint context for HC internal use. + */ +struct xhci_ep_ctx { + __le32 ep_info; + __le32 ep_info2; + __le64 deq; + __le32 tx_info; + /* offset 0x14 - 0x1f reserved for HC internal use */ + __le32 reserved[3]; +}; + +/* ep_info bitmasks */ +/* + * Endpoint State - bits 0:2 + * 0 - disabled + * 1 - running + * 2 - halted due to halt condition - ok to manipulate endpoint ring + * 3 - stopped + * 4 - TRB error + * 5-7 - reserved + */ +#define EP_STATE_MASK 0xf +#define EP_STATE_DISABLED 0x0 +#define EP_STATE_RUNNING 0x1 +#define EP_STATE_HALTED 0x2 +#define EP_STATE_STOPPED 0x3 +#define EP_STATE_ERROR 0x4 +/* Mult - Max number of burtst within an interval, in EP companion desc. */ +#define EP_MULT(p) (((p) & 0x3) << 8) +#define CTX_TO_EP_MULT(p) (((p) >> 8) & 0x3) +/* bits 10:14 are Max Primary Streams */ +/* bit 15 is Linear Stream Array */ +/* Interval - period between requests to an endpoint - 125u increments. */ +#define EP_INTERVAL(p) (((p) & 0xff) << 16) +#define EP_INTERVAL_TO_UFRAMES(p) (1 << (((p) >> 16) & 0xff)) +#define CTX_TO_EP_INTERVAL(p) (((p) >> 16) & 0xff) +#define EP_MAXPSTREAMS_MASK (0x1f << 10) +#define EP_MAXPSTREAMS(p) (((p) << 10) & EP_MAXPSTREAMS_MASK) +/* Endpoint is set up with a Linear Stream Array (vs. Secondary Stream Array) */ +#define EP_HAS_LSA BIT(15) + +/* ep_info2 bitmasks */ +/* + * Force Event - generate transfer events for all TRBs for this endpoint + * This will tell the HC to ignore the IOC and ISP flags (for debugging only). + */ +#define FORCE_EVENT BIT(0) +#define ERROR_COUNT(p) (((p) & 0x3) << 1) +#define CTX_TO_EP_TYPE(p) (((p) >> 3) & 0x7) +#define EP_TYPE(p) ((p) << 3) +#define ISOC_OUT_EP 0x1 +#define BULK_OUT_EP 0x2 +#define INT_OUT_EP 0x3 +#define CTRL_EP 0x4 +#define ISOC_IN_EP 0x5 +#define BULK_IN_EP 0x6 +#define INT_IN_EP 0x7 +/* bit 6 reserved */ +/* bit 7 is Host Initiate Disable - for disabling stream selection */ +#define MAX_BURST(p) (((p) & 0xff) << 8) +#define CTX_TO_MAX_BURST(p) (((p) >> 8) & 0xff) +#define MAX_PACKET(p) (((p) & 0xffff) << 16) +#define MAX_PACKET_MASK (0xffff << 16) +#define MAX_PACKET_DECODED(p) (((p) >> 16) & 0xffff) + +/* Get max packet size from ep desc. Bit 10..0 specify the max packet size. + * USB2.0 spec 9.6.6. + */ +#define GET_MAX_PACKET(p) ((p) & 0x7ff) + +/* tx_info bitmasks */ +#define AVG_TRB_LENGTH_FOR_EP(p) ((p) & 0xffff) +#define MAX_ESIT_PAYLOAD_FOR_EP(p) (((p) & 0xffff) << 16) +#define CTX_TO_MAX_ESIT_PAYLOAD(p) (((p) >> 16) & 0xffff) + +/* deq bitmasks */ +#define EP_CTX_CYCLE_MASK BIT(0) +#define SCTX_DEQ_MASK (~0xfL) + +/** + * struct xhci_input_control_context + * Input control context; see section 6.2.5. + * + * @drop_context: set the bit of the endpoint context you want to disable + * @add_context: set the bit of the endpoint context you want to enable + */ +struct xhci_input_control_ctx { + __le32 drop_flags; + __le32 add_flags; + __le32 rsvd2[6]; +}; + +#define EP_IS_ADDED(ctrl_ctx, i) \ + (le32_to_cpu(ctrl_ctx->add_flags) & (1 << (i + 1))) +#define EP_IS_DROPPED(ctrl_ctx, i) \ + (le32_to_cpu(ctrl_ctx->drop_flags) & (1 << (i + 1))) + +/* drop context bitmasks */ +#define DROP_EP(x) BIT(x) +/* add context bitmasks */ +#define ADD_EP(x) BIT(x) + +struct xhci_stream_ctx { + /* 64-bit stream ring address, cycle state, and stream type */ + __le64 stream_ring; + /* offset 0x14 - 0x1f reserved for HC internal use */ + __le32 reserved[2]; +}; + +/* Stream Context Types (section 6.4.1) - bits 3:1 of stream ctx deq ptr */ +#define SCT_FOR_CTX(p) (((p) & 0x7) << 1) +/* Secondary stream array type, dequeue pointer is to a transfer ring */ +#define SCT_SEC_TR 0x0 +/* Primary stream array type, dequeue pointer is to a transfer ring */ +#define SCT_PRI_TR 0x1 +/* Dequeue pointer is for a secondary stream array (SSA) with 8 entries */ +#define SCT_SSA_8 0x2 +#define SCT_SSA_16 0x3 +#define SCT_SSA_32 0x4 +#define SCT_SSA_64 0x5 +#define SCT_SSA_128 0x6 +#define SCT_SSA_256 0x7 + +#define SMALL_STREAM_ARRAY_SIZE 256 +#define MEDIUM_STREAM_ARRAY_SIZE 1024 + +/* "Block" sizes in bytes the hardware uses for different device speeds. + * The logic in this part of the hardware limits the number of bits the hardware + * can use, so must represent bandwidth in a less precise manner to mimic what + * the scheduler hardware computes. + */ +#define FS_BLOCK 1 +#define HS_BLOCK 4 +#define SS_BLOCK 16 +#define DMI_BLOCK 32 + +/* Each device speed has a protocol overhead (CRC, bit stuffing, etc) associated + * with each byte transferred. SuperSpeed devices have an initial overhead to + * set up bursts. These are in blocks, see above. LS overhead has already been + * translated into FS blocks. + */ +#define DMI_OVERHEAD 8 +#define DMI_OVERHEAD_BURST 4 +#define SS_OVERHEAD 8 +#define SS_OVERHEAD_BURST 32 +#define HS_OVERHEAD 26 +#define FS_OVERHEAD 20 +#define LS_OVERHEAD 128 + +/* The TTs need to claim roughly twice as much bandwidth (94 bytes per + * microframe ~= 24Mbps) of the HS bus as the devices can actually use because + * of overhead associated with split transfers crossing microframe boundaries. + * 31 blocks is pure protocol overhead. + */ +#define TT_HS_OVERHEAD (31 + 94) +#define TT_DMI_OVERHEAD (25 + 12) + +/* Bandwidth limits in blocks */ +#define FS_BW_LIMIT 1285 +#define TT_BW_LIMIT 1320 +#define HS_BW_LIMIT 1607 +#define SS_BW_LIMIT_IN 3906 +#define DMI_BW_LIMIT_IN 3906 +#define SS_BW_LIMIT_OUT 3906 +#define DMI_BW_LIMIT_OUT 3906 + +/* Percentage of bus bandwidth reserved for non-periodic transfers */ +#define FS_BW_RESERVED 10 +#define HS_BW_RESERVED 20 +#define SS_BW_RESERVED 10 + +enum xhci_overhead_type { + LS_OVERHEAD_TYPE = 0, + FS_OVERHEAD_TYPE, + HS_OVERHEAD_TYPE, +}; + +struct xhci_transfer_event { + /* 64-bit buffer address, or immediate data */ + __le64 buffer; + __le32 transfer_len; + /* This field is interpreted differently based on the type of TRB */ + __le32 flags; +}; + +/* Transfer event TRB length bit mask */ +/* bits 0:23 */ +#define EVENT_TRB_LEN(p) ((p) & 0xffffff) + +/** Transfer Event bit fields **/ +#define TRB_TO_EP_ID(p) (((p) >> 16) & 0x1f) + +/* Completion Code - only applicable for some types of TRBs */ +#define COMP_CODE_MASK (0xff << 24) +#define GET_COMP_CODE(p) (((p) & COMP_CODE_MASK) >> 24) +#define COMP_SUCCESS 1 +/* Data Buffer Error */ +#define COMP_DB_ERR 2 +/* Babble Detected Error */ +#define COMP_BABBLE 3 +/* USB Transaction Error */ +#define COMP_TX_ERR 4 +/* TRB Error - some TRB field is invalid */ +#define COMP_TRB_ERR 5 +/* Stall Error - USB device is stalled */ +#define COMP_STALL 6 +/* Resource Error - HC doesn't have memory for that device configuration */ +#define COMP_ENOMEM 7 +/* Bandwidth Error - not enough room in schedule for this dev config */ +#define COMP_BW_ERR 8 +/* No Slots Available Error - HC ran out of device slots */ +#define COMP_ENOSLOTS 9 +/* Invalid Stream Type Error */ +#define COMP_STREAM_ERR 10 +/* Slot Not Enabled Error - doorbell rung for disabled device slot */ +#define COMP_EBADSLT 11 +/* Endpoint Not Enabled Error */ +#define COMP_EBADEP 12 +/* Short Packet */ +#define COMP_SHORT_TX 13 +/* Ring Underrun - doorbell rung for an empty isoc OUT ep ring */ +#define COMP_UNDERRUN 14 +/* Ring Overrun - isoc IN ep ring is empty when ep is scheduled to RX */ +#define COMP_OVERRUN 15 +/* Virtual Function Event Ring Full Error */ +#define COMP_VF_FULL 16 +/* Parameter Error - Context parameter is invalid */ +#define COMP_EINVAL 17 +/* Bandwidth Overrun Error - isoc ep exceeded its allocated bandwidth */ +#define COMP_BW_OVER 18 +/* Context State Error - illegal context state transition requested */ +#define COMP_CTX_STATE 19 +/* No Ping Response Error - HC didn't get PING_RESPONSE in time to TX */ +#define COMP_PING_ERR 20 +/* Event Ring is full */ +#define COMP_ER_FULL 21 +/* Incompatible Device Error */ +#define COMP_DEV_ERR 22 +/* Missed Service Error - HC couldn't service an isoc ep within interval */ +#define COMP_MISSED_INT 23 +/* Successfully stopped command ring */ +#define COMP_CMD_STOP 24 +/* Successfully aborted current command and stopped command ring */ +#define COMP_CMD_ABORT 25 +/* Stopped - transfer was terminated by a stop endpoint command */ +#define COMP_STOP 26 +/* Same as COMP_EP_STOPPED, but the transferred length in the event is invalid */ +#define COMP_STOP_INVAL 27 +/* Control Abort Error - Debug Capability - control pipe aborted */ +#define COMP_DBG_ABORT 28 +/* Max Exit Latency Too Large Error */ +#define COMP_MEL_ERR 29 +/* TRB type 30 reserved */ +/* Isoc Buffer Overrun - an isoc IN ep sent more data than could fit in TD */ +#define COMP_BUFF_OVER 31 +/* Event Lost Error - xHC has an "internal event overrun condition" */ +#define COMP_ISSUES 32 +/* Undefined Error - reported when other error codes don't apply */ +#define COMP_UNKNOWN 33 +/* Invalid Stream ID Error */ +#define COMP_STRID_ERR 34 +/* Secondary Bandwidth Error - may be returned by a Configure Endpoint cmd */ +#define COMP_2ND_BW_ERR 35 +/* Split Transaction Error */ +#define COMP_SPLIT_ERR 36 + +struct xhci_link_trb { + /* 64-bit segment pointer*/ + __le64 segment_ptr; + __le32 intr_target; + __le32 control; +}; + +/* control bitfields */ +#define LINK_TOGGLE BIT(1) + +/* Command completion event TRB */ +struct xhci_event_cmd { + /* Pointer to command TRB, or the value passed by the event data trb */ + __le64 cmd_trb; + __le32 status; + __le32 flags; +}; + +/* flags bitmasks */ + +/* Address device - disable SetAddress */ +#define TRB_BSR BIT(9) +enum xhci_setup_dev { + SETUP_CONTEXT_ONLY, + SETUP_CONTEXT_ADDRESS, +}; + +/* bits 16:23 are the virtual function ID */ +/* bits 24:31 are the slot ID */ +#define TRB_TO_SLOT_ID(p) (((p) & (0xff<<24)) >> 24) +#define SLOT_ID_FOR_TRB(p) (((p) & 0xff) << 24) + +/* Configure Endpoint Command TRB - deconfigure */ +#define TRB_DC BIT(9) + +/* Stop Endpoint TRB - ep_index to endpoint ID for this TRB */ +#define TRB_TO_EP_INDEX(p) ((((p) & (0x1f << 16)) >> 16) - 1) +#define EP_ID_FOR_TRB(p) ((((p) + 1) & 0x1f) << 16) + +#define SUSPEND_PORT_FOR_TRB(p) (((p) & 1) << 23) +#define TRB_TO_SUSPEND_PORT(p) (((p) & (1 << 23)) >> 23) +#define LAST_EP_INDEX 30 + +/* Set TR Dequeue Pointer command TRB fields, 6.4.3.9 */ +#define TRB_TO_STREAM_ID(p) ((((p) & (0xffff << 16)) >> 16)) +#define STREAM_ID_FOR_TRB(p) ((((p)) & 0xffff) << 16) +#define SCT_FOR_TRB(p) (((p) << 1) & 0x7) + +/* Port Status Change Event TRB fields */ +/* Port ID - bits 31:24 */ +#define GET_PORT_ID(p) (((p) & (0xff << 24)) >> 24) + +/* Normal TRB fields */ +/* transfer_len bitmasks - bits 0:16 */ +#define TRB_LEN(p) ((p) & 0x1ffff) +/* Interrupter Target - which MSI-X vector to target the completion event at */ +#define TRB_INTR_TARGET(p) (((p) & 0x3ff) << 22) +#define GET_INTR_TARGET(p) (((p) >> 22) & 0x3ff) +#define TRB_TBC(p) (((p) & 0x3) << 7) +#define TRB_TLBPC(p) (((p) & 0xf) << 16) + +/* Cycle bit - indicates TRB ownership by HC or HCD */ +#define TRB_CYCLE BIT(0) +/* + * Force next event data TRB to be evaluated before task switch. + * Used to pass OS data back after a TD completes. + */ +#define TRB_ENT BIT(1) +/* Interrupt on short packet */ +#define TRB_ISP BIT(2) +/* Set PCIe no snoop attribute */ +#define TRB_NO_SNOOP BIT(3) +/* Chain multiple TRBs into a TD */ +#define TRB_CHAIN BIT(4) +/* Interrupt on completion */ +#define TRB_IOC BIT(5) +/* The buffer pointer contains immediate data */ +#define TRB_IDT BIT(6) + +/* Block Event Interrupt */ +#define TRB_BEI BIT(9) + +/* Control transfer TRB specific fields */ +#define TRB_DIR_IN BIT(16) +#define TRB_TX_TYPE(p) ((p) << 16) +#define TRB_DATA_OUT 2 +#define TRB_DATA_IN 3 + +/* Isochronous TRB specific fields */ +#define TRB_SIA BIT(31) + +struct xhci_generic_trb { + __le32 field[4]; +}; + +union xhci_trb { + struct xhci_link_trb link; + struct xhci_transfer_event trans_event; + struct xhci_event_cmd event_cmd; + struct xhci_generic_trb generic; +}; + +/* TRB bit mask */ +#define TRB_TYPE_BITMASK (0xfc00) +#define TRB_TYPE(p) ((p) << 10) +#define TRB_FIELD_TO_TYPE(p) (((p) & TRB_TYPE_BITMASK) >> 10) +/* TRB type IDs */ +/* bulk, interrupt, isoc scatter/gather, and control data stage */ +#define TRB_NORMAL 1 +/* setup stage for control transfers */ +#define TRB_SETUP 2 +/* data stage for control transfers */ +#define TRB_DATA 3 +/* status stage for control transfers */ +#define TRB_STATUS 4 +/* isoc transfers */ +#define TRB_ISOC 5 +/* TRB for linking ring segments */ +#define TRB_LINK 6 +#define TRB_EVENT_DATA 7 +/* Transfer Ring No-op (not for the command ring) */ +#define TRB_TR_NOOP 8 +/* Command TRBs */ +/* Enable Slot Command */ +#define TRB_ENABLE_SLOT 9 +/* Disable Slot Command */ +#define TRB_DISABLE_SLOT 10 +/* Address Device Command */ +#define TRB_ADDR_DEV 11 +/* Configure Endpoint Command */ +#define TRB_CONFIG_EP 12 +/* Evaluate Context Command */ +#define TRB_EVAL_CONTEXT 13 +/* Reset Endpoint Command */ +#define TRB_RESET_EP 14 +/* Stop Transfer Ring Command */ +#define TRB_STOP_RING 15 +/* Set Transfer Ring Dequeue Pointer Command */ +#define TRB_SET_DEQ 16 +/* Reset Device Command */ +#define TRB_RESET_DEV 17 +/* Force Event Command (opt) */ +#define TRB_FORCE_EVENT 18 +/* Negotiate Bandwidth Command (opt) */ +#define TRB_NEG_BANDWIDTH 19 +/* Set Latency Tolerance Value Command (opt) */ +#define TRB_SET_LT 20 +/* Get port bandwidth Command */ +#define TRB_GET_BW 21 +/* Force Header Command - generate a transaction or link management packet */ +#define TRB_FORCE_HEADER 22 +/* No-op Command - not for transfer rings */ +#define TRB_CMD_NOOP 23 +/* TRB IDs 24-31 reserved */ +/* Event TRBS */ +/* Transfer Event */ +#define TRB_TRANSFER 32 +/* Command Completion Event */ +#define TRB_COMPLETION 33 +/* Port Status Change Event */ +#define TRB_PORT_STATUS 34 +/* Bandwidth Request Event (opt) */ +#define TRB_BANDWIDTH_EVENT 35 +/* Doorbell Event (opt) */ +#define TRB_DOORBELL 36 +/* Host Controller Event */ +#define TRB_HC_EVENT 37 +/* Device Notification Event - device sent function wake notification */ +#define TRB_DEV_NOTE 38 +/* MFINDEX Wrap Event - microframe counter wrapped */ +#define TRB_MFINDEX_WRAP 39 +/* TRB IDs 40-47 reserved, 48-63 is vendor-defined */ + +/* Nec vendor-specific command completion event. */ +#define TRB_NEC_CMD_COMP 48 +/* Get NEC firmware revision. */ +#define TRB_NEC_GET_FW 49 + +#define TRB_TYPE_LINK(x) (((x) & TRB_TYPE_BITMASK) == TRB_TYPE(TRB_LINK)) +/* Above, but for __le32 types -- can avoid work by swapping constants: */ +#define TRB_TYPE_LINK_LE32(x) \ + (((x) & cpu_to_le32(TRB_TYPE_BITMASK)) == cpu_to_le32(TRB_TYPE(TRB_LINK))) +#define TRB_TYPE_NOOP_LE32(x) \ + (((x) & cpu_to_le32(TRB_TYPE_BITMASK)) == cpu_to_le32(TRB_TYPE(TRB_TR_NOOP))) + +#define NEC_FW_MINOR(p) (((p) >> 0) & 0xff) +#define NEC_FW_MAJOR(p) (((p) >> 8) & 0xff) + +/* + * TRBS_PER_SEGMENT must be a multiple of 4, + * since the command ring is 64-byte aligned. + * It must also be greater than 16. + */ +#define TRBS_PER_SEGMENT 64 +/* Allow two commands + a link TRB, along with any reserved command TRBs */ +#define MAX_RSVD_CMD_TRBS (TRBS_PER_SEGMENT - 3) +#define TRB_SEGMENT_SIZE (TRBS_PER_SEGMENT * 16) +#define TRB_SEGMENT_SHIFT (ilog2(TRB_SEGMENT_SIZE)) +/* TRB buffer pointers can't cross 64KB boundaries */ +#define TRB_MAX_BUFF_SHIFT 16 +#define TRB_MAX_BUFF_SIZE (1 << TRB_MAX_BUFF_SHIFT) + +/* xHCI command default timeout value */ +#define XHCI_CMD_DEFAULT_TIMEOUT (5 * SECOND) + +struct xhci_erst_entry { + /* 64-bit event ring segment address */ + __le64 seg_addr; + __le32 seg_size; + /* Set to zero */ + __le32 rsvd; +}; + +/* + * Each segment table entry is 4*32bits long. 1K seems like an ok size: + * (1K bytes * 8bytes/bit) / (4*32 bits) = 64 segment entries in the table, + * meaning 64 ring segments. + * Initial allocated size of the ERST, in number of entries */ +#define ERST_NUM_SEGS 1 +/* Initial allocated size of the ERST, in number of entries */ +#define ERST_SIZE 64 +/* Initial number of event segment rings allocated */ +#define ERST_ENTRIES 1 +/* Poll every 60 seconds */ +#define POLL_TIMEOUT 60 +/* Stop endpoint command timeout (secs) for URB cancellation watchdog timer */ +#define XHCI_STOP_EP_CMD_TIMEOUT 5 +/* XXX: Make these module parameters */ + +/* + * It can take up to 20 ms to transition from RExit to U0 on the + * Intel Lynx Point LP xHCI host. + */ +#define XHCI_MAX_REXIT_TIMEOUT (20 * MSECONDS) + +#define XHCI_MAX_EXT_CAPS 50 + +#define XHCI_EXT_PORT_MAJOR(x) (((x) >> 24) & 0xff) +#define XHCI_EXT_PORT_OFF(x) ((x) & 0xff) +#define XHCI_EXT_PORT_COUNT(x) (((x) >> 8) & 0xff) + +/* Extended capability register fields */ +#define XHCI_EXT_CAPS_ID(p) (((p)>>0)&0xff) +#define XHCI_EXT_CAPS_NEXT(p) (((p)>>8)&0xff) +#define XHCI_EXT_CAPS_VAL(p) ((p)>>16) +/* Extended capability IDs - ID 0 reserved */ +#define XHCI_EXT_CAPS_LEGACY 1 +#define XHCI_EXT_CAPS_PROTOCOL 2 +#define XHCI_EXT_CAPS_PM 3 +#define XHCI_EXT_CAPS_VIRT 4 +#define XHCI_EXT_CAPS_ROUTE 5 +/* IDs 6-9 reserved */ +#define XHCI_EXT_CAPS_DEBUG 10 +/* USB Legacy Support Capability - section 7.1.1 */ +#define XHCI_HC_BIOS_OWNED BIT(16) +#define XHCI_HC_OS_OWNED BIT(24) + +/* USB Legacy Support Capability - section 7.1.1 */ +/* Add this offset, plus the value of xECP in HCCPARAMS to the base address */ +#define XHCI_LEGACY_SUPPORT_OFFSET 0x00 + +/* USB Legacy Support Control and Status Register - section 7.1.2 */ +/* Add this offset, plus the value of xECP in HCCPARAMS to the base address */ +#define XHCI_LEGACY_CONTROL_OFFSET 0x04 +/* bits 1:3, 5:12, and 17:19 need to be preserved; bits 21:28 should be zero */ +#define XHCI_LEGACY_DISABLE_SMI ((0x7 << 1) + (0xff << 5) + (0x7 << 17)) +#define XHCI_LEGACY_SMI_EVENTS (0x7 << 29) + +/* USB 2.0 xHCI 0.96 L1C capability - section 7.2.2.1.3.2 */ +#define XHCI_L1C BIT(16) + +/* USB 2.0 xHCI 1.0 hardware LMP capability - section 7.2.2.1.3.2 */ +#define XHCI_HLC BIT(19) +#define XHCI_BLC BIT(20) + +/* + * Registers should always be accessed with double word or quad word accesses. + * + * Some xHCI implementations may support 64-bit address pointers. Registers + * with 64-bit address pointers should be written to with dword accesses by + * writing the low dword first (ptr[0]), then the high dword (ptr[1]) second. + * xHCI implementations that do not support 64-bit address pointers will ignore + * the high dword, and write order is irrelevant. + */ +static inline u64 xhci_read_64(__le64 __iomem *regs) +{ + __u32 __iomem *ptr = (__u32 __iomem *)regs; + u64 val_lo = readl(ptr); + u64 val_hi = readl(ptr + 1); + return val_lo + (val_hi << 32); +} +static inline void xhci_write_64(const u64 val, __le64 __iomem *regs) +{ + __u32 __iomem *ptr = (__u32 __iomem *)regs; + u32 val_lo = lower_32_bits(val); + u32 val_hi = upper_32_bits(val); + + writel(val_lo, ptr); + writel(val_hi, ptr + 1); +} + +/* + * Barebox xHCI housekeeping structs + */ + +enum xhci_ring_type { + TYPE_CTRL = 0, + TYPE_ISOC, + TYPE_BULK, + TYPE_INTR, + TYPE_STREAM, + TYPE_COMMAND, + TYPE_EVENT, +}; + +struct xhci_ring { + struct list_head list; + union xhci_trb *trbs; + union xhci_trb *enqueue; + union xhci_trb *dequeue; + enum xhci_ring_type type; + int num_trbs; + int cycle_state; +}; + +struct xhci_device_context { + struct xhci_slot_ctx slot; + struct xhci_ep_ctx ep[31]; +}; + +struct xhci_input_context { + struct xhci_input_control_ctx icc; + struct xhci_slot_ctx slot; + struct xhci_ep_ctx ep[31]; +}; + +struct xhci_virtual_device { + struct list_head list; + struct usb_device *udev; + void *dma; + size_t dma_size; + int slot_id; + struct xhci_ring *ep[USB_MAXENDPOINTS]; + struct xhci_input_context *in_ctx; + struct xhci_device_context *out_ctx; +}; + +struct usb_root_hub_info { + struct usb_hub_descriptor hub; + struct usb_device_descriptor device; + struct usb_config_descriptor config; + struct usb_interface_descriptor interface; + struct usb_endpoint_descriptor endpoint; +} __attribute__ ((packed)); + +struct xhci_hcd { + struct usb_host host; + struct device_d *dev; + struct xhci_cap_regs __iomem *cap_regs; + struct xhci_op_regs __iomem *op_regs; + struct xhci_run_regs __iomem *run_regs; + struct xhci_doorbell_array __iomem *dba; + struct xhci_intr_reg __iomem *ir_set; + /* Cached register copies of read-only HC data */ + u32 hcs_params1; + u32 hcs_params2; + u32 hcs_params3; + u32 hcc_capbase; + u32 hcc_params; + u16 hci_version; + int max_slots; + int num_sp; + int page_size; + int page_shift; + void *dma; + size_t dma_size; + __le64 *dcbaa; + void *sp; + __le64 *sp_array; + struct xhci_ring cmd_ring; + struct xhci_ring event_ring; + struct xhci_ring *rings; + struct list_head rings_list; + struct xhci_erst_entry *event_erst; + u8 *port_array; + int rootdev; + struct list_head vdev_list; + u32 *ext_caps; + unsigned int num_ext_caps; + __le32 __iomem **usb_ports; + unsigned int num_usb_ports; + struct usb_root_hub_info usb_info; +}; + +#define to_xhci_hcd(_h) \ + container_of(_h, struct xhci_hcd, host) + +int xhci_handshake(void __iomem *p, u32 mask, u32 done, int usec); + +int xhci_issue_command(struct xhci_hcd *xhci, union xhci_trb *trb); +int xhci_wait_for_event(struct xhci_hcd *xhci, u8 type, union xhci_trb *trb); + +int xhci_virtdev_reset(struct xhci_virtual_device *vdev); +int xhci_virtdev_detach(struct xhci_virtual_device *vdev); + +int xhci_hub_setup_ports(struct xhci_hcd *xhci); +void xhci_hub_port_power(struct xhci_hcd *xhci, int port, bool enable); +int xhci_hub_control(struct usb_device *dev, unsigned long pipe, + void *buffer, int length, struct devrequest *req); + +static inline void xhci_print_trb(struct xhci_hcd *xhci, + union xhci_trb *trb, const char *desc) +{ + dev_dbg(xhci->dev, "%s [%08x %08x %08x %08x]\n", desc, + trb->generic.field[0], trb->generic.field[1], + trb->generic.field[2], trb->generic.field[3]); +} + +#endif diff --git a/include/usb/xhci.h b/include/usb/xhci.h new file mode 100644 index 0000000000..1a3138b132 --- /dev/null +++ b/include/usb/xhci.h @@ -0,0 +1,33 @@ +/* + * xHCI host controller driver + * + * Sebastian Hesselbarth + * + * Some code borrowed from the Linux xHCI driver + * Author: Sarah Sharp + * Copyright (C) 2008 Intel Corp. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 as + * published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, but + * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY + * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + * for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#ifndef __XHCI_HCD_H +#define __XHCI_HCD_H + +struct xhci_data { + void __iomem *regs; +}; + +int xhci_register(struct device_d *dev, struct xhci_data *data); + +#endif -- cgit v1.2.3 From b8d2d4923f199d25d6465e3ef09a90ca420ca3f7 Mon Sep 17 00:00:00 2001 From: Sebastian Hesselbarth Date: Sat, 26 Jul 2014 17:24:45 +0200 Subject: USB: host: add xHCI PCI driver This adds a driver for PCI-attached xHCI controllers. Signed-off-by: Sebastian Hesselbarth Signed-off-by: Sascha Hauer --- drivers/usb/host/Kconfig | 7 +++++++ drivers/usb/host/Makefile | 1 + drivers/usb/host/xhci-pci.c | 45 +++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 53 insertions(+) create mode 100644 drivers/usb/host/xhci-pci.c diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 8c64a3b99c..c5dc2ea532 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -33,3 +33,10 @@ config USB_XHCI This driver currently only supports virtual USB 2.0 ports, if you plan to use USB 3.0 devices, use a USB 2.0 cable in between. + +config USB_XHCI_PCI + depends on PCI + select USB_XHCI + bool "PCI xHCI driver" + help + Enables support for PCI attached xHCI controllers. diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index a5c009ebd6..0478d34272 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -4,3 +4,4 @@ obj-$(CONFIG_USB_EHCI_ATMEL) += ehci-atmel.o obj-$(CONFIG_USB_OHCI) += ohci-hcd.o obj-$(CONFIG_USB_OHCI_AT91) += ohci-at91.o obj-$(CONFIG_USB_XHCI) += xhci-hcd.o xhci-hub.o +obj-$(CONFIG_USB_XHCI_PCI) += xhci-pci.o diff --git a/drivers/usb/host/xhci-pci.c b/drivers/usb/host/xhci-pci.c new file mode 100644 index 0000000000..a140b1dd07 --- /dev/null +++ b/drivers/usb/host/xhci-pci.c @@ -0,0 +1,45 @@ +/* + * PCI driver for xHCI controllers + * + * Sebastian Hesselbarth + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include +#include +#include +#include +#include + +static int xhci_pci_probe(struct pci_dev *pdev, const struct pci_device_id *id) +{ + struct xhci_data data = {}; + + pci_enable_device(pdev); + pci_set_master(pdev); + + data.regs = pci_iomap(pdev, 0); + + return xhci_register(&pdev->dev, &data); +} + +static DEFINE_PCI_DEVICE_TABLE(xhci_pci_tbl) = { + /* handle any USB 3.0 xHCI controller */ + { PCI_DEVICE_CLASS(PCI_CLASS_SERIAL_USB_XHCI, ~0), }, + { }, +}; + +static struct pci_driver xhci_pci_driver = { + .name = "xHCI PCI", + .id_table = xhci_pci_tbl, + .probe = xhci_pci_probe, +}; + +static int xhci_pci_init(void) +{ + return pci_register_driver(&xhci_pci_driver); +} +device_initcall(xhci_pci_init); -- cgit v1.2.3 From 92466472c2116b93ef5ed957a559fe84b9d88c80 Mon Sep 17 00:00:00 2001 From: Antony Pavlov Date: Thu, 24 Jul 2014 19:55:37 +0400 Subject: usb: ehci: use linux-way ehci_readl and ehci_writel Also drop nowhere defined CONFIG_EHCI_DESC_BIG_ENDIAN. Signed-off-by: Antony Pavlov Signed-off-by: Sascha Hauer --- drivers/usb/host/ehci.h | 19 +++++++++++-------- 1 file changed, 11 insertions(+), 8 deletions(-) diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 8cc477aa4f..d71d0565e8 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -18,6 +18,8 @@ #ifndef USB_EHCI_H #define USB_EHCI_H +#include + #if !defined(CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS) #define CONFIG_SYS_USB_EHCI_MAX_ROOT_PORTS 16 #endif @@ -68,14 +70,15 @@ struct ehci_hcor { #define USBMODE_CM_HC (3 << 0) /* host controller mode */ #define USBMODE_CM_IDLE (0 << 0) /* idle state */ -#if defined CONFIG_EHCI_DESC_BIG_ENDIAN -#define ehci_readl(x) (*((volatile u32 *)(x))) -#define ehci_writel(a, b) (*((volatile u32 *)(a)) = ((volatile u32)b)) -#else -#define ehci_readl(x) cpu_to_le32((*((volatile u32 *)(x)))) -#define ehci_writel(a, b) (*((volatile u32 *)(a)) = \ - cpu_to_le32(((volatile u32)b))) -#endif +static inline void ehci_writel(__u32 __iomem *regs, const unsigned int val) +{ + writel(val, regs); +} + +static inline unsigned int ehci_readl(__u32 __iomem *regs) +{ + return readl(regs); +} #if defined CONFIG_EHCI_MMIO_BIG_ENDIAN #define hc32_to_cpu(x) be32_to_cpu((x)) -- cgit v1.2.3