This patch adds test mode support for Langwell gadget driver, and it implements debug interface for Host & Client test mode entry. Change-Id: Ib82d1e577b0c33ad52866bb227a1a6178340012b Signed-off-by: Henry Yuan <hang.yuan@xxxxxxxxx> Signed-off-by: Andy Luo <yifei.luo@xxxxxxxxx> --- Documentation/ABI/testing/sysfs-bus-usb | 14 +++++ drivers/usb/core/sysfs.c | 18 +++++++ drivers/usb/gadget/langwell_udc.c | 20 +++++++ drivers/usb/host/ehci-dbg.c | 86 +++++++++++++++++++++++++++++++ drivers/usb/host/ehci-hub.c | 7 +++ drivers/usb/host/ehci.h | 1 + include/linux/usb/ehci_def.h | 4 ++ 7 files changed, 150 insertions(+), 0 deletions(-) diff --git a/Documentation/ABI/testing/sysfs-bus-usb b/Documentation/ABI/testing/sysfs-bus-usb index 294aa86..0f860ee 100644 --- a/Documentation/ABI/testing/sysfs-bus-usb +++ b/Documentation/ABI/testing/sysfs-bus-usb @@ -142,3 +142,17 @@ Description: such devices. Users: usb_modeswitch + +What: /sys/bus/usb/device/.../test +Date: November 2010 +Contact: Andy Luo <yifei.luo@xxxxxxxxx> +Description: + Writing a Test Mode Selector value to this file will put the + device upstream facing port into test mode. There are the + availeable selector values (refer to USB 2.0 spec Table 9-7 + for more info): + * 1 -> Test_J + * 2 -> Test_K + * 3 -> Test_SE0_NAK + * 4 -> Test_Packet + * 5 -> Test_Force_Enable diff --git a/drivers/usb/core/sysfs.c b/drivers/usb/core/sysfs.c index 448f5b4..e864b8d 100644 --- a/drivers/usb/core/sysfs.c +++ b/drivers/usb/core/sysfs.c @@ -581,6 +581,23 @@ static ssize_t usb_remove_store(struct device *dev, } static DEVICE_ATTR(remove, 0200, NULL, usb_remove_store); +static ssize_t usb_testmode_store(struct device *dev, + struct device_attribute *attr, + const char *buf, size_t count) +{ + int selector; + struct usb_device *udev = to_usb_device(dev); + + if (strict_strtoul(buf, 16, &selector)) + return -EINVAL; + usb_control_msg(udev, usb_sndctrlpipe(udev, 0), USB_REQ_SET_FEATURE, + USB_RECIP_DEVICE, USB_DEVICE_TEST_MODE, + selector << 8, NULL, 0, 1000); + + return count; +} +static DEVICE_ATTR(test, 0200, NULL, usb_testmode_store); + static struct attribute *dev_attrs[] = { /* current configuration's attributes */ @@ -609,6 +626,7 @@ static struct attribute *dev_attrs[] = { &dev_attr_avoid_reset_quirk.attr, &dev_attr_authorized.attr, &dev_attr_remove.attr, + &dev_attr_test.attr, NULL, }; static struct attribute_group dev_attr_grp = { diff --git a/drivers/usb/gadget/langwell_udc.c b/drivers/usb/gadget/langwell_udc.c index 3f17ecf..feaa40a 100644 --- a/drivers/usb/gadget/langwell_udc.c +++ b/drivers/usb/gadget/langwell_udc.c @@ -2264,6 +2264,7 @@ static void handle_setup_packet(struct langwell_udc *dev, u16 wValue = le16_to_cpu(setup->wValue); u16 wIndex = le16_to_cpu(setup->wIndex); u16 wLength = le16_to_cpu(setup->wLength); + u32 portsc1; dev_vdbg(&dev->pdev->dev, "---> %s()\n", __func__); @@ -2364,6 +2365,25 @@ static void handle_setup_packet(struct langwell_udc *dev, dev->gadget.a_alt_hnp_support = 1; dev->dev_status |= (1 << wValue); break; + case USB_DEVICE_TEST_MODE: + dev_dbg(&dev->pdev->dev, "SETUP: TEST MODE\n"); + if ((wIndex & 0xff) || + (dev->gadget.speed != USB_SPEED_HIGH)) + ep0_stall(dev); + switch (wIndex >> 8) { + case 1: + case 2: + case 3: + case 5: + case 4: + portsc1 = readl(&dev->op_regs->portsc1); + portsc1 |= (wIndex & 0xf00) << 8; + writel(portsc1, &dev->op_regs->portsc1); + break; + default: + rc = -EOPNOTSUPP; + } + break; default: rc = -EOPNOTSUPP; break; diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index df5546b..a163e32 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -18,6 +18,10 @@ /* this file is part of ehci-hcd.c */ +#include <linux/seq_file.h> + +static int ehci_hub_test_mode(struct usb_hcd *hcd, u16 wIndex); + #define ehci_dbg(ehci, fmt, args...) \ dev_dbg (ehci_to_hcd(ehci)->self.controller , fmt , ## args ) #define ehci_err(ehci, fmt, args...) \ @@ -360,6 +364,9 @@ static ssize_t debug_lpm_read(struct file *file, char __user *user_buf, static ssize_t debug_lpm_write(struct file *file, const char __user *buffer, size_t count, loff_t *ppos); static int debug_lpm_close(struct inode *inode, struct file *file); +static int debug_testmode_open(struct inode *, struct file *); +static ssize_t debug_testmode_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos); static ssize_t debug_output(struct file*, char __user*, size_t, loff_t*); static int debug_close(struct inode *, struct file *); @@ -389,6 +396,14 @@ static const struct file_operations debug_lpm_fops = { .write = debug_lpm_write, .release = debug_lpm_close, }; +static const struct file_operations debug_testmode_fops = { + .owner = THIS_MODULE, + .open = debug_testmode_open, + .write = debug_testmode_write, + .read = seq_read, + .llseek = seq_lseek, + .release = single_release, +}; static struct dentry *ehci_debug_root; @@ -1043,6 +1058,67 @@ static ssize_t debug_lpm_write(struct file *file, const char __user *user_buf, return count; } +static int debug_testmode_show(struct seq_file *s, void *unused) +{ + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + u32 __iomem *portsc ; + u32 test; + int port; + + hcd = bus_to_hcd(s->private); + ehci = hcd_to_ehci(hcd); + port = HCS_N_PORTS(ehci->hcs_params); + while (port) { + portsc = &ehci->regs->port_status[port-1]; + test = (ehci_readl(ehci, portsc)) & 0xf0000; + + if (test == PORT_TEST_FORCE) + seq_printf(s, "Port%d mode: force enable\n", port); + + if (test == PORT_TEST_PKT) + seq_printf(s, "Port%d mode: test packet\n", port); + + if (test == PORT_TEST_K) + seq_printf(s, "Port%d mode: test K\n", port); + + if (test == PORT_TEST_J) + seq_printf(s, "Port%d mode: test J\n", port); + + if (test == PORT_TEST_SE0_NAK) + seq_printf(s, "Port%d mode: test SE0 NAK\n", port); + port--; + } + + return 0; +} + +static int debug_testmode_open(struct inode *inode, struct file *file) +{ + file->private_data = inode->i_private; + + return single_open(file, debug_testmode_show, inode->i_private); +} + +static ssize_t debug_testmode_write(struct file *file, + const char __user *ubuf, size_t count, loff_t *ppos) +{ + struct usb_hcd *hcd; + unsigned long wIndex; + char buf[8]; + int buf_size; + + hcd = bus_to_hcd(file->private_data); + buf_size = min(count, (sizeof(buf) - 1)); + buf[buf_size] = 0; + if (copy_from_user(buf, ubuf, buf_size)) + return -EFAULT; + if (strict_strtoul(buf, 16, &wIndex)) + return -EINVAL; + ehci_hub_test_mode(hcd, (u16)wIndex); + return count; +} + static inline void create_debug_files (struct ehci_hcd *ehci) { struct usb_bus *bus = &ehci_to_hcd(ehci)->self; @@ -1072,8 +1148,16 @@ static inline void create_debug_files (struct ehci_hcd *ehci) &debug_lpm_fops); if (!ehci->debug_registers) goto registers_error; + + ehci->debug_testmode = debugfs_create_file("testmode", S_IRUGO|S_IWUSR, + ehci->debug_dir, bus, + &debug_testmode_fops); + if (!ehci->debug_testmode) + goto testmode_error; return; +testmode_error: + debugfs_remove(ehci->debug_registers); registers_error: debugfs_remove(ehci->debug_periodic); periodic_error: @@ -1081,6 +1165,7 @@ periodic_error: async_error: debugfs_remove(ehci->debug_dir); dir_error: + ehci->debug_testmode = NULL; ehci->debug_periodic = NULL; ehci->debug_async = NULL; ehci->debug_dir = NULL; @@ -1088,6 +1173,7 @@ dir_error: static inline void remove_debug_files (struct ehci_hcd *ehci) { + debugfs_remove(ehci->debug_testmode); debugfs_remove(ehci->debug_registers); debugfs_remove(ehci->debug_periodic); debugfs_remove(ehci->debug_async); diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index 90773c4..c660235 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -1116,6 +1116,13 @@ error_exit: return retval; } +static int ehci_hub_test_mode(struct usb_hcd *hcd, u16 wIndex) +{ + return ehci_hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_TEST, wIndex, + NULL, 0); +} + static void ehci_relinquish_port(struct usb_hcd *hcd, int portnum) { struct ehci_hcd *ehci = hcd_to_ehci(hcd); diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index fd5c887..2c1ef15 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -169,6 +169,7 @@ struct ehci_hcd { /* one per controller */ struct dentry *debug_periodic; struct dentry *debug_registers; struct dentry *debug_lpm; + struct dentry *debug_testmode; #endif }; diff --git a/include/linux/usb/ehci_def.h b/include/linux/usb/ehci_def.h index 2e262cb..c70752c 100644 --- a/include/linux/usb/ehci_def.h +++ b/include/linux/usb/ehci_def.h @@ -127,7 +127,11 @@ struct ehci_regs { #define PORT_WKDISC_E (1<<21) /* wake on disconnect (enable) */ #define PORT_WKCONN_E (1<<20) /* wake on connect (enable) */ /* 19:16 for port testing */ +#define PORT_TEST_J (0x1<<16) /* Test_J mode */ +#define PORT_TEST_K (0x2<<16) /* Test_K mode */ +#define PORT_TEST_SE0_NAK (0x3<<16) /* Test_SE0_NAK mode */ #define PORT_TEST_PKT (0x4<<16) /* Port Test Control - packet test */ +#define PORT_TEST_FORCE (0x5<<16) /* Test_Force_Enable mode */ #define PORT_LED_OFF (0<<14) #define PORT_LED_AMBER (1<<14) #define PORT_LED_GREEN (2<<14) -- 1.7.2.1 -- To unsubscribe from this list: send the line "unsubscribe linux-usb" in the body of a message to majordomo@xxxxxxxxxxxxxxx More majordomo info at http://vger.kernel.org/majordomo-info.html