335 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			C
		
	
	
	
			
		
		
	
	
			335 lines
		
	
	
		
			7.9 KiB
		
	
	
	
		
			C
		
	
	
	
| // SPDX-License-Identifier: GPL-2.0+
 | |
| /*
 | |
|  * (C) Copyright 2015 Google, Inc
 | |
|  * Written by Simon Glass <sjg@chromium.org>
 | |
|  */
 | |
| 
 | |
| #include <common.h>
 | |
| #include <dm.h>
 | |
| #include <log.h>
 | |
| #include <usb.h>
 | |
| #include <dm/device-internal.h>
 | |
| 
 | |
| /* We only support up to 8 */
 | |
| #define SANDBOX_NUM_PORTS	4
 | |
| 
 | |
| struct sandbox_hub_plat {
 | |
| 	struct usb_dev_plat 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,
 | |
| 	{
 | |
| 		{
 | |
| 			/* all ports removeable */
 | |
| 			.DeviceRemovable	= {0, 0xff}
 | |
| 		}
 | |
| 	}
 | |
| #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,
 | |
| 				       enum usb_device_speed *speed)
 | |
| {
 | |
| 	struct udevice *dev;
 | |
| 	struct usb_generic_descriptor **gen_desc;
 | |
| 	struct usb_device_descriptor **dev_desc;
 | |
| 
 | |
| 	for (device_find_first_child(hub, &dev);
 | |
| 	     dev;
 | |
| 	     device_find_next_child(&dev)) {
 | |
| 		struct sandbox_hub_plat *plat;
 | |
| 
 | |
| 		plat = dev_get_parent_plat(dev);
 | |
| 		if (plat->port == port) {
 | |
| 			gen_desc = plat->plat.desc_list;
 | |
| 			gen_desc = usb_emul_find_descriptor(gen_desc,
 | |
| 							    USB_DT_DEVICE, 0);
 | |
| 			dev_desc = (struct usb_device_descriptor **)gen_desc;
 | |
| 
 | |
| 			switch (le16_to_cpu((*dev_desc)->bcdUSB)) {
 | |
| 			case 0x0100:
 | |
| 				*speed = USB_SPEED_LOW;
 | |
| 				break;
 | |
| 			case 0x0101:
 | |
| 				*speed = USB_SPEED_FULL;
 | |
| 				break;
 | |
| 			case 0x0200:
 | |
| 			default:
 | |
| 				*speed = USB_SPEED_HIGH;
 | |
| 				break;
 | |
| 			}
 | |
| 
 | |
| 			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) {
 | |
| 		enum usb_device_speed speed;
 | |
| 		struct udevice *dev = hub_find_device(hub, port, &speed);
 | |
| 
 | |
| 		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;
 | |
| 					if (speed == USB_SPEED_LOW)
 | |
| 						set |= USB_PORT_STAT_LOW_SPEED;
 | |
| 					else if (speed == USB_SPEED_HIGH)
 | |
| 						set |= USB_PORT_STAT_HIGH_SPEED;
 | |
| 				}
 | |
| 
 | |
| 			} else if (clear & USB_PORT_STAT_POWER) {
 | |
| 				debug("%s: %s: power off, removed, ret=%d\n",
 | |
| 				      __func__, dev->name, ret);
 | |
| 				ret = device_remove(dev, DM_REMOVE_NORMAL);
 | |
| 				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, hub_strings, hub_desc_list);
 | |
| }
 | |
| 
 | |
| static int sandbox_child_post_bind(struct udevice *dev)
 | |
| {
 | |
| 	struct sandbox_hub_plat *plat = dev_get_parent_plat(dev);
 | |
| 	struct usb_emul_plat *emul = dev_get_uclass_plat(dev);
 | |
| 
 | |
| 	plat->port = dev_read_u32_default(dev, "reg", -1);
 | |
| 	emul->port1 = plat->port + 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	= sizeof(struct sandbox_hub_priv),
 | |
| 	.per_child_plat_auto	= sizeof(struct sandbox_hub_plat),
 | |
| 	.child_post_bind = sandbox_child_post_bind,
 | |
| };
 |