USB: EHCI: EHCI 1.1 addendum: preparation
authorAlek Du <alek.du@intel.com>
Fri, 4 Jun 2010 07:47:54 +0000 (15:47 +0800)
committerGreg Kroah-Hartman <gregkh@suse.de>
Tue, 10 Aug 2010 21:35:35 +0000 (14:35 -0700)
EHCI 1.1 addendum introduced several energy efficiency extensions for
EHCI USB host controllers:
1. LPM (link power management)
2. Per-port change
3. Shorter periodic frame list
4. Hardware prefetching

This patch is intended to define the HW bits and debug interface for
EHCI 1.1 addendum. The LPM and Per-port change patches will be sent out
after this patch.

Signed-off-by: Jacob Pan <jacob.jun.pan@intel.com>
Signed-off-by: Alek Du <alek.du@intel.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@suse.de>
drivers/usb/host/ehci-dbg.c
drivers/usb/host/ehci-hcd.c
drivers/usb/host/ehci.h
include/linux/usb/ehci_def.h

index 874d200..df5546b 100644 (file)
@@ -98,13 +98,18 @@ static void dbg_hcc_params (struct ehci_hcd *ehci, char *label)
                        HCC_64BIT_ADDR(params) ? " 64 bit addr" : "");
        } else {
                ehci_dbg (ehci,
-                       "%s hcc_params %04x thresh %d uframes %s%s%s\n",
+                       "%s hcc_params %04x thresh %d uframes %s%s%s%s%s%s%s\n",
                        label,
                        params,
                        HCC_ISOC_THRES(params),
                        HCC_PGM_FRAMELISTLEN(params) ? "256/512/1024" : "1024",
                        HCC_CANPARK(params) ? " park" : "",
-                       HCC_64BIT_ADDR(params) ? " 64 bit addr" : "");
+                       HCC_64BIT_ADDR(params) ? " 64 bit addr" : "",
+                       HCC_LPM(params) ? " LPM" : "",
+                       HCC_PER_PORT_CHANGE_EVENT(params) ? " ppce" : "",
+                       HCC_HW_PREFETCH(params) ? " hw prefetch" : "",
+                       HCC_32FRAME_PERIODIC_LIST(params) ?
+                               " 32 peridic list" : "");
        }
 }
 #else
@@ -191,8 +196,9 @@ static int __maybe_unused
 dbg_status_buf (char *buf, unsigned len, const char *label, u32 status)
 {
        return scnprintf (buf, len,
-               "%s%sstatus %04x%s%s%s%s%s%s%s%s%s%s",
+               "%s%sstatus %04x%s%s%s%s%s%s%s%s%s%s%s",
                label, label [0] ? " " : "", status,
+               (status & STS_PPCE_MASK) ? " PPCE" : "",
                (status & STS_ASS) ? " Async" : "",
                (status & STS_PSS) ? " Periodic" : "",
                (status & STS_RECL) ? " Recl" : "",
@@ -210,8 +216,9 @@ static int __maybe_unused
 dbg_intr_buf (char *buf, unsigned len, const char *label, u32 enable)
 {
        return scnprintf (buf, len,
-               "%s%sintrenable %02x%s%s%s%s%s%s",
+               "%s%sintrenable %02x%s%s%s%s%s%s%s",
                label, label [0] ? " " : "", enable,
+               (enable & STS_PPCE_MASK) ? " PPCE" : "",
                (enable & STS_IAA) ? " IAA" : "",
                (enable & STS_FATAL) ? " FATAL" : "",
                (enable & STS_FLR) ? " FLR" : "",
@@ -228,9 +235,15 @@ static int
 dbg_command_buf (char *buf, unsigned len, const char *label, u32 command)
 {
        return scnprintf (buf, len,
-               "%s%scommand %06x %s=%d ithresh=%d%s%s%s%s period=%s%s %s",
+               "%s%scommand %07x %s%s%s%s%s%s=%d ithresh=%d%s%s%s%s "
+               "period=%s%s %s",
                label, label [0] ? " " : "", command,
-               (command & CMD_PARK) ? "park" : "(park)",
+               (command & CMD_HIRD) ? " HIRD" : "",
+               (command & CMD_PPCEE) ? " PPCEE" : "",
+               (command & CMD_FSP) ? " FSP" : "",
+               (command & CMD_ASPE) ? " ASPE" : "",
+               (command & CMD_PSPE) ? " PSPE" : "",
+               (command & CMD_PARK) ? " park" : "(park)",
                CMD_PARK_CNT (command),
                (command >> 16) & 0x3f,
                (command & CMD_LRESET) ? " LReset" : "",
@@ -257,11 +270,22 @@ dbg_port_buf (char *buf, unsigned len, const char *label, int port, u32 status)
        }
 
        return scnprintf (buf, len,
-               "%s%sport %d status %06x%s%s sig=%s%s%s%s%s%s%s%s%s%s",
+               "%s%sport:%d status %06x %d %s%s%s%s%s%s "
+               "sig=%s%s%s%s%s%s%s%s%s%s%s",
                label, label [0] ? " " : "", port, status,
+               status>>25,/*device address */
+               (status & PORT_SSTS)>>23 == PORTSC_SUSPEND_STS_ACK ?
+                                               " ACK" : "",
+               (status & PORT_SSTS)>>23 == PORTSC_SUSPEND_STS_NYET ?
+                                               " NYET" : "",
+               (status & PORT_SSTS)>>23 == PORTSC_SUSPEND_STS_STALL ?
+                                               " STALL" : "",
+               (status & PORT_SSTS)>>23 == PORTSC_SUSPEND_STS_ERR ?
+                                               " ERR" : "",
                (status & PORT_POWER) ? " POWER" : "",
                (status & PORT_OWNER) ? " OWNER" : "",
                sig,
+               (status & PORT_LPM) ? " LPM" : "",
                (status & PORT_RESET) ? " RESET" : "",
                (status & PORT_SUSPEND) ? " SUSPEND" : "",
                (status & PORT_RESUME) ? " RESUME" : "",
@@ -330,6 +354,13 @@ static int debug_async_open(struct inode *, struct file *);
 static int debug_periodic_open(struct inode *, struct file *);
 static int debug_registers_open(struct inode *, struct file *);
 static int debug_async_open(struct inode *, struct file *);
+static int debug_lpm_open(struct inode *, struct file *);
+static ssize_t debug_lpm_read(struct file *file, char __user *user_buf,
+                                  size_t count, loff_t *ppos);
+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 ssize_t debug_output(struct file*, char __user*, size_t, loff_t*);
 static int debug_close(struct inode *, struct file *);
 
@@ -351,6 +382,13 @@ static const struct file_operations debug_registers_fops = {
        .read           = debug_output,
        .release        = debug_close,
 };
+static const struct file_operations debug_lpm_fops = {
+       .owner          = THIS_MODULE,
+       .open           = debug_lpm_open,
+       .read           = debug_lpm_read,
+       .write          = debug_lpm_write,
+       .release        = debug_lpm_close,
+};
 
 static struct dentry *ehci_debug_root;
 
@@ -917,6 +955,94 @@ static int debug_registers_open(struct inode *inode, struct file *file)
        return file->private_data ? 0 : -ENOMEM;
 }
 
+static int debug_lpm_open(struct inode *inode, struct file *file)
+{
+       file->private_data = inode->i_private;
+       return 0;
+}
+
+static int debug_lpm_close(struct inode *inode, struct file *file)
+{
+       return 0;
+}
+
+static ssize_t debug_lpm_read(struct file *file, char __user *user_buf,
+                                  size_t count, loff_t *ppos)
+{
+       /* TODO: show lpm stats */
+       return 0;
+}
+
+static ssize_t debug_lpm_write(struct file *file, const char __user *user_buf,
+                             size_t count, loff_t *ppos)
+{
+       struct usb_hcd          *hcd;
+       struct ehci_hcd         *ehci;
+       char buf[50];
+       size_t len;
+       u32 temp;
+       unsigned long port;
+       u32 __iomem     *portsc ;
+       u32 params;
+
+       hcd = bus_to_hcd(file->private_data);
+       ehci = hcd_to_ehci(hcd);
+
+       len = min(count, sizeof(buf) - 1);
+       if (copy_from_user(buf, user_buf, len))
+               return -EFAULT;
+       buf[len] = '\0';
+       if (len > 0 && buf[len - 1] == '\n')
+               buf[len - 1] = '\0';
+
+       if (strncmp(buf, "enable", 5) == 0) {
+               if (strict_strtoul(buf + 7, 10, &port))
+                       return -EINVAL;
+               params = ehci_readl(ehci, &ehci->caps->hcs_params);
+               if (port > HCS_N_PORTS(params)) {
+                       ehci_dbg(ehci, "ERR: LPM on bad port %lu\n", port);
+                       return -ENODEV;
+               }
+               portsc = &ehci->regs->port_status[port-1];
+               temp = ehci_readl(ehci, portsc);
+               if (!(temp & PORT_DEV_ADDR)) {
+                       ehci_dbg(ehci, "LPM: no device attached\n");
+                       return -ENODEV;
+               }
+               temp |= PORT_LPM;
+               ehci_writel(ehci, temp, portsc);
+               printk(KERN_INFO "force enable LPM for port %lu\n", port);
+       } else if (strncmp(buf, "hird=", 5) == 0) {
+               unsigned long hird;
+               if (strict_strtoul(buf + 5, 16, &hird))
+                       return -EINVAL;
+               printk(KERN_INFO "setting hird %s %lu\n", buf + 6, hird);
+               temp = ehci_readl(ehci, &ehci->regs->command);
+               temp &= ~CMD_HIRD;
+               temp |= hird << 24;
+               ehci_writel(ehci, temp, &ehci->regs->command);
+       } else if (strncmp(buf, "disable", 7) == 0) {
+               if (strict_strtoul(buf + 8, 10, &port))
+                       return -EINVAL;
+               params = ehci_readl(ehci, &ehci->caps->hcs_params);
+               if (port > HCS_N_PORTS(params)) {
+                       ehci_dbg(ehci, "ERR: LPM off bad port %lu\n", port);
+                       return -ENODEV;
+               }
+               portsc = &ehci->regs->port_status[port-1];
+               temp = ehci_readl(ehci, portsc);
+               if (!(temp & PORT_DEV_ADDR)) {
+                       ehci_dbg(ehci, "ERR: no device attached\n");
+                       return -ENODEV;
+               }
+               temp &= ~PORT_LPM;
+               ehci_writel(ehci, temp, portsc);
+               printk(KERN_INFO "disabled LPM for port %lu\n", port);
+       } else
+               return -EOPNOTSUPP;
+       return count;
+}
+
 static inline void create_debug_files (struct ehci_hcd *ehci)
 {
        struct usb_bus *bus = &ehci_to_hcd(ehci)->self;
@@ -940,6 +1066,10 @@ static inline void create_debug_files (struct ehci_hcd *ehci)
        ehci->debug_registers = debugfs_create_file("registers", S_IRUGO,
                                                    ehci->debug_dir, bus,
                                                    &debug_registers_fops);
+
+       ehci->debug_registers = debugfs_create_file("lpm", S_IRUGO|S_IWUGO,
+                                                   ehci->debug_dir, bus,
+                                                   &debug_lpm_fops);
        if (!ehci->debug_registers)
                goto registers_error;
        return;
index a3ef2a9..20ca6a9 100644 (file)
@@ -36,6 +36,7 @@
 #include <linux/dma-mapping.h>
 #include <linux/debugfs.h>
 #include <linux/slab.h>
+#include <linux/uaccess.h>
 
 #include <asm/byteorder.h>
 #include <asm/io.h>
index 650a687..bfaac16 100644 (file)
@@ -157,6 +157,7 @@ struct ehci_hcd {                   /* one per controller */
        struct dentry           *debug_async;
        struct dentry           *debug_periodic;
        struct dentry           *debug_registers;
+       struct dentry           *debug_lpm;
 #endif
 };
 
index 80287af..2e262cb 100644 (file)
@@ -39,6 +39,12 @@ struct ehci_caps {
 #define HCS_N_PORTS(p)         (((p)>>0)&0xf)  /* bits 3:0, ports on HC */
 
        u32             hcc_params;      /* HCCPARAMS - offset 0x8 */
+/* EHCI 1.1 addendum */
+#define HCC_32FRAME_PERIODIC_LIST(p)   ((p)&(1 << 19))
+#define HCC_PER_PORT_CHANGE_EVENT(p)   ((p)&(1 << 18))
+#define HCC_LPM(p)                     ((p)&(1 << 17))
+#define HCC_HW_PREFETCH(p)             ((p)&(1 << 16))
+
 #define HCC_EXT_CAPS(p)                (((p)>>8)&0xff) /* for pci extended caps */
 #define HCC_ISOC_CACHE(p)       ((p)&(1 << 7))  /* true: can cache isoc frame */
 #define HCC_ISOC_THRES(p)       (((p)>>4)&0x7)  /* bits 6:4, uframes cached */
@@ -54,6 +60,13 @@ struct ehci_regs {
 
        /* USBCMD: offset 0x00 */
        u32             command;
+
+/* EHCI 1.1 addendum */
+#define CMD_HIRD       (0xf<<24)       /* host initiated resume duration */
+#define CMD_PPCEE      (1<<15)         /* per port change event enable */
+#define CMD_FSP                (1<<14)         /* fully synchronized prefetch */
+#define CMD_ASPE       (1<<13)         /* async schedule prefetch enable */
+#define CMD_PSPE       (1<<12)         /* periodic schedule prefetch enable */
 /* 23:16 is r/w intr rate, in microframes; default "8" == 1/msec */
 #define CMD_PARK       (1<<11)         /* enable "park" on async qh */
 #define CMD_PARK_CNT(c)        (((c)>>8)&3)    /* how many transfers to park for */
@@ -67,6 +80,7 @@ struct ehci_regs {
 
        /* USBSTS: offset 0x04 */
        u32             status;
+#define STS_PPCE_MASK  (0xff<<16)      /* Per-Port change event 1-16 */
 #define STS_ASS                (1<<15)         /* Async Schedule Status */
 #define STS_PSS                (1<<14)         /* Periodic Schedule Status */
 #define STS_RECL       (1<<13)         /* Reclamation */
@@ -100,6 +114,14 @@ struct ehci_regs {
 
        /* PORTSC: offset 0x44 */
        u32             port_status[0]; /* up to N_PORTS */
+/* EHCI 1.1 addendum */
+#define PORTSC_SUSPEND_STS_ACK 0
+#define PORTSC_SUSPEND_STS_NYET 1
+#define PORTSC_SUSPEND_STS_STALL 2
+#define PORTSC_SUSPEND_STS_ERR 3
+
+#define PORT_DEV_ADDR  (0x7f<<25)              /* device address */
+#define PORT_SSTS      (0x3<<23)               /* suspend status */
 /* 31:23 reserved */
 #define PORT_WKOC_E    (1<<22)         /* wake on overcurrent (enable) */
 #define PORT_WKDISC_E  (1<<21)         /* wake on disconnect (enable) */
@@ -115,6 +137,7 @@ struct ehci_regs {
 #define PORT_USB11(x) (((x)&(3<<10)) == (1<<10))       /* USB 1.1 device */
 /* 11:10 for detecting lowspeed devices (reset vs release ownership) */
 /* 9 reserved */
+#define PORT_LPM       (1<<9)          /* LPM transaction */
 #define PORT_RESET     (1<<8)          /* reset port */
 #define PORT_SUSPEND   (1<<7)          /* suspend port */
 #define PORT_RESUME    (1<<6)          /* resume it */