220 lines
6.3 KiB
C
220 lines
6.3 KiB
C
/*****************************************************************************
|
|
* Copyright (c) 2013 IBM Corporation
|
|
* All rights reserved.
|
|
* This program and the accompanying materials
|
|
* are made available under the terms of the BSD License
|
|
* which accompanies this distribution, and is available at
|
|
* http://www.opensource.org/licenses/bsd-license.php
|
|
*
|
|
* Contributors:
|
|
* IBM Corporation - initial implementation
|
|
*****************************************************************************/
|
|
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
#include "usb-core.h"
|
|
#include "usb-xhci.h"
|
|
|
|
#undef HUB_DEBUG
|
|
//#define HUB_DEBUG
|
|
#ifdef HUB_DEBUG
|
|
#define dprintf(_x ...) do { printf(_x); } while(0)
|
|
#else
|
|
#define dprintf(_x ...)
|
|
#endif
|
|
|
|
/*
|
|
* USB Spec 1.1
|
|
* 11.16.2 Class-specific Requests
|
|
*/
|
|
struct usb_hub_ps {
|
|
uint16_t wPortStatus;
|
|
uint16_t wPortChange;
|
|
} __attribute__((packed));
|
|
|
|
#define HUB_PS_CONNECTION (1 << 0)
|
|
#define HUB_PS_ENABLE (1 << 1)
|
|
#define HUB_PS_SUSPEND (1 << 2)
|
|
#define HUB_PS_OVER_CURRENT (1 << 3)
|
|
#define HUB_PS_RESET (1 << 4)
|
|
#define HUB_PS_POWER (1 << 8)
|
|
#define HUB_PS_LOW_SPEED (1 << 9)
|
|
#define HUB_PS_HIGH_SPEED (1 << 10)
|
|
|
|
#define HUB_PF_CONNECTION 0
|
|
#define HUB_PF_ENABLE 1
|
|
#define HUB_PF_SUSPEND 2
|
|
#define HUB_PF_OVER_CURRENT 3
|
|
#define HUB_PF_RESET 4
|
|
#define HUB_PF_POWER 8
|
|
#define HUB_PF_LOWSPEED 9
|
|
#define HUB_PF_C_CONNECTION 16
|
|
#define HUB_PF_C_ENABLE 17
|
|
#define HUB_PF_C_SUSPEND 18
|
|
#define HUB_PF_C_OVER_CURRENT 19
|
|
#define HUB_PF_C_RESET 20
|
|
|
|
static int usb_get_hub_desc(struct usb_dev *dev, void *data, size_t size)
|
|
{
|
|
struct usb_dev_req req;
|
|
if (!dev)
|
|
return false;
|
|
req.bmRequestType = REQT_DIR_IN | REQT_TYPE_CLASS | REQT_REC_DEVICE;
|
|
req.bRequest = REQ_GET_DESCRIPTOR;
|
|
req.wIndex = 0;
|
|
req.wLength = cpu_to_le16((uint16_t) size);
|
|
req.wValue = cpu_to_le16(DESCR_TYPE_HUB << 8);
|
|
return usb_send_ctrl(dev->control, &req, data);
|
|
}
|
|
|
|
static int hub_get_port_status(struct usb_dev *dev, int port, void *data, size_t size)
|
|
{
|
|
struct usb_dev_req req;
|
|
if (!dev)
|
|
return false;
|
|
req.bmRequestType = REQT_DIR_IN | REQT_TYPE_CLASS | REQT_REC_OTHER;
|
|
req.bRequest = REQ_GET_STATUS;
|
|
req.wValue = 0;
|
|
req.wIndex = cpu_to_le16((uint16_t)(port + 1));
|
|
req.wLength = cpu_to_le16((uint16_t)size);
|
|
return usb_send_ctrl(dev->control, &req, data);
|
|
}
|
|
|
|
static int hub_set_port_feature(struct usb_dev *dev, int port, int feature)
|
|
{
|
|
struct usb_dev_req req;
|
|
if (!dev)
|
|
return false;
|
|
req.bmRequestType = REQT_DIR_OUT | REQT_TYPE_CLASS | REQT_REC_OTHER;
|
|
req.bRequest = REQ_SET_FEATURE;
|
|
req.wLength = 0;
|
|
req.wValue = cpu_to_le16((uint16_t)feature);
|
|
req.wIndex = cpu_to_le16((uint16_t)(port + 1));
|
|
return usb_send_ctrl(dev->control, &req, NULL);
|
|
}
|
|
|
|
#if 0
|
|
static int hub_clear_port_feature(struct usb_dev *dev, int port, int feature)
|
|
{
|
|
struct usb_dev_req req;
|
|
if (!dev)
|
|
return false;
|
|
req.bmRequestType = REQT_DIR_OUT | REQT_TYPE_CLASS | REQT_REC_OTHER;
|
|
req.bRequest = REQ_CLEAR_FEATURE;
|
|
req.wLength = 0;
|
|
req.wValue = cpu_to_le16((uint16_t)feature);
|
|
req.wIndex = cpu_to_le16((uint16_t)(port + 1));
|
|
return usb_send_ctrl(dev->control, &req, NULL);
|
|
}
|
|
#endif
|
|
|
|
static int hub_check_port(struct usb_dev *dev, int port)
|
|
{
|
|
struct usb_hub_ps ps;
|
|
uint32_t time;
|
|
|
|
if (!hub_get_port_status(dev, port, &ps, sizeof(ps)))
|
|
return false;
|
|
dprintf("Port Status %04X Port Change %04X\n",
|
|
le16_to_cpu(ps.wPortStatus),
|
|
le16_to_cpu(ps.wPortChange));
|
|
|
|
if (!(le16_to_cpu(ps.wPortStatus) & HUB_PS_POWER)) {
|
|
hub_set_port_feature(dev, port, HUB_PF_POWER);
|
|
SLOF_msleep(100);
|
|
time = SLOF_GetTimer() + USB_TIMEOUT;
|
|
while (time > SLOF_GetTimer()) {
|
|
cpu_relax();
|
|
hub_get_port_status(dev, port, &ps, sizeof(ps));
|
|
if (le16_to_cpu(ps.wPortStatus) & HUB_PS_CONNECTION) {
|
|
dprintf("power on Port Status %04X Port Change %04X\n",
|
|
le16_to_cpu(ps.wPortStatus),
|
|
le16_to_cpu(ps.wPortChange));
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (le16_to_cpu(ps.wPortStatus) & HUB_PS_CONNECTION) {
|
|
hub_set_port_feature(dev, port, HUB_PF_RESET);
|
|
SLOF_msleep(100);
|
|
time = SLOF_GetTimer() + USB_TIMEOUT;
|
|
while (time > SLOF_GetTimer()) {
|
|
cpu_relax();
|
|
hub_get_port_status(dev, port, &ps, sizeof(ps));
|
|
if (!(le16_to_cpu(ps.wPortStatus) & HUB_PS_RESET)) {
|
|
dprintf("reset Port Status %04X Port Change %04X\n",
|
|
le16_to_cpu(ps.wPortStatus),
|
|
le16_to_cpu(ps.wPortChange));
|
|
return true;
|
|
}
|
|
}
|
|
}
|
|
return false;
|
|
}
|
|
|
|
static bool usb_hub_init_dev(struct usb_dev *hub_dev, int port)
|
|
{
|
|
struct usb_dev *newdev;
|
|
|
|
if (hub_dev->hcidev->type == USB_XHCI) {
|
|
struct usb_hub_ps ps;
|
|
int slotspeed;
|
|
|
|
hub_get_port_status(hub_dev, port, &ps, sizeof(ps));
|
|
if (le16_to_cpu(ps.wPortStatus) & HUB_PS_LOW_SPEED)
|
|
slotspeed = SLOT_SPEED_LS;
|
|
else if (le16_to_cpu(ps.wPortStatus) & HUB_PS_HIGH_SPEED)
|
|
slotspeed = SLOT_SPEED_HS;
|
|
else
|
|
slotspeed = SLOT_SPEED_FS;
|
|
|
|
/*
|
|
* USB3 devices need special setup (e.g. with assigning
|
|
* a slot ID and route string), which will all be done
|
|
* by usb3_dev_init() - it also calls usb_devpool_get(),
|
|
* usb_setup_new_device() and usb_slof_populate_new_device()
|
|
* internally, so we can return immediately after this step.
|
|
*/
|
|
return usb3_dev_init(hub_dev->hcidev->priv, hub_dev, port,
|
|
slotspeed);
|
|
}
|
|
|
|
newdev = usb_devpool_get();
|
|
dprintf("usb-hub: allocated device %p\n", newdev);
|
|
newdev->hub = hub_dev;
|
|
newdev->hcidev = hub_dev->hcidev;
|
|
if (usb_setup_new_device(newdev, port)) {
|
|
usb_slof_populate_new_device(newdev);
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
unsigned int usb_hub_init(void *hubdev)
|
|
{
|
|
struct usb_dev *dev = hubdev;
|
|
struct usb_dev_hub_descr hub;
|
|
int i;
|
|
|
|
dprintf("%s: enter %p\n", __func__, dev);
|
|
if (!dev) {
|
|
printf("usb-hub: NULL\n");
|
|
return false;
|
|
}
|
|
memset(&hub, 0, sizeof(hub));
|
|
usb_get_hub_desc(dev, &hub, sizeof(hub));
|
|
dprintf("usb-hub: ports connected %d\n", hub.bNbrPorts);
|
|
for (i = 0; i < hub.bNbrPorts; i++) {
|
|
dprintf("usb-hub: ports scanning %d\n", i);
|
|
if (hub_check_port(dev, i)) {
|
|
dprintf("***********************************************\n");
|
|
dprintf("\t\tusb-hub: device found %d\n", i);
|
|
dprintf("***********************************************\n");
|
|
if (!usb_hub_init_dev(dev, i))
|
|
printf("usb-hub: unable to setup device on port %d\n", i);
|
|
}
|
|
}
|
|
return true;
|
|
}
|