diff options
| -rw-r--r-- | drivers/usb/Makefile | 1 | ||||
| -rw-r--r-- | drivers/usb/host/Kconfig | 11 | ||||
| -rw-r--r-- | drivers/usb/host/Makefile | 1 | ||||
| -rw-r--r-- | drivers/usb/host/fusbh200-hcd.c | 5975 | ||||
| -rw-r--r-- | drivers/usb/host/fusbh200.h | 743 |
5 files changed, 6731 insertions, 0 deletions
diff --git a/drivers/usb/Makefile b/drivers/usb/Makefile index c41feba8d5c0..238c5d47cadb 100644 --- a/drivers/usb/Makefile +++ b/drivers/usb/Makefile @@ -25,6 +25,7 @@ obj-$(CONFIG_USB_HWA_HCD) += host/ obj-$(CONFIG_USB_ISP1760_HCD) += host/ obj-$(CONFIG_USB_IMX21_HCD) += host/ obj-$(CONFIG_USB_FSL_MPH_DR_OF) += host/ +obj-$(CONFIG_USB_FUSBH200_HCD) += host/ obj-$(CONFIG_USB_C67X00_HCD) += c67x00/ diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index de94f2699063..605e2773aad4 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -345,6 +345,17 @@ config USB_ISP1362_HCD To compile this driver as a module, choose M here: the module will be called isp1362-hcd. +config USB_FUSBH200_HCD + tristate "FUSBH200 HCD support" + depends on USB + default N + ---help--- + Faraday FUSBH200 is designed to meet USB2.0 EHCI specification + with minor modification. + + To compile this driver as a module, choose M here: the + module will be called fusbh200-hcd. + config USB_OHCI_HCD tristate "OHCI HCD support" depends on USB_ARCH_HAS_OHCI diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 4fb73c156d72..58b7ae87efae 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -52,3 +52,4 @@ obj-$(CONFIG_USB_FSL_MPH_DR_OF) += fsl-mph-dr-of.o obj-$(CONFIG_USB_OCTEON2_COMMON) += octeon2-common.o obj-$(CONFIG_USB_HCD_BCMA) += bcma-hcd.o obj-$(CONFIG_USB_HCD_SSB) += ssb-hcd.o +obj-$(CONFIG_USB_FUSBH200_HCD) += fusbh200-hcd.o diff --git a/drivers/usb/host/fusbh200-hcd.c b/drivers/usb/host/fusbh200-hcd.c new file mode 100644 index 000000000000..79ce799406c9 --- /dev/null +++ b/drivers/usb/host/fusbh200-hcd.c @@ -0,0 +1,5975 @@ +/* + * Faraday FUSBH200 EHCI-like driver + * + * Copyright (c) 2013 Faraday Technology Corporation + * + * Author: Yuan-Hsin Chen <yhchen@faraday-tech.com> + * Feng-Hsin Chiang <john453@faraday-tech.com> + * Po-Yu Chuang <ratbert.chuang@gmail.com> + * + * Most of code borrowed from the Linux-3.7 EHCI driver + * + * This program is free software; you can redistribute it and/or modify it + * under the terms of the GNU General Public License as published by the + * Free Software Foundation; either version 2 of the License, or (at your + * option) any later version. + * + * 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. + * + * 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., 675 Mass Ave, Cambridge, MA 02139, USA. + */ + +#include <linux/module.h> +#include <linux/device.h> +#include <linux/dmapool.h> +#include <linux/kernel.h> +#include <linux/delay.h> +#include <linux/ioport.h> +#include <linux/sched.h> +#include <linux/vmalloc.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/hrtimer.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/usb.h> +#include <linux/usb/hcd.h> +#include <linux/moduleparam.h> +#include <linux/dma-mapping.h> +#include <linux/debugfs.h> +#include <linux/slab.h> +#include <linux/uaccess.h> +#include <linux/platform_device.h> + +#include <asm/byteorder.h> +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/unaligned.h> + +/*-------------------------------------------------------------------------*/ +#define DRIVER_AUTHOR "Yuan-Hsin Chen" +#define DRIVER_DESC "FUSBH200 Host Controller (EHCI) Driver" + +static const char hcd_name [] = "fusbh200_hcd"; + +#undef VERBOSE_DEBUG +#undef FUSBH200_URB_TRACE + +#ifdef DEBUG +#define FUSBH200_STATS +#endif + +/* magic numbers that can affect system performance */ +#define FUSBH200_TUNE_CERR 3 /* 0-3 qtd retries; 0 == don't stop */ +#define FUSBH200_TUNE_RL_HS 4 /* nak throttle; see 4.9 */ +#define FUSBH200_TUNE_RL_TT 0 +#define FUSBH200_TUNE_MULT_HS 1 /* 1-3 transactions/uframe; 4.10.3 */ +#define FUSBH200_TUNE_MULT_TT 1 +/* + * Some drivers think it's safe to schedule isochronous transfers more than + * 256 ms into the future (partly as a result of an old bug in the scheduling + * code). In an attempt to avoid trouble, we will use a minimum scheduling + * length of 512 frames instead of 256. + */ +#define FUSBH200_TUNE_FLS 1 /* (medium) 512-frame schedule */ + +/* Initial IRQ latency: faster than hw default */ +static int log2_irq_thresh = 0; // 0 to 6 +module_param (log2_irq_thresh, int, S_IRUGO); +MODULE_PARM_DESC (log2_irq_thresh, "log2 IRQ latency, 1-64 microframes"); + +/* initial park setting: slower than hw default */ +static unsigned park = 0; +module_param (park, uint, S_IRUGO); +MODULE_PARM_DESC (park, "park setting; 1-3 back-to-back async packets"); + +/* for link power management(LPM) feature */ +static unsigned int hird; +module_param(hird, int, S_IRUGO); +MODULE_PARM_DESC(hird, "host initiated resume duration, +1 for each 75us"); + +#define INTR_MASK (STS_IAA | STS_FATAL | STS_PCD | STS_ERR | STS_INT) + +#include "fusbh200.h" + +/*-------------------------------------------------------------------------*/ + +#define fusbh200_dbg(fusbh200, fmt, args...) \ + dev_dbg (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) +#define fusbh200_err(fusbh200, fmt, args...) \ + dev_err (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) +#define fusbh200_info(fusbh200, fmt, args...) \ + dev_info (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) +#define fusbh200_warn(fusbh200, fmt, args...) \ + dev_warn (fusbh200_to_hcd(fusbh200)->self.controller , fmt , ## args ) + +#ifdef VERBOSE_DEBUG +# define fusbh200_vdbg fusbh200_dbg +#else + static inline void fusbh200_vdbg(struct fusbh200_hcd *fusbh200, ...) {} +#endif + +#ifdef DEBUG + +/* check the values in the HCSPARAMS register + * (host controller _Structural_ parameters) + * see EHCI spec, Table 2-4 for each value + */ +static void dbg_hcs_params (struct fusbh200_hcd *fusbh200, char *label) +{ + u32 params = fusbh200_readl(fusbh200, &fusbh200->caps->hcs_params); + + fusbh200_dbg (fusbh200, + "%s hcs_params 0x%x ports=%d\n", + label, params, + HCS_N_PORTS (params) + ); +} +#else + +static inline void dbg_hcs_params (struct fusbh200_hcd *fusbh200, char *label) {} + +#endif + +#ifdef DEBUG + +/* check the values in the HCCPARAMS register + * (host controller _Capability_ parameters) + * see EHCI Spec, Table 2-5 for each value + * */ +static void dbg_hcc_params (struct fusbh200_hcd *fusbh200, char *label) +{ + u32 params = fusbh200_readl(fusbh200, &fusbh200->caps->hcc_params); + + fusbh200_dbg (fusbh200, + "%s hcc_params %04x uframes %s%s\n", + label, + params, + HCC_PGM_FRAMELISTLEN(params) ? "256/512/1024" : "1024", + HCC_CANPARK(params) ? " park" : ""); +} +#else + +static inline void dbg_hcc_params (struct fusbh200_hcd *fusbh200, char *label) {} + +#endif + +#ifdef DEBUG + +static void __maybe_unused +dbg_qtd (const char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_qtd *qtd) +{ + fusbh200_dbg(fusbh200, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, + hc32_to_cpup(fusbh200, &qtd->hw_next), + hc32_to_cpup(fusbh200, &qtd->hw_alt_next), + hc32_to_cpup(fusbh200, &qtd->hw_token), + hc32_to_cpup(fusbh200, &qtd->hw_buf [0])); + if (qtd->hw_buf [1]) + fusbh200_dbg(fusbh200, " p1=%08x p2=%08x p3=%08x p4=%08x\n", + hc32_to_cpup(fusbh200, &qtd->hw_buf[1]), + hc32_to_cpup(fusbh200, &qtd->hw_buf[2]), + hc32_to_cpup(fusbh200, &qtd->hw_buf[3]), + hc32_to_cpup(fusbh200, &qtd->hw_buf[4])); +} + +static void __maybe_unused +dbg_qh (const char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{ + struct fusbh200_qh_hw *hw = qh->hw; + + fusbh200_dbg (fusbh200, "%s qh %p n%08x info %x %x qtd %x\n", label, + qh, hw->hw_next, hw->hw_info1, hw->hw_info2, hw->hw_current); + dbg_qtd("overlay", fusbh200, (struct fusbh200_qtd *) &hw->hw_qtd_next); +} + +static void __maybe_unused +dbg_itd (const char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_itd *itd) +{ + fusbh200_dbg (fusbh200, "%s [%d] itd %p, next %08x, urb %p\n", + label, itd->frame, itd, hc32_to_cpu(fusbh200, itd->hw_next), + itd->urb); + fusbh200_dbg (fusbh200, + " trans: %08x %08x %08x %08x %08x %08x %08x %08x\n", + hc32_to_cpu(fusbh200, itd->hw_transaction[0]), + hc32_to_cpu(fusbh200, itd->hw_transaction[1]), + hc32_to_cpu(fusbh200, itd->hw_transaction[2]), + hc32_to_cpu(fusbh200, itd->hw_transaction[3]), + hc32_to_cpu(fusbh200, itd->hw_transaction[4]), + hc32_to_cpu(fusbh200, itd->hw_transaction[5]), + hc32_to_cpu(fusbh200, itd->hw_transaction[6]), + hc32_to_cpu(fusbh200, itd->hw_transaction[7])); + fusbh200_dbg (fusbh200, + " buf: %08x %08x %08x %08x %08x %08x %08x\n", + hc32_to_cpu(fusbh200, itd->hw_bufp[0]), + hc32_to_cpu(fusbh200, itd->hw_bufp[1]), + hc32_to_cpu(fusbh200, itd->hw_bufp[2]), + hc32_to_cpu(fusbh200, itd->hw_bufp[3]), + hc32_to_cpu(fusbh200, itd->hw_bufp[4]), + hc32_to_cpu(fusbh200, itd->hw_bufp[5]), + hc32_to_cpu(fusbh200, itd->hw_bufp[6])); + fusbh200_dbg (fusbh200, " index: %d %d %d %d %d %d %d %d\n", + itd->index[0], itd->index[1], itd->index[2], + itd->index[3], itd->index[4], itd->index[5], + itd->index[6], itd->index[7]); +} + +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", + label, label [0] ? " " : "", status, + (status & STS_ASS) ? " Async" : "", + (status & STS_PSS) ? " Periodic" : "", + (status & STS_RECL) ? " Recl" : "", + (status & STS_HALT) ? " Halt" : "", + (status & STS_IAA) ? " IAA" : "", + (status & STS_FATAL) ? " FATAL" : "", + (status & STS_FLR) ? " FLR" : "", + (status & STS_PCD) ? " PCD" : "", + (status & STS_ERR) ? " ERR" : "", + (status & STS_INT) ? " INT" : "" + ); +} + +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", + label, label [0] ? " " : "", enable, + (enable & STS_IAA) ? " IAA" : "", + (enable & STS_FATAL) ? " FATAL" : "", + (enable & STS_FLR) ? " FLR" : "", + (enable & STS_PCD) ? " PCD" : "", + (enable & STS_ERR) ? " ERR" : "", + (enable & STS_INT) ? " INT" : "" + ); +} + +static const char *const fls_strings [] = + { "1024", "512", "256", "??" }; + +static int +dbg_command_buf (char *buf, unsigned len, const char *label, u32 command) +{ + return scnprintf (buf, len, + "%s%scommand %07x %s=%d ithresh=%d%s%s%s " + "period=%s%s %s", + label, label [0] ? " " : "", command, + (command & CMD_PARK) ? " park" : "(park)", + CMD_PARK_CNT (command), + (command >> 16) & 0x3f, + (command & CMD_IAAD) ? " IAAD" : "", + (command & CMD_ASE) ? " Async" : "", + (command & CMD_PSE) ? " Periodic" : "", + fls_strings [(command >> 2) & 0x3], + (command & CMD_RESET) ? " Reset" : "", + (command & CMD_RUN) ? "RUN" : "HALT" + ); +} + +static int +dbg_port_buf (char *buf, unsigned len, const char *label, int port, u32 status) +{ + char *sig; + + /* signaling state */ + switch (status & (3 << 10)) { + case 0 << 10: sig = "se0"; break; + case 1 << 10: sig = "k"; break; /* low speed */ + case 2 << 10: sig = "j"; break; + default: sig = "?"; break; + } + + return scnprintf (buf, len, + "%s%sport:%d status %06x %d " + "sig=%s%s%s%s%s%s%s%s", + label, label [0] ? " " : "", port, status, + status>>25,/*device address */ + sig, + (status & PORT_RESET) ? " RESET" : "", + (status & PORT_SUSPEND) ? " SUSPEND" : "", + (status & PORT_RESUME) ? " RESUME" : "", + (status & PORT_PEC) ? " PEC" : "", + (status & PORT_PE) ? " PE" : "", + (status & PORT_CSC) ? " CSC" : "", + (status & PORT_CONNECT) ? " CONNECT" : ""); +} + +#else +static inline void __maybe_unused +dbg_qh (char *label, struct fusbh200_hcd *fusbh200, struct fusbh200_qh *qh) +{} + +static inline int __maybe_unused +dbg_status_buf (char *buf, unsigned len, const char *label, u32 status) +{ return 0; } + +static inline int __maybe_unused +dbg_command_buf (char *buf, unsigned len, const char *label, u32 command) +{ return 0; } + +static inline int __maybe_unused +dbg_intr_buf (char *buf, unsigned len, const char *label, u32 enable) +{ return 0; } + +static inline int __maybe_unused +dbg_port_buf (char *buf, unsigned len, const char *label, int port, u32 status) +{ return 0; } + +#endif /* DEBUG */ + +/* functions have the "wrong" filename when they're output... */ +#define dbg_status(fusbh200, label, status) { \ + char _buf [80]; \ + dbg_status_buf (_buf, sizeof _buf, label, status); \ + fusbh200_dbg (fusbh200, "%s\n", _buf); \ +} + +#define dbg_cmd(fusbh200, label, command) { \ + char _buf [80]; \ + dbg_command_buf (_buf, sizeof _buf, label, command); \ + fusbh200_dbg (fusbh200, "%s\n", _buf); \ +} + +#define dbg_port(fusbh200, label, port, status) { \ + char _buf [80]; \ + dbg_port_buf (_buf, sizeof _buf, label, port, status); \ + fusbh200_dbg (fusbh200, "%s\n", _buf); \ +} + +/*-------------------------------------------------------------------------*/ + +#ifdef STUB_DEBUG_FILES + +static inline void create_debug_files (struct fusbh200_hcd *bus) { } +static inline void remove_debug_files (struct fusbh200_hcd *bus) { } + +#else + +/* troubleshooting help: expose state in debugfs */ + +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 ssize_t debug_output(struct file*, char __user*, size_t, loff_t*); +static int debug_close(struct inode *, struct file *); + +static const struct file_operations debug_async_fops = { + .owner = THIS_MODULE, + .open = debug_async_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; +static const struct file_operations debug_periodic_fops = { + .owner = THIS_MODULE, + .open = debug_periodic_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; +static const struct file_operations debug_registers_fops = { + .owner = THIS_MODULE, + .open = debug_registers_open, + .read = debug_output, + .release = debug_close, + .llseek = default_llseek, +}; + +static struct dentry *fusbh200_debug_root; + +struct debug_buffer { + ssize_t (*fill_func)(struct debug_buffer *); /* fill method */ + struct usb_bus *bus; + struct mutex mutex; /* protect filling of buffer */ + size_t count; /* number of characters filled into buffer */ + char *output_buf; + size_t alloc_size; +}; + +#define speed_char(info1) ({ char tmp; \ + switch (info1 & (3 << 12)) { \ + case QH_FULL_SPEED: tmp = 'f'; break; \ + case QH_LOW_SPEED: tmp = 'l'; break; \ + case QH_HIGH_SPEED: tmp = 'h'; break; \ + default: tmp = '?'; break; \ + }; tmp; }) + +static inline char token_mark(struct fusbh200_hcd *fusbh200, __hc32 token) +{ + __u32 v = hc32_to_cpu(fusbh200, token); + + if (v & QTD_STS_ACTIVE) + return '*'; + if (v & QTD_STS_HALT) + return '-'; + if (!IS_SHORT_READ (v)) + return ' '; + /* tries to advance through hw_alt_next */ + return '/'; +} + +static void qh_lines ( + struct fusbh200_hcd *fusbh200, + struct fusbh200_qh *qh, + char **nextp, + unsigned *sizep +) +{ + u32 scratch; + u32 hw_curr; + struct list_head *entry; + struct fusbh200_qtd *td; + unsigned temp; + unsigned size = *sizep; + char *next = *nextp; + char mark; + __le32 list_end = FUSBH200_LIST_END(fusbh200); + struct fusbh200_qh_hw *hw = qh->hw; + + if (hw->hw_qtd_next == list_end) /* NEC does this */ + mark = '@'; + else + mark = token_mark(fusbh200, hw->hw_token); + if (mark == '/') { /* qh_alt_next controls qh advance? */ + if ((hw->hw_alt_next & QTD_MASK(fusbh200)) + == fusbh200->async->hw->hw_alt_next) + mark = '#'; /* blocked */ + else if (hw->hw_alt_next == list_end) + mark = '.'; /* use hw_qtd_next */ + /* else alt_next points to some other qtd */ + } + scratch = hc32_to_cpup(fusbh200, &hw->hw_info1); + hw_curr = (mark == '*') ? hc32_to_cpup(fusbh200, &hw->hw_current) : 0; + temp = scnprintf (next, size, + "qh/%p dev%d %cs ep%d %08x %08x (%08x%c %s nak%d)", + qh, scratch & 0x007f, + speed_char (scratch), + (scratch >> 8) & 0x000f, + scratch, hc32_to_cpup(fusbh200, &hw->hw_info2), + hc32_to_cpup(fusbh200, &hw->hw_token), mark, + (cpu_to_hc32(fusbh200, QTD_TOGGLE) & hw->hw_token) + ? "data1" : "data0", + (hc32_to_cpup(fusbh200, &hw->hw_alt_next) >> 1) & 0x0f); + size -= temp; + next += temp; + + /* hc may be modifying the list as we read it ... */ + list_for_each (entry, &qh->qtd_list) { + td = list_entry (entry, struct fusbh200_qtd, qtd_list); + scratch = hc32_to_cpup(fusbh200, &td->hw_token); + mark = ' '; + if (hw_curr == td->qtd_dma) + mark = '*'; + else if (hw->hw_qtd_next == cpu_to_hc32(fusbh200, td->qtd_dma)) + mark = '+'; + else if (QTD_LENGTH (scratch)) { + if (td->hw_alt_next == fusbh200->async->hw->hw_alt_next) + mark = '#'; + else if (td->hw_alt_next != list_end) + mark = '/'; + } + temp = snprintf (next, size, + "\n\t%p%c%s len=%d %08x urb %p", + td, mark, ({ char *tmp; + switch ((scratch>>8)&0x03) { + case 0: tmp = "out"; break; + case 1: tmp = "in"; break; + case 2: tmp = "setup"; break; + default: tmp = "?"; break; + } tmp;}), + (scratch >> 16) & 0x7fff, + scratch, + td->urb); + if (size < temp) + temp = size; + size -= temp; + next += temp; + if (temp == size) + goto done; + } + + temp = snprintf (next, size, "\n"); + if (size < temp) + temp = size; + size -= temp; + next += temp; + +done: + *sizep = size; + *nextp = next; +} + +static ssize_t fill_async_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fusbh200_hcd *fusbh200; + unsigned long flags; + unsigned temp, size; + char *next; + struct fusbh200_qh *qh; + + hcd = bus_to_hcd(buf->bus); + fusbh200 = hcd_to_fusbh200 (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + *next = 0; + + /* dumps a snapshot of the async schedule. + * usually empty except for long-term bulk reads, or head. + * one QH per line, and TDs we know about + */ + spin_lock_irqsave (&fusbh200->lock, flags); + for (qh = fusbh200->async->qh_next.qh; size > 0 && qh; qh = qh->qh_next.qh) + qh_lines (fusbh200, qh, &next, &size); + if (fusbh200->async_unlink && size > 0) { + temp = scnprintf(next, size, "\nunlink =\n"); + size -= temp; + next += temp; + + for (qh = fusbh200->async_unlink; size > 0 && qh; + qh = qh->unlink_next) + qh_lines (fusbh200, qh, &next, &size); + } + spin_unlock_irqrestore (&fusbh200->lock, flags); + + return strlen(buf->output_buf); +} + +#define DBG_SCHED_LIMIT 64 +static ssize_t fill_periodic_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fusbh200_hcd *fusbh200; + unsigned long flags; + union fusbh200_shadow p, *seen; + unsigned temp, size, seen_count; + char *next; + unsigned i; + __hc32 tag; + + if (!(seen = kmalloc (DBG_SCHED_LIMIT * sizeof *seen, GFP_ATOMIC))) + return 0; + seen_count = 0; + + hcd = bus_to_hcd(buf->bus); + fusbh200 = hcd_to_fusbh200 (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + temp = scnprintf (next, size, "size = %d\n", fusbh200->periodic_size); + size -= temp; + next += temp; + + /* dump a snapshot of the periodic schedule. + * iso changes, interrupt usually doesn't. + */ + spin_lock_irqsave (&fusbh200->lock, flags); + for (i = 0; i < fusbh200->periodic_size; i++) { + p = fusbh200->pshadow [i]; + if (likely (!p.ptr)) + continue; + tag = Q_NEXT_TYPE(fusbh200, fusbh200->periodic [i]); + + temp = scnprintf (next, size, "%4d: ", i); + size -= temp; + next += temp; + + do { + struct fusbh200_qh_hw *hw; + + switch (hc32_to_cpu(fusbh200, tag)) { + case Q_TYPE_QH: + hw = p.qh->hw; + temp = scnprintf (next, size, " qh%d-%04x/%p", + p.qh->period, + hc32_to_cpup(fusbh200, + &hw->hw_info2) + /* uframe masks */ + & (QH_CMASK | QH_SMASK), + p.qh); + size -= temp; + next += temp; + /* don't repeat what follows this qh */ + for (temp = 0; temp < seen_count; temp++) { + if (seen [temp].ptr != p.ptr) + continue; + if (p.qh->qh_next.ptr) { + temp = scnprintf (next, size, + " ..."); + size -= temp; + next += temp; + } + break; + } + /* show more info the first time around */ + if (temp == seen_count) { + u32 scratch = hc32_to_cpup(fusbh200, + &hw->hw_info1); + struct fusbh200_qtd *qtd; + char *type = ""; + + /* count tds, get ep direction */ + temp = 0; + list_for_each_entry (qtd, + &p.qh->qtd_list, + qtd_list) { + temp++; + switch (0x03 & (hc32_to_cpu( + fusbh200, + qtd->hw_token) >> 8)) { + case 0: type = "out"; continue; + case 1: type = "in"; continue; + } + } + + temp = scnprintf (next, size, + " (%c%d ep%d%s " + "[%d/%d] q%d p%d)", + speed_char (scratch), + scratch & 0x007f, + (scratch >> 8) & 0x000f, type, + p.qh->usecs, p.qh->c_usecs, + temp, + 0x7ff & (scratch >> 16)); + + if (seen_count < DBG_SCHED_LIMIT) + seen [seen_count++].qh = p.qh; + } else + temp = 0; + tag = Q_NEXT_TYPE(fusbh200, hw->hw_next); + p = p.qh->qh_next; + break; + case Q_TYPE_FSTN: + temp = scnprintf (next, size, + " fstn-%8x/%p", p.fstn->hw_prev, + p.fstn); + tag = Q_NEXT_TYPE(fusbh200, p.fstn->hw_next); + p = p.fstn->fstn_next; + break; + case Q_TYPE_ITD: + temp = scnprintf (next, size, + " itd/%p", p.itd); + tag = Q_NEXT_TYPE(fusbh200, p.itd->hw_next); + p = p.itd->itd_next; + break; + } + size -= temp; + next += temp; + } while (p.ptr); + + temp = scnprintf (next, size, "\n"); + size -= temp; + next += temp; + } + spin_unlock_irqrestore (&fusbh200->lock, flags); + kfree (seen); + + return buf->alloc_size - size; +} +#undef DBG_SCHED_LIMIT + +static const char *rh_state_string(struct fusbh200_hcd *fusbh200) +{ + switch (fusbh200->rh_state) { + case FUSBH200_RH_HALTED: + return "halted"; + case FUSBH200_RH_SUSPENDED: + return "suspended"; + case FUSBH200_RH_RUNNING: + return "running"; + case FUSBH200_RH_STOPPING: + return "stopping"; + } + return "?"; +} + +static ssize_t fill_registers_buffer(struct debug_buffer *buf) +{ + struct usb_hcd *hcd; + struct fusbh200_hcd *fusbh200; + unsigned long flags; + unsigned temp, size, i; + char *next, scratch [80]; + static char fmt [] = "%*s\n"; + static char label [] = ""; + + hcd = bus_to_hcd(buf->bus); + fusbh200 = hcd_to_fusbh200 (hcd); + next = buf->output_buf; + size = buf->alloc_size; + + spin_lock_irqsave (&fusbh200->lock, flags); + + if (!HCD_HW_ACCESSIBLE(hcd)) { + size = scnprintf (next, size, + "bus %s, device %s\n" + "%s\n" + "SUSPENDED (no register access)\n", + hcd->self.controller->bus->name, + dev_name(hcd->self.controller), + hcd->product_desc); + goto done; + } + + /* Capability Registers */ + i = HC_VERSION(fusbh200, fusbh200_readl(fusbh200, &fusbh200->caps->hc_capbase)); + temp = scnprintf (next, size, + "bus %s, device %s\n" + "%s\n" + "EHCI %x.%02x, rh state %s\n", + hcd->self.controller->bus->name, + dev_name(hcd->self.controller), + hcd->product_desc, + i >> 8, i & 0x0ff, rh_state_string(fusbh200)); + size -= temp; + next += temp; + + // FIXME interpret both types of params + i = fusbh200_readl(fusbh200, &fusbh200->caps->hcs_params); + temp = scnprintf (next, size, "structural params 0x%08x\n", i); + size -= temp; + next += temp; + + i = fusbh200_readl(fusbh200, &fusbh200->caps->hcc_params); + temp = scnprintf (next, size, "capability params 0x%08x\n", i); + size -= temp; + next += temp; + + /* Operational Registers */ + temp = dbg_status_buf (scratch, sizeof scratch, label, + fusbh200_readl(fusbh200, &fusbh200->regs->status)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = dbg_command_buf (scratch, sizeof scratch, label, + fusbh200_readl(fusbh200, &fusbh200->regs->command)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = dbg_intr_buf (scratch, sizeof scratch, label, + fusbh200_readl(fusbh200, &fusbh200->regs->intr_enable)); + temp = scnprintf (next, size, fmt, temp, scratch); + size -= temp; + next += temp; + + temp = scnprintf (next, size, "uframe %04x\n", + fusbh200_read_frame_index(fusbh200)); + size -= temp; + next += temp; + + if (fusbh200->async_unlink) { + temp = scnprintf(next, size, "async unlink qh %p\n", + fusbh200->async_unlink); + size -= temp; + next += temp; + } + +#ifdef FUSBH200_STATS + temp = scnprintf (next, size, + "irq normal %ld err %ld iaa %ld (lost %ld)\n", + fusbh200->stats.normal, fusbh200->stats.error, fusbh200->stats.iaa, + fusbh200->stats.lost_iaa); + size -= temp; + next += temp; + + temp = scnprintf (next, size, "complete %ld unlink %ld\n", + fusbh200->stats.complete, fusbh200->stats.unlink); + size -= temp; + next += temp; +#endif + +done: + spin_unlock_irqrestore (&fusbh200->lock, flags); + + return buf->alloc_size - size; +} + +static struct debug_buffer *alloc_buffer(struct usb_bus *bus, + ssize_t (*fill_func)(struct debug_buffer *)) +{ + struct debug_buffer *buf; + + buf = kzalloc(sizeof(struct debug_buffer), GFP_KERNEL); + + if (buf) { + buf->bus = bus; + buf->fill_func = fill_func; + mutex_init(&buf->mutex); + buf->alloc_size = PAGE_SIZE; + } + + return buf; +} + +static int fill_buffer(struct debug_buffer *buf) +{ + int ret = 0; + + if (!buf->output_buf) + buf->output_buf = vmalloc(buf->alloc_size); + + if (!buf->output_buf) { + ret = -ENOMEM; + goto out; + } + + ret = buf->fill_func(buf); + + if (ret >= 0) { + buf->count = ret; + ret = 0; + } + +out: + return ret; +} + +static ssize_t debug_output(struct file *file, char __user *user_buf, + size_t len, loff_t *offset) +{ + struct debug_buffer *buf = file->private_data; + int ret = 0; + + mutex_lock(&buf->mutex); + if (buf->count == 0) { + ret = fill_buffer(buf); + if (ret != 0) { + mutex_unlock(&buf->mutex); + goto out; + } + } + mutex_unlock(&buf->mutex); + + ret = simple_read_from_buffer(user_buf, len, offset, + buf->output_buf, buf->count); + +out: + return ret; + +} + +static int debug_close(struct inode *inode, struct file *file) +{ + struct debug_buffer *buf = file->private_data; + + if (buf) { + vfree(buf->output_buf); + kfree(buf); + } + + return 0; +} +static int debug_async_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, fill_async_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static int debug_periodic_open(struct inode *inode, struct file *file) +{ + struct debug_buffer *buf; + buf = alloc_buffer(inode->i_private, fill_periodic_buffer); + if (!buf) + return -ENOMEM; + + buf->alloc_size = (sizeof(void *) == 4 ? 6 : 8)*PAGE_SIZE; + file->private_data = buf; + return 0; +} + +static int debug_registers_open(struct inode *inode, struct file *file) +{ + file->private_data = alloc_buffer(inode->i_private, + fill_registers_buffer); + + return file->private_data ? 0 : -ENOMEM; +} + +static inline void create_debug_files (struct fusbh200_hcd *fusbh200) +{ + struct usb_bus *bus = &fusbh200_to_hcd(fusbh200)->self; + + fusbh200->debug_dir = debugfs_create_dir(bus->bus_name, fusbh200_debug_root); + if (!fusbh200->debug_dir) + return; + + if (!debugfs_create_file("async", S_IRUGO, fusbh200->debug_dir, bus, + &debug_async_fops)) + goto file_error; + + if (!debugfs_create_file("periodic", S_IRUGO, fusbh200->debug_dir, bus, + &debug_periodic_fops)) + goto file_error; + + if (!debugfs_create_file("registers", S_IRUGO, fusbh200->debug_dir, bus, + &debug_registers_fops)) + goto file_error; + + return; + +file_error: + debugfs_remove_recursive(fusbh200->debug_dir); +} + +static inline void remove_debug_files (struct fusbh200_hcd *fusbh200) +{ + debugfs_remove_recursive(fusbh200->debug_dir); +} + +#endif /* STUB_DEBUG_FILES */ +/*-------------------------------------------------------------------------*/ + +/* + * handshake - spin reading hc until handshake completes or fails + * @ptr: address of hc register to be read + * @mask: bits to look at in result of read + * @done: value of those bits when handshake succeeds + * @usec: timeout in microseconds + * + * Returns negative errno, or zero on success + * + * Success happens when the "mask" bits have the specified value (hardware + * handshake done). There are two failure modes: "usec" have passed (major + * hardware flakeout), or the register reads as all-ones (hardware removed). + * + * That last failure should_only happen in cases like physical cardbus eject + * before driver shutdown. But it also seems to be caused by bugs in cardbus + * bridge shutdown: shutting down the bridge before the devices using it. + */ +static int handshake (struct fusbh200_hcd *fusbh200, void __iomem *ptr, + u32 mask, u32 done, int usec) +{ + u32 result; + + do { + result = fusbh200_readl(fusbh200, ptr); + if (result == ~(u32)0) /* card removed */ + return -ENODEV; + result &= mask; + if (result == done) + return 0; + udelay (1); + usec--; + } while (usec > 0); + return -ETIMEDOUT; +} + +/* + * Force HC to halt state from unknown (EHCI spec section 2.3). + * Must be called with interrupts enabled and the lock not held. + */ +static int fusbh200_halt (struct fusbh200_hcd *fusbh200) +{ + u32 temp; + + spin_lock_irq(&fusbh200->lock); + + /* disable any irqs left enabled by previous code */ + fusbh200_writel(fusbh200, 0, &fusbh200->regs->intr_enable); + + /* + * This routine gets called during probe before fusbh200->command + * has been initialized, so we can't rely on its value. + */ + fusbh200->command &= ~CMD_RUN; + temp = fusbh200_readl(fusbh200, &fusbh200->regs->command); + temp &= ~(CMD_RUN | CMD_IAAD); + fusbh200_writel(fusbh200, temp, &fusbh200->regs->command); + + spin_unlock_irq(&fusbh200->lock); + synchronize_irq(fusbh200_to_hcd(fusbh200)->irq); + + return handshake(fusbh200, &fusbh200->regs->status, + STS_HALT, STS_HALT, 16 * 125); +} + +/* + * Reset a non-running (STS_HALT == 1) controller. + * Must be called with interrupts enabled and the lock not held. + */ +static int fusbh200_reset (struct fusbh200_hcd *fusbh200) +{ + int retval; + u32 command = fusbh200_readl(fusbh200, &fusbh200->regs->command); + + /* If the EHCI debug controller is active, special care must be + * taken before and after a host controller reset */ + if (fusbh200->debug && !dbgp_reset_prep(fusbh200_to_hcd(fusbh200))) + fusbh200->debug = NULL; + + command |= CMD_RESET; + dbg_cmd (fusbh200, "reset", command); + fusbh200_writel(fusbh200, command, &fusbh200->regs->command); + fusbh200->rh_state = FUSBH200_RH_HALTED; + fusbh200->next_statechange = jiffies; + retval = handshake (fusbh200, &fusbh200->regs->command, + CMD_RESET, 0, 250 * 1000); + + if (retval) + return retval; + + if (fusbh200->debug) + dbgp_external_startup(fusbh200_to_hcd(fusbh200)); + + fusbh200->port_c_suspend = fusbh200->suspended_ports = + fusbh200->resuming_ports = 0; + return retval; +} + +/* + * Idle the controller (turn off the schedules). + * Must be called with interrupts enabled and the lock not held. + */ +static void fusbh200_quiesce (struct fusbh200_hcd *fusbh200) +{ + u32 temp; + + if (fusbh200->rh_state != FUSBH200_RH_RUNNING) + return; + + /* wait for any schedule enables/disables to take effect */ + temp = (fusbh200->command << 10) & (STS_ASS | STS_PSS); + handshake(fusbh200, &fusbh200->regs->status, STS_ASS | STS_PSS, temp, 16 * 125); + + /* then disable anything that's still active */ + spin_lock_irq(&fusbh200->lock); + fusbh200->command & |
