diff options
Diffstat (limited to 'drivers/usb/dwc2/rhub.c')
-rw-r--r-- | drivers/usb/dwc2/rhub.c | 384 |
1 files changed, 384 insertions, 0 deletions
diff --git a/drivers/usb/dwc2/rhub.c b/drivers/usb/dwc2/rhub.c new file mode 100644 index 0000000000..de4d2837ac --- /dev/null +++ b/drivers/usb/dwc2/rhub.c @@ -0,0 +1,384 @@ +// SPDX-License-Identifier: GPL-2.0-or-later +#include "dwc2.h" + +static struct descriptor { + 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; +} __packed descriptor = { + .hub = { + .bLength = USB_DT_HUB_NONVAR_SIZE + + ((USB_MAXCHILDREN + 1 + 7) / 8), + .bDescriptorType = USB_DT_HUB, + .bNbrPorts = 1, + .wHubCharacteristics = 0, + .bPwrOn2PwrGood = 0, + .bHubContrCurrent = 0, + .u.hs.DeviceRemovable = {0xff}, + .u.hs.PortPwrCtrlMask = {} + }, + .device = { + .bLength = USB_DT_DEVICE_SIZE, + .bDescriptorType = USB_DT_DEVICE, + .bcdUSB = cpu_to_le16(2), /* v2.0 */ + .bDeviceClass = USB_CLASS_HUB, + .bDeviceSubClass = 0, + .bDeviceProtocol = USB_HUB_PR_HS_NO_TT, + .bMaxPacketSize0 = 64, + .idVendor = 0x0000, + .idProduct = 0x0000, + .bcdDevice = 0x0000, + .iManufacturer = 1, + .iProduct = 2, + .iSerialNumber = 0, + .bNumConfigurations = 1 + }, + .config = { + .bLength = USB_DT_CONFIG_SIZE, + .bDescriptorType = USB_DT_CONFIG, + .wTotalLength = 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 = 1 | USB_DIR_IN, /* 0x81 */ + .bmAttributes = USB_ENDPOINT_XFER_INT, + .wMaxPacketSize = cpu_to_le16( + (USB_MAXCHILDREN + 1 + 7) / 8), + .bInterval = 255 + }, +}; + +static int dwc2_get_port_status(struct dwc2 *dwc2, struct usb_device *dev, + void *buf, int len) +{ + struct usb_port_status *portsts; + uint32_t hprt0; + uint32_t status = 0; + uint32_t change = 0; + int speed; + + if (!buf || len < sizeof(*portsts)) + return -1; + + hprt0 = dwc2_readl(dwc2, HPRT0); + + if (hprt0 & HPRT0_CONNSTS) + status |= USB_PORT_STAT_CONNECTION; + if (hprt0 & HPRT0_ENA) + status |= USB_PORT_STAT_ENABLE; + if (hprt0 & HPRT0_SUSP) + status |= USB_PORT_STAT_SUSPEND; + if (hprt0 & HPRT0_OVRCURRACT) + status |= USB_PORT_STAT_OVERCURRENT; + if (hprt0 & HPRT0_RST) + status |= USB_PORT_STAT_RESET; + if (hprt0 & HPRT0_PWR) + status |= USB_PORT_STAT_POWER; + + speed = (hprt0 & HPRT0_SPD_MASK) >> HPRT0_SPD_SHIFT; + if (speed == HPRT0_SPD_HIGH_SPEED) + status |= USB_PORT_STAT_HIGH_SPEED; + else if (speed == HPRT0_SPD_LOW_SPEED) + status |= USB_PORT_STAT_LOW_SPEED; + + if (hprt0 & HPRT0_ENACHG) + change |= USB_PORT_STAT_C_ENABLE; + if (hprt0 & HPRT0_CONNDET) + change |= USB_PORT_STAT_C_CONNECTION; + if (hprt0 & HPRT0_OVRCURRCHG) + change |= USB_PORT_STAT_C_OVERCURRENT; + + portsts = buf; + portsts->wPortStatus = cpu_to_le16(status); + portsts->wPortChange = cpu_to_le16(change); + + dev->act_len = sizeof(*portsts); + dev->status = 0; + + return 0; +} + +static int dwc2_get_hub_status(struct dwc2 *dwc2, struct usb_device *dev, + void *buf, int len) +{ + if (!buf || len < 4) + return -1; + + *(uint32_t *)buf = 0; + dev->act_len = 4; + dev->status = 0; + + return 0; +} + +static int dwc2_get_hub_descriptor(struct dwc2 *dwc2, struct usb_device *dev, + void *buf, int len) +{ + if (!buf) + return -1; + + dev->act_len = min_t(int, len, descriptor.hub.bLength); + dev->status = 0; + memcpy(buf, &descriptor.hub, dev->act_len); + + return 0; +} + +static void strle16(__le16 *dest, char *src, size_t n) +{ + unsigned int i; + + for (i = 0; i < n && *src != '\0'; i++, src++) + dest[i] = cpu_to_le16(*src); +} + +static int dwc2_get_string_descriptor(struct dwc2 *dwc2, struct usb_device *dev, + void *buf, int len, int index) +{ + char *src, *str = buf; + __le16 *le16 = (__le16 *)(str + 2); + int size; + + if (!buf || len < 2) + return -1; + + switch (index) { + case 0: /* Language */ + src = "\x09\x04"; + size = strlen(src) + 2; + len = min_t(int, len, size); + + str[0] = size; + str[1] = 0x03; + memcpy(str + 2, src, len - 2); + break; + case 1: /* Vendor */ + src = "u-boot"; + size = 2 * strlen(src) + 2; + len = min_t(int, len, size); + + str[0] = size; + str[1] = 0x03; + strle16(le16, src, (len - 2) / 2); + break; + case 2: /* Product */ + src = "DWC2 root hub"; + size = 2 * strlen(src) + 2; + len = min_t(int, len, size); + + str[0] = size; + str[1] = 0x03; + strle16(le16, src, (len - 2) / 2); + break; + default: + dwc2_err(dwc2, "roothub: unknown string descriptor: 0x%x\n", + index); + return -1; + } + + dev->act_len = len; + dev->status = 0; + + return 0; +} + +static int dwc2_get_descriptor(struct dwc2 *dwc2, struct usb_device *dev, + void *buf, int len, int value) +{ + int index = value >> 8; + + if (!buf || len < 0) + return -1; + + switch (index) { + case USB_DT_DEVICE: + len = min(len, (int)descriptor.device.bLength); + memcpy(buf, &descriptor.device, len); + break; + case USB_DT_CONFIG: + len = min(len, (int)descriptor.config.wTotalLength); + memcpy(buf, &descriptor.config, len); + break; + case USB_DT_STRING: + value &= 0xff; + return dwc2_get_string_descriptor(dwc2, dev, buf, len, value); + default: + dwc2_err(dwc2, "roothub: unknown descriptor: 0x%x\n", index); + return -1; + } + + dev->act_len = len; + dev->status = 0; + + return 0; +} + +static int dwc2_set_port_feature(struct dwc2 *dwc2, struct usb_device *dev, + int feature) +{ + uint32_t hprt0; + + hprt0 = dwc2_readl(dwc2, HPRT0); + hprt0 &= ~(HPRT0_ENA | HPRT0_CONNDET | HPRT0_ENACHG | HPRT0_OVRCURRCHG); + + switch (feature) { + case USB_PORT_FEAT_SUSPEND: + break; + case USB_PORT_FEAT_RESET: + hprt0 |= HPRT0_RST; + dwc2_writel(dwc2, hprt0, HPRT0); + + mdelay(60); + + hprt0 = dwc2_readl(dwc2, HPRT0); + hprt0 &= ~HPRT0_RST; + dwc2_writel(dwc2, hprt0, HPRT0); + break; + case USB_PORT_FEAT_POWER: + break; + case USB_PORT_FEAT_ENABLE: + /* Set by the core after a reset */ + break; + default: + dwc2_dbg(dwc2, "roothub: unsupported set port feature 0x%x\n", + feature); + return -1; + } + + dev->act_len = 0; + dev->status = 0; + + return 0; +} + +static int dwc2_clear_port_feature(struct dwc2 *dwc2, struct usb_device *dev, + int feature) +{ + uint32_t hprt0; + + hprt0 = dwc2_readl(dwc2, HPRT0); + hprt0 &= ~(HPRT0_ENA | HPRT0_CONNDET | HPRT0_ENACHG | HPRT0_OVRCURRCHG); + + switch (feature) { + case USB_PORT_FEAT_ENABLE: + hprt0 |= HPRT0_ENA; + break; + case USB_PORT_FEAT_SUSPEND: + break; + case USB_PORT_FEAT_POWER: + break; + case USB_PORT_FEAT_C_CONNECTION: + hprt0 |= HPRT0_CONNDET; + break; + case USB_PORT_FEAT_C_ENABLE: + hprt0 |= HPRT0_ENACHG; + break; + case USB_PORT_FEAT_C_OVER_CURRENT: + hprt0 |= HPRT0_OVRCURRCHG; + break; + default: + dwc2_dbg(dwc2, "roothub: unsupported clear port feature 0x%x\n", + feature); + return -1; + } + + dwc2_writel(dwc2, hprt0, HPRT0); + + dev->act_len = 0; + dev->status = 0; + + return 0; +} + +static int dwc2_set_address(struct dwc2 *dwc2, struct usb_device *dev, int addr) +{ + dwc2_dbg(dwc2, "roothub: set address to %d\n", addr); + dwc2->root_hub_devnum = addr; + + dev->act_len = 0; + dev->status = 0; + + return 0; +} + +int dwc2_submit_roothub(struct dwc2 *dwc2, struct usb_device *dev, + unsigned long pipe, void *buf, int len, + struct devrequest *setup) +{ + unsigned char reqtype = setup->requesttype; + unsigned char request = setup->request; + unsigned short value = le16_to_cpu(setup->value); + unsigned short size = le16_to_cpu(setup->length); + int minlen = min_t(int, len, size); + + if (usb_pipeint(pipe)) { + dwc2_err(dwc2, "roothub: submit IRQ NOT implemented\n"); + return 0; + } + + dev->act_len = 0; + dev->status = USB_ST_STALLED; + +#define REQ(l, u) ((l) | ((u) << 8)) + + switch (REQ(request, reqtype)) { + case REQ(USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB): + return dwc2_get_hub_descriptor(dwc2, dev, buf, minlen); + + case REQ(USB_REQ_GET_DESCRIPTOR, USB_DIR_IN): + return dwc2_get_descriptor(dwc2, dev, buf, minlen, value); + + case REQ(USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_HUB): + return dwc2_get_hub_status(dwc2, dev, buf, len); + + case REQ(USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT): + return dwc2_get_port_status(dwc2, dev, buf, len); + + case REQ(USB_REQ_SET_FEATURE, USB_DIR_OUT | USB_RT_PORT): + return dwc2_set_port_feature(dwc2, dev, value); + + case REQ(USB_REQ_CLEAR_FEATURE, USB_DIR_OUT | USB_RT_PORT): + return dwc2_clear_port_feature(dwc2, dev, value); + + case REQ(USB_REQ_SET_ADDRESS, USB_DIR_OUT): + return dwc2_set_address(dwc2, dev, value); + + case REQ(USB_REQ_SET_CONFIGURATION, USB_DIR_OUT): + dev->act_len = 0; + dev->status = 0; + return 0; + + case REQ(USB_REQ_GET_CONFIGURATION, USB_DIR_IN): + *(char *)buf = 1; + dev->act_len = 1; + dev->status = 0; + return 0; + } + + dwc2_err(dwc2, "roothub: unsupported request 0x%x requesttype 0x%x\n", + request, reqtype); + + return 0; +} |