diff options
author | Stefan Agner <stefan.agner@toradex.com> | 2014-03-18 11:25:11 +0100 |
---|---|---|
committer | Stefan Agner <stefan.agner@toradex.com> | 2014-03-18 11:25:11 +0100 |
commit | 72249651843f93d50c302833546fb2880052a80f (patch) | |
tree | 172a8af74ce161bc276856d7331dc0076055093c | |
parent | aef4075a0bcc7860eafa17be0c6e6cfcecc68206 (diff) | |
parent | 50c4c848d6b8743894cfcec166db475ef6140504 (diff) |
Merge tag '3.0-vybrid-ts2.13' into colibri_vfColibri_VF_LinuxImageV2.1Beta3_20140318
3.0-vybrid-ts2.13
-rw-r--r-- | arch/arm/configs/twr_vf600_defconfig | 7 | ||||
-rw-r--r-- | arch/arm/mach-mvf/board-twr-vf700.c | 8 | ||||
-rw-r--r-- | arch/arm/mach-mvf/usb_dr.c | 12 | ||||
-rw-r--r-- | arch/arm/mach-mvf/usb_dr2.c | 12 | ||||
-rw-r--r-- | arch/arm/plat-mxc/global_timer.c | 1 | ||||
-rw-r--r-- | arch/arm/plat-mxc/include/mach/iomux-mvf.h | 19 | ||||
-rw-r--r-- | drivers/i2c/busses/i2c-imx.c | 12 | ||||
-rw-r--r-- | drivers/mtd/devices/m25p80.c | 1 | ||||
-rw-r--r-- | drivers/usb/core/hub.c | 74 | ||||
-rw-r--r-- | drivers/usb/gadget/file_storage.c | 6 | ||||
-rw-r--r-- | drivers/usb/gadget/storage_common.c | 4 | ||||
-rwxr-xr-x | drivers/usb/host/Kconfig | 7 | ||||
-rw-r--r-- | drivers/usb/host/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/host/arc_hset.c | 468 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 31 | ||||
-rw-r--r-- | drivers/usb/host/ehci-q.c | 181 | ||||
-rw-r--r-- | drivers/usb/misc/Kconfig | 7 | ||||
-rw-r--r-- | drivers/usb/misc/Makefile | 1 | ||||
-rw-r--r-- | drivers/usb/misc/otg-eh-test.c | 241 |
19 files changed, 1079 insertions, 14 deletions
diff --git a/arch/arm/configs/twr_vf600_defconfig b/arch/arm/configs/twr_vf600_defconfig index d5267daeb899..e8a59f92e298 100644 --- a/arch/arm/configs/twr_vf600_defconfig +++ b/arch/arm/configs/twr_vf600_defconfig @@ -292,7 +292,6 @@ CONFIG_ISP1504_MXC=y # CONFIG_MXC_PWM is not set # CONFIG_MXC_DEBUG_BOARD is not set CONFIG_HAVE_PIT=y -CONFIG_MXC_USE_PIT=y CONFIG_ARCH_MXC_IOMUX_V3=y CONFIG_ARCH_MXC_AUDMUX_V2=y CONFIG_IRAM_ALLOC=y @@ -355,8 +354,9 @@ CONFIG_ARM_GIC=y # # Kernel Features # -# CONFIG_NO_HZ is not set -# CONFIG_HIGH_RES_TIMERS is not set +CONFIG_TICK_ONESHOT=y +CONFIG_NO_HZ=y +CONFIG_HIGH_RES_TIMERS=y CONFIG_GENERIC_CLOCKEVENTS_BUILD=y # CONFIG_VMSPLIT_3G is not set CONFIG_VMSPLIT_2G=y @@ -1432,6 +1432,7 @@ CONFIG_SND_OSSEMUL=y # CONFIG_SND_MIXER_OSS is not set CONFIG_SND_PCM_OSS=y CONFIG_SND_PCM_OSS_PLUGINS=y +# CONFIG_SND_HRTIMER is not set # CONFIG_SND_DYNAMIC_MINORS is not set CONFIG_SND_SUPPORT_OLD_API=y CONFIG_SND_VERBOSE_PROCFS=y diff --git a/arch/arm/mach-mvf/board-twr-vf700.c b/arch/arm/mach-mvf/board-twr-vf700.c index 5e5fec968711..3c19b1c70ec9 100644 --- a/arch/arm/mach-mvf/board-twr-vf700.c +++ b/arch/arm/mach-mvf/board-twr-vf700.c @@ -101,6 +101,8 @@ static iomux_v3_cfg_t mvf600_pads[] = { /*I2C0*/ MVF600_PAD36_PTB14__I2C0_SCL, MVF600_PAD37_PTB15__I2C0_SDA, + + MVF600_PAD38_PTB16_USER_BTN1, #if 0 /*CAN1*/ @@ -252,6 +254,12 @@ static struct imxuart_platform_data mvf_uart1_pdata = { .dma_req_tx = DMA_MUX03_UART1_TX, }; +static struct imxuart_platform_data mvf_uart0_pdata = { + .flags = IMXUART_FIFO | IMXUART_EDMA, + .dma_req_rx = DMA_MUX03_UART0_RX, + .dma_req_tx = DMA_MUX03_UART0_TX, +}; + static inline void mvf_vf700_init_uart(void) { mvf_add_imx_uart(1, &mvf_uart1_pdata); diff --git a/arch/arm/mach-mvf/usb_dr.c b/arch/arm/mach-mvf/usb_dr.c index bbd29f3b45a9..c063193364d1 100644 --- a/arch/arm/mach-mvf/usb_dr.c +++ b/arch/arm/mach-mvf/usb_dr.c @@ -90,6 +90,7 @@ static int usb_phy_enable(struct fsl_usb2_platform_data *pdata) u32 tmp; void __iomem *phy_reg = MVF_IO_ADDRESS(MVF_USBPHY0_BASE_ADDR); void __iomem *phy_ctrl; + void __iomem *phy_param; /* Stop then Reset */ UOG_USBCMD &= ~UCMD_RUN_STOP; @@ -115,6 +116,17 @@ static int usb_phy_enable(struct fsl_usb2_platform_data *pdata) /* Power up the PHY */ __raw_writel(0, phy_reg + HW_USBPHY_PWD); + /* + * For USB Certification + * TX: set edge rate to max, increase the amplitude + * with 2 steps (Level = ~ 437 mV). + * RX: reduce transmission envelope detector level with about 20 mV + */ + phy_param = phy_reg + HW_USBPHY_TX; + __raw_writel(0x1c060605, phy_param); + phy_param = phy_reg + HW_USBPHY_RX; + __raw_writel(0x1, phy_param); + return 0; } /* Notes: configure USB clock*/ diff --git a/arch/arm/mach-mvf/usb_dr2.c b/arch/arm/mach-mvf/usb_dr2.c index 67e3fcdbc921..8a83ac1ea360 100644 --- a/arch/arm/mach-mvf/usb_dr2.c +++ b/arch/arm/mach-mvf/usb_dr2.c @@ -85,6 +85,7 @@ static int usb_phy_enable(struct fsl_usb2_platform_data *pdata) u32 tmp; void __iomem *phy_reg = MVF_IO_ADDRESS(MVF_USBPHY1_BASE_ADDR); void __iomem *phy_ctrl; + void __iomem *phy_param; /* Stop then Reset */ UOG2_USBCMD &= ~UCMD_RUN_STOP; @@ -117,6 +118,17 @@ static int usb_phy_enable(struct fsl_usb2_platform_data *pdata) phy_reg + HW_USBPHY_CTRL_SET); } + /* + * For USB Certification + * TX: set edge rate to max, increase the amplitude + * with 2 steps (Level = ~ 437 mV). + * RX: reduce transmission envelope detector level with about 20 mV + */ + phy_param = phy_reg + HW_USBPHY_TX; + __raw_writel(0x1c060605, phy_param); + phy_param = phy_reg + HW_USBPHY_RX; + __raw_writel(0x1, phy_param); + return 0; } /* Notes: configure USB clock*/ diff --git a/arch/arm/plat-mxc/global_timer.c b/arch/arm/plat-mxc/global_timer.c index 607c861d2752..abbccb955af3 100644 --- a/arch/arm/plat-mxc/global_timer.c +++ b/arch/arm/plat-mxc/global_timer.c @@ -18,6 +18,7 @@ #include <linux/cpufreq.h> #include <linux/err.h> #include <linux/seq_file.h> +#include <linux/ftrace.h> #include <asm/sched_clock.h> #include <asm/hardware/gic.h> diff --git a/arch/arm/plat-mxc/include/mach/iomux-mvf.h b/arch/arm/plat-mxc/include/mach/iomux-mvf.h index 1bbf8158051a..9978dc02f832 100644 --- a/arch/arm/plat-mxc/include/mach/iomux-mvf.h +++ b/arch/arm/plat-mxc/include/mach/iomux-mvf.h @@ -118,6 +118,11 @@ typedef enum iomux_config { IOMUX_PAD(0x0094, 0x0094, 2, 0x0340, 1, \ MVF600_I2C_PAD_CTRL | PAD_CTL_OBE_IBE_ENABLE) +/*SW1*/ +#define MVF600_PAD38_PTB16_USER_BTN1 \ + IOMUX_PAD(0x0098, 0x0098, 0, 0x0000, 0, \ + MVF600_GPIO_GENERAL_CTRL | PAD_CTL_IBE_ENABLE) + /*CAN1*/ #define MVF600_PAD38_PTB16__CAN1_RX \ IOMUX_PAD(0x0098, 0x0098, 1, 0x0000, 0, 0) @@ -413,6 +418,20 @@ typedef enum iomux_config { #define MVF600_PAD30_PTB8_INT \ IOMUX_PAD(0x0078, 0x0078, 0, 0x032C, 0, MVF600_FTM1_CH_CTRL) +#define MVF600_PAD28_PTB6_UART2_TX \ + IOMUX_PAD(0x0070, 0x0070, 7, 0x038C, 0, \ + MVF600_UART_PAD_CTRL | PAD_CTL_OBE_ENABLE) +#define MVF600_PAD29_PTB7_UART2_RX \ + IOMUX_PAD(0x0074, 0x0074, 7, 0x0388, 0, \ + MVF600_UART_PAD_CTRL | PAD_CTL_IBE_ENABLE) + +#define MVF600_PAD10_PTA20_UART3_TX \ + IOMUX_PAD(0x0028, 0x0028, 6, 0x0394, 0, \ + MVF600_UART_PAD_CTRL | PAD_CTL_OBE_ENABLE) +#define MVF600_PAD11_PTA21_UART3_RX \ + IOMUX_PAD(0x002C, 0x002C, 6, 0x0390, 0, \ + MVF600_UART_PAD_CTRL | PAD_CTL_IBE_ENABLE) + /* FlexTimer channel pin */ #define MVF600_PAD22_PTB0_FTM0CH0 \ IOMUX_PAD(0x0058, 0x0058, 1, 0x0000, 0, MVF600_FTM0_CH_CTRL) diff --git a/drivers/i2c/busses/i2c-imx.c b/drivers/i2c/busses/i2c-imx.c index e13e020cab2e..bd5269165aa0 100644 --- a/drivers/i2c/busses/i2c-imx.c +++ b/drivers/i2c/busses/i2c-imx.c @@ -125,6 +125,7 @@ static u16 __initdata i2c_clk_div[60][2] = { }; static MVF_SEMA4* sema4; +static int i2c_sema4_assigned = 0; #else static u16 __initdata i2c_clk_div[50][2] = { { 22, 0x20 }, { 24, 0x21 }, { 26, 0x22 }, { 28, 0x23 }, @@ -621,9 +622,14 @@ static int __init i2c_imx_probe(struct platform_device *pdev) #ifdef CONFIG_ARCH_MVF // for makeing sure not in use by MQX concurrently - if(mvf_sema4_assign(MVF_I2C_SEMAPHORE_NUMBER, &sema4)) { - dev_err(&pdev->dev, "could not assign MQX semaphore %d\n", MVF_I2C_SEMAPHORE_NUMBER); - goto fail5; + if(!i2c_sema4_assigned) + { + if(mvf_sema4_assign(MVF_I2C_SEMAPHORE_NUMBER, &sema4)) { + dev_err(&pdev->dev, "could not assign MQX semaphore %d\n", MVF_I2C_SEMAPHORE_NUMBER); + goto fail5; + } + // mark the semaphore as assigned + i2c_sema4_assigned = 1; } #endif diff --git a/drivers/mtd/devices/m25p80.c b/drivers/mtd/devices/m25p80.c index 4c6b4f68239c..6d2f4ef3b16c 100644 --- a/drivers/mtd/devices/m25p80.c +++ b/drivers/mtd/devices/m25p80.c @@ -932,6 +932,7 @@ static int __devinit m25p_probe(struct spi_device *spi) flash->mtd.dev.parent = &spi->dev; flash->page_size = info->page_size; + flash->mtd.writebufsize = flash->page_size; if (info->addr_width) flash->addr_width = info->addr_width; diff --git a/drivers/usb/core/hub.c b/drivers/usb/core/hub.c index 694476ea22ac..ea6cd7feea01 100644 --- a/drivers/usb/core/hub.c +++ b/drivers/usb/core/hub.c @@ -165,7 +165,50 @@ EXPORT_SYMBOL_GPL(ehci_cf_port_reset_rwsem); #define HUB_DEBOUNCE_STEP 25 #define HUB_DEBOUNCE_STABLE 100 +#ifdef CONFIG_FSL_USB_TEST_MODE +static u8 usb_device_white_list[] = { + USB_CLASS_HID, + USB_CLASS_HUB, + USB_CLASS_MASS_STORAGE +}; + +static inline int in_white_list(u8 interfaceclass) +{ + int i; + for (i = 0; i < sizeof(usb_device_white_list); i++) { + if (interfaceclass == usb_device_white_list[i]) + return 1; + } + return 0; +} + +static inline int device_in_white_list(struct usb_device *udev) +{ + int i; + int num_configs; + struct usb_host_config *c; + + /* for test fixture, we always return 1 */ + if (udev->descriptor.idVendor == 0x1A0A) + return 1; + + c = udev->config; + num_configs = udev->descriptor.bNumConfigurations; + for (i = 0; i < num_configs; (i++, c++)) { + struct usb_interface_descriptor *desc = NULL; + + /* It's possible that a config has no interfaces! */ + if (c->desc.bNumInterfaces > 0) + desc = &c->intf_cache[0]->altsetting->desc; + + if (desc && !in_white_list((u8)desc->bInterfaceClass)) + continue; + return 1; + } + return 0; +} +#endif static int usb_reset_and_verify_device(struct usb_device *udev); static inline char *portspeed(struct usb_hub *hub, int portstatus) @@ -1696,7 +1739,18 @@ void usb_disconnect(struct usb_device **pdev) * for de-configuring the device and invoking the remove-device * notifier chain (used by usbfs and possibly others). */ - device_del(&udev->dev); +/* device_del(&udev->dev); */ + /* If error occur during enumeration, maybe the device_add + * will not call at all, so we need to identify whether this + * device has been added or not, here we use dev.driver to + * tell it + */ + if (udev->dev.driver) { + device_del(&udev->dev); + printk(KERN_DEBUG "device_del called\n"); + } else { + printk(KERN_DEBUG "device_del not need to call\n"); + } /* Free the device number and delete the parent's children[] * (or root_hub) pointer. @@ -1914,6 +1968,14 @@ int usb_new_device(struct usb_device *udev) udev->dev.devt = MKDEV(USB_DEVICE_MAJOR, (((udev->bus->busnum-1) * 128) + (udev->devnum-1))); +#ifdef CONFIG_FSL_USB_TEST_MODE + if (!device_in_white_list(udev)) { + printk(KERN_ERR "unsupported device: not in white list\n"); + goto fail; + } else { + printk(KERN_DEBUG "supported device\n"); + } +#endif /* Tell the world! */ announce_device(udev); @@ -2645,7 +2707,7 @@ static int hub_suspend(struct usb_interface *intf, pm_message_t msg) struct usb_device *udev; udev = hdev->children [port1-1]; - if (udev && udev->can_submit) { + if (udev && udev->can_submit && udev->dev.driver) { dev_warn(&intf->dev, "port %d nyet suspended\n", port1); if (msg.event & PM_EVENT_AUTO) return -EBUSY; @@ -3300,6 +3362,10 @@ static void hub_port_connect_change(struct usb_hub *hub, int port1, usb_set_device_state(udev, USB_STATE_POWERED); udev->bus_mA = hub->mA_per_port; udev->level = hdev->level + 1; +#ifdef CONFIG_FSL_USB_TEST_MODE + printk(KERN_INFO "+++ %s:udev->level :%d", __func__, + udev->level); +#endif udev->wusb = hub_is_wusb(hub); /* Only USB 3.0 devices are connected to SuperSpeed hubs. */ @@ -3518,6 +3584,10 @@ static void hub_events(void) if (ret < 0) continue; +#ifdef CONFIG_FSL_USB_TEST_MODE + if (portstatus & USB_PORT_STAT_TEST) + continue; +#endif if (portchange & USB_PORT_STAT_C_CONNECTION) { clear_port_feature(hdev, i, USB_PORT_FEAT_C_CONNECTION); diff --git a/drivers/usb/gadget/file_storage.c b/drivers/usb/gadget/file_storage.c index 390d43b0e2a1..b226412b926d 100644 --- a/drivers/usb/gadget/file_storage.c +++ b/drivers/usb/gadget/file_storage.c @@ -280,7 +280,7 @@ static const char fsg_string_product[] = DRIVER_DESC; static const char fsg_string_config[] = "Self-powered"; static const char fsg_string_interface[] = "Mass Storage"; - +#define FSG_NO_OTG 1 1 #include "storage_common.c" @@ -3491,8 +3491,8 @@ static int __ref fsg_bind(struct usb_gadget *gadget) fsg_fs_intr_in_desc.bEndpointAddress; } - if (gadget_is_otg(gadget)) - fsg_otg_desc.bmAttributes |= USB_OTG_HNP; +// if (gadget_is_otg(gadget)) +// fsg_otg_desc.bmAttributes |= USB_OTG_HNP; rc = -ENOMEM; diff --git a/drivers/usb/gadget/storage_common.c b/drivers/usb/gadget/storage_common.c index 1fa4f705b0b4..70658fe21bd7 100644 --- a/drivers/usb/gadget/storage_common.c +++ b/drivers/usb/gadget/storage_common.c @@ -64,8 +64,8 @@ * DO NOT REUSE THESE IDs with any other driver!! Ever!! * Instead: allocate your own, using normal USB-IF procedures. */ -#define FSG_VENDOR_ID 0x0525 /* NetChip */ -#define FSG_PRODUCT_ID 0xa4a5 /* Linux-USB File-backed Storage Gadget */ +#define FSG_VENDOR_ID 0x15a2 /* NetChip:0x0525 */ +#define FSG_PRODUCT_ID 0x78 /* Linux-USB File-backed Storage Gadget: a4a5*/ /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 9e99765ee61c..c9f9a658e384 100755 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -59,6 +59,13 @@ config USB_EHCI_HCD To compile this driver as a module, choose M here: the module will be called ehci-hcd. +config FSL_USB_TEST_MODE + bool "FSL High-speed Electrical Test Mode Support" + depends on USB_EHCI_ARC + default n + ---help--- + enable freescale test mode + config USB_EHCI_ARC bool "Support for Freescale controller" depends on USB_EHCI_HCD && (ARCH_MXC || ARCH_STMP3XXX || ARCH_MXS) diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 624a362f2fee..0bcc66555507 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -35,3 +35,4 @@ obj-$(CONFIG_USB_HWA_HCD) += hwa-hc.o obj-$(CONFIG_USB_IMX21_HCD) += imx21-hcd.o obj-$(CONFIG_USB_FSL_MPH_DR_OF) += fsl-mph-dr-of.o obj-$(CONFIG_USB_OCTEON2_COMMON) += octeon2-common.o +obj-$(CONFIG_FSL_USB_TEST_MODE) += arc_hset.o diff --git a/drivers/usb/host/arc_hset.c b/drivers/usb/host/arc_hset.c new file mode 100644 index 000000000000..2ec9055c317e --- /dev/null +++ b/drivers/usb/host/arc_hset.c @@ -0,0 +1,468 @@ +/* + * HS USB Host-mode HSET driver for ARC USB controller + * Copyright 2011-2013 Freescale Semiconductor, Inc. All Rights Reserved. + * Zhang Yan <jasper.zhang@freescale.com> + * Peter Chen <peter.chen@freescale.com> */ + +#include <linux/kernel.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/slab.h> +#include <linux/module.h> +#include <linux/delay.h> +#include <linux/usb.h> +#include <linux/proc_fs.h> +#include <linux/seq_file.h> + +#define EHCI_IAA_MSECS 10 /* arbitrary */ +#include <linux/usb/hcd.h> +#include "../core/usb.h" +#include "ehci-fsl.h" +#include <linux/uaccess.h> +#include <linux/io.h> +#include <mach/arc_otg.h> +#include "ehci.h" + +#define rh_level 1 +#define SINGLE_STEP_SLEEP_COUNT 15000 +#define USB_HIGH_SPEED 0x01 +#define USB_FULL_SPEED 0x02 +#define USB_LOW_SPEED 0x03 + +/* ARC port test mode */ +#define PORTSC_PTC_TEST_MODE_DISABLE 0x00 +#define PORTSC_PTC_TEST_J 0x01 +#define PORTSC_PTC_TEST_K 0x02 +#define PORTSC_PTC_SE0_NAK 0x03 +#define PORTSC_PTC_TEST_PACKET 0x04 +#define PORTSC_PTC_FORCE_ENABLE_HS 0x05 +#define PORTSC_PTC_FORCE_ENABLE_FS 0x06 +#define PORTSC_PTC_FORCE_ENABLE_LS 0x07 + +/* Other test */ +#define HSET_TEST_SUSPEND_RESUME 0x08 +#define HSET_TEST_GET_DEV_DESC 0x09 +#define HSET_TEST_GET_DEV_DESC_DATA 0x0A + + +#define TEST_DEVICE_VID 0x1A0A +static struct usb_device_id archset_table[] = { + { USB_DEVICE(TEST_DEVICE_VID, 0x0101) }, /* Test_SE0_NAK */ + { USB_DEVICE(TEST_DEVICE_VID, 0x0102) }, /* Test_J */ + { USB_DEVICE(TEST_DEVICE_VID, 0x0103) }, /* Test_K */ + { USB_DEVICE(TEST_DEVICE_VID, 0x0104) }, /* Test_Packet */ + { USB_DEVICE(TEST_DEVICE_VID, 0x0105) }, /* Force enable */ + { USB_DEVICE(TEST_DEVICE_VID, 0x0106) }, /* HS_HOST_PORT_SUSPEND_RESUME */ + { USB_DEVICE(TEST_DEVICE_VID, 0x0107) }, /* SINGLE_STEP_GET_DEV_DESC */ + { USB_DEVICE(TEST_DEVICE_VID, 0x0108) }, /* SINGLE_STEP_GET_DEV_DESC_DATA */ + { } /* Terminating entry */ +}; +MODULE_DEVICE_TABLE(usb, archset_table); + +/* Private data */ +struct usb_archset { + struct usb_device *udev; /* the usb device for this device */ + struct usb_interface *interface; /* the interface for this device */ + struct kref kref; + struct ehci_hcd *ehci; /* the usb controller */ + char ptcname[24]; +}; + + void set_single_step_desc_data_on(void); + void clear_single_step_desc_data_on(void); + +static void arc_kill_per_sched(struct ehci_hcd *ehci) +{ + u32 command = 0; + command = ehci_readl(ehci, &ehci->regs->command); + command &= ~(CMD_PSE); + ehci_writel(ehci, command, &ehci->regs->command); + printk(KERN_INFO "+++ %s: Periodic Schedule Stopped", __func__); +} + +static void arc_hset_test(struct ehci_hcd *ehci, int mode) +{ + u32 portsc = 0; + + portsc = ehci_readl(ehci, &ehci->regs->port_status[0]); + portsc &= ~(0xf << 16); + portsc |= (mode << 16); + ehci_writel(ehci, portsc, &ehci->regs->port_status[0]); +} + +static inline void hub_set_testmode(struct usb_device *udev, u8 + test_mode) +{ + struct usb_device *hub_dev; + int i; + int max_port; + int status; + u8 level = udev->level; + + if (level == 0) + return; + else { + printk(KERN_INFO "run %s at tie %d hub\n", __func__, level); + /* get the port number of parent */ + if (udev->parent != NULL) { + hub_dev = udev->parent; + max_port = hub_dev->maxchild; + } else { + printk(KERN_INFO "device don't have parent hub\n"); + return; + } + + for (i = 0; i < max_port; i++) { + status = usb_control_msg(hub_dev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, + USB_PORT_FEAT_SUSPEND, i+1, + NULL, 0, 1000); + if (status >= 0) + printk(KERN_INFO "send port_suspend to port %d\n", i+1); + else { + printk(KERN_INFO "send port_suspend error %d to port %d\n", + status, i+1); + continue; + } + } + msleep(1000); + status = usb_control_msg(hub_dev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE/*USB_REQ_CLEAR_FEATURE*/, USB_RT_PORT, + USB_PORT_FEAT_TEST, (__u16) ((test_mode << 8) | udev->portnum), + NULL, 0, 1000); + if (status >= 0) + printk(KERN_INFO "send cmd %d to port %d\n", test_mode, udev->portnum); + else + printk(KERN_INFO "send cmd %d error %d to port %d\n", test_mode, status, udev->portnum); + } +} + +static inline void test_j(struct usb_archset *hset) +{ + struct usb_device *udev = hset->udev; + u8 level = udev->level; + if (level == rh_level) { + printk(KERN_INFO "%s at tie %d hub\n", __func__, level); + arc_hset_test(hset->ehci, PORTSC_PTC_TEST_J); + } else + hub_set_testmode(udev, PORTSC_PTC_TEST_J); +} + +static inline void test_k(struct usb_archset *hset) +{ + struct usb_device *udev = hset->udev; + u8 level = udev->level; + printk(KERN_INFO "%s at tie %d hub\n", __func__, level); + if (level == rh_level) + arc_hset_test(hset->ehci, PORTSC_PTC_TEST_K); + else + hub_set_testmode(udev, PORTSC_PTC_TEST_K); +} + +static inline void test_se0_nak(struct usb_archset *hset) +{ + struct usb_device *udev = hset->udev; + u8 level = udev->level; + printk(KERN_INFO "%s at tie %d hub\n", __func__, level); + if (level == rh_level) + arc_hset_test(hset->ehci, PORTSC_PTC_SE0_NAK); + else + hub_set_testmode(udev, PORTSC_PTC_SE0_NAK); +} + +static inline void test_packet(struct usb_archset *hset) +{ + struct usb_device *udev = hset->udev; + u8 level = udev->level; + printk(KERN_INFO "%s at tie %d hub\n", __func__, level); + if (level == rh_level) + arc_hset_test(hset->ehci, PORTSC_PTC_TEST_PACKET); + else + hub_set_testmode(udev, PORTSC_PTC_TEST_PACKET); +} + +static inline void test_force_enable(struct usb_archset *hset, int + forcemode) +{ + struct usb_device *udev = hset->udev; + u8 level = udev->level; + int ptc_fmode = 0; + printk(KERN_INFO "%s at tie %d hub\n", __func__, level); + if (level == 0) { + switch (forcemode) { + case USB_HIGH_SPEED: + ptc_fmode = PORTSC_PTC_FORCE_ENABLE_HS; + break; + case USB_FULL_SPEED: + ptc_fmode = PORTSC_PTC_FORCE_ENABLE_FS; + break; + case USB_LOW_SPEED: + ptc_fmode = PORTSC_PTC_FORCE_ENABLE_LS; + break; + default: + printk(KERN_ERR "unknown speed mode %d\n", forcemode); + return; + } + arc_hset_test(hset->ehci, ptc_fmode); + } else + hub_set_testmode(udev, PORTSC_PTC_FORCE_ENABLE_HS); +} + +static void suspend(struct usb_archset *hset) +{ + struct ehci_hcd *ehci = hset->ehci; + u32 portsc = 0; + + portsc = ehci_readl(ehci, &ehci->regs->port_status[0]); + portsc |= PORT_SUSPEND; /* Set suspend */ + ehci_writel(ehci, portsc, &ehci->regs->port_status[0]); +} + + +static void resume(struct usb_archset *hset) +{ + struct ehci_hcd *ehci = hset->ehci; + u32 portsc = 0; + + portsc = ehci_readl(ehci, &ehci->regs->port_status[0]); + portsc |= PORT_RESUME; + ehci_writel(ehci, portsc, &ehci->regs->port_status[0]); +} + +static void test_suspend_resume(struct usb_archset *hset) +{ + printk(KERN_INFO "%s\n", __func__); + + suspend(hset); + msleep(15000); /* Wait for 15s */ + resume(hset); +} + +static void test_single_step_get_dev_desc(struct usb_archset *hset) +{ + struct ehci_hcd *ehci = hset->ehci; + struct usb_bus *bus = hcd_to_bus(ehci_to_hcd(ehci)); + struct usb_device *udev = hset->udev; + int result; + + if (!ehci || !bus || !bus->root_hub) { + printk(KERN_ERR "Host controller not ready!\n"); + return; + } + + if (!udev) { + printk(KERN_ERR "No device connected.\n"); + return; + } + + /* Stop Periodic schedule to prevent polling of hubs */ + arc_kill_per_sched(hset->ehci); + + msleep(SINGLE_STEP_SLEEP_COUNT); /* SOF for 15s */ + + result = usb_get_device_descriptor(udev, sizeof(struct usb_device_descriptor)); + if (result < 0 && result != -ETIMEDOUT) + printk(KERN_ERR "usb_get_device_descriptor failed %d\n", result); + else + printk(KERN_INFO "usb_get_device_descriptor passed\n"); +} +static void test_single_step_get_dev_desc_data(struct usb_archset *hset) +{ + struct usb_device *udev = hset->udev; + struct usb_device_descriptor *desc; + int result; + unsigned int size = sizeof(struct usb_device_descriptor); + printk(KERN_INFO "%s size = %d\n", __func__, size); + desc = kmalloc(sizeof(*desc), GFP_NOIO); + if (!desc) { + printk(KERN_ERR "alloc desc failed\n"); + return ; + } + + set_single_step_desc_data_on(); + + result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + (0x01 << 8) + 0, 0, desc, size, USB_CTRL_GET_TIMEOUT); + if (result < 0 && result != -ETIMEDOUT) + printk(KERN_ERR "the setup transaction failed %d\n", result); + else + printk(KERN_INFO "the setup transaction passed\n"); + + + /* Stop Periodic schedule to prevent polling of hubs */ + arc_kill_per_sched(hset->ehci); + + msleep(SINGLE_STEP_SLEEP_COUNT); + + result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + (0x01 << 8) + 0, 0, desc, size, USB_CTRL_GET_TIMEOUT); + if (result <= 0 && result != -ETIMEDOUT) + printk(KERN_ERR "the data transaction failed %d\n", result); + else + printk(KERN_INFO "the data transaction passed\n"); + + result = usb_control_msg(udev, usb_rcvctrlpipe(udev, 0), + USB_REQ_GET_DESCRIPTOR, USB_DIR_IN, + (0x01 << 8) + 0, 0, desc, size, USB_CTRL_GET_TIMEOUT); + if (result < 0 && result != -ETIMEDOUT) + printk(KERN_ERR "the status transaction failed %d\n", result); + else + printk(KERN_INFO "the status transaction passed\n"); + + clear_single_step_desc_data_on(); + + kfree(desc); + + msleep(SINGLE_STEP_SLEEP_COUNT); /* SOF for 15s */ + printk(KERN_INFO "test_single_step_get_dev_desc_data finished\n"); +} + + +void test_single_step_set_feature(struct usb_archset *hset) +{ + struct ehci_hcd *ehci = hset->ehci; + struct usb_bus *bus = hcd_to_bus(ehci_to_hcd(ehci)); + struct usb_device *udev; + + if (!ehci || !bus || !bus->root_hub) { + printk(KERN_ERR "Host controller not ready!\n"); + return; + } + udev = bus->root_hub->children[0]; + if (!udev) { + printk(KERN_ERR "No device connected.\n"); + return; + } + usb_control_msg(udev, usb_sndctrlpipe(udev, 0), + USB_REQ_SET_FEATURE, 0, 0, 0, + NULL, 0, USB_CTRL_SET_TIMEOUT); +} + +static struct usb_archset *the_hset[4]; +static struct usb_archset *init_hset_dev(void *controller) +{ + struct ehci_hcd *ehci = (struct ehci_hcd *)controller; + struct usb_archset *hset = NULL; + int ctrid = 0; + + ctrid = ehci_to_hcd(ehci)->self.busnum-1; + if (the_hset[ctrid]) { + kref_get(&the_hset[ctrid]->kref); + return the_hset[ctrid]; + } + + hset = kzalloc(sizeof(struct usb_archset), GFP_KERNEL); + if (hset == NULL) { + printk(KERN_ERR "Out of memory!\n"); + return NULL; + } + hset->ehci = (struct ehci_hcd *)controller; + kref_init(&hset->kref); + the_hset[ctrid] = hset; + + return hset; +} + + +static void hset_delete(struct kref *kref) +{ + struct usb_archset *hset = container_of(kref, struct usb_archset, + kref); + + kfree(hset); +} + +static int archset_probe(struct usb_interface *iface, + const struct usb_device_id *id) +{ + struct usb_archset *hset; + struct usb_device *udev; + struct usb_hcd *hcd; + + udev = usb_get_dev(interface_to_usbdev(iface)); + hcd = bus_to_hcd(udev->bus); + hset = init_hset_dev((void *)hcd_to_ehci(hcd)); + if (hset == NULL) + return -ENOMEM; + + hset->udev = udev; + usb_set_intfdata(iface, hset); + switch (id->idProduct) { + case 0x0101: + test_se0_nak(hset); + break; + case 0x0102: + test_j(hset); + break; + case 0x0103: + test_k(hset); + break; + case 0x0104: + test_packet(hset); + break; + case 0x0105: + printk(KERN_INFO "Force FS/FS/LS ?\n"); + test_force_enable(hset, USB_HIGH_SPEED); + break; + case 0x0106: + test_suspend_resume(hset); + break; + case 0x0107: + printk(KERN_INFO "Begin SINGLE_STEP_GET_DEVICE_DESCRIPTOR\n"); + test_single_step_get_dev_desc(hset); + break; + case 0x0108: + printk(KERN_INFO "Begin SINGLE_STEP_GET_DEVICE_DESCRIPTOR_DATA\n"); + test_single_step_get_dev_desc_data(hset); + break; + } + + return 0; +} + +static void archset_disconnect(struct usb_interface *iface) +{ + struct usb_archset *hset; + + hset = usb_get_intfdata(iface); + usb_set_intfdata(iface, NULL); + + usb_put_dev(hset->udev); + kref_put(&hset->kref, hset_delete); +} + + +static struct usb_driver archset_driver = { + .name = "arc hset", + .probe = archset_probe, + .disconnect = archset_disconnect, + .id_table = archset_table, +}; + +static int __init usb_hset_init(void) +{ + int ret = 0; + printk(KERN_ERR "usb register error with %d\n", ret); + /* register driver to usb subsystem */ + ret = usb_register(&archset_driver); + if (ret) + printk(KERN_ERR "usb register error with %d\n", ret); + else + printk(KERN_INFO "usb hset init succeed\n"); + + return ret; +} + +static void __exit usb_hset_exit(void) +{ + usb_deregister(&archset_driver); +} + +module_init(usb_hset_init); +module_exit(usb_hset_exit); +MODULE_DESCRIPTION("Freescale USB Host Test Mode Driver"); +MODULE_AUTHOR("Jasper Zhang"); +MODULE_LICENSE("GPL"); + diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index ffc22bf1d825..c486480ab982 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -48,6 +48,21 @@ #include <asm/system.h> #include <asm/unaligned.h> + +#ifdef CONFIG_FSL_USB_TEST_MODE +static u32 single_step_desc_data_on; +void set_single_step_desc_data_on(void) +{ + single_step_desc_data_on = 1; +} +EXPORT_SYMBOL_GPL(set_single_step_desc_data_on); + +void clear_single_step_desc_data_on(void) +{ + single_step_desc_data_on = 0; +} +EXPORT_SYMBOL_GPL(clear_single_step_desc_data_on); +#endif /*-------------------------------------------------------------------------*/ /* @@ -959,8 +974,22 @@ static int ehci_urb_enqueue ( /* FALLTHROUGH */ /* case PIPE_BULK: */ default: - if (!qh_urb_transaction (ehci, urb, &qtd_list, mem_flags)) +#ifdef CONFIG_FSL_USB_TEST_MODE + if (!single_step_desc_data_on) { + printk(KERN_DEBUG "in test mode, but single step NOT on\n"); + if (!qh_urb_transaction(ehci, urb, &qtd_list, + mem_flags)) + return -ENOMEM; + } else { + printk(KERN_DEBUG "in test mode, single step on\n"); + if (!single_step_qh_urb_transaction(ehci, urb, + &qtd_list, mem_flags)) + return -ENOMEM; + } +#else + if (!qh_urb_transaction(ehci, urb, &qtd_list, mem_flags)) return -ENOMEM; +#endif return submit_async(ehci, urb, &qtd_list, mem_flags); case PIPE_INTERRUPT: diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index 271987952e33..f8fac3aca850 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -592,6 +592,187 @@ static void qtd_list_free ( } } +#ifdef CONFIG_FSL_USB_TEST_MODE +/* + * create a list of filled qtds for this URB; won't link into qh. + */ +static struct list_head * +single_step_qh_urb_transaction( + struct ehci_hcd *ehci, + struct urb *urb, + struct list_head *head, + gfp_t flags +) { + struct ehci_qtd *qtd, *qtd_prev; + dma_addr_t buf; + int len, this_sg_len, maxpacket; + int is_input; + u32 token; + int i; + struct scatterlist *sg; +#define SINGLE_STEP_PHASE_SETUP 0 +#define SINGLE_STEP_PHASE_DATA 1 +#define SINGLE_STEP_PHASE_STATUS 2 +#define SINGLE_STEP_PHASE_NONE 0xFF + static u32 phase_state = SINGLE_STEP_PHASE_NONE; + + /* + * URBs map to sequences of QTDs: one logical transaction + */ + qtd = ehci_qtd_alloc(ehci, flags); + if (unlikely(!qtd)) + return NULL; + list_add_tail(&qtd->qtd_list, head); + qtd->urb = urb; + + token = QTD_STS_ACTIVE; + token |= (EHCI_TUNE_CERR << 10); + /* for split transactions, SplitXState initialized to zero */ + len = urb->transfer_buffer_length; + is_input = usb_pipein(urb->pipe); + if (!is_input) { + printk(KERN_INFO "single step can only send IN\n"); + return NULL; + } + + if (!usb_pipecontrol(urb->pipe)) { + printk(KERN_INFO "single step can only send control pipe\n"); + return NULL; + } + + if (phase_state == SINGLE_STEP_PHASE_NONE) { + /* + * SETUP pid + * we use transfer_buffer_length to identfiy whether it + * is in setup phase or data phase + */ + qtd_fill(ehci, qtd, urb->setup_dma, + sizeof(struct usb_ctrlrequest), + token | (2 /* "setup" */ << 8), 8); + + /* ... and always at least one more pid */ + qtd->urb = urb; + phase_state = SINGLE_STEP_PHASE_SETUP; + } + /* + * data transfer stage: buffer setup + */ + else + if (phase_state == SINGLE_STEP_PHASE_SETUP && + len != 0) { + i = urb->num_sgs; + if (len > 0 && i > 0) { + sg = urb->sg; + buf = sg_dma_address(sg); + + /* urb->transfer_buffer_length may be smaller than the + * size of the scatterlist (or vice versa) + */ + this_sg_len = min_t(int, sg_dma_len(sg), len); + } else { + sg = NULL; + buf = urb->transfer_dma; + this_sg_len = len; + } + + if (is_input) + token |= (1 /* "in" */ << 8); + /* else it's already initted to "out" pid (0 << 8) */ + maxpacket = max_packet(usb_maxpacket(urb->dev, + urb->pipe, !is_input)); + /* for the first data qtd, the toggle should be 1 */ + token ^= QTD_TOGGLE; + + /* + * buffer gets wrapped in one or more qtds; + * last one may be "short" (including zero len) + * and may serve as a control status ack + */ + for (;;) { + int this_qtd_len; + + this_qtd_len = qtd_fill(ehci, qtd, buf, this_sg_len, + token, maxpacket); + this_sg_len -= this_qtd_len; + len -= this_qtd_len; + buf += this_qtd_len; + + /* + * short reads advance to a "magic" dummy instead of + * the next qtd ... that forces the queue to stop, for + * manual cleanup. (will usually be overridden later.) + */ + if (is_input) + qtd->hw_alt_next = ehci->async->hw->hw_alt_next; + + /* qh makes control packets use qtd toggle; switch it*/ + if ((maxpacket & (this_qtd_len + (maxpacket - 1))) == 0) + token ^= QTD_TOGGLE; + + if (likely(this_sg_len <= 0)) { + if (--i <= 0 || len <= 0) + break; + sg = sg_next(sg); + buf = sg_dma_address(sg); + this_sg_len = min_t(int, sg_dma_len(sg), len); + } + + qtd_prev = qtd; + qtd = ehci_qtd_alloc(ehci, flags); + if (unlikely(!qtd)) + goto cleanup; + qtd->urb = urb; + qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma); + list_add_tail(&qtd->qtd_list, head); + } + + phase_state = SINGLE_STEP_PHASE_DATA; + + /* + * unless the caller requires manual cleanup after short reads, + * have the alt_next mechanism keep the queue running after the + * last data qtd (the only one, for control and other cases). + */ + if (likely((urb->transfer_flags & URB_SHORT_NOT_OK) == 0 + || usb_pipecontrol(urb->pipe))) + qtd->hw_alt_next = EHCI_LIST_END(ehci); + } + + /* + * control requests may need a terminating data "status" ack; + * bulk ones may need a terminating short packet (zero length). + */ + else + if (phase_state == SINGLE_STEP_PHASE_DATA) { + int one_more = 0; + if (usb_pipecontrol(urb->pipe)) { + one_more = 1; + /* for single step, it always be out here */ + token &= ~(3 << 8); + token |= QTD_TOGGLE; /* force DATA1 */ + } else if (usb_pipebulk(urb->pipe) + && (urb->transfer_flags & URB_ZERO_PACKET) + && !(urb->transfer_buffer_length % maxpacket)) { + one_more = 1; + } + if (one_more) { + /* never any data in such packets */ + qtd_fill(ehci, qtd, 0, 0, token, 0); + } + } + + /* by default, enable interrupt on urb completion */ + if (likely(!(urb->transfer_flags & URB_NO_INTERRUPT))) + qtd->hw_token |= cpu_to_hc32(ehci, QTD_IOC); + return head; + +cleanup: + qtd_list_free(ehci, urb, head); + phase_state = SINGLE_STEP_PHASE_NONE; + return NULL; +} +#endif + /* * create a list of filled qtds for this URB; won't link into qh. */ diff --git a/drivers/usb/misc/Kconfig b/drivers/usb/misc/Kconfig index 1bfcd02ebeb5..de8933e5e78b 100644 --- a/drivers/usb/misc/Kconfig +++ b/drivers/usb/misc/Kconfig @@ -218,6 +218,13 @@ config USB_TEST See <http://www.linux-usb.org/usbtest/> for more information, including sample test device firmware and "how to use it". +config USB_OTG_EH_TEST + tristate "USB OTG & EH Test Mode Support driver" + depends on USB && USB_SUSPEND + help + This driver is for running tests define on OTG & Embedded Host + Specification. + config USB_ISIGHTFW tristate "iSight firmware loading support" depends on USB diff --git a/drivers/usb/misc/Makefile b/drivers/usb/misc/Makefile index 796ce7ebccc8..6b17e8c64e45 100644 --- a/drivers/usb/misc/Makefile +++ b/drivers/usb/misc/Makefile @@ -21,6 +21,7 @@ obj-$(CONFIG_USB_LED) += usbled.o obj-$(CONFIG_USB_LEGOTOWER) += legousbtower.o obj-$(CONFIG_USB_RIO500) += rio500.o obj-$(CONFIG_USB_TEST) += usbtest.o +obj-$(CONFIG_USB_OTG_EH_TEST) += otg-eh-test.o obj-$(CONFIG_USB_TRANCEVIBRATOR) += trancevibrator.o obj-$(CONFIG_USB_USS720) += uss720.o obj-$(CONFIG_USB_SEVSEG) += usbsevseg.o diff --git a/drivers/usb/misc/otg-eh-test.c b/drivers/usb/misc/otg-eh-test.c new file mode 100644 index 000000000000..d638149d809f --- /dev/null +++ b/drivers/usb/misc/otg-eh-test.c @@ -0,0 +1,241 @@ +/** + * otg-eh-test.c - OTG & Embedded Host Test Support Driver + * + * Copyright (C) 2012 Texas Instruments Incorporated - http://www.ti.com + * + * Author: Felipe Balbi <balbi@ti.com> + * + * This program is free software: you can redistribute it and/or modify it + * under the terms of the GNU General Public License version 2 as published by + * the Free Software Foundation. + * + * ALTERNATIVELY, this software may be distributed under the terms of the + * GNU General Public License ("GPL") version 2, as published by the Free + * Software Foundation. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS + * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR + * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, + * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, + * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR + * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF + * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING + * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS + * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * This program is distributed in the hope that 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. + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/usb.h> + +#include <linux/usb/ch11.h> +#include <linux/usb/ch9.h> + +#define USB_IF_TEST_VID 0x1a0a + +#define USB_IF_TEST_SE0_NAK 0x0101 +#define USB_IF_TEST_J 0x0102 +#define USB_IF_TEST_K 0x0103 +#define USB_IF_TEST_PACKET 0x0104 +#define USB_IF_HS_HOST_PORT_SUSPEND_RESUME 0x0106 +#define USB_IF_SINGLE_STEP_GET_DEV_DESC 0x0107 +#define USB_IF_SINGLE_STEP_GET_DEV_DESC_DATA 0x0108 + +#define USB_IF_PROTOCOL_OTG_ELECTRICAL_TEST 0x0200 + +static int usb_set_hub_port_test(struct usb_device *udev, int test) +{ + struct usb_device *hub = udev->parent; + int port = udev->portnum; + + return usb_control_msg(hub, usb_sndctrlpipe(hub, 0), + USB_REQ_SET_FEATURE, USB_RT_PORT, USB_PORT_FEAT_TEST, + (test << 8) | port, NULL, 0, 1000); +} + +static int otg_eh_probe(struct usb_interface *intf, + const struct usb_device_id *id) +{ + struct usb_device_descriptor desc; + struct usb_device *udev; + int ret; + + udev = interface_to_usbdev(intf); + + usb_enable_autosuspend(udev); + usb_autopm_get_interface(intf); + + switch (id->idProduct) { + case USB_IF_TEST_SE0_NAK: + /* + * Upon enumerating VID 0x1A0A/PID 0x0101, the host’s + * downstream port shall enter a high-speed receive mode as + * described in Section 7.1.20 [USB2.0] and drives an SE0 until + * the controller is reset. + */ + usb_set_hub_port_test(udev, TEST_SE0_NAK); + break; + case USB_IF_TEST_J: + /* + * Upon enumerating VID 0x1A0A/PID 0x0102, the host’s + * downstream port shall enter a high-speed J state as + * described in Section 7.1.20 of [USB2.0] until the host + * controller is reset. + */ + usb_set_hub_port_test(udev, TEST_J); + break; + case USB_IF_TEST_K: + /* + * Upon enumerating VID 0x1A0A/PID 0x0103, the host’s + * downstream port shall enter a high-speed K state as + * described in Section 7.1.20 of [USB2.0] until the host + * controller is reset. + */ + usb_set_hub_port_test(udev, TEST_K); + break; + case USB_IF_TEST_PACKET: + /* + * Upon enumerating VID 0x1A0A/PID 0x0104, the host shall begin + * sending test packets as described in Section 7.1.20 of + * [USB2.0] until the host controller is reset. + */ + usb_set_hub_port_test(udev, TEST_PACKET); + break; + case USB_IF_HS_HOST_PORT_SUSPEND_RESUME: + /* + * Upon enumerating VID:0x1A0A/PID 0x0106, the host shall + * continue sending SOFs for 15 seconds, then suspend the + * downstream port under test per Section 7.1.7.6.1 of + * [USB2.0]. After 15 seconds has elapsed, the host shall issue + * a ResumeK state on the bus, then continue sending SOFs. + */ + msleep(15000); + usb_autopm_put_interface(intf); + msleep(15000); + usb_autopm_get_interface(intf); + break; + case USB_IF_SINGLE_STEP_GET_DEV_DESC: + /* + * When the host discovers a device with VID:0x1A0A/PID 0x0107, + * the following steps are executed by the host and the device. + * + * 1. The host enumerates the test device, reads VID:0x1A0A/PID + * 0x0107, then completes its enumeration procedure. + * + * 2. The host issues SOFs for 15 seconds allowing the test + * engineer to raise the scope trigger just above the SOF + * voltage level. + * + * 3. The host sends a complete GetDescriptor(Device) transfer + * + * 4. The device ACKs the request, triggering the scope. (Note: + * SOFs continue.) + */ + msleep(15000); + ret = usb_get_descriptor(udev, USB_DT_DEVICE, 0, &desc, USB_DT_DEVICE_SIZE); + if (ret < 0) + dev_err(&intf->dev, "failed to get device descriptor\n"); + + break; + case USB_IF_SINGLE_STEP_GET_DEV_DESC_DATA: + /* + * When the host discovers a device with VID:0x1A0A/PID 0x0108, + * the following steps are executed by the host and the device. + * + * 1. The host enumerates the test device and reads + * VID:0x1A0A/PID 0x0108, then completes its enumeration + * procedure + * + * 2. After enumerating the device, the host sends + * GetDescriptor(Device) + * + * 3. The device ACKs the request + * + * 4. The host issues SOFs for 15 seconds allowing the test + * engineer to raise the scope trigger just above the SOF + * voltage level + * + * 5. The host sends an IN packet + * + * 6. The device sends data in response to the IN packet, + * triggering the scope + * + * 7. The host sends an ACK in response to the data. (Note: + * SOFs may follow the IN transaction). + */ + ret = usb_get_descriptor(udev, USB_DT_DEVICE, 0, &desc, USB_DT_DEVICE_SIZE); + if (ret < 0) + dev_err(&intf->dev, "failed to get device descriptor\n"); + msleep(15000); + break; + case USB_IF_PROTOCOL_OTG_ELECTRICAL_TEST: + /* OTG-A Device */ + if (udev->bus->otg_port == udev->portnum) { + ret = usb_driver_set_configuration(udev, 1); + if (ret < 0) + dev_err(&intf->dev, "unable to set configuration #1\n"); + } else if (udev->bus->is_b_host) { + /* configuration -1 means "unconfigured" */ + ret = usb_driver_set_configuration(udev, -1); + if (ret < 0) + dev_err(&intf->dev, "unable to unconfigure device\n"); + + /* forcefully suspend */ + usb_autopm_put_interface(intf); + } + + break; + default: + dev_err(&intf->dev, "Unsupported device\n"); + } + + usb_disable_autosuspend(udev); + + return 0; +} + +static void otg_eh_disconnect(struct usb_interface *intf) +{ + struct usb_device *udev; + + udev = interface_to_usbdev(intf); + + usb_lock_device_for_reset(udev, intf); + usb_reset_device(udev); + usb_unlock_device(udev); +} + +static const struct usb_device_id otg_eh_id_table[] __devinitconst = { + { USB_DEVICE(USB_IF_TEST_VID, USB_IF_TEST_SE0_NAK), }, + { USB_DEVICE(USB_IF_TEST_VID, USB_IF_TEST_J), }, + { USB_DEVICE(USB_IF_TEST_VID, USB_IF_TEST_K), }, + { USB_DEVICE(USB_IF_TEST_VID, USB_IF_TEST_PACKET), }, + { USB_DEVICE(USB_IF_TEST_VID, USB_IF_HS_HOST_PORT_SUSPEND_RESUME), }, + { USB_DEVICE(USB_IF_TEST_VID, USB_IF_SINGLE_STEP_GET_DEV_DESC), }, + { USB_DEVICE(USB_IF_TEST_VID, USB_IF_SINGLE_STEP_GET_DEV_DESC_DATA), }, + { USB_DEVICE(USB_IF_TEST_VID, USB_IF_PROTOCOL_OTG_ELECTRICAL_TEST), }, + + { } /* Terminating Entry */ +}; +MODULE_DEVICE_TABLE(usb, otg_eh_id_table); + +static struct usb_driver otg_eh_driver = { + .name = "otg-eh-test", + .probe = otg_eh_probe, + .disconnect = otg_eh_disconnect, + .id_table = otg_eh_id_table, + .supports_autosuspend = true, +}; + +module_usb_driver(otg_eh_driver); + +MODULE_AUTHOR("Felipe Balbi <balbi@ti.com>"); +MODULE_LICENSE("GPL v2"); +MODULE_DESCRIPTION("USB OTG & EH Test Driver"); |