blob: ddfcadc984750487ebb8cbc1aa55f55dcd193a8e [file] [log] [blame]
/*
* USB2 Test Mode driver
* Copyright (C) 2011, Intel Corporation.
*
* This program is free software; you can redistribute it and/or modify it
* under the terms and conditions of the GNU General Public License,
* version 2, as published by the Free Software Foundation.
*
* This program is distributed in the hope it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
* more details.
*
* You should have received a copy of the GNU General Public License along with
* this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA.
*
*/
/* This driver supports USB Host Test Mode initiated by test device with
* VID 0x1a0a for USB controller in high-speed.
*/
#include <linux/kernel.h>
#include <linux/errno.h>
#include <linux/init.h>
#include <linux/slab.h>
#include <linux/mm.h>
#include <linux/module.h>
#include <linux/usb.h>
#include <linux/usb/hcd.h>
#include "../core/usb.h"
struct usb_tm_dev {
struct usb_device *udev;
struct usb_hcd *hcd;
#define TBUF_SIZE 256
u8 *buf;
};
static int
usb_tm_probe(struct usb_interface *intf, const struct usb_device_id *id)
{
struct usb_tm_dev *dev;
int retval, port_num;
dev_dbg(&intf->dev, "USB test mode is initiated.\n");
dev = kzalloc(sizeof(*dev), GFP_KERNEL);
if (!dev)
return -ENOMEM;
dev->buf = kmalloc(TBUF_SIZE, GFP_KERNEL);
if (!dev->buf) {
kfree(dev);
return -ENOMEM;
}
dev->udev = usb_get_dev(interface_to_usbdev(intf));
dev->hcd = usb_get_hcd(bus_to_hcd(dev->udev->bus));
usb_set_intfdata(intf, dev);
port_num = dev->udev->portnum & 0xff;
dev_dbg(&intf->dev, "test mode PID 0x%04x\n",
le16_to_cpu(dev->udev->descriptor.idProduct));
switch (le16_to_cpu(dev->udev->descriptor.idProduct)) {
case 0x0101:
/* TEST_SE0_NAK */
dev->hcd->driver->hub_control(dev->hcd, SetPortFeature,
USB_PORT_FEAT_TEST, 0x300 + port_num, NULL, 0);
break;
case 0x0102:
/* TEST_J */
dev->hcd->driver->hub_control(dev->hcd, SetPortFeature,
USB_PORT_FEAT_TEST, 0x100 + port_num, NULL, 0);
break;
case 0x0103:
/* TEST_K */
dev->hcd->driver->hub_control(dev->hcd, SetPortFeature,
USB_PORT_FEAT_TEST, 0x200 + port_num, NULL, 0);
break;
case 0x0104:
/* TEST_PACKET */
dev->hcd->driver->hub_control(dev->hcd, SetPortFeature,
USB_PORT_FEAT_TEST, 0x400 + port_num, NULL, 0);
break;
case 0x0106:
/* HS_HOST_PORT_SUSPEND_RESUME */
msleep(15000);
dev->hcd->driver->hub_control(dev->hcd, SetPortFeature,
USB_PORT_FEAT_SUSPEND, port_num, NULL, 0);
msleep(15000);
dev->hcd->driver->hub_control(dev->hcd, ClearPortFeature,
USB_PORT_FEAT_SUSPEND, port_num, NULL, 0);
break;
case 0x0107:
/* SINGLE_STEP_GET_DEV_DESC */
msleep(15000);
retval = usb_control_msg(dev->udev,
usb_rcvctrlpipe(dev->udev, 0),
USB_REQ_GET_DESCRIPTOR,
USB_DIR_IN | USB_RECIP_DEVICE,
cpu_to_le16(USB_DT_DEVICE << 8),
0, dev->buf,
USB_DT_DEVICE_SIZE,
USB_CTRL_GET_TIMEOUT);
break;
case 0x0108:
/* SINGLE_STEP_SET_FEATURE */
/* FIXME */
/* set size = 0 to ignore DATA phase */
retval = usb_control_msg(dev->udev,
usb_rcvctrlpipe(dev->udev, 0),
USB_REQ_GET_DESCRIPTOR,
USB_DIR_IN | USB_RECIP_DEVICE,
cpu_to_le16(USB_DT_DEVICE << 8),
0, dev->buf, 0,
USB_CTRL_GET_TIMEOUT);
msleep(15000);
retval = usb_control_msg(dev->udev,
usb_rcvctrlpipe(dev->udev, 0),
USB_REQ_GET_DESCRIPTOR,
USB_DIR_IN | USB_RECIP_DEVICE,
cpu_to_le16(USB_DT_DEVICE << 8),
0, dev->buf,
USB_DT_DEVICE_SIZE,
USB_CTRL_GET_TIMEOUT);
break;
default:
dev_info(&intf->dev, "unknown test mode with PID 0x%04x",
id->idProduct);
}
return 0;
}
static void usb_tm_disconnect(struct usb_interface *intf)
{
struct usb_tm_dev *dev = usb_get_intfdata(intf);
usb_put_hcd(dev->hcd);
usb_put_dev(dev->udev);
usb_set_intfdata(intf, NULL);
dev_dbg(&intf->dev, "disconnect\n");
kfree(dev->buf);
kfree(dev);
}
static const struct usb_device_id id_table[] = {
/* USB Test Device */
{ .match_flags = USB_DEVICE_ID_MATCH_VENDOR,
.idVendor = 0x1A0A,
},
{ }
};
MODULE_DEVICE_TABLE(usb, id_table);
static struct usb_driver usb_tm_driver = {
.name = "usb_tm",
.id_table = id_table,
.probe = usb_tm_probe,
.disconnect = usb_tm_disconnect,
};
/*-------------------------------------------------------------------------*/
static int __init usb_tm_init(void)
{
int result;
result = usb_register(&usb_tm_driver);
if (result)
pr_err("usb_tm: usb_register failed. error number %d",
result);
return result;
}
module_init(usb_tm_init);
static void __exit usb_tm_exit(void)
{
usb_deregister(&usb_tm_driver);
}
module_exit(usb_tm_exit);
MODULE_DESCRIPTION("USB Test Mode Driver");
MODULE_LICENSE("GPL");