libpayload: Port usb hub driver to use the generic code

This is mostly a rewrite, don't even try to read a diff.

Tested with an internal rate matching hub on a QM77 board and three hubs
integrated into DELL monitors.

Original-Change-Id: Ib12fa2aa90af4e0f37143d2ed92c4a1705b6d774
Signed-off-by: Nico Huber <nico.huber@secunet.com>
Reviewed-on: http://review.coreboot.org/3451
Tested-by: build bot (Jenkins)
Reviewed-by: Paul Menzel <paulepanter@users.sourceforge.net>
Reviewed-by: Stefan Reinauer <stefan.reinauer@coreboot.org>
(cherry picked from commit 5736fab4beb17ea1a04088d1cf16121c57ccf744)

BUG=chrome-os-partner:21969
TEST=None

Change-Id: Idec16258a5b7286de48b5d3974eeefcab45a7e50
Signed-off-by: Julius Werner <jwerner@chromium.org>
Reviewed-on: https://chromium-review.googlesource.com/168096
diff --git a/payloads/libpayload/Config.in b/payloads/libpayload/Config.in
index ecefe28..4af3114 100644
--- a/payloads/libpayload/Config.in
+++ b/payloads/libpayload/Config.in
@@ -400,7 +400,8 @@
 
 config USB_GEN_HUB
 	bool
-	default n
+	default n if (!USB_HUB)
+	default y if (USB_HUB)
 
 endmenu
 
diff --git a/payloads/libpayload/configs/config.bolt b/payloads/libpayload/configs/config.bolt
index 1cdbb22..b6e5669 100644
--- a/payloads/libpayload/configs/config.bolt
+++ b/payloads/libpayload/configs/config.bolt
@@ -65,6 +65,7 @@
 CONFIG_LP_USB_MSC=y
 CONFIG_LP_USB_PCI=y
 # CONFIG_LP_USB_MEMORY is not set
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 CONFIG_LP_IO_ADDRESS_SPACE=y
diff --git a/payloads/libpayload/configs/config.daisy b/payloads/libpayload/configs/config.daisy
index 11b84b1..4103bfa 100644
--- a/payloads/libpayload/configs/config.daisy
+++ b/payloads/libpayload/configs/config.daisy
@@ -56,6 +56,7 @@
 CONFIG_LP_USB_MEMORY=y
 CONFIG_LP_USB_OHCI_BASE_ADDRESS=0x12120000
 CONFIG_LP_USB_EHCI_BASE_ADDRESS=0x12110000
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 # CONFIG_LP_IO_ADDRESS_SPACE is not set
diff --git a/payloads/libpayload/configs/config.falco b/payloads/libpayload/configs/config.falco
index 1cdbb22..b6e5669 100644
--- a/payloads/libpayload/configs/config.falco
+++ b/payloads/libpayload/configs/config.falco
@@ -65,6 +65,7 @@
 CONFIG_LP_USB_MSC=y
 CONFIG_LP_USB_PCI=y
 # CONFIG_LP_USB_MEMORY is not set
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 CONFIG_LP_IO_ADDRESS_SPACE=y
diff --git a/payloads/libpayload/configs/config.fox_baskingridge b/payloads/libpayload/configs/config.fox_baskingridge
index 70d95b4..531259c 100644
--- a/payloads/libpayload/configs/config.fox_baskingridge
+++ b/payloads/libpayload/configs/config.fox_baskingridge
@@ -65,6 +65,7 @@
 CONFIG_LP_USB_MSC=y
 CONFIG_LP_USB_PCI=y
 # CONFIG_LP_USB_MEMORY is not set
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 CONFIG_LP_IO_ADDRESS_SPACE=y
diff --git a/payloads/libpayload/configs/config.fox_wtm2 b/payloads/libpayload/configs/config.fox_wtm2
index 1cdbb22..b6e5669 100644
--- a/payloads/libpayload/configs/config.fox_wtm2
+++ b/payloads/libpayload/configs/config.fox_wtm2
@@ -65,6 +65,7 @@
 CONFIG_LP_USB_MSC=y
 CONFIG_LP_USB_PCI=y
 # CONFIG_LP_USB_MEMORY is not set
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 CONFIG_LP_IO_ADDRESS_SPACE=y
diff --git a/payloads/libpayload/configs/config.link b/payloads/libpayload/configs/config.link
index 70d95b4..531259c 100644
--- a/payloads/libpayload/configs/config.link
+++ b/payloads/libpayload/configs/config.link
@@ -65,6 +65,7 @@
 CONFIG_LP_USB_MSC=y
 CONFIG_LP_USB_PCI=y
 # CONFIG_LP_USB_MEMORY is not set
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 CONFIG_LP_IO_ADDRESS_SPACE=y
diff --git a/payloads/libpayload/configs/config.lumpy b/payloads/libpayload/configs/config.lumpy
index 70d95b4..531259c 100644
--- a/payloads/libpayload/configs/config.lumpy
+++ b/payloads/libpayload/configs/config.lumpy
@@ -65,6 +65,7 @@
 CONFIG_LP_USB_MSC=y
 CONFIG_LP_USB_PCI=y
 # CONFIG_LP_USB_MEMORY is not set
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 CONFIG_LP_IO_ADDRESS_SPACE=y
diff --git a/payloads/libpayload/configs/config.peach_kirby b/payloads/libpayload/configs/config.peach_kirby
index baf2d68..a068bdd 100644
--- a/payloads/libpayload/configs/config.peach_kirby
+++ b/payloads/libpayload/configs/config.peach_kirby
@@ -56,6 +56,7 @@
 CONFIG_LP_USB_MEMORY=y
 CONFIG_LP_USB_OHCI_BASE_ADDRESS=0x12120000
 CONFIG_LP_USB_EHCI_BASE_ADDRESS=0x12110000
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 # CONFIG_LP_IO_ADDRESS_SPACE is not set
diff --git a/payloads/libpayload/configs/config.peach_pit b/payloads/libpayload/configs/config.peach_pit
index 69bda81..3c63dfd 100644
--- a/payloads/libpayload/configs/config.peach_pit
+++ b/payloads/libpayload/configs/config.peach_pit
@@ -56,6 +56,7 @@
 CONFIG_LP_USB_MEMORY=y
 CONFIG_LP_USB_OHCI_BASE_ADDRESS=0x12120000
 CONFIG_LP_USB_EHCI_BASE_ADDRESS=0x12110000
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 # CONFIG_LP_IO_ADDRESS_SPACE is not set
diff --git a/payloads/libpayload/configs/config.peppy b/payloads/libpayload/configs/config.peppy
index 1cdbb22..b6e5669 100644
--- a/payloads/libpayload/configs/config.peppy
+++ b/payloads/libpayload/configs/config.peppy
@@ -65,6 +65,7 @@
 CONFIG_LP_USB_MSC=y
 CONFIG_LP_USB_PCI=y
 # CONFIG_LP_USB_MEMORY is not set
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 CONFIG_LP_IO_ADDRESS_SPACE=y
diff --git a/payloads/libpayload/configs/config.slippy b/payloads/libpayload/configs/config.slippy
index 1cdbb22..b6e5669 100644
--- a/payloads/libpayload/configs/config.slippy
+++ b/payloads/libpayload/configs/config.slippy
@@ -65,6 +65,7 @@
 CONFIG_LP_USB_MSC=y
 CONFIG_LP_USB_PCI=y
 # CONFIG_LP_USB_MEMORY is not set
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 CONFIG_LP_IO_ADDRESS_SPACE=y
diff --git a/payloads/libpayload/configs/defconfig b/payloads/libpayload/configs/defconfig
index 30213d1..a8fca14 100644
--- a/payloads/libpayload/configs/defconfig
+++ b/payloads/libpayload/configs/defconfig
@@ -72,6 +72,7 @@
 CONFIG_LP_USB_MSC=y
 CONFIG_LP_USB_PCI=y
 # CONFIG_LP_USB_MEMORY is not set
+CONFIG_LP_USB_GEN_HUB=y
 # CONFIG_LP_BIG_ENDIAN is not set
 CONFIG_LP_LITTLE_ENDIAN=y
 CONFIG_LP_IO_ADDRESS_SPACE=y
diff --git a/payloads/libpayload/drivers/usb/usbhub.c b/payloads/libpayload/drivers/usb/usbhub.c
index 6ba1ed6..4e077f6 100644
--- a/payloads/libpayload/drivers/usb/usbhub.c
+++ b/payloads/libpayload/drivers/usb/usbhub.c
@@ -1,7 +1,7 @@
 /*
  * This file is part of the libpayload project.
  *
- * Copyright (C) 2008-2010 coresystems GmbH
+ * Copyright (C) 2013 secunet Security Networks AG
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, are permitted provided that the following conditions
@@ -30,170 +30,110 @@
 //#define USB_DEBUG
 
 #include <usb/usb.h>
+#include "generic_hub.h"
 
-// assume that host_to_device is overwritten if necessary
+/* assume that host_to_device is overwritten if necessary */
 #define DR_PORT gen_bmRequestType(host_to_device, class_type, other_recp)
-/* status bits */
+/* status (and status change) bits */
 #define PORT_CONNECTION 0x1
 #define PORT_ENABLE 0x2
 #define PORT_RESET 0x10
-/* status change bits */
-#define C_PORT_CONNECTION 0x1
 /* feature selectors (for setting / clearing features) */
 #define SEL_PORT_RESET 0x4
 #define SEL_PORT_POWER 0x8
 #define SEL_C_PORT_CONNECTION 0x10
 
-typedef struct {
-	int num_ports;
-	int *ports;
-	hub_descriptor_t *descriptor;
-} usbhub_inst_t;
-
-#define HUB_INST(dev) ((usbhub_inst_t*)(dev)->data)
-
-static void
-usb_hub_destroy (usbdev_t *dev)
+static int
+usb_hub_port_status_changed(usbdev_t *const dev, const int port)
 {
-	int i;
-
-	/* First, detach all devices behind this hub. */
-	int *const ports = HUB_INST (dev)->ports;
-	for (i = 1; i <= HUB_INST (dev)->num_ports; i++) {
-		if (ports[i] != -1) {
-			usb_detach_device(dev->controller, ports[i]);
-			ports[i] = -1;
-		}
-	}
-
-	free (HUB_INST (dev)->ports);
-	free (HUB_INST (dev)->descriptor);
-	free (HUB_INST (dev));
-}
-
-static void
-usb_hub_scanport (usbdev_t *dev, int port)
-{
-	int timeout;
-
-	unsigned short buf[2];
+	unsigned short buf[2] = { 0, 0 };
 	get_status (dev, port, DR_PORT, 4, buf);
-	if (!(buf[1] & C_PORT_CONNECTION))
-		/* no change */
-		return;
-
-	/* clear Port Connection status change */
 	clear_feature (dev, port, SEL_C_PORT_CONNECTION, DR_PORT);
-
-	int devno = HUB_INST (dev)->ports[port];
-	if (devno != -1) {
-		/* detach device, either because of re-/ or disconnect */
-		usb_detach_device(dev->controller, devno);
-		HUB_INST (dev)->ports[port] = -1;
-	}
-
-	if (!(buf[0] & PORT_CONNECTION))
-		/* no device connected, nothing to do */
-		return;
-
-	/* wait 100ms for port to become stable */
-	mdelay (100); // usb20 spec 9.1.2
-
-	/* reset port */
-	set_feature (dev, port, SEL_PORT_RESET, DR_PORT);
-	/* wait at least 10ms (usb2.0 spec 11.5.1.5: 10ms to 20ms) */
-	mdelay (10);
-	/* wait for hub to finish reset */
-	timeout = 30; /* time out after 10ms (spec) + 20ms (kindly) */
-	do {
-		get_status (dev, port, DR_PORT, 4, buf);
-		mdelay(1); timeout--;
-	} while ((buf[0] & PORT_RESET) && timeout);
-	if (!timeout)
-		usb_debug("Warning: usbhub: port reset timed out.\n");
-
-	/* wait for port to be enabled. the hub is responsible for this */
-	timeout = 500; /* time out after 500ms */
-	do {
-		get_status (dev, port, DR_PORT, 4, buf);
-		mdelay(1); timeout--;
-	} while (!(buf[0] & PORT_ENABLE) && timeout);
-	if (!timeout)
-		usb_debug("Warning: usbhub: port enabling timed out.\n");
-
-	get_status (dev, port, DR_PORT, 4, buf);
-	/* bit  10  9
-	 *      0   0  full speed
-	 *      0   1  low speed
-	 *      1   0  high speed
-	 */
-	int speed = ((buf[0] >> 9) & 3) ;
-
-	HUB_INST (dev)->ports[port] = usb_attach_device(dev->controller, dev->address, port, speed);
-
-	/* clear Port Connection status change */
-	clear_feature (dev, port, SEL_C_PORT_CONNECTION, DR_PORT);
+	return buf[1] & PORT_CONNECTION;
 }
 
 static int
-usb_hub_report_port_changes (usbdev_t *dev)
+usb_hub_port_connected(usbdev_t *const dev, const int port)
 {
-	int port;
-	unsigned short buf[2];
-	for (port = 1; port <= HUB_INST (dev)->num_ports; port++) {
-		get_status (dev, port, DR_PORT, 4, buf);
-		if (buf[1] & C_PORT_CONNECTION)
-			return port;
+	unsigned short buf[2] = { 0, 0 };
+	get_status (dev, port, DR_PORT, 4, buf);
+	return buf[0] & PORT_CONNECTION;
+}
+
+static int
+usb_hub_port_in_reset(usbdev_t *const dev, const int port)
+{
+	unsigned short buf[2] = { 0, 0 };
+	get_status (dev, port, DR_PORT, 4, buf);
+	return buf[0] & PORT_RESET;
+}
+
+static int
+usb_hub_port_enabled(usbdev_t *const dev, const int port)
+{
+	unsigned short buf[2] = { 0, 0 };
+	get_status (dev, port, DR_PORT, 4, buf);
+	return (buf[0] & PORT_ENABLE) != 0;
+}
+
+static int
+usb_hub_port_speed(usbdev_t *const dev, const int port)
+{
+	unsigned short buf[2] = { 0, 0 };
+	get_status (dev, port, DR_PORT, 4, buf);
+	if (buf[0] & PORT_ENABLE) {
+		/* bit  10  9
+		 *      0   0  full speed
+		 *      0   1  low speed
+		 *      1   0  high speed
+		 */
+		return (buf[0] >> 9) & 0x3;
+	} else {
+		return -1;
 	}
-
-	// no change
-	return -1;
 }
 
-static void
-usb_hub_enable_port (usbdev_t *dev, int port)
+static int
+usb_hub_enable_port(usbdev_t *const dev, const int port)
 {
-	set_feature (dev, port, SEL_PORT_POWER, DR_PORT);
-	mdelay (20);
+	set_feature(dev, port, SEL_PORT_POWER, DR_PORT);
+	return 0;
 }
 
-#if 0
-static void
-usb_hub_disable_port (usbdev_t *dev, int port)
+static int
+usb_hub_start_port_reset(usbdev_t *const dev, const int port)
 {
+	set_feature (dev, port, SEL_PORT_RESET, DR_PORT);
+	return 0;
 }
-#endif
 
-static void
-usb_hub_poll (usbdev_t *dev)
-{
-	int port;
-	while ((port = usb_hub_report_port_changes (dev)) != -1)
-		usb_hub_scanport (dev, port);
-}
+static const generic_hub_ops_t usb_hub_ops = {
+	.hub_status_changed	= NULL,
+	.port_status_changed	= usb_hub_port_status_changed,
+	.port_connected		= usb_hub_port_connected,
+	.port_in_reset		= usb_hub_port_in_reset,
+	.port_enabled		= usb_hub_port_enabled,
+	.port_speed		= usb_hub_port_speed,
+	.enable_port		= usb_hub_enable_port,
+	.disable_port		= NULL,
+	.start_port_reset	= usb_hub_start_port_reset,
+	.reset_port		= generic_hub_resetport,
+};
 
 void
-usb_hub_init (usbdev_t *dev)
+usb_hub_init(usbdev_t *const dev)
 {
-	int i;
-	dev->destroy = usb_hub_destroy;
-	dev->poll = usb_hub_poll;
+	hub_descriptor_t *const descriptor = (hub_descriptor_t *)
+		get_descriptor(
+			dev,
+			gen_bmRequestType(device_to_host, class_type, dev_recp),
+			0x29, 0, 0);
+	if (!descriptor) {
+		usb_debug("usbhub: ERROR: Failed to fetch hub descriptor\n");
+		return;
+	}
+	const int num_ports = descriptor->bNbrPorts;
+	free(descriptor);
 
-	dev->data = malloc (sizeof (usbhub_inst_t));
-
-	if (!dev->data)
-		fatal("Not enough memory for USB hub.\n");
-
-	HUB_INST (dev)->descriptor = (hub_descriptor_t *) get_descriptor(dev,
-		gen_bmRequestType(device_to_host, class_type, dev_recp), 0x29, 0, 0);
-	HUB_INST (dev)->num_ports = HUB_INST (dev)->descriptor->bNbrPorts;
-	HUB_INST (dev)->ports =
-		malloc (sizeof (int) * (HUB_INST (dev)->num_ports + 1));
-	if (! HUB_INST (dev)->ports)
-		fatal("Not enough memory for USB hub ports.\n");
-
-	for (i = 1; i <= HUB_INST (dev)->num_ports; i++)
-		HUB_INST (dev)->ports[i] = -1;
-	for (i = 1; i <= HUB_INST (dev)->num_ports; i++)
-		usb_hub_enable_port (dev, i);
+	generic_hub_init(dev, num_ports, &usb_hub_ops);
 }