diff options
author | Yuan-Hsin Chen <yuanlmm@gmail.com> | 2013-05-17 10:14:14 +0000 |
---|---|---|
committer | Greg Kroah-Hartman <gregkh@linuxfoundation.org> | 2013-05-17 10:12:52 -0700 |
commit | 6c920bfb98d07a883918733075a4bc4287e36946 (patch) | |
tree | abcc093ac580efd2f8444dc37e7b5eb3cecd75d8 | |
parent | a1fefaab1bbf9cd409cb11887953567d0a8faeb6 (diff) |
usb host: Faraday USB2.0 FUSBH200-HCD driver
FUSBH200-HCD is an USB2.0 hcd for Faraday FUSBH200.
FUSBH200 is an ehci-like controller with some differences.
First, register layout of FUSBH200 is incompatible with EHCI.
Furthermore, FUSBH200 is lack of siTDs which means iTDs
are used for both HS and FS ISO transfer.
Signed-off-by: Yuan-Hsin Chen <yhchen@faraday-tech.com>
Signed-off-by: Greg Kroah-Hartman <gregkh@linuxfoundation.org>
-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 c41feba8d5c..238c5d47cad 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 de94f269906..605e2773aad 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 4fb73c156d7..58b7ae87efa 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 00000000000..79ce799406c --- /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); |