diff options
Diffstat (limited to 'drivers/usb/misc')
| -rw-r--r-- | drivers/usb/misc/Kconfig | 7 | ||||
| -rw-r--r-- | drivers/usb/misc/Makefile | 1 | ||||
| -rw-r--r-- | drivers/usb/misc/lvstest.c | 460 | ||||
| -rw-r--r-- | drivers/usb/misc/sisusbvga/sisusb.c | 1 | ||||
| -rw-r--r-- | drivers/usb/misc/usb3503.c | 37 | 
5 files changed, 503 insertions, 3 deletions
diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 1bca274dc3b5..76d77206e011 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -248,3 +248,10 @@ config USB_HSIC_USB3503         select REGMAP_I2C         help           This option enables support for SMSC USB3503 HSIC to USB 2.0 Driver. + +config USB_LINK_LAYER_TEST +	tristate "USB Link Layer Test driver" +	help +	  This driver is for generating specific traffic for Super Speed Link +	  Layer Test Device. Say Y only when you want to conduct USB Super Speed +	  Link Layer Test for host controllers. diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index e748fd5dbe94..65b0402c1ca1 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -27,3 +27,4 @@ obj-$(CONFIG_USB_YUREX)			+= yurex.o  obj-$(CONFIG_USB_HSIC_USB3503)		+= usb3503.o  obj-$(CONFIG_USB_SISUSBVGA)		+= sisusbvga/ +obj-$(CONFIG_USB_LINK_LAYER_TEST)	+= lvstest.o diff --git a/drivers/usb/misc/lvstest.c b/drivers/usb/misc/lvstest.c new file mode 100644 index 000000000000..7d589c156fb1 --- /dev/null +++ b/drivers/usb/misc/lvstest.c @@ -0,0 +1,460 @@ +/* + * drivers/usb/misc/lvstest.c + * + * Test pattern generation for Link Layer Validation System Tests + * + * Copyright (C) 2014 ST Microelectronics + * Pratyush Anand <pratyush.anand@st.com> + * + * This file is licensed under the terms of the GNU General Public + * License version 2. This program is licensed "as is" without any + * warranty of any kind, whether express or implied. + */ + +#include <linux/init.h> +#include <linux/kernel.h> +#include <linux/module.h> +#include <linux/platform_device.h> +#include <linux/slab.h> +#include <linux/usb.h> +#include <linux/usb/ch11.h> +#include <linux/usb/hcd.h> +#include <linux/usb/phy.h> + +struct lvs_rh { +	/* root hub interface */ +	struct usb_interface *intf; +	/* if lvs device connected */ +	bool present; +	/* port no at which lvs device is present */ +	int portnum; +	/* urb buffer */ +	u8 buffer[8]; +	/* class descriptor */ +	struct usb_hub_descriptor descriptor; +	/* urb for polling interrupt pipe */ +	struct urb *urb; +	/* LVS RH work queue */ +	struct workqueue_struct *rh_queue; +	/* LVH RH work */ +	struct work_struct	rh_work; +	/* RH port status */ +	struct usb_port_status port_status; +}; + +static struct usb_device *create_lvs_device(struct usb_interface *intf) +{ +	struct usb_device *udev, *hdev; +	struct usb_hcd *hcd; +	struct lvs_rh *lvs = usb_get_intfdata(intf); + +	if (!lvs->present) { +		dev_err(&intf->dev, "No LVS device is present\n"); +		return NULL; +	} + +	hdev = interface_to_usbdev(intf); +	hcd = bus_to_hcd(hdev->bus); + +	udev = usb_alloc_dev(hdev, hdev->bus, lvs->portnum); +	if (!udev) { +		dev_err(&intf->dev, "Could not allocate lvs udev\n"); +		return NULL; +	} +	udev->speed = USB_SPEED_SUPER; +	udev->ep0.desc.wMaxPacketSize = cpu_to_le16(512); +	usb_set_device_state(udev, USB_STATE_DEFAULT); + +	if (hcd->driver->enable_device) { +		if (hcd->driver->enable_device(hcd, udev) < 0) { +			dev_err(&intf->dev, "Failed to enable\n"); +			usb_put_dev(udev); +			return NULL; +		} +	} + +	return udev; +} + +static void destroy_lvs_device(struct usb_device *udev) +{ +	struct usb_device *hdev = udev->parent; +	struct usb_hcd *hcd = bus_to_hcd(hdev->bus); + +	if (hcd->driver->free_dev) +		hcd->driver->free_dev(hcd, udev); + +	usb_put_dev(udev); +} + +static int lvs_rh_clear_port_feature(struct usb_device *hdev, +		int port1, int feature) +{ +	return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), +		USB_REQ_CLEAR_FEATURE, USB_RT_PORT, feature, port1, +		NULL, 0, 1000); +} + +static int lvs_rh_set_port_feature(struct usb_device *hdev, +		int port1, int feature) +{ +	return usb_control_msg(hdev, usb_sndctrlpipe(hdev, 0), +		USB_REQ_SET_FEATURE, USB_RT_PORT, feature, port1, +		NULL, 0, 1000); +} + +static ssize_t u3_entry_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct usb_interface *intf = to_usb_interface(dev); +	struct usb_device *hdev = interface_to_usbdev(intf); +	struct lvs_rh *lvs = usb_get_intfdata(intf); +	struct usb_device *udev; +	int ret; + +	udev = create_lvs_device(intf); +	if (!udev) { +		dev_err(dev, "failed to create lvs device\n"); +		return -ENOMEM; +	} + +	ret = lvs_rh_set_port_feature(hdev, lvs->portnum, +			USB_PORT_FEAT_SUSPEND); +	if (ret < 0) +		dev_err(dev, "can't issue U3 entry %d\n", ret); + +	destroy_lvs_device(udev); + +	if (ret < 0) +		return ret; + +	return count; +} +static DEVICE_ATTR_WO(u3_entry); + +static ssize_t u3_exit_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct usb_interface *intf = to_usb_interface(dev); +	struct usb_device *hdev = interface_to_usbdev(intf); +	struct lvs_rh *lvs = usb_get_intfdata(intf); +	struct usb_device *udev; +	int ret; + +	udev = create_lvs_device(intf); +	if (!udev) { +		dev_err(dev, "failed to create lvs device\n"); +		return -ENOMEM; +	} + +	ret = lvs_rh_clear_port_feature(hdev, lvs->portnum, +			USB_PORT_FEAT_SUSPEND); +	if (ret < 0) +		dev_err(dev, "can't issue U3 exit %d\n", ret); + +	destroy_lvs_device(udev); + +	if (ret < 0) +		return ret; + +	return count; +} +static DEVICE_ATTR_WO(u3_exit); + +static ssize_t hot_reset_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct usb_interface *intf = to_usb_interface(dev); +	struct usb_device *hdev = interface_to_usbdev(intf); +	struct lvs_rh *lvs = usb_get_intfdata(intf); +	int ret; + +	ret = lvs_rh_set_port_feature(hdev, lvs->portnum, +			USB_PORT_FEAT_RESET); +	if (ret < 0) { +		dev_err(dev, "can't issue hot reset %d\n", ret); +		return ret; +	} + +	return count; +} +static DEVICE_ATTR_WO(hot_reset); + +static ssize_t u2_timeout_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct usb_interface *intf = to_usb_interface(dev); +	struct usb_device *hdev = interface_to_usbdev(intf); +	struct lvs_rh *lvs = usb_get_intfdata(intf); +	unsigned long val; +	int ret; + +	ret = kstrtoul(buf, 10, &val); +	if (ret < 0) { +		dev_err(dev, "couldn't parse string %d\n", ret); +		return ret; +	} + +	if (val < 0 || val > 127) +		return -EINVAL; + +	ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8), +			USB_PORT_FEAT_U2_TIMEOUT); +	if (ret < 0) { +		dev_err(dev, "Error %d while setting U2 timeout %ld\n", ret, val); +		return ret; +	} + +	return count; +} +static DEVICE_ATTR_WO(u2_timeout); + +static ssize_t u1_timeout_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct usb_interface *intf = to_usb_interface(dev); +	struct usb_device *hdev = interface_to_usbdev(intf); +	struct lvs_rh *lvs = usb_get_intfdata(intf); +	unsigned long val; +	int ret; + +	ret = kstrtoul(buf, 10, &val); +	if (ret < 0) { +		dev_err(dev, "couldn't parse string %d\n", ret); +		return ret; +	} + +	if (val < 0 || val > 127) +		return -EINVAL; + +	ret = lvs_rh_set_port_feature(hdev, lvs->portnum | (val << 8), +			USB_PORT_FEAT_U1_TIMEOUT); +	if (ret < 0) { +		dev_err(dev, "Error %d while setting U1 timeout %ld\n", ret, val); +		return ret; +	} + +	return count; +} +static DEVICE_ATTR_WO(u1_timeout); + +static ssize_t get_dev_desc_store(struct device *dev, +		struct device_attribute *attr, const char *buf, size_t count) +{ +	struct usb_interface *intf = to_usb_interface(dev); +	struct usb_device *udev; +	struct usb_device_descriptor *descriptor; +	int ret; + +	descriptor = kmalloc(sizeof(*descriptor), GFP_KERNEL); +	if (!descriptor) { +		dev_err(dev, "failed to allocate descriptor memory\n"); +		return -ENOMEM; +	} + +	udev = create_lvs_device(intf); +	if (!udev) { +		dev_err(dev, "failed to create lvs device\n"); +		ret = -ENOMEM; +		goto free_desc; +	} + +	ret = usb_control_msg(udev, (PIPE_CONTROL << 30) | USB_DIR_IN, +			USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, USB_DT_DEVICE << 8, +			0, descriptor, sizeof(*descriptor), +			USB_CTRL_GET_TIMEOUT); +	if (ret < 0) +		dev_err(dev, "can't read device descriptor %d\n", ret); + +	destroy_lvs_device(udev); + +free_desc: +	kfree(descriptor); + +	if (ret < 0) +		return ret; + +	return count; +} +static DEVICE_ATTR_WO(get_dev_desc); + +static struct attribute *lvs_attributes[] = { +	&dev_attr_get_dev_desc.attr, +	&dev_attr_u1_timeout.attr, +	&dev_attr_u2_timeout.attr, +	&dev_attr_hot_reset.attr, +	&dev_attr_u3_entry.attr, +	&dev_attr_u3_exit.attr, +	NULL +}; + +static const struct attribute_group lvs_attr_group = { +	.attrs = lvs_attributes, +}; + +static void lvs_rh_work(struct work_struct *work) +{ +	struct lvs_rh *lvs = container_of(work, struct lvs_rh, rh_work); +	struct usb_interface *intf = lvs->intf; +	struct usb_device *hdev = interface_to_usbdev(intf); +	struct usb_hcd *hcd = bus_to_hcd(hdev->bus); +	struct usb_hub_descriptor *descriptor = &lvs->descriptor; +	struct usb_port_status *port_status = &lvs->port_status; +	int i, ret = 0; +	u16 portchange; + +	/* Examine each root port */ +	for (i = 1; i <= descriptor->bNbrPorts; i++) { +		ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), +			USB_REQ_GET_STATUS, USB_DIR_IN | USB_RT_PORT, 0, i, +			port_status, sizeof(*port_status), 1000); +		if (ret < 4) +			continue; + +		portchange = le16_to_cpu(port_status->wPortChange); + +		if (portchange & USB_PORT_STAT_C_LINK_STATE) +			lvs_rh_clear_port_feature(hdev, i, +					USB_PORT_FEAT_C_PORT_LINK_STATE); +		if (portchange & USB_PORT_STAT_C_ENABLE) +			lvs_rh_clear_port_feature(hdev, i, +					USB_PORT_FEAT_C_ENABLE); +		if (portchange & USB_PORT_STAT_C_RESET) +			lvs_rh_clear_port_feature(hdev, i, +					USB_PORT_FEAT_C_RESET); +		if (portchange & USB_PORT_STAT_C_BH_RESET) +			lvs_rh_clear_port_feature(hdev, i, +					USB_PORT_FEAT_C_BH_PORT_RESET); +		if (portchange & USB_PORT_STAT_C_CONNECTION) { +			lvs_rh_clear_port_feature(hdev, i, +					USB_PORT_FEAT_C_CONNECTION); + +			if (le16_to_cpu(port_status->wPortStatus) & +					USB_PORT_STAT_CONNECTION) { +				lvs->present = true; +				lvs->portnum = i; +				if (hcd->phy) +					usb_phy_notify_connect(hcd->phy, +							USB_SPEED_SUPER); +			} else { +				lvs->present = false; +				if (hcd->phy) +					usb_phy_notify_disconnect(hcd->phy, +							USB_SPEED_SUPER); +			} +			break; +		} +	} + +	ret = usb_submit_urb(lvs->urb, GFP_KERNEL); +	if (ret != 0 && ret != -ENODEV && ret != -EPERM) +		dev_err(&intf->dev, "urb resubmit error %d\n", ret); +} + +static void lvs_rh_irq(struct urb *urb) +{ +	struct lvs_rh *lvs = urb->context; + +	queue_work(lvs->rh_queue, &lvs->rh_work); +} + +static int lvs_rh_probe(struct usb_interface *intf, +		const struct usb_device_id *id) +{ +	struct usb_device *hdev; +	struct usb_host_interface *desc; +	struct usb_endpoint_descriptor *endpoint; +	struct lvs_rh *lvs; +	unsigned int pipe; +	int ret, maxp; + +	hdev = interface_to_usbdev(intf); +	desc = intf->cur_altsetting; +	endpoint = &desc->endpoint[0].desc; + +	/* valid only for SS root hub */ +	if (hdev->descriptor.bDeviceProtocol != USB_HUB_PR_SS || hdev->parent) { +		dev_err(&intf->dev, "Bind LVS driver with SS root Hub only\n"); +		return -EINVAL; +	} + +	lvs = devm_kzalloc(&intf->dev, sizeof(*lvs), GFP_KERNEL); +	if (!lvs) +		return -ENOMEM; + +	lvs->intf = intf; +	usb_set_intfdata(intf, lvs); + +	/* how many number of ports this root hub has */ +	ret = usb_control_msg(hdev, usb_rcvctrlpipe(hdev, 0), +			USB_REQ_GET_DESCRIPTOR, USB_DIR_IN | USB_RT_HUB, +			USB_DT_SS_HUB << 8, 0, &lvs->descriptor, +			USB_DT_SS_HUB_SIZE, USB_CTRL_GET_TIMEOUT); +	if (ret < (USB_DT_HUB_NONVAR_SIZE + 2)) { +		dev_err(&hdev->dev, "wrong root hub descriptor read %d\n", ret); +		return ret; +	} + +	/* submit urb to poll interrupt endpoint */ +	lvs->urb = usb_alloc_urb(0, GFP_KERNEL); +	if (!lvs->urb) { +		dev_err(&intf->dev, "couldn't allocate lvs urb\n"); +		return -ENOMEM; +	} + +	lvs->rh_queue = create_singlethread_workqueue("lvs_rh_queue"); +	if (!lvs->rh_queue) { +		dev_err(&intf->dev, "couldn't create workqueue\n"); +		ret = -ENOMEM; +		goto free_urb; +	} + +	INIT_WORK(&lvs->rh_work, lvs_rh_work); + +	ret = sysfs_create_group(&intf->dev.kobj, &lvs_attr_group); +	if (ret < 0) { +		dev_err(&intf->dev, "Failed to create sysfs node %d\n", ret); +		goto destroy_queue; +	} + +	pipe = usb_rcvintpipe(hdev, endpoint->bEndpointAddress); +	maxp = usb_maxpacket(hdev, pipe, usb_pipeout(pipe)); +	usb_fill_int_urb(lvs->urb, hdev, pipe, &lvs->buffer[0], maxp, +			lvs_rh_irq, lvs, endpoint->bInterval); + +	ret = usb_submit_urb(lvs->urb, GFP_KERNEL); +	if (ret < 0) { +		dev_err(&intf->dev, "couldn't submit lvs urb %d\n", ret); +		goto sysfs_remove; +	} + +	return ret; + +sysfs_remove: +	sysfs_remove_group(&intf->dev.kobj, &lvs_attr_group); +destroy_queue: +	destroy_workqueue(lvs->rh_queue); +free_urb: +	usb_free_urb(lvs->urb); +	return ret; +} + +static void lvs_rh_disconnect(struct usb_interface *intf) +{ +	struct lvs_rh *lvs = usb_get_intfdata(intf); + +	sysfs_remove_group(&intf->dev.kobj, &lvs_attr_group); +	destroy_workqueue(lvs->rh_queue); +	usb_free_urb(lvs->urb); +} + +static struct usb_driver lvs_driver = { +	.name =		"lvs", +	.probe =	lvs_rh_probe, +	.disconnect =	lvs_rh_disconnect, +}; + +module_usb_driver(lvs_driver); + +MODULE_DESCRIPTION("Link Layer Validation System Driver"); +MODULE_LICENSE("GPL"); diff --git a/drivers/usb/misc/sisusbvga/sisusb.c b/drivers/usb/misc/sisusbvga/sisusb.c index 06b5d77cd9ad..633caf643122 100644 --- a/drivers/usb/misc/sisusbvga/sisusb.c +++ b/drivers/usb/misc/sisusbvga/sisusb.c @@ -3250,6 +3250,7 @@ static const struct usb_device_id sisusb_table[] = {  	{ USB_DEVICE(0x0711, 0x0918) },  	{ USB_DEVICE(0x0711, 0x0920) },  	{ USB_DEVICE(0x0711, 0x0950) }, +	{ USB_DEVICE(0x0711, 0x5200) },  	{ USB_DEVICE(0x182d, 0x021c) },  	{ USB_DEVICE(0x182d, 0x0269) },  	{ } diff --git a/drivers/usb/misc/usb3503.c b/drivers/usb/misc/usb3503.c index f43c61989cef..47cb143716a1 100644 --- a/drivers/usb/misc/usb3503.c +++ b/drivers/usb/misc/usb3503.c @@ -149,8 +149,6 @@ static int usb3503_switch_mode(struct usb3503 *hub, enum usb3503_mode mode)  	case USB3503_MODE_STANDBY:  		usb3503_reset(hub, 0); - -		hub->mode = mode;  		dev_info(dev, "switched to STANDBY mode\n");  		break; @@ -192,7 +190,8 @@ static int usb3503_probe(struct usb3503 *hub)  		clk = devm_clk_get(dev, "refclk");  		if (IS_ERR(clk) && PTR_ERR(clk) != -ENOENT) { -			dev_err(dev, "unable to request refclk (%d)\n", err); +			dev_err(dev, "unable to request refclk (%ld)\n", +					PTR_ERR(clk));  			return PTR_ERR(clk);  		} @@ -346,6 +345,37 @@ static int usb3503_platform_probe(struct platform_device *pdev)  	return usb3503_probe(hub);  } +#ifdef CONFIG_PM_SLEEP +static int usb3503_i2c_suspend(struct device *dev) +{ +	struct i2c_client *client = to_i2c_client(dev); +	struct usb3503 *hub = i2c_get_clientdata(client); + +	usb3503_switch_mode(hub, USB3503_MODE_STANDBY); + +	if (hub->clk) +		clk_disable_unprepare(hub->clk); + +	return 0; +} + +static int usb3503_i2c_resume(struct device *dev) +{ +	struct i2c_client *client = to_i2c_client(dev); +	struct usb3503 *hub = i2c_get_clientdata(client); + +	if (hub->clk) +		clk_prepare_enable(hub->clk); + +	usb3503_switch_mode(hub, hub->mode); + +	return 0; +} +#endif + +static SIMPLE_DEV_PM_OPS(usb3503_i2c_pm_ops, usb3503_i2c_suspend, +		usb3503_i2c_resume); +  static const struct i2c_device_id usb3503_id[] = {  	{ USB3503_I2C_NAME, 0 },  	{ } @@ -364,6 +394,7 @@ MODULE_DEVICE_TABLE(of, usb3503_of_match);  static struct i2c_driver usb3503_i2c_driver = {  	.driver = {  		.name = USB3503_I2C_NAME, +		.pm = &usb3503_i2c_pm_ops,  		.of_match_table = of_match_ptr(usb3503_of_match),  	},  	.probe		= usb3503_i2c_probe,  | 
