dm: usb: sandbox: Add an emulator for USB hub emulation

All USB controllers need a root hub. Add a sandbox emulation for this so
that we can add USB devices to sandbox.

Signed-off-by: Simon Glass <sjg@chromium.org>
Reviewed-by: Marek Vasut <marex@denx.de>
diff --git a/drivers/usb/emul/Makefile b/drivers/usb/emul/Makefile
index 1d5acce..8fd83d5 100644
--- a/drivers/usb/emul/Makefile
+++ b/drivers/usb/emul/Makefile
@@ -6,4 +6,5 @@
 #
 
 obj-$(CONFIG_USB_EMUL) += sandbox_flash.o
+obj-$(CONFIG_USB_EMUL) += sandbox_hub.o
 obj-$(CONFIG_USB_EMUL) += usb-emul-uclass.o
diff --git a/drivers/usb/emul/sandbox_hub.c b/drivers/usb/emul/sandbox_hub.c
new file mode 100644
index 0000000..280c708
--- /dev/null
+++ b/drivers/usb/emul/sandbox_hub.c
@@ -0,0 +1,303 @@
+/*
+ * (C) Copyright 2015 Google, Inc
+ * Written by Simon Glass <sjg@chromium.org>
+ *
+ * SPDX-License-Identifier:	GPL-2.0+
+ */
+
+#include <common.h>
+#include <dm.h>
+#include <usb.h>
+#include <dm/device-internal.h>
+
+DECLARE_GLOBAL_DATA_PTR;
+
+/* We only support up to 8 */
+#define SANDBOX_NUM_PORTS	2
+
+struct sandbox_hub_platdata {
+	struct usb_dev_platdata plat;
+	int port;	/* Port number (numbered from 0) */
+};
+
+enum {
+	STRING_MANUFACTURER = 1,
+	STRING_PRODUCT,
+	STRING_SERIAL,
+
+	STRING_count,
+};
+
+static struct usb_string hub_strings[] = {
+	{STRING_MANUFACTURER,	"sandbox"},
+	{STRING_PRODUCT,	"hub"},
+	{STRING_SERIAL,		"2345"},
+};
+
+static struct usb_device_descriptor hub_device_desc = {
+	.bLength =		sizeof(hub_device_desc),
+	.bDescriptorType =	USB_DT_DEVICE,
+
+	.bcdUSB =		__constant_cpu_to_le16(0x0200),
+
+	.bDeviceClass =		USB_CLASS_HUB,
+	.bDeviceSubClass =	0,
+	.bDeviceProtocol =	0,
+
+	.idVendor =		__constant_cpu_to_le16(0x1234),
+	.idProduct =		__constant_cpu_to_le16(0x5678),
+	.iManufacturer =	STRING_MANUFACTURER,
+	.iProduct =		STRING_PRODUCT,
+	.iSerialNumber =	STRING_SERIAL,
+	.bNumConfigurations =	1,
+};
+
+static struct usb_config_descriptor hub_config1 = {
+	.bLength		= sizeof(hub_config1),
+	.bDescriptorType	= USB_DT_CONFIG,
+
+	/* wTotalLength is set up by usb-emul-uclass */
+	.bNumInterfaces		= 1,
+	.bConfigurationValue	= 0,
+	.iConfiguration		= 0,
+	.bmAttributes		= 1 << 7,
+	.bMaxPower		= 50,
+};
+
+static struct usb_interface_descriptor hub_interface0 = {
+	.bLength		= sizeof(hub_interface0),
+	.bDescriptorType	= USB_DT_INTERFACE,
+
+	.bInterfaceNumber	= 0,
+	.bAlternateSetting	= 0,
+	.bNumEndpoints		= 1,
+	.bInterfaceClass	= USB_CLASS_HUB,
+	.bInterfaceSubClass	= 0,
+	.bInterfaceProtocol	= US_PR_CB,
+	.iInterface		= 0,
+};
+
+static struct usb_endpoint_descriptor hub_endpoint0_in = {
+	.bLength		= USB_DT_ENDPOINT_SIZE,
+	.bDescriptorType	= USB_DT_ENDPOINT,
+
+	.bEndpointAddress	= 1 | USB_DIR_IN,
+	.bmAttributes		= USB_ENDPOINT_XFER_INT,
+	.wMaxPacketSize		= __constant_cpu_to_le16(1024),
+	.bInterval		= 0,
+};
+
+static struct usb_hub_descriptor hub_desc = {
+	.bLength		= sizeof(hub_desc),
+	.bDescriptorType	= USB_DT_HUB,
+	.bNbrPorts		= SANDBOX_NUM_PORTS,
+	.wHubCharacteristics	= __constant_cpu_to_le16(1 << 0 | 1 << 3 |
+								1 << 7),
+	.bPwrOn2PwrGood		= 2,
+	.bHubContrCurrent	= 5,
+	.DeviceRemovable	= {0, 0xff}, /* all ports removeable */
+#if SANDBOX_NUM_PORTS > 8
+#error "This code sets up an incorrect mask"
+#endif
+};
+
+static void *hub_desc_list[] = {
+	&hub_device_desc,
+	&hub_config1,
+	&hub_interface0,
+	&hub_endpoint0_in,
+	&hub_desc,
+	NULL,
+};
+
+struct sandbox_hub_priv {
+	int status[SANDBOX_NUM_PORTS];
+	int change[SANDBOX_NUM_PORTS];
+};
+
+static struct udevice *hub_find_device(struct udevice *hub, int port)
+{
+	struct udevice *dev;
+
+	for (device_find_first_child(hub, &dev);
+	     dev;
+	     device_find_next_child(&dev)) {
+		struct sandbox_hub_platdata *plat;
+
+		plat = dev_get_parent_platdata(dev);
+		if (plat->port == port)
+			return dev;
+	}
+
+	return NULL;
+}
+
+static int clrset_post_state(struct udevice *hub, int port, int clear, int set)
+{
+	struct sandbox_hub_priv *priv = dev_get_priv(hub);
+	int *status = &priv->status[port];
+	int *change = &priv->change[port];
+	int ret = 0;
+
+	if ((clear | set) & USB_PORT_STAT_POWER) {
+		struct udevice *dev = hub_find_device(hub, port);
+
+		if (dev) {
+			if (set & USB_PORT_STAT_POWER) {
+				ret = device_probe(dev);
+				debug("%s: %s: power on, probed, ret=%d\n",
+				      __func__, dev->name, ret);
+				if (!ret) {
+					set |= USB_PORT_STAT_CONNECTION |
+						USB_PORT_STAT_ENABLE;
+				}
+
+			} else if (clear & USB_PORT_STAT_POWER) {
+				debug("%s: %s: power off, removed, ret=%d\n",
+				      __func__, dev->name, ret);
+				ret = device_remove(dev);
+				clear |= USB_PORT_STAT_CONNECTION;
+			}
+		}
+	}
+	*change |= *status & clear;
+	*change |= ~*status & set;
+	*change &= 0x1f;
+	*status = (*status & ~clear) | set;
+
+	return ret;
+}
+
+static int sandbox_hub_submit_control_msg(struct udevice *bus,
+					  struct usb_device *udev,
+					  unsigned long pipe,
+					  void *buffer, int length,
+					  struct devrequest *setup)
+{
+	struct sandbox_hub_priv *priv = dev_get_priv(bus);
+	int ret = 0;
+
+	if (pipe == usb_rcvctrlpipe(udev, 0)) {
+		switch (setup->requesttype) {
+		case USB_RT_HUB | USB_DIR_IN:
+			switch (setup->request) {
+			case USB_REQ_GET_STATUS: {
+				struct usb_hub_status *hubsts = buffer;
+
+				hubsts->wHubStatus = 0;
+				hubsts->wHubChange = 0;
+				udev->status = 0;
+				udev->act_len = sizeof(*hubsts);
+				return 0;
+			}
+			default:
+				debug("%s: rx ctl requesttype=%x, request=%x\n",
+				      __func__, setup->requesttype,
+				      setup->request);
+				break;
+			}
+		case USB_RT_PORT | USB_DIR_IN:
+			switch (setup->request) {
+			case USB_REQ_GET_STATUS: {
+				struct usb_port_status *portsts = buffer;
+				int port;
+
+				port = (setup->index & USB_HUB_PORT_MASK) - 1;
+				portsts->wPortStatus = priv->status[port];
+				portsts->wPortChange = priv->change[port];
+				udev->status = 0;
+				udev->act_len = sizeof(*portsts);
+				return 0;
+			}
+			}
+		default:
+			debug("%s: rx ctl requesttype=%x, request=%x\n",
+			      __func__, setup->requesttype, setup->request);
+			break;
+		}
+	} else if (pipe == usb_sndctrlpipe(udev, 0)) {
+		switch (setup->requesttype) {
+		case USB_RT_PORT:
+			switch (setup->request) {
+			case USB_REQ_SET_FEATURE: {
+				int port;
+
+				port = (setup->index & USB_HUB_PORT_MASK) - 1;
+				debug("set feature port=%x, feature=%x\n",
+				      port, setup->value);
+				if (setup->value < USB_PORT_FEAT_C_CONNECTION) {
+					ret = clrset_post_state(bus, port, 0,
+							1 << setup->value);
+				} else {
+					debug("  ** Invalid feature\n");
+				}
+				return ret;
+			}
+			case USB_REQ_CLEAR_FEATURE: {
+				int port;
+
+				port = (setup->index & USB_HUB_PORT_MASK) - 1;
+				debug("clear feature port=%x, feature=%x\n",
+				      port, setup->value);
+				if (setup->value < USB_PORT_FEAT_C_CONNECTION) {
+					ret = clrset_post_state(bus, port,
+							1 << setup->value, 0);
+				} else {
+					priv->change[port] &= 1 <<
+						(setup->value - 16);
+				}
+				udev->status = 0;
+				return 0;
+			}
+			default:
+				debug("%s: tx ctl requesttype=%x, request=%x\n",
+				      __func__, setup->requesttype,
+				      setup->request);
+				break;
+			}
+		default:
+			debug("%s: tx ctl requesttype=%x, request=%x\n",
+			      __func__, setup->requesttype, setup->request);
+			break;
+		}
+	}
+	debug("pipe=%lx\n", pipe);
+
+	return -EIO;
+}
+
+static int sandbox_hub_bind(struct udevice *dev)
+{
+	return usb_emul_setup_device(dev, PACKET_SIZE_64, hub_strings,
+				     hub_desc_list);
+}
+
+static int sandbox_child_post_bind(struct udevice *dev)
+{
+	struct sandbox_hub_platdata *plat = dev_get_parent_platdata(dev);
+
+	plat->port = fdtdec_get_int(gd->fdt_blob, dev->of_offset, "reg", -1);
+
+	return 0;
+}
+
+static const struct dm_usb_ops sandbox_usb_hub_ops = {
+	.control	= sandbox_hub_submit_control_msg,
+};
+
+static const struct udevice_id sandbox_usb_hub_ids[] = {
+	{ .compatible = "sandbox,usb-hub" },
+	{ }
+};
+
+U_BOOT_DRIVER(usb_sandbox_hub) = {
+	.name	= "usb_sandbox_hub",
+	.id	= UCLASS_USB_EMUL,
+	.of_match = sandbox_usb_hub_ids,
+	.bind	= sandbox_hub_bind,
+	.ops	= &sandbox_usb_hub_ops,
+	.priv_auto_alloc_size = sizeof(struct sandbox_hub_priv),
+	.per_child_platdata_auto_alloc_size =
+			sizeof(struct sandbox_hub_platdata),
+	.child_post_bind = sandbox_child_post_bind,
+};
diff --git a/include/usb_defs.h b/include/usb_defs.h
index d7f7465..27ddc47 100644
--- a/include/usb_defs.h
+++ b/include/usb_defs.h
@@ -286,6 +286,9 @@
 #define HUB_CHANGE_LOCAL_POWER	0x0001
 #define HUB_CHANGE_OVERCURRENT	0x0002
 
+/* Mask for wIndex in get/set port feature */
+#define USB_HUB_PORT_MASK	0xf
+
 /*
  * CBI style
  */