diff options
author | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-07-12 16:46:58 -0700 |
---|---|---|
committer | Linus Torvalds <torvalds@woody.linux-foundation.org> | 2007-07-12 16:46:58 -0700 |
commit | 9374430a52dfae5c013b88f7f030c04a6774d410 (patch) | |
tree | ce1ee8eee4e79fbb9486e810278d1092afc74a44 /drivers/usb/host | |
parent | 66f49739fe1591197364f2dad1b67b975e8f5e85 (diff) | |
parent | 13f9966b3ba5b45f47f2ea0eb0a90afceedfbb1f (diff) |
Merge master.kernel.org:/pub/scm/linux/kernel/git/gregkh/usb-2.6
* master.kernel.org:/pub/scm/linux/kernel/git/gregkh/usb-2.6: (149 commits)
USB: ohci-pnx4008: Remove unnecessary cast of return value of kzalloc
USB: additions to the quirk list
usb-storage: implement autosuspend
USB: cdc-acm: add new device id to option driver
USB: goku_udc trivial cleanups
USB: usb gadget stack can now -DDEBUG with Kconfig
usb gadget stack: remove usb_ep_*_buffer(), part 2
usb gadget stack: remove usb_ep_*_buffer(), part 1
USB: pxa2xx_udc -- cleanups, mostly removing dma hooks
USB: pxa2xx_udc: use generic gpio layer
USB: quirk for samsung printer
USB: usb/dma doc updates
USB: drivers/usb/storage/unusual_devs.h whitespace cleanup
USB: remove Makefile reference to obsolete OHCI_AT91
USB: io_*: remove bogus termios no change checks
USB: mos7720: remove bogus no termios change check
USB: visor and whiteheat: remove bogus termios change checks
USB: pl2303: remove bogus checks and fix speed support to use tty_get_baud_rate()
USB: mos7840.c: turn this into a serial driver
USB: make the usb_device numa_node get assigned from controller
...
Diffstat (limited to 'drivers/usb/host')
-rw-r--r-- | drivers/usb/host/Kconfig | 28 | ||||
-rw-r--r-- | drivers/usb/host/Makefile | 2 | ||||
-rw-r--r-- | drivers/usb/host/ehci-dbg.c | 183 | ||||
-rw-r--r-- | drivers/usb/host/ehci-fsl.c | 10 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hcd.c | 118 | ||||
-rw-r--r-- | drivers/usb/host/ehci-hub.c | 122 | ||||
-rw-r--r-- | drivers/usb/host/ehci-mem.c | 27 | ||||
-rw-r--r-- | drivers/usb/host/ehci-pci.c | 7 | ||||
-rw-r--r-- | drivers/usb/host/ehci-ppc-soc.c | 182 | ||||
-rw-r--r-- | drivers/usb/host/ehci-ps3.c | 86 | ||||
-rw-r--r-- | drivers/usb/host/ehci-q.c | 96 | ||||
-rw-r--r-- | drivers/usb/host/ehci-sched.c | 339 | ||||
-rw-r--r-- | drivers/usb/host/ehci.h | 234 | ||||
-rw-r--r-- | drivers/usb/host/ohci-dbg.c | 4 | ||||
-rw-r--r-- | drivers/usb/host/ohci-hcd.c | 92 | ||||
-rw-r--r-- | drivers/usb/host/ohci-hub.c | 5 | ||||
-rw-r--r-- | drivers/usb/host/ohci-mem.c | 1 | ||||
-rw-r--r-- | drivers/usb/host/ohci-pci.c | 57 | ||||
-rw-r--r-- | drivers/usb/host/ohci-pnx4008.c | 2 | ||||
-rw-r--r-- | drivers/usb/host/ohci-ps3.c | 87 | ||||
-rw-r--r-- | drivers/usb/host/ohci.h | 2 | ||||
-rw-r--r-- | drivers/usb/host/r8a66597-hcd.c | 2244 | ||||
-rw-r--r-- | drivers/usb/host/r8a66597.h | 634 | ||||
-rw-r--r-- | drivers/usb/host/uhci-hcd.c | 5 |
24 files changed, 4157 insertions, 410 deletions
diff --git a/drivers/usb/host/Kconfig b/drivers/usb/host/Kconfig index 62711870f8e..2f529828c74 100644 --- a/drivers/usb/host/Kconfig +++ b/drivers/usb/host/Kconfig @@ -69,8 +69,20 @@ config USB_EHCI_TT_NEWSCHED config USB_EHCI_BIG_ENDIAN_MMIO bool - depends on USB_EHCI_HCD - default n + depends on USB_EHCI_HCD && (PPC_CELLEB || PPC_PS3 || 440EPX) + default y + +config USB_EHCI_BIG_ENDIAN_DESC + bool + depends on USB_EHCI_HCD && 440EPX + default y + +config USB_EHCI_FSL + bool + select USB_EHCI_ROOT_HUB_TT + default y if MPC834x || PPC_MPC831x + ---help--- + Variation of ARC USB block used in some Freescale chips. config USB_ISP116X_HCD tristate "ISP116X HCD support" @@ -224,3 +236,15 @@ config USB_SL811_CS To compile this driver as a module, choose M here: the module will be called "sl811_cs". +config USB_R8A66597_HCD + tristate "R8A66597 HCD suppoort" + depends on USB + help + The R8A66597 is a USB 2.0 host and peripheral controller. + + Enable this option if your board has this chip, and you want + to use it as a host controller. If unsure, say N. + + To compile this driver as a module, choose M here: the + module will be called r8a66597-hcd. + diff --git a/drivers/usb/host/Makefile b/drivers/usb/host/Makefile index 2ff396bd180..bb8e9d44f37 100644 --- a/drivers/usb/host/Makefile +++ b/drivers/usb/host/Makefile @@ -15,3 +15,5 @@ obj-$(CONFIG_USB_UHCI_HCD) += uhci-hcd.o obj-$(CONFIG_USB_SL811_HCD) += sl811-hcd.o obj-$(CONFIG_USB_SL811_CS) += sl811_cs.o obj-$(CONFIG_USB_U132_HCD) += u132-hcd.o +obj-$(CONFIG_USB_R8A66597_HCD) += r8a66597-hcd.o + diff --git a/drivers/usb/host/ehci-dbg.c b/drivers/usb/host/ehci-dbg.c index 43eddaecc3d..c9cc4413198 100644 --- a/drivers/usb/host/ehci-dbg.c +++ b/drivers/usb/host/ehci-dbg.c @@ -52,7 +52,7 @@ static void dbg_hcs_params (struct ehci_hcd *ehci, char *label) HCS_INDICATOR (params) ? " ind" : "", HCS_N_CC (params), HCS_N_PCC (params), - HCS_PORTROUTED (params) ? "" : " ordered", + HCS_PORTROUTED (params) ? "" : " ordered", HCS_PPC (params) ? "" : " !ppc", HCS_N_PORTS (params) ); @@ -91,20 +91,20 @@ static void dbg_hcc_params (struct ehci_hcd *ehci, char *label) if (HCC_ISOC_CACHE (params)) { ehci_dbg (ehci, - "%s hcc_params %04x caching frame %s%s%s\n", - label, params, - HCC_PGM_FRAMELISTLEN (params) ? "256/512/1024" : "1024", - HCC_CANPARK (params) ? " park" : "", - HCC_64BIT_ADDR (params) ? " 64 bit addr" : ""); + "%s hcc_params %04x caching frame %s%s%s\n", + label, params, + HCC_PGM_FRAMELISTLEN(params) ? "256/512/1024" : "1024", + HCC_CANPARK(params) ? " park" : "", + HCC_64BIT_ADDR(params) ? " 64 bit addr" : ""); } else { ehci_dbg (ehci, - "%s hcc_params %04x thresh %d uframes %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" : ""); + "%s hcc_params %04x thresh %d uframes %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" : ""); } } #else @@ -115,23 +115,23 @@ static inline void dbg_hcc_params (struct ehci_hcd *ehci, char *label) {} #ifdef DEBUG -static void __attribute__((__unused__)) +static void __maybe_unused dbg_qtd (const char *label, struct ehci_hcd *ehci, struct ehci_qtd *qtd) { - ehci_dbg (ehci, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, - le32_to_cpup (&qtd->hw_next), - le32_to_cpup (&qtd->hw_alt_next), - le32_to_cpup (&qtd->hw_token), - le32_to_cpup (&qtd->hw_buf [0])); + ehci_dbg(ehci, "%s td %p n%08x %08x t%08x p0=%08x\n", label, qtd, + hc32_to_cpup(ehci, &qtd->hw_next), + hc32_to_cpup(ehci, &qtd->hw_alt_next), + hc32_to_cpup(ehci, &qtd->hw_token), + hc32_to_cpup(ehci, &qtd->hw_buf [0])); if (qtd->hw_buf [1]) - ehci_dbg (ehci, " p1=%08x p2=%08x p3=%08x p4=%08x\n", - le32_to_cpup (&qtd->hw_buf [1]), - le32_to_cpup (&qtd->hw_buf [2]), - le32_to_cpup (&qtd->hw_buf [3]), - le32_to_cpup (&qtd->hw_buf [4])); + ehci_dbg(ehci, " p1=%08x p2=%08x p3=%08x p4=%08x\n", + hc32_to_cpup(ehci, &qtd->hw_buf[1]), + hc32_to_cpup(ehci, &qtd->hw_buf[2]), + hc32_to_cpup(ehci, &qtd->hw_buf[3]), + hc32_to_cpup(ehci, &qtd->hw_buf[4])); } -static void __attribute__((__unused__)) +static void __maybe_unused dbg_qh (const char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) { ehci_dbg (ehci, "%s qh %p n%08x info %x %x qtd %x\n", label, @@ -140,51 +140,53 @@ dbg_qh (const char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) dbg_qtd ("overlay", ehci, (struct ehci_qtd *) &qh->hw_qtd_next); } -static void __attribute__((__unused__)) +static void __maybe_unused dbg_itd (const char *label, struct ehci_hcd *ehci, struct ehci_itd *itd) { ehci_dbg (ehci, "%s [%d] itd %p, next %08x, urb %p\n", - label, itd->frame, itd, le32_to_cpu(itd->hw_next), itd->urb); + label, itd->frame, itd, hc32_to_cpu(ehci, itd->hw_next), + itd->urb); ehci_dbg (ehci, " trans: %08x %08x %08x %08x %08x %08x %08x %08x\n", - le32_to_cpu(itd->hw_transaction[0]), - le32_to_cpu(itd->hw_transaction[1]), - le32_to_cpu(itd->hw_transaction[2]), - le32_to_cpu(itd->hw_transaction[3]), - le32_to_cpu(itd->hw_transaction[4]), - le32_to_cpu(itd->hw_transaction[5]), - le32_to_cpu(itd->hw_transaction[6]), - le32_to_cpu(itd->hw_transaction[7])); + hc32_to_cpu(ehci, itd->hw_transaction[0]), + hc32_to_cpu(ehci, itd->hw_transaction[1]), + hc32_to_cpu(ehci, itd->hw_transaction[2]), + hc32_to_cpu(ehci, itd->hw_transaction[3]), + hc32_to_cpu(ehci, itd->hw_transaction[4]), + hc32_to_cpu(ehci, itd->hw_transaction[5]), + hc32_to_cpu(ehci, itd->hw_transaction[6]), + hc32_to_cpu(ehci, itd->hw_transaction[7])); ehci_dbg (ehci, " buf: %08x %08x %08x %08x %08x %08x %08x\n", - le32_to_cpu(itd->hw_bufp[0]), - le32_to_cpu(itd->hw_bufp[1]), - le32_to_cpu(itd->hw_bufp[2]), - le32_to_cpu(itd->hw_bufp[3]), - le32_to_cpu(itd->hw_bufp[4]), - le32_to_cpu(itd->hw_bufp[5]), - le32_to_cpu(itd->hw_bufp[6])); + hc32_to_cpu(ehci, itd->hw_bufp[0]), + hc32_to_cpu(ehci, itd->hw_bufp[1]), + hc32_to_cpu(ehci, itd->hw_bufp[2]), + hc32_to_cpu(ehci, itd->hw_bufp[3]), + hc32_to_cpu(ehci, itd->hw_bufp[4]), + hc32_to_cpu(ehci, itd->hw_bufp[5]), + hc32_to_cpu(ehci, itd->hw_bufp[6])); ehci_dbg (ehci, " 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 void __attribute__((__unused__)) +static void __maybe_unused dbg_sitd (const char *label, struct ehci_hcd *ehci, struct ehci_sitd *sitd) { ehci_dbg (ehci, "%s [%d] sitd %p, next %08x, urb %p\n", - label, sitd->frame, sitd, le32_to_cpu(sitd->hw_next), sitd->urb); + label, sitd->frame, sitd, hc32_to_cpu(ehci, sitd->hw_next), + sitd->urb); ehci_dbg (ehci, " addr %08x sched %04x result %08x buf %08x %08x\n", - le32_to_cpu(sitd->hw_fullspeed_ep), - le32_to_cpu(sitd->hw_uframe), - le32_to_cpu(sitd->hw_results), - le32_to_cpu(sitd->hw_buf [0]), - le32_to_cpu(sitd->hw_buf [1])); + hc32_to_cpu(ehci, sitd->hw_fullspeed_ep), + hc32_to_cpu(ehci, sitd->hw_uframe), + hc32_to_cpu(ehci, sitd->hw_results), + hc32_to_cpu(ehci, sitd->hw_buf[0]), + hc32_to_cpu(ehci, sitd->hw_buf[1])); } -static int __attribute__((__unused__)) +static int __maybe_unused dbg_status_buf (char *buf, unsigned len, const char *label, u32 status) { return scnprintf (buf, len, @@ -203,7 +205,7 @@ dbg_status_buf (char *buf, unsigned len, const char *label, u32 status) ); } -static int __attribute__((__unused__)) +static int __maybe_unused dbg_intr_buf (char *buf, unsigned len, const char *label, u32 enable) { return scnprintf (buf, len, @@ -267,28 +269,27 @@ dbg_port_buf (char *buf, unsigned len, const char *label, int port, u32 status) (status & PORT_PEC) ? " PEC" : "", (status & PORT_PE) ? " PE" : "", (status & PORT_CSC) ? " CSC" : "", - (status & PORT_CONNECT) ? " CONNECT" : "" - ); + (status & PORT_CONNECT) ? " CONNECT" : ""); } #else -static inline void __attribute__((__unused__)) +static inline void __maybe_unused dbg_qh (char *label, struct ehci_hcd *ehci, struct ehci_qh *qh) {} -static inline int __attribute__((__unused__)) +static inline int __maybe_unused dbg_status_buf (char *buf, unsigned len, const char *label, u32 status) { return 0; } -static inline int __attribute__((__unused__)) +static inline int __maybe_unused dbg_command_buf (char *buf, unsigned len, const char *label, u32 command) { return 0; } -static inline int __attribute__((__unused__)) +static inline int __maybe_unused dbg_intr_buf (char *buf, unsigned len, const char *label, u32 enable) { return 0; } -static inline int __attribute__((__unused__)) +static inline int __maybe_unused dbg_port_buf (char *buf, unsigned len, const char *label, int port, u32 status) { return 0; } @@ -332,9 +333,10 @@ static inline void remove_debug_files (struct ehci_hcd *bus) { } default: tmp = '?'; break; \ }; tmp; }) -static inline char token_mark (__le32 token) +static inline char token_mark(struct ehci_hcd *ehci, __hc32 token) { - __u32 v = le32_to_cpu (token); + __u32 v = hc32_to_cpu(ehci, token); + if (v & QTD_STS_ACTIVE) return '*'; if (v & QTD_STS_HALT) @@ -360,46 +362,48 @@ static void qh_lines ( unsigned size = *sizep; char *next = *nextp; char mark; + u32 list_end = EHCI_LIST_END(ehci); - if (qh->hw_qtd_next == EHCI_LIST_END) /* NEC does this */ + if (qh->hw_qtd_next == list_end) /* NEC does this */ mark = '@'; else - mark = token_mark (qh->hw_token); + mark = token_mark(ehci, qh->hw_token); if (mark == '/') { /* qh_alt_next controls qh advance? */ - if ((qh->hw_alt_next & QTD_MASK) == ehci->async->hw_alt_next) + if ((qh->hw_alt_next & QTD_MASK(ehci)) + == ehci->async->hw_alt_next) mark = '#'; /* blocked */ - else if (qh->hw_alt_next == EHCI_LIST_END) + else if (qh->hw_alt_next == list_end) mark = '.'; /* use hw_qtd_next */ /* else alt_next points to some other qtd */ } - scratch = le32_to_cpup (&qh->hw_info1); - hw_curr = (mark == '*') ? le32_to_cpup (&qh->hw_current) : 0; + scratch = hc32_to_cpup(ehci, &qh->hw_info1); + hw_curr = (mark == '*') ? hc32_to_cpup(ehci, &qh->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, le32_to_cpup (&qh->hw_info2), - le32_to_cpup (&qh->hw_token), mark, - (__constant_cpu_to_le32 (QTD_TOGGLE) & qh->hw_token) + scratch, hc32_to_cpup(ehci, &qh->hw_info2), + hc32_to_cpup(ehci, &qh->hw_token), mark, + (cpu_to_hc32(ehci, QTD_TOGGLE) & qh->hw_token) ? "data1" : "data0", - (le32_to_cpup (&qh->hw_alt_next) >> 1) & 0x0f); + (hc32_to_cpup(ehci, &qh->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 ehci_qtd, qtd_list); - scratch = le32_to_cpup (&td->hw_token); + scratch = hc32_to_cpup(ehci, &td->hw_token); mark = ' '; if (hw_curr == td->qtd_dma) mark = '*'; - else if (qh->hw_qtd_next == cpu_to_le32(td->qtd_dma)) + else if (qh->hw_qtd_next == cpu_to_hc32(ehci, td->qtd_dma)) mark = '+'; else if (QTD_LENGTH (scratch)) { if (td->hw_alt_next == ehci->async->hw_alt_next) mark = '#'; - else if (td->hw_alt_next != EHCI_LIST_END) + else if (td->hw_alt_next != list_end) mark = '/'; } temp = snprintf (next, size, @@ -490,7 +494,7 @@ show_periodic (struct class_device *class_dev, char *buf) unsigned temp, size, seen_count; char *next; unsigned i; - __le32 tag; + __hc32 tag; if (!(seen = kmalloc (DBG_SCHED_LIMIT * sizeof *seen, GFP_ATOMIC))) return 0; @@ -514,18 +518,19 @@ show_periodic (struct class_device *class_dev, char *buf) p = ehci->pshadow [i]; if (likely (!p.ptr)) continue; - tag = Q_NEXT_TYPE (ehci->periodic [i]); + tag = Q_NEXT_TYPE(ehci, ehci->periodic [i]); temp = scnprintf (next, size, "%4d: ", i); size -= temp; next += temp; do { - switch (tag) { + switch (hc32_to_cpu(ehci, tag)) { case Q_TYPE_QH: temp = scnprintf (next, size, " qh%d-%04x/%p", p.qh->period, - le32_to_cpup (&p.qh->hw_info2) + hc32_to_cpup(ehci, + &p.qh->hw_info2) /* uframe masks */ & (QH_CMASK | QH_SMASK), p.qh); @@ -543,7 +548,7 @@ show_periodic (struct class_device *class_dev, char *buf) } /* show more info the first time around */ if (temp == seen_count && p.ptr) { - u32 scratch = le32_to_cpup ( + u32 scratch = hc32_to_cpup(ehci, &p.qh->hw_info1); struct ehci_qtd *qtd; char *type = ""; @@ -554,7 +559,8 @@ show_periodic (struct class_device *class_dev, char *buf) &p.qh->qtd_list, qtd_list) { temp++; - switch (0x03 & (le32_to_cpu ( + switch (0x03 & (hc32_to_cpu( + ehci, qtd->hw_token) >> 8)) { case 0: type = "out"; continue; case 1: type = "in"; continue; @@ -576,7 +582,7 @@ show_periodic (struct class_device *class_dev, char *buf) } else temp = 0; if (p.qh) { - tag = Q_NEXT_TYPE (p.qh->hw_next); + tag = Q_NEXT_TYPE(ehci, p.qh->hw_next); p = p.qh->qh_next; } break; @@ -584,23 +590,23 @@ show_periodic (struct class_device *class_dev, char *buf) temp = scnprintf (next, size, " fstn-%8x/%p", p.fstn->hw_prev, p.fstn); - tag = Q_NEXT_TYPE (p.fstn->hw_next); + tag = Q_NEXT_TYPE(ehci, 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 (p.itd->hw_next); + tag = Q_NEXT_TYPE(ehci, p.itd->hw_next); p = p.itd->itd_next; break; case Q_TYPE_SITD: temp = scnprintf (next, size, " sitd%d-%04x/%p", p.sitd->stream->interval, - le32_to_cpup (&p.sitd->hw_uframe) + hc32_to_cpup(ehci, &p.sitd->hw_uframe) & 0x0000ffff, p.sitd); - tag = Q_NEXT_TYPE (p.sitd->hw_next); + tag = Q_NEXT_TYPE(ehci, p.sitd->hw_next); p = p.sitd->sitd_next; break; } @@ -673,7 +679,8 @@ show_registers (struct class_device *class_dev, char *buf) unsigned count = 256/4; pdev = to_pci_dev(ehci_to_hcd(ehci)->self.controller); - offset = HCC_EXT_CAPS (ehci_readl(ehci, &ehci->caps->hcc_params)); + offset = HCC_EXT_CAPS(ehci_readl(ehci, + &ehci->caps->hcc_params)); while (offset && count--) { pci_read_config_dword (pdev, offset, &cap); switch (cap & 0xff) { @@ -740,14 +747,16 @@ show_registers (struct class_device *class_dev, char *buf) for (i = 1; i <= HCS_N_PORTS (ehci->hcs_params); i++) { temp = dbg_port_buf (scratch, sizeof scratch, label, i, - ehci_readl(ehci, &ehci->regs->port_status [i - 1])); + ehci_readl(ehci, + &ehci->regs->port_status[i - 1])); temp = scnprintf (next, size, fmt, temp, scratch); size -= temp; next += temp; if (i == HCS_DEBUG_PORT(ehci->hcs_params) && ehci->debug) { temp = scnprintf (next, size, " debug control %08x\n", - ehci_readl(ehci, &ehci->debug->control)); + ehci_readl(ehci, + &ehci->debug->control)); size -= temp; next += temp; } diff --git a/drivers/usb/host/ehci-fsl.c b/drivers/usb/host/ehci-fsl.c index c7a7c590426..b7b7bfbce52 100644 --- a/drivers/usb/host/ehci-fsl.c +++ b/drivers/usb/host/ehci-fsl.c @@ -67,7 +67,8 @@ int usb_hcd_fsl_probe(const struct hc_driver *driver, * in host mode. */ if (!((pdata->operating_mode == FSL_USB2_DR_HOST) || - (pdata->operating_mode == FSL_USB2_MPH_HOST))) { + (pdata->operating_mode == FSL_USB2_MPH_HOST) || + (pdata->operating_mode == FSL_USB2_DR_OTG))) { dev_err(&pdev->dev, "Non Host Mode configured for %s. Wrong driver linked.\n", pdev->dev.bus_id); @@ -185,12 +186,14 @@ static void mpc83xx_usb_setup(struct usb_hcd *hcd) struct ehci_hcd *ehci = hcd_to_ehci(hcd); struct fsl_usb2_platform_data *pdata; void __iomem *non_ehci = hcd->regs; + u32 temp; pdata = (struct fsl_usb2_platform_data *)hcd->self.controller-> platform_data; /* Enable PHY interface in the control reg. */ - out_be32(non_ehci + FSL_SOC_USB_CTRL, 0x00000004); + temp = in_be32(non_ehci + FSL_SOC_USB_CTRL); + out_be32(non_ehci + FSL_SOC_USB_CTRL, temp | 0x00000004); out_be32(non_ehci + FSL_SOC_USB_SNOOP1, 0x0000001b); #if defined(CONFIG_PPC32) && !defined(CONFIG_NOT_COHERENT_CACHE) @@ -206,7 +209,8 @@ static void mpc83xx_usb_setup(struct usb_hcd *hcd) out_be32(non_ehci + FSL_SOC_USB_SNOOP2, 0x80000000 | SNOOP_SIZE_2GB); #endif - if (pdata->operating_mode == FSL_USB2_DR_HOST) + if ((pdata->operating_mode == FSL_USB2_DR_HOST) || + (pdata->operating_mode == FSL_USB2_DR_OTG)) mpc83xx_setup_phy(ehci, pdata->phy_mode, 0); if (pdata->operating_mode == FSL_USB2_MPH_HOST) { diff --git a/drivers/usb/host/ehci-hcd.c b/drivers/usb/host/ehci-hcd.c index 099aff64f53..c4e15ed1405 100644 --- a/drivers/usb/host/ehci-hcd.c +++ b/drivers/usb/host/ehci-hcd.c @@ -41,10 +41,6 @@ #include <asm/irq.h> #include <asm/system.h> #include <asm/unaligned.h> -#ifdef CONFIG_PPC_PS3 -#include <asm/firmware.h> -#endif - /*-------------------------------------------------------------------------*/ @@ -201,9 +197,15 @@ static void tdi_reset (struct ehci_hcd *ehci) u32 __iomem *reg_ptr; u32 tmp; - reg_ptr = (u32 __iomem *)(((u8 __iomem *)ehci->regs) + 0x68); + reg_ptr = (u32 __iomem *)(((u8 __iomem *)ehci->regs) + USBMODE); tmp = ehci_readl(ehci, reg_ptr); - tmp |= 0x3; + tmp |= USBMODE_CM_HC; + /* The default byte access to MMR space is LE after + * controller reset. Set the required endian mode + * for transfer buffers to match the host microprocessor + */ + if (ehci_big_endian_mmio(ehci)) + tmp |= USBMODE_BE; ehci_writel(ehci, tmp, reg_ptr); } @@ -273,6 +275,58 @@ static void ehci_work(struct ehci_hcd *ehci); /*-------------------------------------------------------------------------*/ +#ifdef CONFIG_CPU_FREQ + +#include <linux/cpufreq.h> + +static void ehci_cpufreq_pause (struct ehci_hcd *ehci) +{ + unsigned long flags; + + spin_lock_irqsave(&ehci->lock, flags); + if (!ehci->cpufreq_changing++) + qh_inactivate_split_intr_qhs(ehci); + spin_unlock_irqrestore(&ehci->lock, flags); +} + +static void ehci_cpufreq_unpause (struct ehci_hcd *ehci) +{ + unsigned long flags; + + spin_lock_irqsave(&ehci->lock, flags); + if (!--ehci->cpufreq_changing) + qh_reactivate_split_intr_qhs(ehci); + spin_unlock_irqrestore(&ehci->lock, flags); +} + +/* + * ehci_cpufreq_notifier is needed to avoid MMF errors that occur when + * EHCI controllers that don't cache many uframes get delayed trying to + * read main memory during CPU frequency transitions. This can cause + * split interrupt transactions to not be completed in the required uframe. + * This has been observed on the Broadcom/ServerWorks HT1000 controller. + */ +static int ehci_cpufreq_notifier(struct notifier_block *nb, unsigned long val, + void *data) +{ + struct ehci_hcd *ehci = container_of(nb, struct ehci_hcd, + cpufreq_transition); + + switch (val) { + case CPUFREQ_PRECHANGE: + ehci_cpufreq_pause(ehci); + break; + case CPUFREQ_POSTCHANGE: + ehci_cpufreq_unpause(ehci); + break; + } + return 0; +} + +#endif + +/*-------------------------------------------------------------------------*/ + static void ehci_watchdog (unsigned long param) { struct ehci_hcd *ehci = (struct ehci_hcd *) param; @@ -347,6 +401,8 @@ static void ehci_port_power (struct ehci_hcd *ehci, int is_on) is_on ? SetPortFeature : ClearPortFeature, USB_PORT_FEAT_POWER, port--, NULL, 0); + /* Flush those writes */ + ehci_readl(ehci, &ehci->regs->command); msleep(20); } @@ -404,6 +460,10 @@ static void ehci_stop (struct usb_hcd *hcd) ehci_writel(ehci, 0, &ehci->regs->intr_enable); spin_unlock_irq(&ehci->lock); +#ifdef CONFIG_CPU_FREQ + cpufreq_unregister_notifier(&ehci->cpufreq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +#endif /* let companion controllers work when we aren't */ ehci_writel(ehci, 0, &ehci->regs->configured_flag); @@ -470,12 +530,12 @@ static int ehci_init(struct usb_hcd *hcd) * from automatically advancing to the next td after short reads. */ ehci->async->qh_next.qh = NULL; - ehci->async->hw_next = QH_NEXT(ehci->async->qh_dma); - ehci->async->hw_info1 = cpu_to_le32(QH_HEAD); - ehci->async->hw_token = cpu_to_le32(QTD_STS_HALT); - ehci->async->hw_qtd_next = EHCI_LIST_END; + ehci->async->hw_next = QH_NEXT(ehci, ehci->async->qh_dma); + ehci->async->hw_info1 = cpu_to_hc32(ehci, QH_HEAD); + ehci->async->hw_token = cpu_to_hc32(ehci, QTD_STS_HALT); + ehci->async->hw_qtd_next = EHCI_LIST_END(ehci); ehci->async->qh_state = QH_STATE_LINKED; - ehci->async->hw_alt_next = QTD_NEXT(ehci->async->dummy->qtd_dma); + ehci->async->hw_alt_next = QTD_NEXT(ehci, ehci->async->dummy->qtd_dma); /* clear interrupt enables, set irq latency */ if (log2_irq_thresh < 0 || log2_irq_thresh > 6) @@ -509,6 +569,17 @@ static int ehci_init(struct usb_hcd *hcd) } ehci->command = temp; +#ifdef CONFIG_CPU_FREQ + INIT_LIST_HEAD(&ehci->split_intr_qhs); + /* + * If the EHCI controller caches enough uframes, this probably + * isn't needed unless there are so many low/full speed devices + * that the controller's can't cache it all. + */ + ehci->cpufreq_transition.notifier_call = ehci_cpufreq_notifier; + cpufreq_register_notifier(&ehci->cpufreq_transition, + CPUFREQ_TRANSITION_NOTIFIER); +#endif return 0; } @@ -925,7 +996,7 @@ MODULE_LICENSE ("GPL"); #define PCI_DRIVER ehci_pci_driver #endif -#ifdef CONFIG_MPC834x +#ifdef CONFIG_USB_EHCI_FSL #include "ehci-fsl.c" #define PLATFORM_DRIVER ehci_fsl_driver #endif @@ -937,7 +1008,12 @@ MODULE_LICENSE ("GPL"); #ifdef CONFIG_PPC_PS3 #include "ehci-ps3.c" -#define PS3_SYSTEM_BUS_DRIVER ps3_ehci_sb_driver +#define PS3_SYSTEM_BUS_DRIVER ps3_ehci_driver +#endif + +#ifdef CONFIG_440EPX +#include "ehci-ppc-soc.c" +#define PLATFORM_DRIVER ehci_ppc_soc_driver #endif #if !defined(PCI_DRIVER) && !defined(PLATFORM_DRIVER) && \ @@ -971,18 +1047,15 @@ static int __init ehci_hcd_init(void) #endif #ifdef PS3_SYSTEM_BUS_DRIVER - if (firmware_has_feature(FW_FEATURE_PS3_LV1)) { - retval = ps3_system_bus_driver_register( - &PS3_SYSTEM_BUS_DRIVER); - if (retval < 0) { + retval = ps3_ehci_driver_register(&PS3_SYSTEM_BUS_DRIVER); + if (retval < 0) { #ifdef PLATFORM_DRIVER - platform_driver_unregister(&PLATFORM_DRIVER); + platform_driver_unregister(&PLATFORM_DRIVER); #endif #ifdef PCI_DRIVER - pci_unregister_driver(&PCI_DRIVER); + pci_unregister_driver(&PCI_DRIVER); #endif - return retval; - } + return retval; } #endif @@ -999,8 +1072,7 @@ static void __exit ehci_hcd_cleanup(void) pci_unregister_driver(&PCI_DRIVER); #endif #ifdef PS3_SYSTEM_BUS_DRIVER - if (firmware_has_feature(FW_FEATURE_PS3_LV1)) - ps3_system_bus_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); + ps3_ehci_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); #endif } module_exit(ehci_hcd_cleanup); diff --git a/drivers/usb/host/ehci-hub.c b/drivers/usb/host/ehci-hub.c index f4d301bc83b..0dcb4164dc8 100644 --- a/drivers/usb/host/ehci-hub.c +++ b/drivers/usb/host/ehci-hub.c @@ -28,6 +28,89 @@ /*-------------------------------------------------------------------------*/ +#ifdef CONFIG_USB_PERSIST + +static int ehci_hub_control( + struct usb_hcd *hcd, + u16 typeReq, + u16 wValue, + u16 wIndex, + char *buf, + u16 wLength +); + +/* After a power loss, ports that were owned by the companion must be + * reset so that the companion can still own them. + */ +static void ehci_handover_companion_ports(struct ehci_hcd *ehci) +{ + u32 __iomem *reg; + u32 status; + int port; + __le32 buf; + struct usb_hcd *hcd = ehci_to_hcd(ehci); + + if (!ehci->owned_ports) + return; + + /* Give the connections some time to appear */ + msleep(20); + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + if (test_bit(port, &ehci->owned_ports)) { + reg = &ehci->regs->port_status[port]; + status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; + + /* Port already owned by companion? */ + if (status & PORT_OWNER) + clear_bit(port, &ehci->owned_ports); + else if (test_bit(port, &ehci->companion_ports)) + ehci_writel(ehci, status & ~PORT_PE, reg); + else + ehci_hub_control(hcd, SetPortFeature, + USB_PORT_FEAT_RESET, port + 1, + NULL, 0); + } + } + + if (!ehci->owned_ports) + return; + msleep(90); /* Wait for resets to complete */ + + port = HCS_N_PORTS(ehci->hcs_params); + while (port--) { + if (test_bit(port, &ehci->owned_ports)) { + ehci_hub_control(hcd, GetPortStatus, + 0, port + 1, + (char *) &buf, sizeof(buf)); + + /* The companion should now own the port, + * but if something went wrong the port must not + * remain enabled. + */ + reg = &ehci->regs->port_status[port]; + status = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; + if (status & PORT_OWNER) + ehci_writel(ehci, status | PORT_CSC, reg); + else { + ehci_dbg(ehci, "failed handover port %d: %x\n", + port + 1, status); + ehci_writel(ehci, status & ~PORT_PE, reg); + } + } + } + + ehci->owned_ports = 0; +} + +#else /* CONFIG_USB_PERSIST */ + +static inline void ehci_handover_companion_ports(struct ehci_hcd *ehci) +{ } + +#endif + #ifdef CONFIG_PM static int ehci_bus_suspend (struct usb_hcd *hcd) @@ -60,14 +143,16 @@ static int ehci_bus_suspend (struct usb_hcd *hcd) * then manually resume them in the bus_resume() routine. */ ehci->bus_suspended = 0; + ehci->owned_ports = 0; while (port--) { u32 __iomem *reg = &ehci->regs->port_status [port]; u32 t1 = ehci_readl(ehci, reg) & ~PORT_RWC_BITS; u32 t2 = t1; /* keep track of which ports we suspend */ - if ((t1 & PORT_PE) && !(t1 & PORT_OWNER) && - !(t1 & PORT_SUSPEND)) { + if (t1 & PORT_OWNER) + set_bit(port, &ehci->owned_ports); + else if ((t1 & PORT_PE) && !(t1 & PORT_SUSPEND)) { t2 |= PORT_SUSPEND; set_bit(port, &ehci->bus_suspended); } @@ -108,11 +193,16 @@ static int ehci_bus_resume (struct usb_hcd *hcd) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); u32 temp; + u32 power_okay; int i; if (time_before (jiffies, ehci->next_statechange)) msleep(5); spin_lock_irq (&ehci->lock); + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { + spin_unlock_irq(&ehci->lock); + return -ESHUTDOWN; + } /* Ideally and we've got a real resume here, and no port's power * was lost. (For PCI, that means Vaux was maintained.) But we @@ -120,8 +210,9 @@ static int ehci_bus_resume (struct usb_hcd *hcd) * the last user of the controller, not reset/pm hardware keeping * state we gave to it. */ - temp = ehci_readl(ehci, &ehci->regs->intr_enable); - ehci_dbg(ehci, "resume root hub%s\n", temp ? "" : " after power loss"); + power_okay = ehci_readl(ehci, &ehci->regs->intr_enable); + ehci_dbg(ehci, "resume root hub%s\n", + power_okay ? "" : " after power loss"); /* at least some APM implementations will try to deliver * IRQs right away, so delay them until we're ready. @@ -184,6 +275,9 @@ static int ehci_bus_resume (struct usb_hcd *hcd) ehci_writel(ehci, INTR_MASK, &ehci->regs->intr_enable); spin_unlock_irq (&ehci->lock); + + if (!power_okay) + ehci_handover_companion_ports(ehci); return 0; } @@ -448,7 +542,8 @@ static int ehci_hub_control ( ) { struct ehci_hcd *ehci = hcd_to_ehci (hcd); int ports = HCS_N_PORTS (ehci->hcs_params); - u32 __iomem *status_reg = &ehci->regs->port_status[wIndex - 1]; + u32 __iomem *status_reg = &ehci->regs->port_status[ + (wIndex & 0xff) - 1]; u32 temp, status; unsigned long flags; int retval = 0; @@ -556,9 +651,24 @@ static int ehci_hub_control ( status |= 1 << USB_PORT_FEAT_C_CONNECTION; if (temp & PORT_PEC) status |= 1 << USB_PORT_FEAT_C_ENABLE; - if ((temp & PORT_OCC) && !ignore_oc) + + if ((temp & PORT_OCC) && !ignore_oc){ status |= 1 << USB_PORT_FEAT_C_OVER_CURRENT; + /* + * Hubs should disable port power on over-current. + * However, not all EHCI implementations do this + * automatically, even if they _do_ support per-port + * power switching; they're allowed to just limit the + * current. khubd will turn the power back on. + */ + if (HCS_PPC (ehci->hcs_params)){ + ehci_writel(ehci, + temp & ~(PORT_RWC_BITS | PORT_POWER), + status_reg); + } + } + /* whoever resumes must GetPortStatus to complete it!! */ if (temp & PORT_RESUME) { diff --git a/drivers/usb/host/ehci-mem.c b/drivers/usb/host/ehci-mem.c index a8ba2e1497a..8816d09903d 100644 --- a/drivers/usb/host/ehci-mem.c +++ b/drivers/usb/host/ehci-mem.c @@ -27,7 +27,7 @@ * need to use dma_pool or dma_alloc_coherent * - driver buffers, read/written by HC ... single shot DMA mapped * - * There's also PCI "register" data, which is memory mapped. + * There's also "register" data (e.g. PCI or SOC), which is memory mapped. * No memory seen by this driver is pageable. */ @@ -35,13 +35,14 @@ /* Allocate the key transfer structures from the previously allocated pool */ -static inline void ehci_qtd_init (struct ehci_qtd *qtd, dma_addr_t dma) +static inline void ehci_qtd_init(struct ehci_hcd *ehci, struct ehci_qtd *qtd, + dma_addr_t dma) { memset (qtd, 0, sizeof *qtd); qtd->qtd_dma = dma; qtd->hw_token = cpu_to_le32 (QTD_STS_HALT); - qtd->hw_next = EHCI_LIST_END; - qtd->hw_alt_next = EHCI_LIST_END; + qtd->hw_next = EHCI_LIST_END(ehci); + qtd->hw_alt_next = EHCI_LIST_END(ehci); INIT_LIST_HEAD (&qtd->qtd_list); } @@ -52,7 +53,7 @@ static struct ehci_qtd *ehci_qtd_alloc (struct ehci_hcd *ehci, gfp_t flags) qtd = dma_pool_alloc (ehci->qtd_pool, flags, &dma); if (qtd != NULL) { - ehci_qtd_init (qtd, dma); + ehci_qtd_init(ehci, qtd, dma); } return qtd; } @@ -63,9 +64,8 @@ static inline void ehci_qtd_free (struct ehci_hcd *ehci, struct ehci_qtd *qtd) } -static void qh_destroy (struct kref *kref) +static void qh_destroy(struct ehci_qh *qh) { - struct ehci_qh *qh = container_of(kref, struct ehci_qh, kref); struct ehci_hcd *ehci = qh->ehci; /* clean qtds first, and know this is not linked */ @@ -89,11 +89,14 @@ static struct ehci_qh *ehci_qh_alloc (struct ehci_hcd *ehci, gfp_t flags) return qh; memset (qh, 0, sizeof *qh); - kref_init(&qh->kref); + qh->refcount = 1; qh->ehci = ehci; qh->qh_dma = dma; // INIT_LIST_HEAD (&qh->qh_list); INIT_LIST_HEAD (&qh->qtd_list); +#ifdef CONFIG_CPU_FREQ + INIT_LIST_HEAD (&qh->split_intr_qhs); +#endif /* dummy td enables safe urb queuing */ qh->dummy = ehci_qtd_alloc (ehci, flags); @@ -108,13 +111,15 @@ static struct ehci_qh *ehci_qh_alloc (struct ehci_hcd *ehci, gfp_t flags) /* to share a qh (cpu threads, or hc) */ static inline struct ehci_qh *qh_get (struct ehci_qh *qh) { - kref_get(&qh->kref); + WARN_ON(!qh->refcount); + qh->refcount++; return qh; } static inline void qh_put (struct ehci_qh *qh) { - kref_put(&qh->kref, qh_destroy); + if (!--qh->refcount) + qh_destroy(qh); } /*-------------------------------------------------------------------------*/ @@ -217,7 +222,7 @@ static int ehci_mem_init (struct ehci_hcd *ehci, gfp_t flags) goto fail; } for (i = 0; i < ehci->periodic_size; i++) - ehci->periodic [i] = EHCI_LIST_END; + ehci->periodic [i] = EHCI_LIST_END(ehci); /* software shadow of hardware table */ ehci->pshadow = kcalloc(ehci->periodic_size, sizeof(void *), flags); diff --git a/drivers/usb/host/ehci-pci.c b/drivers/usb/host/ehci-pci.c index 966965f7233..a7816e392a8 100644 --- a/drivers/usb/host/ehci-pci.c +++ b/drivers/usb/host/ehci-pci.c @@ -312,13 +312,14 @@ static int ehci_pci_resume(struct usb_hcd *hcd) ehci_work(ehci); spin_unlock_irq(&ehci->lock); - /* here we "know" root ports should always stay powered */ - ehci_port_power(ehci, 1); - ehci_writel(ehci, ehci->command, &ehci->regs->command); ehci_writel(ehci, FLAG_CF, &ehci->regs->configured_flag); ehci_readl(ehci, &ehci->regs->command); /* unblock posted writes */ + /* here we "know" root ports should always stay powered */ + ehci_port_power(ehci, 1); + ehci_handover_companion_ports(ehci); + hcd->state = HC_STATE_SUSPENDED; return 0; } diff --git a/drivers/usb/host/ehci-ppc-soc.c b/drivers/usb/host/ehci-ppc-soc.c new file mode 100644 index 00000000000..c2cedb09ed8 --- /dev/null +++ b/drivers/usb/host/ehci-ppc-soc.c @@ -0,0 +1,182 @@ +/* + * EHCI HCD (Host Controller Driver) for USB. + * + * (C) Copyright 2006-2007 Stefan Roese <sr@denx.de>, DENX Software Engineering + * + * Bus Glue for PPC On-Chip EHCI driver + * Tested on AMCC 440EPx + * + * Based on "ehci-au12xx.c" by David Brownell <dbrownell@users.sourceforge.net> + * + * This file is licenced under the GPL. + */ + +#include <linux/platform_device.h> + +extern int usb_disabled(void); + +/** + * usb_ehci_ppc_soc_probe - initialize PPC-SoC-based HCDs + * Context: !in_interrupt() + * + * Allocates basic resources for this USB host controller, and + * then invokes the start() method for the HCD associated with it + * through the hotplug entry's driver_data. + * + */ +int usb_ehci_ppc_soc_probe(const struct hc_driver *driver, + struct usb_hcd **hcd_out, + struct platform_device *dev) +{ + int retval; + struct usb_hcd *hcd; + struct ehci_hcd *ehci; + + if (dev->resource[1].flags != IORESOURCE_IRQ) { + pr_debug("resource[1] is not IORESOURCE_IRQ"); + retval = -ENOMEM; + } + hcd = usb_create_hcd(driver, &dev->dev, "PPC-SOC EHCI"); + if (!hcd) + return -ENOMEM; + hcd->rsrc_start = dev->resource[0].start; + hcd->rsrc_len = dev->resource[0].end - dev->resource[0].start + 1; + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) { + pr_debug("request_mem_region failed"); + retval = -EBUSY; + goto err1; + } + + hcd->regs = ioremap(hcd->rsrc_start, hcd->rsrc_len); + if (!hcd->regs) { + pr_debug("ioremap failed"); + retval = -ENOMEM; + goto err2; + } + + ehci = hcd_to_ehci(hcd); + ehci->big_endian_mmio = 1; + ehci->big_endian_desc = 1; + ehci->caps = hcd->regs; + ehci->regs = hcd->regs + HC_LENGTH(ehci_readl(ehci, &ehci->caps->hc_capbase)); + + /* cache this readonly data; minimize chip reads */ + ehci->hcs_params = ehci_readl(ehci, &ehci->caps->hcs_params); + +#if defined(CONFIG_440EPX) + /* + * 440EPx Errata USBH_3 + * Fix: Enable Break Memory Transfer (BMT) in INSNREG3 + */ + out_be32((void *)((ulong)(&ehci->regs->command) + 0x8c), (1 << 0)); + ehci_dbg(ehci, "Break Memory Transfer (BMT) has beed enabled!\n"); +#endif + + retval = usb_add_hcd(hcd, dev->resource[1].start, IRQF_DISABLED); + if (retval == 0) + return retval; + + iounmap(hcd->regs); +err2: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); +err1: + usb_put_hcd(hcd); + return retval; +} + +/* may be called without controller electrically present */ +/* may be called with controller, bus, and devices active */ + +/** + * usb_ehci_hcd_ppc_soc_remove - shutdown processing for PPC-SoC-based HCDs + * @dev: USB Host Controller being removed + * Context: !in_interrupt() + * + * Reverses the effect of usb_ehci_hcd_ppc_soc_probe(), first invoking + * the HCD's stop() method. It is always called from a thread + * context, normally "rmmod", "apmd", or something similar. + * + */ +void usb_ehci_ppc_soc_remove(struct usb_hcd *hcd, struct platform_device *dev) +{ + usb_remove_hcd(hcd); + iounmap(hcd->regs); + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); +} + +static const struct hc_driver ehci_ppc_soc_hc_driver = { + .description = hcd_name, + .product_desc = "PPC-SOC EHCI", + .hcd_priv_size = sizeof(struct ehci_hcd), + + /* + * generic hardware linkage + */ + .irq = ehci_irq, + .flags = HCD_MEMORY | HCD_USB2, + + /* + * basic lifecycle operations + */ + .reset = ehci_init, + .start = ehci_run, + .stop = ehci_stop, + .shutdown = ehci_shutdown, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = ehci_urb_enqueue, + .urb_dequeue = ehci_urb_dequeue, + .endpoint_disable = ehci_endpoint_disable, + + /* + * scheduling support + */ + .get_frame_number = ehci_get_frame, + + /* + * root hub support + */ + .hub_status_data = ehci_hub_status_data, + .hub_control = ehci_hub_control, +#ifdef CONFIG_PM + .hub_suspend = ehci_hub_suspend, + .hub_resume = ehci_hub_resume, +#endif +}; + +static int ehci_hcd_ppc_soc_drv_probe(struct platform_device *pdev) +{ + struct usb_hcd *hcd = NULL; + int ret; + + pr_debug("In ehci_hcd_ppc_soc_drv_probe\n"); + + if (usb_disabled()) + return -ENODEV; + + ret = usb_ehci_ppc_soc_probe(&ehci_ppc_soc_hc_driver, &hcd, pdev); + return ret; +} + +static int ehci_hcd_ppc_soc_drv_remove(struct platform_device *pdev) +{ + struct usb_hcd *hcd = platform_get_drvdata(pdev); + + usb_ehci_ppc_soc_remove(hcd, pdev); + return 0; +} + +MODULE_ALIAS("ppc-soc-ehci"); +static struct platform_driver ehci_ppc_soc_driver = { + .probe = ehci_hcd_ppc_soc_drv_probe, + .remove = ehci_hcd_ppc_soc_drv_remove, + .shutdown = usb_hcd_platform_shutdown, + .driver = { + .name = "ppc-soc-ehci", + .bus = &platform_bus_type + } +}; diff --git a/drivers/usb/host/ehci-ps3.c b/drivers/usb/host/ehci-ps3.c index 37b83ba0996..829fe649a98 100644 --- a/drivers/usb/host/ehci-ps3.c +++ b/drivers/usb/host/ehci-ps3.c @@ -18,6 +18,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <asm/firmware.h> #include <asm/ps3.h> static int ps3_ehci_hc_reset(struct usb_hcd *hcd) @@ -73,7 +74,7 @@ static const struct hc_driver ps3_ehci_hc_driver = { #endif }; -static int ps3_ehci_sb_probe(struct ps3_system_bus_device *dev) +static int ps3_ehci_probe(struct ps3_system_bus_device *dev) { int result; struct usb_hcd *hcd; @@ -85,13 +86,30 @@ static int ps3_ehci_sb_probe(struct ps3_system_bus_device *dev) goto fail_start; } + result = ps3_open_hv_device(dev); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_open_hv_device failed\n", + __func__, __LINE__); + goto fail_open; + } + + result = ps3_dma_region_create(dev->d_region); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_dma_region_create failed: " + "(%d)\n", __func__, __LINE__, result); + BUG_ON("check region type"); + goto fail_dma_region; + } + result = ps3_mmio_region_create(dev->m_region); if (result) { dev_dbg(&dev->core, "%s:%d: ps3_map_mmio_region failed\n", __func__, __LINE__); result = -EPERM; - goto fail_mmio; + goto fail_mmio_region; } dev_dbg(&dev->core, "%s:%d: mmio mapped_addr %lxh\n", __func__, @@ -120,6 +138,11 @@ static int ps3_ehci_sb_probe(struct ps3_system_bus_device *dev) hcd->rsrc_start = dev->m_region->lpar_addr; hcd->rsrc_len = dev->m_region->len; + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) + dev_dbg(&dev->core, "%s:%d: request_mem_region failed\n", + __func__, __LINE__); + hcd->regs = ioremap(dev->m_region->lpar_addr, dev->m_region->len); if (!hcd->regs) { @@ -153,34 +176,73 @@ static int ps3_ehci_sb_probe(struct ps3_system_bus_device *dev) fail_add_hcd: iounmap(hcd->regs); fail_ioremap: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); fail_create_hcd: ps3_io_irq_destroy(virq); fail_irq: ps3_free_mmio_region(dev->m_region); -fail_mmio: +fail_mmio_region: + ps3_dma_region_free(dev->d_region); +fail_dma_region: + ps3_close_hv_device(dev); +fail_open: fail_start: return result; } -static int ps3_ehci_sb_remove(struct ps3_system_bus_device *dev) +static int ps3_ehci_remove(struct ps3_system_bus_device *dev) { + unsigned int tmp; struct usb_hcd *hcd = (struct usb_hcd *)ps3_system_bus_get_driver_data(dev); - usb_put_hcd(hcd); + BUG_ON(!hcd); + + dev_dbg(&dev->core, "%s:%d: regs %p\n", __func__, __LINE__, hcd->regs); + dev_dbg(&dev->core, "%s:%d: irq %u\n", __func__, __LINE__, hcd->irq); + + tmp = hcd->irq; + + usb_remove_hcd(hcd); + ps3_system_bus_set_driver_data(dev, NULL); + BUG_ON(!hcd->regs); + iounmap(hcd->regs); + + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + ps3_io_irq_destroy(tmp); + ps3_free_mmio_region(dev->m_region); + + ps3_dma_region_free(dev->d_region); + ps3_close_hv_device(dev); + return 0; } -MODULE_ALIAS("ps3-ehci"); +static int ps3_ehci_driver_register(struct ps3_system_bus_driver *drv) +{ + return firmware_has_feature(FW_FEATURE_PS3_LV1) + ? ps3_system_bus_driver_register(drv) + : 0; +} + +static void ps3_ehci_driver_unregister(struct ps3_system_bus_driver *drv) +{ + if (firmware_has_feature(FW_FEATURE_PS3_LV1)) + ps3_system_bus_driver_unregister(drv); +} + +MODULE_ALIAS(PS3_MODULE_ALIAS_EHCI); -static struct ps3_system_bus_driver ps3_ehci_sb_driver = { +static struct ps3_system_bus_driver ps3_ehci_driver = { + .core.name = "ps3-ehci-driver", + .core.owner = THIS_MODULE, .match_id = PS3_MATCH_ID_EHCI, - .core = { - .name = "ps3-ehci-driver", - }, - .probe = ps3_ehci_sb_probe, - .remove = ps3_ehci_sb_remove, + .probe = ps3_ehci_probe, + .remove = ps3_ehci_remove, + .shutdown = ps3_ehci_remove, }; diff --git a/drivers/usb/host/ehci-q.c b/drivers/usb/host/ehci-q.c index e7fbbd00e7c..2284028f8aa 100644 --- a/drivers/usb/host/ehci-q.c +++ b/drivers/usb/host/ehci-q.c @@ -43,15 +43,15 @@ /* fill a qtd, returning how much of the buffer we were able to queue up */ static int -qtd_fill (struct ehci_qtd *qtd, dma_addr_t buf, size_t len, - int token, int maxpacket) +qtd_fill(struct ehci_hcd *ehci, struct ehci_qtd *qtd, dma_addr_t buf, + size_t len, int token, int maxpacket) { int i, count; u64 addr = buf; /* one buffer entry per 4K ... first might be short or unaligned */ - qtd->hw_buf [0] = cpu_to_le32 ((u32)addr); - qtd->hw_buf_hi [0] = cpu_to_le32 ((u32)(addr >> 32)); + qtd->hw_buf[0] = cpu_to_hc32(ehci, (u32)addr); + qtd->hw_buf_hi[0] = cpu_to_hc32(ehci, (u32)(addr >> 32)); count = 0x1000 - (buf & 0x0fff); /* rest of that page */ if (likely (len < count)) /* ... iff needed */ count = len; @@ -62,8 +62,9 @@ qtd_fill (struct ehci_qtd *qtd, dma_addr_t buf, size_t len, /* per-qtd limit: from 16K to 20K (best alignment) */ for (i = 1; count < len && i < 5; i++) { addr = buf; - qtd->hw_buf [i] = cpu_to_le32 ((u32)addr); - qtd->hw_buf_hi [i] = cpu_to_le32 ((u32)(addr >> 32)); + qtd->hw_buf[i] = cpu_to_hc32(ehci, (u32)addr); + qtd->hw_buf_hi[i] = cpu_to_hc32(ehci, + (u32)(addr >> 32)); buf += 0x1000; if ((count + 0x1000) < len) count += 0x1000; @@ -75,7 +76,7 @@ qtd_fill (struct ehci_qtd *qtd, dma_addr_t buf, size_t len, if (count != len) count -= (count % maxpacket); } - qtd->hw_token = cpu_to_le32 ((count << 16) | token); + qtd->hw_token = cpu_to_hc32(ehci, (count << 16) | token); qtd->length = count; return count; @@ -89,28 +90,28 @@ qh_update (struct ehci_hcd *ehci, struct ehci_qh *qh, struct ehci_qtd *qtd) /* writes to an active overlay are unsafe */ BUG_ON(qh->qh_state != QH_STATE_IDLE); - qh->hw_qtd_next = QTD_NEXT (qtd->qtd_dma); - qh->hw_alt_next = EHCI_LIST_END; + qh->hw_qtd_next = QTD_NEXT(ehci, qtd->qtd_dma); + qh->hw_alt_next = EHCI_LIST_END(ehci); /* Except for control endpoints, we make hardware maintain data * toggle (like OHCI) ... here (re)initialize the toggle in the QH, * and set the pseudo-toggle in udev. Only usb_clear_halt() will * ever clear it. */ - if (!(qh->hw_info1 & cpu_to_le32(1 << 14))) { + if (!(qh->hw_info1 & cpu_to_hc32(ehci, 1 << 14))) { unsigned is_out, epnum; - is_out = !(qtd->hw_token & cpu_to_le32(1 << 8)); - epnum = (le32_to_cpup(&qh->hw_info1) >> 8) & 0x0f; + is_out = !(qtd->hw_token & cpu_to_hc32(ehci, 1 << 8)); + epnum = (hc32_to_cpup(ehci, &qh->hw_info1) >> 8) & 0x0f; if (unlikely (!usb_gettoggle (qh->dev, epnum, is_out))) { - qh->hw_token &= ~__constant_cpu_to_le32 (QTD_TOGGLE); + qh->hw_token &= ~cpu_to_hc32(ehci, QTD_TOGGLE); usb_settoggle (qh->dev, epnum, is_out, 1); } } /* HC must see latest qtd and qh data before we clear ACTIVE+HALT */ wmb (); - qh->hw_token &= __constant_cpu_to_le32 (QTD_TOGGLE | QTD_STS_PING); + qh->hw_token &= cpu_to_hc32(ehci, QTD_TOGGLE | QTD_STS_PING); } /* if it weren't for a common silicon quirk (writing the dummy into the qh @@ -128,7 +129,7 @@ qh_refresh (struct ehci_hcd *ehci, struct ehci_qh *qh) qtd = list_entry (qh->qtd_list.next, struct ehci_qtd, qtd_list); /* first qtd may already be partially processed */ - if (cpu_to_le32 (qtd->qtd_dma) == qh->hw_current) + if (cpu_to_hc32(ehci, qtd->qtd_dma) == qh->hw_current) qtd = NULL; } @@ -222,7 +223,7 @@ __acquires(ehci->lock) struct ehci_qh *qh = (struct ehci_qh *) urb->hcpriv; /* S-mask in a QH means it's an interrupt urb */ - if ((qh->hw_info2 & __constant_cpu_to_le32 (QH_SMASK)) != 0) { + if ((qh->hw_info2 & cpu_to_hc32(ehci, QH_SMASK)) != 0) { /* ... update hc-wide periodic stats (for usbfs) */ ehci_to_hcd(ehci)->self.bandwidth_int_reqs--; @@ -277,7 +278,6 @@ static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh); * Chases up to qh->hw_current. Returns number of completions called, * indicating how much "real" work we did. */ -#define HALT_BIT __constant_cpu_to_le32(QTD_STS_HALT) static unsigned qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) { @@ -287,6 +287,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) unsigned count = 0; int do_status = 0; u8 state; + u32 halt = HALT_BIT(ehci); if (unlikely (list_empty (&qh->qtd_list))) return count; @@ -311,6 +312,10 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) struct urb *urb; u32 token = 0; + /* ignore QHs that are currently inactive */ + if (qh->hw_info1 & __constant_cpu_to_le32(QH_INACTIVATE)) + break; + qtd = list_entry (entry, struct ehci_qtd, qtd_list); urb = qtd->urb; @@ -330,7 +335,7 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) /* hardware copies qtd out of qh overlay */ rmb (); - token = le32_to_cpu (qtd->hw_token); + token = hc32_to_cpu(ehci, qtd->hw_token); /* always clean up qtds the hc de-activated */ if ((token & QTD_STS_ACTIVE) == 0) { @@ -342,7 +347,8 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) * that silicon quirk can kick in with this dummy too. */ } else if (IS_SHORT_READ (token) - && !(qtd->hw_alt_next & EHCI_LIST_END)) { + && !(qtd->hw_alt_next + & EHCI_LIST_END(ehci))) { stopped = 1; goto halt; } @@ -374,17 +380,17 @@ qh_completions (struct ehci_hcd *ehci, struct ehci_qh *qh) /* token in overlay may be most current */ if (state == QH_STATE_IDLE - && cpu_to_le32 (qtd->qtd_dma) + && cpu_to_hc32(ehci, qtd->qtd_dma) == qh->hw_current) - token = le32_to_cpu (qh->hw_token); + token = hc32_to_cpu(ehci, qh->hw_token); /* force halt for unlinked or blocked qh, so we'll * patch the qh later and so that completions can't * activate it while we "know" it's stopped. */ - if ((HALT_BIT & qh->hw_token) == 0) { + if ((halt & qh->hw_token) == 0) { halt: - qh->hw_token |= HALT_BIT; + qh->hw_token |= halt; wmb (); } } @@ -419,7 +425,7 @@ halt: * it after fault cleanup, or recovering from silicon wrongly * overlaying the dummy qtd (which reduces DMA chatter). */ - if (stopped != 0 || qh->hw_qtd_next == EHCI_LIST_END) { + if (stopped != 0 || qh->hw_qtd_next == EHCI_LIST_END(ehci)) { switch (state) { case QH_STATE_IDLE: qh_refresh(ehci, qh); @@ -428,7 +434,7 @@ halt: /* should be rare for periodic transfers, * except maybe high bandwidth ... */ - if ((__constant_cpu_to_le32 (QH_SMASK) + if ((cpu_to_hc32(ehci, QH_SMASK) & qh->hw_info2) != 0) { intr_deschedule (ehci, qh); (void) qh_schedule (ehci, qh); @@ -502,8 +508,9 @@ qh_urb_transaction ( is_input = usb_pipein (urb->pipe); if (usb_pipecontrol (urb->pipe)) { /* SETUP pid */ - qtd_fill (qtd, urb->setup_dma, sizeof (struct usb_ctrlrequest), - token | (2 /* "setup" */ << 8), 8); + qtd_fill(ehci, qtd, urb->setup_dma, + sizeof (struct usb_ctrlrequest), + token | (2 /* "setup" */ << 8), 8); /* ... and always at least one more pid */ token ^= QTD_TOGGLE; @@ -512,7 +519,7 @@ qh_urb_transaction ( if (unlikely (!qtd)) goto cleanup; qtd->urb = urb; - qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); + qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma); list_add_tail (&qtd->qtd_list, head); /* for zero length DATA stages, STATUS is always IN */ @@ -539,7 +546,7 @@ qh_urb_transaction ( for (;;) { int this_qtd_len; - this_qtd_len = qtd_fill (qtd, buf, len, token, maxpacket); + this_qtd_len = qtd_fill(ehci, qtd, buf, len, token, maxpacket); len -= this_qtd_len; buf += this_qtd_len; if (is_input) @@ -557,7 +564,7 @@ qh_urb_transaction ( if (unlikely (!qtd)) goto cleanup; qtd->urb = urb; - qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); + qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma); list_add_tail (&qtd->qtd_list, head); } @@ -566,7 +573,7 @@ qh_urb_transaction ( */ if (likely ((urb->transfer_flags & URB_SHORT_NOT_OK) == 0 || usb_pipecontrol (urb->pipe))) - qtd->hw_alt_next = EHCI_LIST_END; + qtd->hw_alt_next = EHCI_LIST_END(ehci); /* * control requests may need a terminating data "status" ack; @@ -590,17 +597,17 @@ qh_urb_transaction ( if (unlikely (!qtd)) goto cleanup; qtd->urb = urb; - qtd_prev->hw_next = QTD_NEXT (qtd->qtd_dma); + qtd_prev->hw_next = QTD_NEXT(ehci, qtd->qtd_dma); list_add_tail (&qtd->qtd_list, head); /* never any data in such packets */ - qtd_fill (qtd, 0, 0, token, 0); + 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 |= __constant_cpu_to_le32 (QTD_IOC); + qtd->hw_token |= cpu_to_hc32(ehci, QTD_IOC); return head; cleanup: @@ -769,8 +776,8 @@ done: /* init as live, toggle clear, advance to dummy */ qh->qh_state = QH_STATE_IDLE; - qh->hw_info1 = cpu_to_le32 (info1); - qh->hw_info2 = cpu_to_le32 (info2); + qh->hw_info1 = cpu_to_hc32(ehci, info1); + qh->hw_info2 = cpu_to_hc32(ehci, info2); usb_settoggle (urb->dev, usb_pipeendpoint (urb->pipe), !is_input, 1); qh_refresh (ehci, qh); return qh; @@ -782,7 +789,7 @@ done: static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) { - __le32 dma = QH_NEXT (qh->qh_dma); + __hc32 dma = QH_NEXT(ehci, qh->qh_dma); struct ehci_qh *head; /* (re)start the async schedule? */ @@ -820,8 +827,6 @@ static void qh_link_async (struct ehci_hcd *ehci, struct ehci_qh *qh) /*-------------------------------------------------------------------------*/ -#define QH_ADDR_MASK __constant_cpu_to_le32(0x7f) - /* * For control/bulk/interrupt, return QH with these TDs appended. * Allocates and initializes the QH if necessary. @@ -837,6 +842,7 @@ static struct ehci_qh *qh_append_tds ( ) { struct ehci_qh *qh = NULL; + u32 qh_addr_mask = cpu_to_hc32(ehci, 0x7f); qh = (struct ehci_qh *) *ptr; if (unlikely (qh == NULL)) { @@ -858,7 +864,7 @@ static struct ehci_qh *qh_append_tds ( /* usb_reset_device() briefly reverts to address 0 */ if (usb_pipedevice (urb->pipe) == 0) - qh->hw_info1 &= ~QH_ADDR_MASK; + qh->hw_info1 &= ~qh_addr_mask; } /* just one way to queue requests: swap with the dummy qtd. @@ -867,7 +873,7 @@ static struct ehci_qh *qh_append_tds ( if (likely (qtd != NULL)) { struct ehci_qtd *dummy; dma_addr_t dma; - __le32 token; + __hc32 token; /* to avoid racing the HC, use the dummy td instead of * the first td of our list (becomes new dummy). both @@ -875,7 +881,7 @@ static struct ehci_qh *qh_append_tds ( * HC is allowed to fetch the old dummy (4.10.2). */ token = qtd->hw_token; - qtd->hw_token = HALT_BIT; + qtd->hw_token = HALT_BIT(ehci); wmb (); dummy = qh->dummy; @@ -887,14 +893,14 @@ static struct ehci_qh *qh_append_tds ( list_add (&dummy->qtd_list, qtd_list); __list_splice (qtd_list, qh->qtd_list.prev); - ehci_qtd_init (qtd, qtd->qtd_dma); + ehci_qtd_init(ehci, qtd, qtd->qtd_dma); qh->dummy = qtd; /* hc must see the new dummy at list end */ dma = qtd->qtd_dma; qtd = list_entry (qh->qtd_list.prev, struct ehci_qtd, qtd_list); - qtd->hw_next = QTD_NEXT (dma); + qtd->hw_next = QTD_NEXT(ehci, dma); /* let the hc process these next qtds */ wmb (); @@ -970,7 +976,7 @@ static void end_unlink_async (struct ehci_hcd *ehci) timer_action_done (ehci, TIMER_IAA_WATCHDOG); - // qh->hw_next = cpu_to_le32 (qh->qh_dma); + // qh->hw_next = cpu_to_hc32(qh->qh_dma); qh->qh_state = QH_STATE_IDLE; qh->qh_next.qh = NULL; qh_put (qh); // refcount from reclaim diff --git a/drivers/usb/host/ehci-sched.c b/drivers/usb/host/ehci-sched.c index 7b5ae7111f2..d4a8ace4967 100644 --- a/drivers/usb/host/ehci-sched.c +++ b/drivers/usb/host/ehci-sched.c @@ -44,9 +44,10 @@ static int ehci_get_frame (struct usb_hcd *hcd); * @tag: hardware tag for type of this record */ static union ehci_shadow * -periodic_next_shadow (union ehci_shadow *periodic, __le32 tag) +periodic_next_shadow(struct ehci_hcd *ehci, union ehci_shadow *periodic, + __hc32 tag) { - switch (tag) { + switch (hc32_to_cpu(ehci, tag)) { case Q_TYPE_QH: return &periodic->qh->qh_next; case Q_TYPE_FSTN: @@ -62,13 +63,14 @@ periodic_next_shadow (union ehci_shadow *periodic, __le32 tag) /* caller must hold ehci->lock */ static void periodic_unlink (struct ehci_hcd *ehci, unsigned frame, void *ptr) { - union ehci_shadow *prev_p = &ehci->pshadow [frame]; - __le32 *hw_p = &ehci->periodic [frame]; + union ehci_shadow *prev_p = &ehci->pshadow[frame]; + __hc32 *hw_p = &ehci->periodic[frame]; union ehci_shadow here = *prev_p; /* find predecessor of "ptr"; hw and shadow lists are in sync */ while (here.ptr && here.ptr != ptr) { - prev_p = periodic_next_shadow (prev_p, Q_NEXT_TYPE (*hw_p)); + prev_p = periodic_next_shadow(ehci, prev_p, + Q_NEXT_TYPE(ehci, *hw_p)); hw_p = here.hw_next; here = *prev_p; } @@ -79,7 +81,8 @@ static void periodic_unlink (struct ehci_hcd *ehci, unsigned frame, void *ptr) /* update shadow and hardware lists ... the old "next" pointers * from ptr may still be in use, the caller updates them. */ - *prev_p = *periodic_next_shadow (&here, Q_NEXT_TYPE (*hw_p)); + *prev_p = *periodic_next_shadow(ehci, &here, + Q_NEXT_TYPE(ehci, *hw_p)); *hw_p = *here.hw_next; } @@ -87,18 +90,19 @@ static void periodic_unlink (struct ehci_hcd *ehci, unsigned frame, void *ptr) static unsigned short periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe) { - __le32 *hw_p = &ehci->periodic [frame]; + __hc32 *hw_p = &ehci->periodic [frame]; union ehci_shadow *q = &ehci->pshadow [frame]; unsigned usecs = 0; while (q->ptr) { - switch (Q_NEXT_TYPE (*hw_p)) { + switch (hc32_to_cpu(ehci, Q_NEXT_TYPE(ehci, *hw_p))) { case Q_TYPE_QH: /* is it in the S-mask? */ - if (q->qh->hw_info2 & cpu_to_le32 (1 << uframe)) + if (q->qh->hw_info2 & cpu_to_hc32(ehci, 1 << uframe)) usecs += q->qh->usecs; /* ... or C-mask? */ - if (q->qh->hw_info2 & cpu_to_le32 (1 << (8 + uframe))) + if (q->qh->hw_info2 & cpu_to_hc32(ehci, + 1 << (8 + uframe))) usecs += q->qh->c_usecs; hw_p = &q->qh->hw_next; q = &q->qh->qh_next; @@ -108,7 +112,7 @@ periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe) /* for "save place" FSTNs, count the relevant INTR * bandwidth from the previous frame */ - if (q->fstn->hw_prev != EHCI_LIST_END) { + if (q->fstn->hw_prev != EHCI_LIST_END(ehci)) { ehci_dbg (ehci, "ignoring FSTN cost ...\n"); } hw_p = &q->fstn->hw_next; @@ -121,9 +125,10 @@ periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe) break; case Q_TYPE_SITD: /* is it in the S-mask? (count SPLIT, DATA) */ - if (q->sitd->hw_uframe & cpu_to_le32 (1 << uframe)) { + if (q->sitd->hw_uframe & cpu_to_hc32(ehci, + 1 << uframe)) { if (q->sitd->hw_fullspeed_ep & - __constant_cpu_to_le32 (1<<31)) + cpu_to_hc32(ehci, 1<<31)) usecs += q->sitd->stream->usecs; else /* worst case for OUT start-split */ usecs += HS_USECS_ISO (188); @@ -131,7 +136,7 @@ periodic_usecs (struct ehci_hcd *ehci, unsigned frame, unsigned uframe) /* ... C-mask? (count CSPLIT, DATA) */ if (q->sitd->hw_uframe & - cpu_to_le32 (1 << (8 + uframe))) { + cpu_to_hc32(ehci, 1 << (8 + uframe))) { /* worst case for IN complete-split */ usecs += q->sitd->stream->c_usecs; } @@ -173,9 +178,9 @@ static int same_tt (struct usb_device *dev1, struct usb_device *dev2) * will cause a transfer in "B-frame" uframe 0. "B-frames" lag * "H-frames" by 1 uframe. See the EHCI spec sec 4.5 and figure 4.7. */ -static inline unsigned char tt_start_uframe(struct ehci_hcd *ehci, __le32 mask) +static inline unsigned char tt_start_uframe(struct ehci_hcd *ehci, __hc32 mask) { - unsigned char smask = QH_SMASK & le32_to_cpu(mask); + unsigned char smask = QH_SMASK & hc32_to_cpu(ehci, mask); if (!smask) { ehci_err(ehci, "invalid empty smask!\n"); /* uframe 7 can't have bw so this will indicate failure */ @@ -217,14 +222,14 @@ periodic_tt_usecs ( unsigned short tt_usecs[8] ) { - __le32 *hw_p = &ehci->periodic [frame]; + __hc32 *hw_p = &ehci->periodic [frame]; union ehci_shadow *q = &ehci->pshadow [frame]; unsigned char uf; memset(tt_usecs, 0, 16); while (q->ptr) { - switch (Q_NEXT_TYPE(*hw_p)) { + switch (hc32_to_cpu(ehci, Q_NEXT_TYPE(ehci, *hw_p))) { case Q_TYPE_ITD: hw_p = &q->itd->hw_next; q = &q->itd->itd_next; @@ -247,8 +252,8 @@ periodic_tt_usecs ( continue; // case Q_TYPE_FSTN: default: - ehci_dbg(ehci, - "ignoring periodic frame %d FSTN\n", frame); + ehci_dbg(ehci, "ignoring periodic frame %d FSTN\n", + frame); hw_p = &q->fstn->hw_next; q = &q->fstn->fstn_next; } @@ -368,41 +373,42 @@ static int tt_no_collision ( */ for (; frame < ehci->periodic_size; frame += period) { union ehci_shadow here; - __le32 type; + __hc32 type; here = ehci->pshadow [frame]; - type = Q_NEXT_TYPE (ehci->periodic [frame]); + type = Q_NEXT_TYPE(ehci, ehci->periodic [frame]); while (here.ptr) { - switch (type) { + switch (hc32_to_cpu(ehci, type)) { case Q_TYPE_ITD: - type = Q_NEXT_TYPE (here.itd->hw_next); + type = Q_NEXT_TYPE(ehci, here.itd->hw_next); here = here.itd->itd_next; continue; case Q_TYPE_QH: if (same_tt (dev, here.qh->dev)) { u32 mask; - mask = le32_to_cpu (here.qh->hw_info2); + mask = hc32_to_cpu(ehci, + here.qh->hw_info2); /* "knows" no gap is needed */ mask |= mask >> 8; if (mask & uf_mask) break; } - type = Q_NEXT_TYPE (here.qh->hw_next); + type = Q_NEXT_TYPE(ehci, here.qh->hw_next); here = here.qh->qh_next; continue; case Q_TYPE_SITD: if (same_tt (dev, here.sitd->urb->dev)) { u16 mask; - mask = le32_to_cpu (here.sitd + mask = hc32_to_cpu(ehci, here.sitd ->hw_uframe); /* FIXME assumes no gap for IN! */ mask |= mask >> 8; if (mask & uf_mask) break; } - type = Q_NEXT_TYPE (here.sitd->hw_next); + type = Q_NEXT_TYPE(ehci, here.sitd->hw_next); here = here.sitd->sitd_next; continue; // case Q_TYPE_FSTN: @@ -473,6 +479,109 @@ static int disable_periodic (struct ehci_hcd *ehci) } /*-------------------------------------------------------------------------*/ +#ifdef CONFIG_CPU_FREQ + +static int safe_to_modify_i (struct ehci_hcd *ehci, struct ehci_qh *qh) +{ + int now; /* current (frame * 8) + uframe */ + int prev_start, next_start; /* uframes from/to split start */ + int start_uframe = ffs(le32_to_cpup (&qh->hw_info2) & QH_SMASK); + int end_uframe = fls((le32_to_cpup (&qh->hw_info2) & QH_CMASK) >> 8); + int split_duration = end_uframe - start_uframe; + + now = readl(&ehci->regs->frame_index) % (ehci->periodic_size << 3); + + next_start = ((1024 << 3) + (qh->start << 3) + start_uframe - now) + % (qh->period << 3); + prev_start = (qh->period << 3) - next_start; + + /* + * Make sure there will be at least one uframe when qh is safe. + */ + if ((qh->period << 3) <= (ehci->i_thresh + 2 + split_duration)) + /* never safe */ + return -EINVAL; + + /* + * Wait 1 uframe after transaction should have started, to make + * sure controller has time to write back overlay, so we can + * check QTD_STS_STS to see if transaction is in progress. + */ + if ((next_start > ehci->i_thresh) && (prev_start > 1)) + /* safe to set "i" bit if split isn't in progress */ + return (qh->hw_token & STATUS_BIT(ehci)) ? 0 : 1; + else + return 0; +} + +/* Set inactivate bit for all the split interrupt QHs. */ +static void qh_inactivate_split_intr_qhs (struct ehci_hcd *ehci) +{ + struct ehci_qh *qh; + int not_done, safe; + u32 inactivate = INACTIVATE_BIT(ehci); + u32 active = ACTIVE_BIT(ehci); + + do { + not_done = 0; + list_for_each_entry(qh, &ehci->split_intr_qhs, + split_intr_qhs) { + if (qh->hw_info1 & inactivate) + /* already off */ + continue; + /* + * To avoid setting "I" after the start split happens, + * don't set it if the QH might be cached in the + * controller. Some HCs (Broadcom/ServerWorks HT1000) + * will stop in the middle of a split transaction when + * the "I" bit is set. + */ + safe = safe_to_modify_i(ehci, qh); + if (safe == 0) { + not_done = 1; + } else if (safe > 0) { + qh->was_active = qh->hw_token & active; + qh->hw_info1 |= inactivate; + } + } + } while (not_done); + wmb(); +} + +static void qh_reactivate_split_intr_qhs (struct ehci_hcd *ehci) +{ + struct ehci_qh *qh; + u32 token; + int not_done, safe; + u32 inactivate = INACTIVATE_BIT(ehci); + u32 active = ACTIVE_BIT(ehci); + u32 halt = HALT_BIT(ehci); + + do { + not_done = 0; + list_for_each_entry(qh, &ehci->split_intr_qhs, split_intr_qhs) { + if (!(qh->hw_info1 & inactivate)) /* already on */ + continue; + /* + * Don't reactivate if cached, or controller might + * overwrite overlay after we modify it! + */ + safe = safe_to_modify_i(ehci, qh); + if (safe == 0) { + not_done = 1; + } else if (safe > 0) { + /* See EHCI 1.0 section 4.15.2.4. */ + token = qh->hw_token; + qh->hw_token = (token | halt) & ~active; + wmb(); + qh->hw_info1 &= ~inactivate; + wmb(); + qh->hw_token = (token & ~halt) | qh->was_active; + } + } + } while (not_done); +} +#endif /* periodic schedule slots have iso tds (normal or split) first, then a * sparse tree for active interrupt transfers. @@ -487,25 +596,36 @@ static int qh_link_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh) dev_dbg (&qh->dev->dev, "link qh%d-%04x/%p start %d [%d/%d us]\n", - period, le32_to_cpup (&qh->hw_info2) & (QH_CMASK | QH_SMASK), + period, hc32_to_cpup(ehci, &qh->hw_info2) & (QH_CMASK | QH_SMASK), qh, qh->start, qh->usecs, qh->c_usecs); +#ifdef CONFIG_CPU_FREQ + /* + * If low/full speed interrupt QHs are inactive (because of + * cpufreq changing processor speeds), start QH with I flag set-- + * it will automatically be cleared when cpufreq is done. + */ + if (ehci->cpufreq_changing) + if (!(qh->hw_info1 & (cpu_to_le32(1 << 13)))) + qh->hw_info1 |= INACTIVATE_BIT(ehci); +#endif + /* high bandwidth, or otherwise every microframe */ if (period == 0) period = 1; for (i = qh->start; i < ehci->periodic_size; i += period) { - union ehci_shadow *prev = &ehci->pshadow [i]; - __le32 *hw_p = &ehci->periodic [i]; + union ehci_shadow *prev = &ehci->pshadow[i]; + __hc32 *hw_p = &ehci->periodic[i]; union ehci_shadow here = *prev; - __le32 type = 0; + __hc32 type = 0; /* skip the iso nodes at list head */ while (here.ptr) { - type = Q_NEXT_TYPE (*hw_p); - if (type == Q_TYPE_QH) + type = Q_NEXT_TYPE(ehci, *hw_p); + if (type == cpu_to_hc32(ehci, Q_TYPE_QH)) break; - prev = periodic_next_shadow (prev, type); + prev = periodic_next_shadow(ehci, prev, type); hw_p = &here.qh->hw_next; here = *prev; } @@ -527,7 +647,7 @@ static int qh_link_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh) qh->hw_next = *hw_p; wmb (); prev->qh = qh; - *hw_p = QH_NEXT (qh->qh_dma); + *hw_p = QH_NEXT (ehci, qh->qh_dma); } } qh->qh_state = QH_STATE_LINKED; @@ -538,6 +658,12 @@ static int qh_link_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh) ? ((qh->usecs + qh->c_usecs) / qh->period) : (qh->usecs * 8); +#ifdef CONFIG_CPU_FREQ + /* add qh to list of low/full speed interrupt QHs, if applicable */ + if (!(qh->hw_info1 & (cpu_to_le32(1 << 13)))) { + list_add(&qh->split_intr_qhs, &ehci->split_intr_qhs); + } +#endif /* maybe enable periodic schedule processing */ if (!ehci->periodic_sched++) return enable_periodic (ehci); @@ -555,7 +681,14 @@ static void qh_unlink_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh) // and this qh is active in the current uframe // (and overlay token SplitXstate is false?) // THEN - // qh->hw_info1 |= __constant_cpu_to_le32 (1 << 7 /* "ignore" */); + // qh->hw_info1 |= __constant_cpu_to_hc32(1 << 7 /* "ignore" */); + +#ifdef CONFIG_CPU_FREQ + /* remove qh from list of low/full speed interrupt QHs */ + if (!(qh->hw_info1 & (cpu_to_le32(1 << 13)))) { + list_del_init(&qh->split_intr_qhs); + } +#endif /* high bandwidth, or otherwise part of every microframe */ if ((period = qh->period) == 0) @@ -572,7 +705,7 @@ static void qh_unlink_periodic (struct ehci_hcd *ehci, struct ehci_qh *qh) dev_dbg (&qh->dev->dev, "unlink qh%d-%04x/%p start %d [%d/%d us]\n", qh->period, - le32_to_cpup (&qh->hw_info2) & (QH_CMASK | QH_SMASK), + hc32_to_cpup(ehci, &qh->hw_info2) & (QH_CMASK | QH_SMASK), qh, qh->start, qh->usecs, qh->c_usecs); /* qh->qh_next still "live" to HC */ @@ -598,7 +731,7 @@ static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh) * active high speed queues may need bigger delays... */ if (list_empty (&qh->qtd_list) - || (__constant_cpu_to_le32 (QH_CMASK) + || (cpu_to_hc32(ehci, QH_CMASK) & qh->hw_info2) != 0) wait = 2; else @@ -606,7 +739,7 @@ static void intr_deschedule (struct ehci_hcd *ehci, struct ehci_qh *qh) udelay (wait); qh->qh_state = QH_STATE_IDLE; - qh->hw_next = EHCI_LIST_END; + qh->hw_next = EHCI_LIST_END(ehci); wmb (); } @@ -663,7 +796,7 @@ static int check_intr_schedule ( unsigned frame, unsigned uframe, const struct ehci_qh *qh, - __le32 *c_maskp + __hc32 *c_maskp ) { int retval = -ENOSPC; @@ -695,7 +828,7 @@ static int check_intr_schedule ( retval = 0; - *c_maskp = cpu_to_le32 (mask << 8); + *c_maskp = cpu_to_hc32(ehci, mask << 8); } #else /* Make sure this tt's buffer is also available for CSPLITs. @@ -706,7 +839,7 @@ static int check_intr_schedule ( * one smart pass... */ mask = 0x03 << (uframe + qh->gap_uf); - *c_maskp = cpu_to_le32 (mask << 8); + *c_maskp = cpu_to_hc32(ehci, mask << 8); mask |= 1 << uframe; if (tt_no_collision (ehci, qh->period, qh->dev, frame, mask)) { @@ -726,20 +859,20 @@ done: /* "first fit" scheduling policy used the first time through, * or when the previous schedule slot can't be re-used. */ -static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh) +static int qh_schedule(struct ehci_hcd *ehci, struct ehci_qh *qh) { int status; unsigned uframe; - __le32 c_mask; + __hc32 c_mask; unsigned frame; /* 0..(qh->period - 1), or NO_FRAME */ qh_refresh(ehci, qh); - qh->hw_next = EHCI_LIST_END; + qh->hw_next = EHCI_LIST_END(ehci); frame = qh->start; /* reuse the previous schedule slots, if we can */ if (frame < qh->period) { - uframe = ffs (le32_to_cpup (&qh->hw_info2) & QH_SMASK); + uframe = ffs(hc32_to_cpup(ehci, &qh->hw_info2) & QH_SMASK); status = check_intr_schedule (ehci, frame, --uframe, qh, &c_mask); } else { @@ -775,10 +908,10 @@ static int qh_schedule (struct ehci_hcd *ehci, struct ehci_qh *qh) qh->start = frame; /* reset S-frame and (maybe) C-frame masks */ - qh->hw_info2 &= __constant_cpu_to_le32(~(QH_CMASK | QH_SMASK)); + qh->hw_info2 &= cpu_to_hc32(ehci, ~(QH_CMASK | QH_SMASK)); qh->hw_info2 |= qh->period - ? cpu_to_le32 (1 << uframe) - : __constant_cpu_to_le32 (QH_SMASK); + ? cpu_to_hc32(ehci, 1 << uframe) + : cpu_to_hc32(ehci, QH_SMASK); qh->hw_info2 |= c_mask; } else ehci_dbg (ehci, "reused qh %p schedule\n", qh); @@ -808,7 +941,7 @@ static int intr_submit ( spin_lock_irqsave (&ehci->lock, flags); if (unlikely(!test_bit(HCD_FLAG_HW_ACCESSIBLE, - &ehci_to_hcd(ehci)->flags))) { + &ehci_to_hcd(ehci)->flags))) { status = -ESHUTDOWN; goto done; } @@ -898,9 +1031,9 @@ iso_stream_init ( buf1 |= maxp; maxp *= multi; - stream->buf0 = cpu_to_le32 ((epnum << 8) | dev->devnum); - stream->buf1 = cpu_to_le32 (buf1); - stream->buf2 = cpu_to_le32 (multi); + stream->buf0 = cpu_to_hc32(ehci, (epnum << 8) | dev->devnum); + stream->buf1 = cpu_to_hc32(ehci, buf1); + stream->buf2 = cpu_to_hc32(ehci, multi); /* usbfs wants to report the average usecs per frame tied up * when transfers on this endpoint are scheduled ... @@ -943,7 +1076,7 @@ iso_stream_init ( bandwidth /= 1 << (interval + 2); /* stream->splits gets created from raw_mask later */ - stream->address = cpu_to_le32 (addr); + stream->address = cpu_to_hc32(ehci, addr); } stream->bandwidth = bandwidth; @@ -1077,7 +1210,8 @@ iso_sched_alloc (unsigned packets, gfp_t mem_flags) } static inline void -itd_sched_init ( +itd_sched_init( + struct ehci_hcd *ehci, struct ehci_iso_sched *iso_sched, struct ehci_iso_stream *stream, struct urb *urb @@ -1107,7 +1241,7 @@ itd_sched_init ( && !(urb->transfer_flags & URB_NO_INTERRUPT)) trans |= EHCI_ITD_IOC; trans |= length << 16; - uframe->transaction = cpu_to_le32 (trans); + uframe->transaction = cpu_to_hc32(ehci, trans); /* might need to cross a buffer page within a uframe */ uframe->bufp = (buf & ~(u64)0x0fff); @@ -1149,7 +1283,7 @@ itd_urb_transaction ( if (unlikely (sched == NULL)) return -ENOMEM; - itd_sched_init (sched, stream, urb); + itd_sched_init(ehci, sched, stream, urb); if (urb->interval < 8) num_itds = 1 + (sched->span + 7) / 8; @@ -1167,7 +1301,7 @@ itd_urb_transaction ( /* prefer previously-allocated itds */ if (likely (!list_empty(&stream->free_list))) { itd = list_entry (stream->free_list.prev, - struct ehci_itd, itd_list); + struct ehci_itd, itd_list); list_del (&itd->itd_list); itd_dma = itd->itd_dma; } else @@ -1294,7 +1428,7 @@ sitd_slot_ok ( uframe += period_uframes; } while (uframe < mod); - stream->splits = cpu_to_le32(stream->raw_mask << (uframe & 7)); + stream->splits = cpu_to_hc32(ehci, stream->raw_mask << (uframe & 7)); return 1; } @@ -1415,12 +1549,13 @@ ready: /*-------------------------------------------------------------------------*/ static inline void -itd_init (struct ehci_iso_stream *stream, struct ehci_itd *itd) +itd_init(struct ehci_hcd *ehci, struct ehci_iso_stream *stream, + struct ehci_itd *itd) { int i; /* it's been recently zeroed */ - itd->hw_next = EHCI_LIST_END; + itd->hw_next = EHCI_LIST_END(ehci); itd->hw_bufp [0] = stream->buf0; itd->hw_bufp [1] = stream->buf1; itd->hw_bufp [2] = stream->buf2; @@ -1432,7 +1567,8 @@ itd_init (struct ehci_iso_stream *stream, struct ehci_itd *itd) } static inline void -itd_patch ( +itd_patch( + struct ehci_hcd *ehci, struct ehci_itd *itd, struct ehci_iso_sched *iso_sched, unsigned index, @@ -1447,17 +1583,18 @@ itd_patch ( uframe &= 0x07; itd->index [uframe] = index; - itd->hw_transaction [uframe] = uf->transaction; - itd->hw_transaction [uframe] |= cpu_to_le32 (pg << 12); - itd->hw_bufp [pg] |= cpu_to_le32 (uf->bufp & ~(u32)0); - itd->hw_bufp_hi [pg] |= cpu_to_le32 ((u32)(uf->bufp >> 32)); + itd->hw_transaction[uframe] = uf->transaction; + itd->hw_transaction[uframe] |= cpu_to_hc32(ehci, pg << 12); + itd->hw_bufp[pg] |= cpu_to_hc32(ehci, uf->bufp & ~(u32)0); + itd->hw_bufp_hi[pg] |= cpu_to_hc32(ehci, (u32)(uf->bufp >> 32)); /* iso_frame_desc[].offset must be strictly increasing */ if (unlikely (uf->cross)) { u64 bufp = uf->bufp + 4096; + itd->pg = ++pg; - itd->hw_bufp [pg] |= cpu_to_le32 (bufp & ~(u32)0); - itd->hw_bufp_hi [pg] |= cpu_to_le32 ((u32)(bufp >> 32)); + itd->hw_bufp[pg] |= cpu_to_hc32(ehci, bufp & ~(u32)0); + itd->hw_bufp_hi[pg] |= cpu_to_hc32(ehci, (u32)(bufp >> 32)); } } @@ -1470,7 +1607,7 @@ itd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_itd *itd) ehci->pshadow [frame].itd = itd; itd->frame = frame; wmb (); - ehci->periodic [frame] = cpu_to_le32 (itd->itd_dma) | Q_TYPE_ITD; + ehci->periodic[frame] = cpu_to_hc32(ehci, itd->itd_dma | Q_TYPE_ITD); } /* fit urb's itds into the selected schedule slot; activate as needed */ @@ -1515,14 +1652,14 @@ itd_link_urb ( list_move_tail (&itd->itd_list, &stream->td_list); itd->stream = iso_stream_get (stream); itd->urb = usb_get_urb (urb); - itd_init (stream, itd); + itd_init (ehci, stream, itd); } uframe = next_uframe & 0x07; frame = next_uframe >> 3; itd->usecs [uframe] = stream->usecs; - itd_patch (itd, iso_sched, packet, uframe); + itd_patch(ehci, itd, iso_sched, packet, uframe); next_uframe += stream->interval; stream->depth += stream->interval; @@ -1570,7 +1707,7 @@ itd_complete ( urb_index = itd->index[uframe]; desc = &urb->iso_frame_desc [urb_index]; - t = le32_to_cpup (&itd->hw_transaction [uframe]); + t = hc32_to_cpup(ehci, &itd->hw_transaction [uframe]); itd->hw_transaction [uframe] = 0; stream->depth -= stream->interval; @@ -1700,7 +1837,8 @@ done: */ static inline void -sitd_sched_init ( +sitd_sched_init( + struct ehci_hcd *ehci, struct ehci_iso_sched *iso_sched, struct ehci_iso_stream *stream, struct urb *urb @@ -1729,7 +1867,7 @@ sitd_sched_init ( && !(urb->transfer_flags & URB_NO_INTERRUPT)) trans |= SITD_IOC; trans |= length << 16; - packet->transaction = cpu_to_le32 (trans); + packet->transaction = cpu_to_hc32(ehci, trans); /* might need to cross a buffer page within a td */ packet->bufp = buf; @@ -1765,7 +1903,7 @@ sitd_urb_transaction ( if (iso_sched == NULL) return -ENOMEM; - sitd_sched_init (iso_sched, stream, urb); + sitd_sched_init(ehci, iso_sched, stream, urb); /* allocate/init sITDs */ spin_lock_irqsave (&ehci->lock, flags); @@ -1817,7 +1955,8 @@ sitd_urb_transaction ( /*-------------------------------------------------------------------------*/ static inline void -sitd_patch ( +sitd_patch( + struct ehci_hcd *ehci, struct ehci_iso_stream *stream, struct ehci_sitd *sitd, struct ehci_iso_sched *iso_sched, @@ -1827,20 +1966,20 @@ sitd_patch ( struct ehci_iso_packet *uf = &iso_sched->packet [index]; u64 bufp = uf->bufp; - sitd->hw_next = EHCI_LIST_END; + sitd->hw_next = EHCI_LIST_END(ehci); sitd->hw_fullspeed_ep = stream->address; sitd->hw_uframe = stream->splits; sitd->hw_results = uf->transaction; - sitd->hw_backpointer = EHCI_LIST_END; + sitd->hw_backpointer = EHCI_LIST_END(ehci); bufp = uf->bufp; - sitd->hw_buf [0] = cpu_to_le32 (bufp); - sitd->hw_buf_hi [0] = cpu_to_le32 (bufp >> 32); + sitd->hw_buf[0] = cpu_to_hc32(ehci, bufp); + sitd->hw_buf_hi[0] = cpu_to_hc32(ehci, bufp >> 32); - sitd->hw_buf [1] = cpu_to_le32 (uf->buf1); + sitd->hw_buf[1] = cpu_to_hc32(ehci, uf->buf1); if (uf->cross) bufp += 4096; - sitd->hw_buf_hi [1] = cpu_to_le32 (bufp >> 32); + sitd->hw_buf_hi[1] = cpu_to_hc32(ehci, bufp >> 32); sitd->index = index; } @@ -1853,7 +1992,7 @@ sitd_link (struct ehci_hcd *ehci, unsigned frame, struct ehci_sitd *sitd) ehci->pshadow [frame].sitd = sitd; sitd->frame = frame; wmb (); - ehci->periodic [frame] = cpu_to_le32 (sitd->sitd_dma) | Q_TYPE_SITD; + ehci->periodic[frame] = cpu_to_hc32(ehci, sitd->sitd_dma | Q_TYPE_SITD); } /* fit urb's sitds into the selected schedule slot; activate as needed */ @@ -1881,7 +2020,7 @@ sitd_link_urb ( urb->dev->devpath, stream->bEndpointAddress & 0x0f, (stream->bEndpointAddress & USB_DIR_IN) ? "in" : "out", (next_uframe >> 3) % ehci->periodic_size, - stream->interval, le32_to_cpu (stream->splits)); + stream->interval, hc32_to_cpu(ehci, stream->splits)); stream->start = jiffies; } ehci_to_hcd(ehci)->self.bandwidth_isoc_reqs++; @@ -1902,7 +2041,7 @@ sitd_link_urb ( sitd->stream = iso_stream_get (stream); sitd->urb = usb_get_urb (urb); - sitd_patch (stream, sitd, sched, packet); + sitd_patch(ehci, stream, sitd, sched, packet); sitd_link (ehci, (next_uframe >> 3) % ehci->periodic_size, sitd); @@ -1940,7 +2079,7 @@ sitd_complete ( urb_index = sitd->index; desc = &urb->iso_frame_desc [urb_index]; - t = le32_to_cpup (&sitd->hw_results); + t = hc32_to_cpup(ehci, &sitd->hw_results); /* report transfer status */ if (t & SITD_ERRS) { @@ -2095,7 +2234,7 @@ scan_periodic (struct ehci_hcd *ehci) for (;;) { union ehci_shadow q, *q_p; - __le32 type, *hw_p; + __hc32 type, *hw_p; unsigned uframes; /* don't scan past the live uframe */ @@ -2113,7 +2252,7 @@ restart: q_p = &ehci->pshadow [frame]; hw_p = &ehci->periodic [frame]; q.ptr = q_p->ptr; - type = Q_NEXT_TYPE (*hw_p); + type = Q_NEXT_TYPE(ehci, *hw_p); modified = 0; while (q.ptr != NULL) { @@ -2122,11 +2261,11 @@ restart: int live; live = HC_IS_RUNNING (ehci_to_hcd(ehci)->state); - switch (type) { + switch (hc32_to_cpu(ehci, type)) { case Q_TYPE_QH: /* handle any completions */ temp.qh = qh_get (q.qh); - type = Q_NEXT_TYPE (q.qh->hw_next); + type = Q_NEXT_TYPE(ehci, q.qh->hw_next); q = q.qh->qh_next; modified = qh_completions (ehci, temp.qh); if (unlikely (list_empty (&temp.qh->qtd_list))) @@ -2137,10 +2276,10 @@ restart: /* for "save place" FSTNs, look at QH entries * in the previous frame for completions. */ - if (q.fstn->hw_prev != EHCI_LIST_END) { + if (q.fstn->hw_prev != EHCI_LIST_END(ehci)) { dbg ("ignoring completions from FSTNs"); } - type = Q_NEXT_TYPE (q.fstn->hw_next); + type = Q_NEXT_TYPE(ehci, q.fstn->hw_next); q = q.fstn->fstn_next; break; case Q_TYPE_ITD: @@ -2148,11 +2287,12 @@ restart: rmb (); for (uf = live ? uframes : 8; uf < 8; uf++) { if (0 == (q.itd->hw_transaction [uf] - & ITD_ACTIVE)) + & ITD_ACTIVE(ehci))) continue; q_p = &q.itd->itd_next; hw_p = &q.itd->hw_next; - type = Q_NEXT_TYPE (q.itd->hw_next); + type = Q_NEXT_TYPE(ehci, + q.itd->hw_next); q = *q_p; break; } @@ -2164,23 +2304,24 @@ restart: */ *q_p = q.itd->itd_next; *hw_p = q.itd->hw_next; - type = Q_NEXT_TYPE (q.itd->hw_next); + type = Q_NEXT_TYPE(ehci, q.itd->hw_next); wmb(); modified = itd_complete (ehci, q.itd); q = *q_p; break; case Q_TYPE_SITD: - if ((q.sitd->hw_results & SITD_ACTIVE) + if ((q.sitd->hw_results & SITD_ACTIVE(ehci)) && live) { q_p = &q.sitd->sitd_next; hw_p = &q.sitd->hw_next; - type = Q_NEXT_TYPE (q.sitd->hw_next); + type = Q_NEXT_TYPE(ehci, + q.sitd->hw_next); q = *q_p; break; } *q_p = q.sitd->sitd_next; *hw_p = q.sitd->hw_next; - type = Q_NEXT_TYPE (q.sitd->hw_next); + type = Q_NEXT_TYPE(ehci, q.sitd->hw_next); wmb(); modified = sitd_complete (ehci, q.sitd); q = *q_p; diff --git a/drivers/usb/host/ehci.h b/drivers/usb/host/ehci.h index 46fa57a520d..2c68a04230c 100644 --- a/drivers/usb/host/ehci.h +++ b/drivers/usb/host/ehci.h @@ -21,6 +21,22 @@ /* definitions used for the EHCI driver */ +/* + * __hc32 and __hc16 are "Host Controller" types, they may be equivalent to + * __leXX (normally) or __beXX (given EHCI_BIG_ENDIAN_DESC), depending on + * the host controller implementation. + * + * To facilitate the strongest possible byte-order checking from "sparse" + * and so on, we use __leXX unless that's not practical. + */ +#ifdef CONFIG_USB_EHCI_BIG_ENDIAN_DESC +typedef __u32 __bitwise __hc32; +typedef __u16 __bitwise __hc16; +#else +#define __hc32 __le32 +#define __hc16 __le16 +#endif + /* statistics can be kept for for tuning/monitoring */ struct ehci_stats { /* irq usage */ @@ -55,6 +71,12 @@ struct ehci_hcd { /* one per controller */ __u32 hcs_params; /* cached register copy */ spinlock_t lock; +#ifdef CONFIG_CPU_FREQ + struct notifier_block cpufreq_transition; + int cpufreq_changing; + struct list_head split_intr_qhs; +#endif + /* async schedule support */ struct ehci_qh *async; struct ehci_qh *reclaim; @@ -64,7 +86,7 @@ struct ehci_hcd { /* one per controller */ /* periodic schedule support */ #define DEFAULT_I_TDPS 1024 /* some HCs can do less */ unsigned periodic_size; - __le32 *periodic; /* hw periodic table */ + __hc32 *periodic; /* hw periodic table */ dma_addr_t periodic_dma; unsigned i_thresh; /* uframes HC might cache */ @@ -74,11 +96,14 @@ struct ehci_hcd { /* one per controller */ /* per root hub port */ unsigned long reset_done [EHCI_MAX_ROOT_PORTS]; + /* bit vectors (one bit per port) */ unsigned long bus_suspended; /* which ports were already suspended at the start of a bus suspend */ unsigned long companion_ports; /* which ports are dedicated to the companion controller */ + unsigned long owned_ports; /* which ports are + owned by the companion during a bus suspend */ /* per-HC memory pools (could be per-bus, but ...) */ struct dma_pool *qh_pool; /* qh per active urb */ @@ -97,6 +122,7 @@ struct ehci_hcd { /* one per controller */ unsigned no_selective_suspend:1; unsigned has_fsl_port_bug:1; /* FreeScale */ unsigned big_endian_mmio:1; + unsigned big_endian_desc:1; u8 sbrn; /* packed release number */ @@ -276,6 +302,12 @@ struct ehci_regs { #define PORT_RWC_BITS (PORT_CSC | PORT_PEC | PORT_OCC) } __attribute__ ((packed)); +#define USBMODE 0x68 /* USB Device mode */ +#define USBMODE_SDIS (1<<3) /* Stream disable */ +#define USBMODE_BE (1<<2) /* BE/LE endianness select */ +#define USBMODE_CM_HC (3<<0) /* host controller mode */ +#define USBMODE_CM_IDLE (0<<0) /* idle state */ + /* Appendix C, Debug port ... intended for use with special "debug devices" * that can help if there's no serial console. (nonstandard enumeration.) */ @@ -303,7 +335,7 @@ struct ehci_dbg_port { /*-------------------------------------------------------------------------*/ -#define QTD_NEXT(dma) cpu_to_le32((u32)dma) +#define QTD_NEXT(ehci, dma) cpu_to_hc32(ehci, (u32)dma) /* * EHCI Specification 0.95 Section 3.5 @@ -315,9 +347,9 @@ struct ehci_dbg_port { */ struct ehci_qtd { /* first part defined by EHCI spec */ - __le32 hw_next; /* see EHCI 3.5.1 */ - __le32 hw_alt_next; /* see EHCI 3.5.2 */ - __le32 hw_token; /* see EHCI 3.5.3 */ + __hc32 hw_next; /* see EHCI 3.5.1 */ + __hc32 hw_alt_next; /* see EHCI 3.5.2 */ + __hc32 hw_token; /* see EHCI 3.5.3 */ #define QTD_TOGGLE (1 << 31) /* data toggle */ #define QTD_LENGTH(tok) (((tok)>>16) & 0x7fff) #define QTD_IOC (1 << 15) /* interrupt on complete */ @@ -331,8 +363,13 @@ struct ehci_qtd { #define QTD_STS_MMF (1 << 2) /* incomplete split transaction */ #define QTD_STS_STS (1 << 1) /* split transaction state */ #define QTD_STS_PING (1 << 0) /* issue PING? */ - __le32 hw_buf [5]; /* see EHCI 3.5.4 */ - __le32 hw_buf_hi [5]; /* Appendix B */ + +#define ACTIVE_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_ACTIVE) +#define HALT_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_HALT) +#define STATUS_BIT(ehci) cpu_to_hc32(ehci, QTD_STS_STS) + + __hc32 hw_buf [5]; /* see EHCI 3.5.4 */ + __hc32 hw_buf_hi [5]; /* Appendix B */ /* the rest is HCD-private */ dma_addr_t qtd_dma; /* qtd address */ @@ -342,26 +379,33 @@ struct ehci_qtd { } __attribute__ ((aligned (32))); /* mask NakCnt+T in qh->hw_alt_next */ -#define QTD_MASK __constant_cpu_to_le32 (~0x1f) +#define QTD_MASK(ehci) cpu_to_hc32 (ehci, ~0x1f) #define IS_SHORT_READ(token) (QTD_LENGTH (token) != 0 && QTD_PID (token) == 1) /*-------------------------------------------------------------------------*/ /* type tag from {qh,itd,sitd,fstn}->hw_next */ -#define Q_NEXT_TYPE(dma) ((dma) & __constant_cpu_to_le32 (3 << 1)) +#define Q_NEXT_TYPE(ehci,dma) ((dma) & cpu_to_hc32(ehci, 3 << 1)) +/* + * Now the following defines are not converted using the + * __constant_cpu_to_le32() macro anymore, since we have to support + * "dynamic" switching between be and le support, so that the driver + * can be used on one system with SoC EHCI controller using big-endian + * descriptors as well as a normal little-endian PCI EHCI controller. + */ /* values for that type tag */ -#define Q_TYPE_ITD __constant_cpu_to_le32 (0 << 1) -#define Q_TYPE_QH __constant_cpu_to_le32 (1 << 1) -#define Q_TYPE_SITD __constant_cpu_to_le32 (2 << 1) -#define Q_TYPE_FSTN __constant_cpu_to_le32 (3 << 1) +#define Q_TYPE_ITD (0 << 1) +#define Q_TYPE_QH (1 << 1) +#define Q_TYPE_SITD (2 << 1) +#define Q_TYPE_FSTN (3 << 1) /* next async queue entry, or pointer to interrupt/periodic QH */ -#define QH_NEXT(dma) (cpu_to_le32(((u32)dma)&~0x01f)|Q_TYPE_QH) +#define QH_NEXT(ehci,dma) (cpu_to_hc32(ehci, (((u32)dma)&~0x01f)|Q_TYPE_QH)) /* for periodic/async schedules and qtd lists, mark end of list */ -#define EHCI_LIST_END __constant_cpu_to_le32(1) /* "null pointer" to hw */ +#define EHCI_LIST_END(ehci) cpu_to_hc32(ehci, 1) /* "null pointer" to hw */ /* * Entries in periodic shadow table are pointers to one of four kinds @@ -376,7 +420,7 @@ union ehci_shadow { struct ehci_itd *itd; /* Q_TYPE_ITD */ struct ehci_sitd *sitd; /* Q_TYPE_SITD */ struct ehci_fstn *fstn; /* Q_TYPE_FSTN */ - __le32 *hw_next; /* (all types) */ + __hc32 *hw_next; /* (all types) */ void *ptr; }; @@ -392,23 +436,27 @@ union ehci_shadow { struct ehci_qh { /* first part defined by EHCI spec */ - __le32 hw_next; /* see EHCI 3.6.1 */ - __le32 hw_info1; /* see EHCI 3.6.2 */ + __hc32 hw_next; /* see EHCI 3.6.1 */ + __hc32 hw_info1; /* see EHCI 3.6.2 */ #define QH_HEAD 0x00008000 - __le32 hw_info2; /* see EHCI 3.6.2 */ +#define QH_INACTIVATE 0x00000080 + +#define INACTIVATE_BIT(ehci) cpu_to_hc32(ehci, QH_INACTIVATE) + + __hc32 hw_info2; /* see EHCI 3.6.2 */ #define QH_SMASK 0x000000ff #define QH_CMASK 0x0000ff00 #define QH_HUBADDR 0x007f0000 #define QH_HUBPORT 0x3f800000 #define QH_MULT 0xc0000000 - __le32 hw_current; /* qtd list - see EHCI 3.6.4 */ + __hc32 hw_current; /* qtd list - see EHCI 3.6.4 */ /* qtd overlay (hardware parts of a struct ehci_qtd) */ - __le32 hw_qtd_next; - __le32 hw_alt_next; - __le32 hw_token; - __le32 hw_buf [5]; - __le32 hw_buf_hi [5]; + __hc32 hw_qtd_next; + __hc32 hw_alt_next; + __hc32 hw_token; + __hc32 hw_buf [5]; + __hc32 hw_buf_hi [5]; /* the rest is HCD-private */ dma_addr_t qh_dma; /* address of qh */ @@ -418,7 +466,14 @@ struct ehci_qh { struct ehci_qh *reclaim; /* next to reclaim */ struct ehci_hcd *ehci; - struct kref kref; + + /* + * Do NOT use atomic operations for QH refcounting. On some CPUs + * (PPC7448 for example), atomic operations cannot be performed on + * memory that is cache-inhibited (i.e. being used for DMA). + * Spinlocks are used to protect all QH fields. + */ + u32 refcount; unsigned stamp; u8 qh_state; @@ -437,6 +492,10 @@ struct ehci_qh { unsigned short start; /* where polling starts */ #define NO_FRAME ((unsigned short)~0) /* pick new start */ struct usb_device *dev; /* access to TT */ +#ifdef CONFIG_CPU_FREQ + struct list_head split_intr_qhs; /* list of split qhs */ + __le32 was_active; /* active bit before "i" set */ +#endif } __attribute__ ((aligned (32))); /*-------------------------------------------------------------------------*/ @@ -445,7 +504,7 @@ struct ehci_qh { struct ehci_iso_packet { /* These will be copied to iTD when scheduling */ u64 bufp; /* itd->hw_bufp{,_hi}[pg] |= */ - __le32 transaction; /* itd->hw_transaction[i] |= */ + __hc32 transaction; /* itd->hw_transaction[i] |= */ u8 cross; /* buf crosses pages */ /* for full speed OUT splits */ u32 buf1; @@ -467,8 +526,8 @@ struct ehci_iso_sched { */ struct ehci_iso_stream { /* first two fields match QH, but info1 == 0 */ - __le32 hw_next; - __le32 hw_info1; + __hc32 hw_next; + __hc32 hw_info1; u32 refcount; u8 bEndpointAddress; @@ -483,7 +542,7 @@ struct ehci_iso_stream { unsigned long start; /* jiffies */ unsigned long rescheduled; int next_uframe; - __le32 splits; + __hc32 splits; /* the rest is derived from the endpoint descriptor, * trusting urb->interval == f(epdesc->bInterval) and @@ -497,12 +556,12 @@ struct ehci_iso_stream { unsigned bandwidth; /* This is used to initialize iTD's hw_bufp fields */ - __le32 buf0; - __le32 buf1; - __le32 buf2; + __hc32 buf0; + __hc32 buf1; + __hc32 buf2; /* this is used to initialize sITD's tt info */ - __le32 address; + __hc32 address; }; /*-------------------------------------------------------------------------*/ @@ -515,8 +574,8 @@ struct ehci_iso_stream { */ struct ehci_itd { /* first part defined by EHCI spec */ - __le32 hw_next; /* see EHCI 3.3.1 */ - __le32 hw_transaction [8]; /* see EHCI 3.3.2 */ + __hc32 hw_next; /* see EHCI 3.3.1 */ + __hc32 hw_transaction [8]; /* see EHCI 3.3.2 */ #define EHCI_ISOC_ACTIVE (1<<31) /* activate transfer this slot */ #define EHCI_ISOC_BUF_ERR (1<<30) /* Data buffer error */ #define EHCI_ISOC_BABBLE (1<<29) /* babble detected */ @@ -524,10 +583,10 @@ struct ehci_itd { #define EHCI_ITD_LENGTH(tok) (((tok)>>16) & 0x0fff) #define EHCI_ITD_IOC (1 << 15) /* interrupt on complete */ -#define ITD_ACTIVE __constant_cpu_to_le32(EHCI_ISOC_ACTIVE) +#define ITD_ACTIVE(ehci) cpu_to_hc32(ehci, EHCI_ISOC_ACTIVE) - __le32 hw_bufp [7]; /* see EHCI 3.3.3 */ - __le32 hw_bufp_hi [7]; /* Appendix B */ + __hc32 hw_bufp [7]; /* see EHCI 3.3.3 */ + __hc32 hw_bufp_hi [7]; /* Appendix B */ /* the rest is HCD-private */ dma_addr_t itd_dma; /* for this itd */ @@ -554,11 +613,11 @@ struct ehci_itd { */ struct ehci_sitd { /* first part defined by EHCI spec */ - __le32 hw_next; + __hc32 hw_next; /* uses bit field macros above - see EHCI 0.95 Table 3-8 */ - __le32 hw_fullspeed_ep; /* EHCI table 3-9 */ - __le32 hw_uframe; /* EHCI table 3-10 */ - __le32 hw_results; /* EHCI table 3-11 */ + __hc32 hw_fullspeed_ep; /* EHCI table 3-9 */ + __hc32 hw_uframe; /* EHCI table 3-10 */ + __hc32 hw_results; /* EHCI table 3-11 */ #define SITD_IOC (1 << 31) /* interrupt on completion */ #define SITD_PAGE (1 << 30) /* buffer 0/1 */ #define SITD_LENGTH(x) (0x3ff & ((x)>>16)) @@ -570,11 +629,11 @@ struct ehci_sitd { #define SITD_STS_MMF (1 << 2) /* incomplete split transaction */ #define SITD_STS_STS (1 << 1) /* split transaction state */ -#define SITD_ACTIVE __constant_cpu_to_le32(SITD_STS_ACTIVE) +#define SITD_ACTIVE(ehci) cpu_to_hc32(ehci, SITD_STS_ACTIVE) - __le32 hw_buf [2]; /* EHCI table 3-12 */ - __le32 hw_backpointer; /* EHCI table 3-13 */ - __le32 hw_buf_hi [2]; /* Appendix B */ + __hc32 hw_buf [2]; /* EHCI table 3-12 */ + __hc32 hw_backpointer; /* EHCI table 3-13 */ + __hc32 hw_buf_hi [2]; /* Appendix B */ /* the rest is HCD-private */ dma_addr_t sitd_dma; @@ -599,8 +658,8 @@ struct ehci_sitd { * it hits a "restore" FSTN; then it returns to finish other uframe 0/1 work. */ struct ehci_fstn { - __le32 hw_next; /* any periodic q entry */ - __le32 hw_prev; /* qh or EHCI_LIST_END */ + __hc32 hw_next; /* any periodic q entry */ + __hc32 hw_prev; /* qh or EHCI_LIST_END */ /* the rest is HCD-private */ dma_addr_t fstn_dma; @@ -672,8 +731,21 @@ ehci_port_speed(struct ehci_hcd *ehci, unsigned int portsc) #define ehci_big_endian_mmio(e) 0 #endif -static inline unsigned int ehci_readl (const struct ehci_hcd *ehci, - __u32 __iomem * regs) +/* + * Big-endian read/write functions are arch-specific. + * Other arches can be added if/when they're needed. + * + * REVISIT: arch/powerpc now has readl/writel_be, so the + * definition below can die once the 4xx support is + * finally ported over. + */ +#if defined(CONFIG_PPC) +#define readl_be(addr) in_be32((__force unsigned *)addr) +#define writel_be(val, addr) out_be32((__force unsigned *)addr, val) +#endif + +static inline unsigned int ehci_readl(const struct ehci_hcd *ehci, + __u32 __iomem * regs) { #ifdef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO return ehci_big_endian_mmio(ehci) ? @@ -684,8 +756,8 @@ static inline unsigned int ehci_readl (const struct ehci_hcd *ehci, #endif } -static inline void ehci_writel (const struct ehci_hcd *ehci, - const unsigned int val, __u32 __iomem *regs) +static inline void ehci_writel(const struct ehci_hcd *ehci, + const unsigned int val, __u32 __iomem *regs) { #ifdef CONFIG_USB_EHCI_BIG_ENDIAN_MMIO ehci_big_endian_mmio(ehci) ? @@ -698,6 +770,62 @@ static inline void ehci_writel (const struct ehci_hcd *ehci, /*-------------------------------------------------------------------------*/ +/* + * The AMCC 440EPx not only implements its EHCI registers in big-endian + * format, but also its DMA data structures (descriptors). + * + * EHCI controllers accessed through PCI work normally (little-endian + * everywhere), so we won't bother supporting a BE-only mode for now. + */ +#ifdef CONFIG_USB_EHCI_BIG_ENDIAN_DESC +#define ehci_big_endian_desc(e) ((e)->big_endian_desc) + +/* cpu to ehci */ +static inline __hc32 cpu_to_hc32 (const struct ehci_hcd *ehci, const u32 x) +{ + return ehci_big_endian_desc(ehci) + ? (__force __hc32)cpu_to_be32(x) + : (__force __hc32)cpu_to_le32(x); +} + +/* ehci to cpu */ +static inline u32 hc32_to_cpu (const struct ehci_hcd *ehci, const __hc32 x) +{ + return ehci_big_endian_desc(ehci) + ? be32_to_cpu((__force __be32)x) + : le32_to_cpu((__force __le32)x); +} + +static inline u32 hc32_to_cpup (const struct ehci_hcd *ehci, const __hc32 *x) +{ + return ehci_big_endian_desc(ehci) + ? be32_to_cpup((__force __be32 *)x) + : le32_to_cpup((__force __le32 *)x); +} + +#else + +/* cpu to ehci */ +static inline __hc32 cpu_to_hc32 (const struct ehci_hcd *ehci, const u32 x) +{ + return cpu_to_le32(x); +} + +/* ehci to cpu */ +static inline u32 hc32_to_cpu (const struct ehci_hcd *ehci, const __hc32 x) +{ + return le32_to_cpu(x); +} + +static inline u32 hc32_to_cpup (const struct ehci_hcd *ehci, const __hc32 *x) +{ + return le32_to_cpup(x); +} + +#endif + +/*-------------------------------------------------------------------------*/ + #ifndef DEBUG #define STUB_DEBUG_FILES #endif /* DEBUG */ diff --git a/drivers/usb/host/ohci-dbg.c b/drivers/usb/host/ohci-dbg.c index 273d5ddb72b..6f9e43e9a6c 100644 --- a/drivers/usb/host/ohci-dbg.c +++ b/drivers/usb/host/ohci-dbg.c @@ -23,7 +23,7 @@ /* debug| print the main components of an URB * small: 0) header + data packets 1) just header */ -static void __attribute__((unused)) +static void __maybe_unused urb_print (struct urb * urb, char * str, int small) { unsigned int pipe= urb->pipe; @@ -338,7 +338,7 @@ static void ohci_dump_td (const struct ohci_hcd *ohci, const char *label, } /* caller MUST own hcd spinlock if verbose is set! */ -static void __attribute__((unused)) +static void __maybe_unused ohci_dump_ed (const struct ohci_hcd *ohci, const char *label, const struct ed *ed, int verbose) { diff --git a/drivers/usb/host/ohci-hcd.c b/drivers/usb/host/ohci-hcd.c index a66637e725f..2038125b7f8 100644 --- a/drivers/usb/host/ohci-hcd.c +++ b/drivers/usb/host/ohci-hcd.c @@ -35,15 +35,13 @@ #include <linux/dma-mapping.h> #include <linux/dmapool.h> #include <linux/reboot.h> +#include <linux/workqueue.h> #include <asm/io.h> #include <asm/irq.h> #include <asm/system.h> #include <asm/unaligned.h> #include <asm/byteorder.h> -#ifdef CONFIG_PPC_PS3 -#include <asm/firmware.h> -#endif #include "../core/hcd.h" @@ -82,6 +80,8 @@ static const char hcd_name [] = "ohci_hcd"; static void ohci_dump (struct ohci_hcd *ohci, int verbose); static int ohci_init (struct ohci_hcd *ohci); static void ohci_stop (struct usb_hcd *hcd); +static int ohci_restart (struct ohci_hcd *ohci); +static void ohci_quirk_nec_worker (struct work_struct *work); #include "ohci-hub.c" #include "ohci-dbg.c" @@ -510,15 +510,7 @@ static int ohci_run (struct ohci_hcd *ohci) // flush the writes (void) ohci_readl (ohci, &ohci->regs->control); msleep(temp); - temp = roothub_a (ohci); - if (!(temp & RH_A_NPS)) { - /* power down each port */ - for (temp = 0; temp < ohci->num_ports; temp++) - ohci_writel (ohci, RH_PS_LSDA, - &ohci->regs->roothub.portstatus [temp]); - } - // flush those writes - (void) ohci_readl (ohci, &ohci->regs->control); + memset (ohci->hcca, 0, sizeof (struct ohci_hcca)); /* 2msec timelimit here means no irqs/preempt */ @@ -659,9 +651,20 @@ static irqreturn_t ohci_irq (struct usb_hcd *hcd) } if (ints & OHCI_INTR_UE) { - disable (ohci); - ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n"); // e.g. due to PCI Master/Target Abort + if (ohci->flags & OHCI_QUIRK_NEC) { + /* Workaround for a silicon bug in some NEC chips used + * in Apple's PowerBooks. Adapted from Darwin code. + */ + ohci_err (ohci, "OHCI Unrecoverable Error, scheduling NEC chip restart\n"); + + ohci_writel (ohci, OHCI_INTR_UE, ®s->intrdisable); + + schedule_work (&ohci->nec_work); + } else { + disable (ohci); + ohci_err (ohci, "OHCI Unrecoverable Error, disabled\n"); + } ohci_dump (ohci, 1); ohci_usb_reset (ohci); @@ -763,23 +766,16 @@ static void ohci_stop (struct usb_hcd *hcd) /*-------------------------------------------------------------------------*/ /* must not be called from interrupt context */ - -#ifdef CONFIG_PM - static int ohci_restart (struct ohci_hcd *ohci) { int temp; int i; struct urb_priv *priv; - /* mark any devices gone, so they do nothing till khubd disconnects. - * recycle any "live" eds/tds (and urbs) right away. - * later, khubd disconnect processing will recycle the other state, - * (either as disconnect/reconnect, or maybe someday as a reset). - */ spin_lock_irq(&ohci->lock); disable (ohci); - usb_root_hub_lost_power(ohci_to_hcd(ohci)->self.root_hub); + + /* Recycle any "live" eds/tds (and urbs). */ if (!list_empty (&ohci->pending)) ohci_dbg(ohci, "abort schedule...\n"); list_for_each_entry (priv, &ohci->pending, pending) { @@ -826,20 +822,31 @@ static int ohci_restart (struct ohci_hcd *ohci) if ((temp = ohci_run (ohci)) < 0) { ohci_err (ohci, "can't restart, %d\n", temp); return temp; - } else { - /* here we "know" root ports should always stay powered, - * and that if we try to turn them back on the root hub - * will respond to CSC processing. - */ - i = ohci->num_ports; - while (i--) - ohci_writel (ohci, RH_PS_PSS, - &ohci->regs->roothub.portstatus [i]); - ohci_dbg (ohci, "restart complete\n"); } + ohci_dbg(ohci, "restart complete\n"); return 0; } -#endif + +/*-------------------------------------------------------------------------*/ + +/* NEC workaround */ +static void ohci_quirk_nec_worker(struct work_struct *work) +{ + struct ohci_hcd *ohci = container_of(work, struct ohci_hcd, nec_work); + int status; + + status = ohci_init(ohci); + if (status != 0) { + ohci_err(ohci, "Restarting NEC controller failed " + "in ohci_init, %d\n", status); + return; + } + + status = ohci_restart(ohci); + if (status != 0) + ohci_err(ohci, "Restarting NEC controller failed " + "in ohci_restart, %d\n", status); +} /*-------------------------------------------------------------------------*/ @@ -917,7 +924,7 @@ MODULE_LICENSE ("GPL"); #ifdef CONFIG_PPC_PS3 #include "ohci-ps3.c" -#define PS3_SYSTEM_BUS_DRIVER ps3_ohci_sb_driver +#define PS3_SYSTEM_BUS_DRIVER ps3_ohci_driver #endif #if !defined(PCI_DRIVER) && \ @@ -940,12 +947,9 @@ static int __init ohci_hcd_mod_init(void) sizeof (struct ed), sizeof (struct td)); #ifdef PS3_SYSTEM_BUS_DRIVER - if (firmware_has_feature(FW_FEATURE_PS3_LV1)) { - retval = ps3_system_bus_driver_register( - &PS3_SYSTEM_BUS_DRIVER); - if (retval < 0) - goto error_ps3; - } + retval = ps3_ohci_driver_register(&PS3_SYSTEM_BUS_DRIVER); + if (retval < 0) + goto error_ps3; #endif #ifdef PLATFORM_DRIVER @@ -991,8 +995,7 @@ static int __init ohci_hcd_mod_init(void) error_platform: #endif #ifdef PS3_SYSTEM_BUS_DRIVER - if (firmware_has_feature(FW_FEATURE_PS3_LV1)) - ps3_system_bus_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); + ps3_ohci_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); error_ps3: #endif return retval; @@ -1014,8 +1017,7 @@ static void __exit ohci_hcd_mod_exit(void) platform_driver_unregister(&PLATFORM_DRIVER); #endif #ifdef PS3_SYSTEM_BUS_DRIVER - if (firmware_has_feature(FW_FEATURE_PS3_LV1)) - ps3_system_bus_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); + ps3_ohci_driver_unregister(&PS3_SYSTEM_BUS_DRIVER); #endif } module_exit(ohci_hcd_mod_exit); diff --git a/drivers/usb/host/ohci-hub.c b/drivers/usb/host/ohci-hub.c index bb9cc595219..48e4b11f4d3 100644 --- a/drivers/usb/host/ohci-hub.c +++ b/drivers/usb/host/ohci-hub.c @@ -55,8 +55,6 @@ static void dl_done_list (struct ohci_hcd *); static void finish_unlinks (struct ohci_hcd *, u16); #ifdef CONFIG_PM -static int ohci_restart(struct ohci_hcd *ohci); - static int ohci_rh_suspend (struct ohci_hcd *ohci, int autostop) __releases(ohci->lock) __acquires(ohci->lock) @@ -191,6 +189,9 @@ __acquires(ohci->lock) spin_unlock_irq (&ohci->lock); (void) ohci_init (ohci); status = ohci_restart (ohci); + + usb_root_hub_lost_power(hcd->self.root_hub); + spin_lock_irq (&ohci->lock); } return status; diff --git a/drivers/usb/host/ohci-mem.c b/drivers/usb/host/ohci-mem.c index 2f20d3dc895..450c7b460c5 100644 --- a/drivers/usb/host/ohci-mem.c +++ b/drivers/usb/host/ohci-mem.c @@ -28,6 +28,7 @@ static void ohci_hcd_init (struct ohci_hcd *ohci) ohci->next_statechange = jiffies; spin_lock_init (&ohci->lock); INIT_LIST_HEAD (&ohci->pending); + INIT_WORK (&ohci->nec_work, ohci_quirk_nec_worker); } /*-------------------------------------------------------------------------*/ diff --git a/drivers/usb/host/ohci-pci.c b/drivers/usb/host/ohci-pci.c index ca62cb58322..a5e2eb85d07 100644 --- a/drivers/usb/host/ohci-pci.c +++ b/drivers/usb/host/ohci-pci.c @@ -111,6 +111,18 @@ static int ohci_quirk_toshiba_scc(struct usb_hcd *hcd) #endif } +/* Check for NEC chip and apply quirk for allegedly lost interrupts. + */ +static int ohci_quirk_nec(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci (hcd); + + ohci->flags |= OHCI_QUIRK_NEC; + ohci_dbg (ohci, "enabled NEC chipset lost interrupt quirk\n"); + + return 0; +} + /* List of quirks for OHCI */ static const struct pci_device_id ohci_pci_quirks[] = { { @@ -134,6 +146,10 @@ static const struct pci_device_id ohci_pci_quirks[] = { .driver_data = (unsigned long)ohci_quirk_toshiba_scc, }, { + PCI_DEVICE(PCI_VENDOR_ID_NEC, PCI_DEVICE_ID_NEC_USB), + .driver_data = (unsigned long)ohci_quirk_nec, + }, + { /* Toshiba portege 4000 */ .vendor = PCI_VENDOR_ID_AL, .device = 0x5237, @@ -202,6 +218,42 @@ static int __devinit ohci_pci_start (struct usb_hcd *hcd) return ret; } +#if defined(CONFIG_USB_PERSIST) && (defined(CONFIG_USB_EHCI_HCD) || \ + defined(CONFIG_USB_EHCI_HCD_MODULE)) + +/* Following a power loss, we must prepare to regain control of the ports + * we used to own. This means turning on the port power before ehci-hcd + * tries to switch ownership. + * + * This isn't a 100% perfect solution. On most systems the OHCI controllers + * lie at lower PCI addresses than the EHCI controller, so they will be + * discovered (and hence resumed) first. But there is no guarantee things + * will always work this way. If the EHCI controller is resumed first and + * the OHCI ports are unpowered, then the handover will fail. + */ +static void prepare_for_handover(struct usb_hcd *hcd) +{ + struct ohci_hcd *ohci = hcd_to_ohci(hcd); + int port; + + /* Here we "know" root ports should always stay powered */ + ohci_dbg(ohci, "powerup ports\n"); + for (port = 0; port < ohci->num_ports; port++) + ohci_writel(ohci, RH_PS_PPS, + &ohci->regs->roothub.portstatus[port]); + + /* Flush those writes */ + ohci_readl(ohci, &ohci->regs->control); + msleep(20); +} + +#else + +static inline void prepare_for_handover(struct usb_hcd *hcd) +{ } + +#endif /* CONFIG_USB_PERSIST etc. */ + #ifdef CONFIG_PM static int ohci_pci_suspend (struct usb_hcd *hcd, pm_message_t message) @@ -241,7 +293,10 @@ static int ohci_pci_suspend (struct usb_hcd *hcd, pm_message_t message) static int ohci_pci_resume (struct usb_hcd *hcd) { set_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags); - usb_hcd_resume_root_hub(hcd); + + /* FIXME: we should try to detect loss of VBUS power here */ + prepare_for_handover(hcd); + return 0; } diff --git a/drivers/usb/host/ohci-pnx4008.c b/drivers/usb/host/ohci-pnx4008.c index d601bbb9387..ca2a6abbc11 100644 --- a/drivers/usb/host/ohci-pnx4008.c +++ b/drivers/usb/host/ohci-pnx4008.c @@ -134,7 +134,7 @@ static int isp1301_attach(struct i2c_adapter *adap, int addr, int kind) { struct i2c_client *c; - c = (struct i2c_client *)kzalloc(sizeof(*c), GFP_KERNEL); + c = kzalloc(sizeof(*c), GFP_KERNEL); if (!c) return -ENOMEM; diff --git a/drivers/usb/host/ohci-ps3.c b/drivers/usb/host/ohci-ps3.c index d7cf07288b0..01a0caeaa6b 100644 --- a/drivers/usb/host/ohci-ps3.c +++ b/drivers/usb/host/ohci-ps3.c @@ -18,6 +18,7 @@ * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA */ +#include <asm/firmware.h> #include <asm/ps3.h> static int ps3_ohci_hc_reset(struct usb_hcd *hcd) @@ -75,7 +76,7 @@ static const struct hc_driver ps3_ohci_hc_driver = { #endif }; -static int ps3_ohci_sb_probe(struct ps3_system_bus_device *dev) +static int ps3_ohci_probe(struct ps3_system_bus_device *dev) { int result; struct usb_hcd *hcd; @@ -87,13 +88,31 @@ static int ps3_ohci_sb_probe(struct ps3_system_bus_device *dev) goto fail_start; } + result = ps3_open_hv_device(dev); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_open_hv_device failed: %s\n", + __func__, __LINE__, ps3_result(result)); + result = -EPERM; + goto fail_open; + } + + result = ps3_dma_region_create(dev->d_region); + + if (result) { + dev_dbg(&dev->core, "%s:%d: ps3_dma_region_create failed: " + "(%d)\n", __func__, __LINE__, result); + BUG_ON("check region type"); + goto fail_dma_region; + } + result = ps3_mmio_region_create(dev->m_region); if (result) { dev_dbg(&dev->core, "%s:%d: ps3_map_mmio_region failed\n", __func__, __LINE__); result = -EPERM; - goto fail_mmio; + goto fail_mmio_region; } dev_dbg(&dev->core, "%s:%d: mmio mapped_addr %lxh\n", __func__, @@ -122,6 +141,11 @@ static int ps3_ohci_sb_probe(struct ps3_system_bus_device *dev) hcd->rsrc_start = dev->m_region->lpar_addr; hcd->rsrc_len = dev->m_region->len; + + if (!request_mem_region(hcd->rsrc_start, hcd->rsrc_len, hcd_name)) + dev_dbg(&dev->core, "%s:%d: request_mem_region failed\n", + __func__, __LINE__); + hcd->regs = ioremap(dev->m_region->lpar_addr, dev->m_region->len); if (!hcd->regs) { @@ -155,34 +179,73 @@ static int ps3_ohci_sb_probe(struct ps3_system_bus_device *dev) fail_add_hcd: iounmap(hcd->regs); fail_ioremap: + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); usb_put_hcd(hcd); fail_create_hcd: ps3_io_irq_destroy(virq); fail_irq: ps3_free_mmio_region(dev->m_region); -fail_mmio: +fail_mmio_region: + ps3_dma_region_free(dev->d_region); +fail_dma_region: + ps3_close_hv_device(dev); +fail_open: fail_start: return result; } -static int ps3_ohci_sb_remove (struct ps3_system_bus_device *dev) +static int ps3_ohci_remove (struct ps3_system_bus_device *dev) { + unsigned int tmp; struct usb_hcd *hcd = (struct usb_hcd *)ps3_system_bus_get_driver_data(dev); - usb_put_hcd(hcd); + BUG_ON(!hcd); + + dev_dbg(&dev->core, "%s:%d: regs %p\n", __func__, __LINE__, hcd->regs); + dev_dbg(&dev->core, "%s:%d: irq %u\n", __func__, __LINE__, hcd->irq); + + tmp = hcd->irq; + + usb_remove_hcd(hcd); + ps3_system_bus_set_driver_data(dev, NULL); + BUG_ON(!hcd->regs); + iounmap(hcd->regs); + + release_mem_region(hcd->rsrc_start, hcd->rsrc_len); + usb_put_hcd(hcd); + + ps3_io_irq_destroy(tmp); + ps3_free_mmio_region(dev->m_region); + + ps3_dma_region_free(dev->d_region); + ps3_close_hv_device(dev); + return 0; } -MODULE_ALIAS("ps3-ohci"); +static int ps3_ohci_driver_register(struct ps3_system_bus_driver *drv) +{ + return firmware_has_feature(FW_FEATURE_PS3_LV1) + ? ps3_system_bus_driver_register(drv) + : 0; +} + +static void ps3_ohci_driver_unregister(struct ps3_system_bus_driver *drv) +{ + if (firmware_has_feature(FW_FEATURE_PS3_LV1)) + ps3_system_bus_driver_unregister(drv); +} + +MODULE_ALIAS(PS3_MODULE_ALIAS_OHCI); -static struct ps3_system_bus_driver ps3_ohci_sb_driver = { +static struct ps3_system_bus_driver ps3_ohci_driver = { + .core.name = "ps3-ohci-driver", + .core.owner = THIS_MODULE, .match_id = PS3_MATCH_ID_OHCI, - .core = { - .name = "ps3-ohci-driver", - }, - .probe = ps3_ohci_sb_probe, - .remove = ps3_ohci_sb_remove, + .probe = ps3_ohci_probe, + .remove = ps3_ohci_remove, + .shutdown = ps3_ohci_remove, }; diff --git a/drivers/usb/host/ohci.h b/drivers/usb/host/ohci.h index c2b5ecfe5e9..4ada43cf138 100644 --- a/drivers/usb/host/ohci.h +++ b/drivers/usb/host/ohci.h @@ -397,8 +397,10 @@ struct ohci_hcd { #define OHCI_QUIRK_BE_DESC 0x08 /* BE descriptors */ #define OHCI_QUIRK_BE_MMIO 0x10 /* BE registers */ #define OHCI_QUIRK_ZFMICRO 0x20 /* Compaq ZFMicro chipset*/ +#define OHCI_QUIRK_NEC 0x40 /* lost interrupts */ // there are also chip quirks/bugs in init logic + struct work_struct nec_work; /* Worker for NEC quirk */ }; /* convert between an hcd pointer and the corresponding ohci_hcd */ diff --git a/drivers/usb/host/r8a66597-hcd.c b/drivers/usb/host/r8a66597-hcd.c new file mode 100644 index 00000000000..a7a7070c6e2 --- /dev/null +++ b/drivers/usb/host/r8a66597-hcd.c @@ -0,0 +1,2244 @@ +/* + * R8A66597 HCD (Host Controller Driver) + * + * Copyright (C) 2006-2007 Renesas Solutions Corp. + * Portions Copyright (C) 2004 Psion Teklogix (for NetBook PRO) + * Portions Copyright (C) 2004-2005 David Brownell + * Portions Copyright (C) 1999 Roman Weissgaerber + * + * Author : Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> + * + * 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; version 2 of the License. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#include <linux/module.h> +#include <linux/kernel.h> +#include <linux/sched.h> +#include <linux/smp_lock.h> +#include <linux/errno.h> +#include <linux/init.h> +#include <linux/timer.h> +#include <linux/delay.h> +#include <linux/list.h> +#include <linux/interrupt.h> +#include <linux/usb.h> +#include <linux/platform_device.h> + +#include <asm/io.h> +#include <asm/irq.h> +#include <asm/system.h> + +#include "../core/hcd.h" +#include "r8a66597.h" + +MODULE_DESCRIPTION("R8A66597 USB Host Controller Driver"); +MODULE_LICENSE("GPL"); +MODULE_AUTHOR("Yoshihiro Shimoda"); + +#define DRIVER_VERSION "29 May 2007" + +static const char hcd_name[] = "r8a66597_hcd"; + +/* module parameters */ +static unsigned short clock = XTAL12; +module_param(clock, ushort, 0644); +MODULE_PARM_DESC(clock, "input clock: 48MHz=32768, 24MHz=16384, 12MHz=0(default=0)"); +static unsigned short vif = LDRV; +module_param(vif, ushort, 0644); +MODULE_PARM_DESC(vif, "input VIF: 3.3V=32768, 1.5V=0(default=32768)"); +static unsigned short endian = 0; +module_param(endian, ushort, 0644); +MODULE_PARM_DESC(endian, "data endian: big=256, little=0(default=0)"); +static unsigned short irq_sense = INTL; +module_param(irq_sense, ushort, 0644); +MODULE_PARM_DESC(irq_sense, "IRQ sense: low level=32, falling edge=0(default=32)"); + +static void packet_write(struct r8a66597 *r8a66597, u16 pipenum); +static int r8a66597_get_frame(struct usb_hcd *hcd); + +/* this function must be called with interrupt disabled */ +static void enable_pipe_irq(struct r8a66597 *r8a66597, u16 pipenum, + unsigned long reg) +{ + u16 tmp; + + tmp = r8a66597_read(r8a66597, INTENB0); + r8a66597_bclr(r8a66597, BEMPE | NRDYE | BRDYE, INTENB0); + r8a66597_bset(r8a66597, 1 << pipenum, reg); + r8a66597_write(r8a66597, tmp, INTENB0); +} + +/* this function must be called with interrupt disabled */ +static void disable_pipe_irq(struct r8a66597 *r8a66597, u16 pipenum, + unsigned long reg) +{ + u16 tmp; + + tmp = r8a66597_read(r8a66597, INTENB0); + r8a66597_bclr(r8a66597, BEMPE | NRDYE | BRDYE, INTENB0); + r8a66597_bclr(r8a66597, 1 << pipenum, reg); + r8a66597_write(r8a66597, tmp, INTENB0); +} + +static void set_devadd_reg(struct r8a66597 *r8a66597, u8 r8a66597_address, + u16 usbspd, u8 upphub, u8 hubport, int port) +{ + u16 val; + unsigned long devadd_reg = get_devadd_addr(r8a66597_address); + + val = (upphub << 11) | (hubport << 8) | (usbspd << 6) | (port & 0x0001); + r8a66597_write(r8a66597, val, devadd_reg); +} + +static int enable_controller(struct r8a66597 *r8a66597) +{ + u16 tmp; + int i = 0; + + do { + r8a66597_write(r8a66597, USBE, SYSCFG0); + tmp = r8a66597_read(r8a66597, SYSCFG0); + if (i++ > 1000) { + err("register access fail."); + return -ENXIO; + } + } while ((tmp & USBE) != USBE); + r8a66597_bclr(r8a66597, USBE, SYSCFG0); + r8a66597_mdfy(r8a66597, clock, XTAL, SYSCFG0); + + i = 0; + r8a66597_bset(r8a66597, XCKE, SYSCFG0); + do { + msleep(1); + tmp = r8a66597_read(r8a66597, SYSCFG0); + if (i++ > 500) { + err("register access fail."); + return -ENXIO; + } + } while ((tmp & SCKE) != SCKE); + + r8a66597_bset(r8a66597, DCFM | DRPD, SYSCFG0); + r8a66597_bset(r8a66597, DRPD, SYSCFG1); + + r8a66597_bset(r8a66597, vif & LDRV, PINCFG); + r8a66597_bset(r8a66597, HSE, SYSCFG0); + r8a66597_bset(r8a66597, HSE, SYSCFG1); + r8a66597_bset(r8a66597, USBE, SYSCFG0); + + r8a66597_bset(r8a66597, BEMPE | NRDYE | BRDYE, INTENB0); + r8a66597_bset(r8a66597, irq_sense & INTL, SOFCFG); + r8a66597_bset(r8a66597, BRDY0, BRDYENB); + r8a66597_bset(r8a66597, BEMP0, BEMPENB); + + r8a66597_write(r8a66597, BURST | CPU_ADR_RD_WR, DMA0CFG); + r8a66597_write(r8a66597, BURST | CPU_ADR_RD_WR, DMA1CFG); + + r8a66597_bset(r8a66597, endian & BIGEND, CFIFOSEL); + r8a66597_bset(r8a66597, endian & BIGEND, D0FIFOSEL); + r8a66597_bset(r8a66597, endian & BIGEND, D1FIFOSEL); + + r8a66597_bset(r8a66597, TRNENSEL, SOFCFG); + + r8a66597_bset(r8a66597, SIGNE | SACKE, INTENB1); + r8a66597_bclr(r8a66597, DTCHE, INTENB1); + r8a66597_bset(r8a66597, ATTCHE, INTENB1); + r8a66597_bclr(r8a66597, DTCHE, INTENB2); + r8a66597_bset(r8a66597, ATTCHE, INTENB2); + + return 0; +} + +static void disable_controller(struct r8a66597 *r8a66597) +{ + u16 tmp; + + r8a66597_write(r8a66597, 0, INTENB0); + r8a66597_write(r8a66597, 0, INTENB1); + r8a66597_write(r8a66597, 0, INTENB2); + r8a66597_write(r8a66597, 0, INTSTS0); + r8a66597_write(r8a66597, 0, INTSTS1); + r8a66597_write(r8a66597, 0, INTSTS2); + + r8a66597_port_power(r8a66597, 0, 0); + r8a66597_port_power(r8a66597, 1, 0); + + do { + tmp = r8a66597_read(r8a66597, SOFCFG) & EDGESTS; + udelay(640); + } while (tmp == EDGESTS); + + r8a66597_bclr(r8a66597, DCFM | DRPD, SYSCFG0); + r8a66597_bclr(r8a66597, DRPD, SYSCFG1); + r8a66597_bclr(r8a66597, HSE, SYSCFG0); + r8a66597_bclr(r8a66597, HSE, SYSCFG1); + + r8a66597_bclr(r8a66597, SCKE, SYSCFG0); + udelay(1); + r8a66597_bclr(r8a66597, PLLC, SYSCFG0); + r8a66597_bclr(r8a66597, XCKE, SYSCFG0); + r8a66597_bclr(r8a66597, USBE, SYSCFG0); +} + +static int get_parent_r8a66597_address(struct r8a66597 *r8a66597, + struct usb_device *udev) +{ + struct r8a66597_device *dev; + + if (udev->parent && udev->parent->devnum != 1) + udev = udev->parent; + + dev = dev_get_drvdata(&udev->dev); + if (dev) + return dev->address; + else + return 0; +} + +static int is_child_device(char *devpath) +{ + return (devpath[2] ? 1 : 0); +} + +static int is_hub_limit(char *devpath) +{ + return ((strlen(devpath) >= 4) ? 1 : 0); +} + +static void get_port_number(char *devpath, u16 *root_port, u16 *hub_port) +{ + if (root_port) { + *root_port = (devpath[0] & 0x0F) - 1; + if (*root_port >= R8A66597_MAX_ROOT_HUB) + err("illegal root port number"); + } + if (hub_port) + *hub_port = devpath[2] & 0x0F; +} + +static u16 get_r8a66597_usb_speed(enum usb_device_speed speed) +{ + u16 usbspd = 0; + + switch (speed) { + case USB_SPEED_LOW: + usbspd = LSMODE; + break; + case USB_SPEED_FULL: + usbspd = FSMODE; + break; + case USB_SPEED_HIGH: + usbspd = HSMODE; + break; + default: + err("unknown speed"); + break; + } + + return usbspd; +} + +static void set_child_connect_map(struct r8a66597 *r8a66597, int address) +{ + int idx; + + idx = address / 32; + r8a66597->child_connect_map[idx] |= 1 << (address % 32); +} + +static void put_child_connect_map(struct r8a66597 *r8a66597, int address) +{ + int idx; + + idx = address / 32; + r8a66597->child_connect_map[idx] &= ~(1 << (address % 32)); +} + +static void set_pipe_reg_addr(struct r8a66597_pipe *pipe, u8 dma_ch) +{ + u16 pipenum = pipe->info.pipenum; + unsigned long fifoaddr[] = {D0FIFO, D1FIFO, CFIFO}; + unsigned long fifosel[] = {D0FIFOSEL, D1FIFOSEL, CFIFOSEL}; + unsigned long fifoctr[] = {D0FIFOCTR, D1FIFOCTR, CFIFOCTR}; + + if (dma_ch > R8A66597_PIPE_NO_DMA) /* dma fifo not use? */ + dma_ch = R8A66597_PIPE_NO_DMA; + + pipe->fifoaddr = fifoaddr[dma_ch]; + pipe->fifosel = fifosel[dma_ch]; + pipe->fifoctr = fifoctr[dma_ch]; + + if (pipenum == 0) + pipe->pipectr = DCPCTR; + else + pipe->pipectr = get_pipectr_addr(pipenum); + + if (check_bulk_or_isoc(pipenum)) { + pipe->pipetre = get_pipetre_addr(pipenum); + pipe->pipetrn = get_pipetrn_addr(pipenum); + } else { + pipe->pipetre = 0; + pipe->pipetrn = 0; + } +} + +static struct r8a66597_device * +get_urb_to_r8a66597_dev(struct r8a66597 *r8a66597, struct urb *urb) +{ + if (usb_pipedevice(urb->pipe) == 0) + return &r8a66597->device0; + + return dev_get_drvdata(&urb->dev->dev); +} + +static int make_r8a66597_device(struct r8a66597 *r8a66597, + struct urb *urb, u8 addr) +{ + struct r8a66597_device *dev; + int usb_address = urb->setup_packet[2]; /* urb->pipe is address 0 */ + + dev = kzalloc(sizeof(struct r8a66597_device), GFP_KERNEL); + if (dev == NULL) + return -ENOMEM; + + dev_set_drvdata(&urb->dev->dev, dev); + dev->udev = urb->dev; + dev->address = addr; + dev->usb_address = usb_address; + dev->state = USB_STATE_ADDRESS; + dev->ep_in_toggle = 0; + dev->ep_out_toggle = 0; + INIT_LIST_HEAD(&dev->device_list); + list_add_tail(&dev->device_list, &r8a66597->child_device); + + get_port_number(urb->dev->devpath, &dev->root_port, &dev->hub_port); + if (!is_child_device(urb->dev->devpath)) + r8a66597->root_hub[dev->root_port].dev = dev; + + set_devadd_reg(r8a66597, dev->address, + get_r8a66597_usb_speed(urb->dev->speed), + get_parent_r8a66597_address(r8a66597, urb->dev), + dev->hub_port, dev->root_port); + + return 0; +} + +/* this function must be called with interrupt disabled */ +static u8 alloc_usb_address(struct r8a66597 *r8a66597, struct urb *urb) +{ + u8 addr; /* R8A66597's address */ + struct r8a66597_device *dev; + + if (is_hub_limit(urb->dev->devpath)) { + err("Externel hub limit reached."); + return 0; + } + + dev = get_urb_to_r8a66597_dev(r8a66597, urb); + if (dev && dev->state >= USB_STATE_ADDRESS) + return dev->address; + + for (addr = 1; addr <= R8A66597_MAX_DEVICE; addr++) { + if (r8a66597->address_map & (1 << addr)) + continue; + + dbg("alloc_address: r8a66597_addr=%d", addr); + r8a66597->address_map |= 1 << addr; + + if (make_r8a66597_device(r8a66597, urb, addr) < 0) + return 0; + + return addr; + } + + err("cannot communicate with a USB device more than 10.(%x)", + r8a66597->address_map); + + return 0; +} + +/* this function must be called with interrupt disabled */ +static void free_usb_address(struct r8a66597 *r8a66597, + struct r8a66597_device *dev) +{ + int port; + + if (!dev) + return; + + dbg("free_addr: addr=%d", dev->address); + + dev->state = USB_STATE_DEFAULT; + r8a66597->address_map &= ~(1 << dev->address); + dev->address = 0; + dev_set_drvdata(&dev->udev->dev, NULL); + list_del(&dev->device_list); + kfree(dev); + + for (port = 0; port < R8A66597_MAX_ROOT_HUB; port++) { + if (r8a66597->root_hub[port].dev == dev) { + r8a66597->root_hub[port].dev = NULL; + break; + } + } +} + +static void r8a66597_reg_wait(struct r8a66597 *r8a66597, unsigned long reg, + u16 mask, u16 loop) +{ + u16 tmp; + int i = 0; + + do { + tmp = r8a66597_read(r8a66597, reg); + if (i++ > 1000000) { + err("register%lx, loop %x is timeout", reg, loop); + break; + } + ndelay(1); + } while ((tmp & mask) != loop); +} + +/* this function must be called with interrupt disabled */ +static void pipe_start(struct r8a66597 *r8a66597, struct r8a66597_pipe *pipe) +{ + u16 tmp; + + tmp = r8a66597_read(r8a66597, pipe->pipectr) & PID; + if ((pipe->info.pipenum != 0) & ((tmp & PID_STALL) != 0)) /* stall? */ + r8a66597_mdfy(r8a66597, PID_NAK, PID, pipe->pipectr); + r8a66597_mdfy(r8a66597, PID_BUF, PID, pipe->pipectr); +} + +/* this function must be called with interrupt disabled */ +static void pipe_stop(struct r8a66597 *r8a66597, struct r8a66597_pipe *pipe) +{ + u16 tmp; + + tmp = r8a66597_read(r8a66597, pipe->pipectr) & PID; + if ((tmp & PID_STALL11) != PID_STALL11) /* force stall? */ + r8a66597_mdfy(r8a66597, PID_STALL, PID, pipe->pipectr); + r8a66597_mdfy(r8a66597, PID_NAK, PID, pipe->pipectr); + r8a66597_reg_wait(r8a66597, pipe->pipectr, PBUSY, 0); +} + +/* this function must be called with interrupt disabled */ +static void clear_all_buffer(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe) +{ + u16 tmp; + + if (!pipe || pipe->info.pipenum == 0) + return; + + pipe_stop(r8a66597, pipe); + r8a66597_bset(r8a66597, ACLRM, pipe->pipectr); + tmp = r8a66597_read(r8a66597, pipe->pipectr); + tmp = r8a66597_read(r8a66597, pipe->pipectr); + tmp = r8a66597_read(r8a66597, pipe->pipectr); + r8a66597_bclr(r8a66597, ACLRM, pipe->pipectr); +} + +/* this function must be called with interrupt disabled */ +static void r8a66597_pipe_toggle(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe, int toggle) +{ + if (toggle) + r8a66597_bset(r8a66597, SQSET, pipe->pipectr); + else + r8a66597_bset(r8a66597, SQCLR, pipe->pipectr); +} + +/* this function must be called with interrupt disabled */ +static inline void cfifo_change(struct r8a66597 *r8a66597, u16 pipenum) +{ + r8a66597_mdfy(r8a66597, MBW | pipenum, MBW | CURPIPE, CFIFOSEL); + r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, pipenum); +} + +/* this function must be called with interrupt disabled */ +static inline void fifo_change_from_pipe(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe) +{ + cfifo_change(r8a66597, 0); + r8a66597_mdfy(r8a66597, MBW | 0, MBW | CURPIPE, D0FIFOSEL); + r8a66597_mdfy(r8a66597, MBW | 0, MBW | CURPIPE, D1FIFOSEL); + + r8a66597_mdfy(r8a66597, MBW | pipe->info.pipenum, MBW | CURPIPE, + pipe->fifosel); + r8a66597_reg_wait(r8a66597, pipe->fifosel, CURPIPE, pipe->info.pipenum); +} + +static u16 r8a66597_get_pipenum(struct urb *urb, struct usb_host_endpoint *hep) +{ + struct r8a66597_pipe *pipe = hep->hcpriv; + + if (usb_pipeendpoint(urb->pipe) == 0) + return 0; + else + return pipe->info.pipenum; +} + +static u16 get_urb_to_r8a66597_addr(struct r8a66597 *r8a66597, struct urb *urb) +{ + struct r8a66597_device *dev = get_urb_to_r8a66597_dev(r8a66597, urb); + + return (usb_pipedevice(urb->pipe) == 0) ? 0 : dev->address; +} + +static unsigned short *get_toggle_pointer(struct r8a66597_device *dev, + int urb_pipe) +{ + if (!dev) + return NULL; + + return usb_pipein(urb_pipe) ? &dev->ep_in_toggle : &dev->ep_out_toggle; +} + +/* this function must be called with interrupt disabled */ +static void pipe_toggle_set(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe, + struct urb *urb, int set) +{ + struct r8a66597_device *dev = get_urb_to_r8a66597_dev(r8a66597, urb); + unsigned char endpoint = usb_pipeendpoint(urb->pipe); + unsigned short *toggle = get_toggle_pointer(dev, urb->pipe); + + if (!toggle) + return; + + if (set) + *toggle |= 1 << endpoint; + else + *toggle &= ~(1 << endpoint); +} + +/* this function must be called with interrupt disabled */ +static void pipe_toggle_save(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe, + struct urb *urb) +{ + if (r8a66597_read(r8a66597, pipe->pipectr) & SQMON) + pipe_toggle_set(r8a66597, pipe, urb, 1); + else + pipe_toggle_set(r8a66597, pipe, urb, 0); +} + +/* this function must be called with interrupt disabled */ +static void pipe_toggle_restore(struct r8a66597 *r8a66597, + struct r8a66597_pipe *pipe, + struct urb *urb) +{ + struct r8a66597_device *dev = get_urb_to_r8a66597_dev(r8a66597, urb); + unsigned char endpoint = usb_pipeendpoint(urb->pipe); + unsigned short *toggle = get_toggle_pointer(dev, urb->pipe); + + if (!toggle) + return; + + r8a66597_pipe_toggle(r8a66597, pipe, *toggle & (1 << endpoint)); +} + +/* this function must be called with interrupt disabled */ +static void pipe_buffer_setting(struct r8a66597 *r8a66597, + struct r8a66597_pipe_info *info) +{ + u16 val = 0; + + if (info->pipenum == 0) + return; + + r8a66597_bset(r8a66597, ACLRM, get_pipectr_addr(info->pipenum)); + r8a66597_bclr(r8a66597, ACLRM, get_pipectr_addr(info->pipenum)); + r8a66597_write(r8a66597, info->pipenum, PIPESEL); + if (!info->dir_in) + val |= R8A66597_DIR; + if (info->type == R8A66597_BULK && info->dir_in) + val |= R8A66597_DBLB | R8A66597_SHTNAK; + val |= info->type | info->epnum; + r8a66597_write(r8a66597, val, PIPECFG); + + r8a66597_write(r8a66597, (info->buf_bsize << 10) | (info->bufnum), + PIPEBUF); + r8a66597_write(r8a66597, make_devsel(info->address) | info->maxpacket, + PIPEMAXP); + if (info->interval) + info->interval--; + r8a66597_write(r8a66597, info->interval, PIPEPERI); +} + + + +/* this function must be called with interrupt disabled */ +static void pipe_setting(struct r8a66597 *r8a66597, struct r8a66597_td *td) +{ + struct r8a66597_pipe_info *info; + struct urb *urb = td->urb; + + if (td->pipenum > 0) { + info = &td->pipe->info; + cfifo_change(r8a66597, 0); + pipe_buffer_setting(r8a66597, info); + + if (!usb_gettoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe)) && + !usb_pipecontrol(urb->pipe)) { + r8a66597_pipe_toggle(r8a66597, td->pipe, 0); + pipe_toggle_set(r8a66597, td->pipe, urb, 0); + clear_all_buffer(r8a66597, td->pipe); + usb_settoggle(urb->dev, usb_pipeendpoint(urb->pipe), + usb_pipeout(urb->pipe), 1); + } + pipe_toggle_restore(r8a66597, td->pipe, urb); + } +} + +/* this function must be called with interrupt disabled */ +static u16 get_empty_pipenum(struct r8a66597 *r8a66597, + struct usb_endpoint_descriptor *ep) +{ + u16 array[R8A66597_MAX_NUM_PIPE], i = 0, min; + + memset(array, 0, sizeof(array)); + switch(ep->bmAttributes & USB_ENDPOINT_XFERTYPE_MASK) { + case USB_ENDPOINT_XFER_BULK: + if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + array[i++] = 4; + else { + array[i++] = 3; + array[i++] = 5; + } + break; + case USB_ENDPOINT_XFER_INT: + if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) { + array[i++] = 6; + array[i++] = 7; + array[i++] = 8; + } else + array[i++] = 9; + break; + case USB_ENDPOINT_XFER_ISOC: + if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + array[i++] = 2; + else + array[i++] = 1; + break; + default: + err("Illegal type"); + return 0; + } + + i = 1; + min = array[0]; + while (array[i] != 0) { + if (r8a66597->pipe_cnt[min] > r8a66597->pipe_cnt[array[i]]) + min = array[i]; + i++; + } + + return min; +} + +static u16 get_r8a66597_type(__u8 type) +{ + u16 r8a66597_type; + + switch(type) { + case USB_ENDPOINT_XFER_BULK: + r8a66597_type = R8A66597_BULK; + break; + case USB_ENDPOINT_XFER_INT: + r8a66597_type = R8A66597_INT; + break; + case USB_ENDPOINT_XFER_ISOC: + r8a66597_type = R8A66597_ISO; + break; + default: + err("Illegal type"); + r8a66597_type = 0x0000; + break; + } + + return r8a66597_type; +} + +static u16 get_bufnum(u16 pipenum) +{ + u16 bufnum = 0; + + if (pipenum == 0) + bufnum = 0; + else if (check_bulk_or_isoc(pipenum)) + bufnum = 8 + (pipenum - 1) * R8A66597_BUF_BSIZE*2; + else if (check_interrupt(pipenum)) + bufnum = 4 + (pipenum - 6); + else + err("Illegal pipenum (%d)", pipenum); + + return bufnum; +} + +static u16 get_buf_bsize(u16 pipenum) +{ + u16 buf_bsize = 0; + + if (pipenum == 0) + buf_bsize = 3; + else if (check_bulk_or_isoc(pipenum)) + buf_bsize = R8A66597_BUF_BSIZE - 1; + else if (check_interrupt(pipenum)) + buf_bsize = 0; + else + err("Illegal pipenum (%d)", pipenum); + + return buf_bsize; +} + +/* this function must be called with interrupt disabled */ +static void enable_r8a66597_pipe_dma(struct r8a66597 *r8a66597, + struct r8a66597_device *dev, + struct r8a66597_pipe *pipe, + struct urb *urb) +{ + int i; + struct r8a66597_pipe_info *info = &pipe->info; + + if ((pipe->info.pipenum != 0) && (info->type != R8A66597_INT)) { + for (i = 0; i < R8A66597_MAX_DMA_CHANNEL; i++) { + if ((r8a66597->dma_map & (1 << i)) != 0) + continue; + + info("address %d, EndpointAddress 0x%02x use DMA FIFO", + usb_pipedevice(urb->pipe), + info->dir_in ? USB_ENDPOINT_DIR_MASK + info->epnum + : info->epnum); + + r8a66597->dma_map |= 1 << i; + dev->dma_map |= 1 << i; + set_pipe_reg_addr(pipe, i); + + cfifo_change(r8a66597, 0); + r8a66597_mdfy(r8a66597, MBW | pipe->info.pipenum, + MBW | CURPIPE, pipe->fifosel); + + r8a66597_reg_wait(r8a66597, pipe->fifosel, CURPIPE, + pipe->info.pipenum); + r8a66597_bset(r8a66597, BCLR, pipe->fifoctr); + break; + } + } +} + +/* this function must be called with interrupt disabled */ +static void enable_r8a66597_pipe(struct r8a66597 *r8a66597, struct urb *urb, + struct usb_host_endpoint *hep, + struct r8a66597_pipe_info *info) +{ + struct r8a66597_device *dev = get_urb_to_r8a66597_dev(r8a66597, urb); + struct r8a66597_pipe *pipe = hep->hcpriv; + + dbg("enable_pipe:"); + + pipe->info = *info; + set_pipe_reg_addr(pipe, R8A66597_PIPE_NO_DMA); + r8a66597->pipe_cnt[pipe->info.pipenum]++; + dev->pipe_cnt[pipe->info.pipenum]++; + + enable_r8a66597_pipe_dma(r8a66597, dev, pipe, urb); +} + +/* this function must be called with interrupt disabled */ +static void force_dequeue(struct r8a66597 *r8a66597, u16 pipenum, u16 address) +{ + struct r8a66597_td *td, *next; + struct urb *urb; + struct list_head *list = &r8a66597->pipe_queue[pipenum]; + + if (list_empty(list)) + return; + + list_for_each_entry_safe(td, next, list, queue) { + if (!td) + continue; + if (td->address != address) + continue; + + urb = td->urb; + list_del(&td->queue); + kfree(td); + + if (urb) { + urb->status = -ENODEV; + urb->hcpriv = NULL; + spin_unlock(&r8a66597->lock); + usb_hcd_giveback_urb(r8a66597_to_hcd(r8a66597), urb); + spin_lock(&r8a66597->lock); + } + break; + } +} + +/* this function must be called with interrupt disabled */ +static void disable_r8a66597_pipe_all(struct r8a66597 *r8a66597, + struct r8a66597_device *dev) +{ + int check_ep0 = 0; + u16 pipenum; + + if (!dev) + return; + + for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + if (!dev->pipe_cnt[pipenum]) + continue; + + if (!check_ep0) { + check_ep0 = 1; + force_dequeue(r8a66597, 0, dev->address); + } + + r8a66597->pipe_cnt[pipenum] -= dev->pipe_cnt[pipenum]; + dev->pipe_cnt[pipenum] = 0; + force_dequeue(r8a66597, pipenum, dev->address); + } + + dbg("disable_pipe"); + + r8a66597->dma_map &= ~(dev->dma_map); + dev->dma_map = 0; +} + +/* this function must be called with interrupt disabled */ +static void init_pipe_info(struct r8a66597 *r8a66597, struct urb *urb, + struct usb_host_endpoint *hep, + struct usb_endpoint_descriptor *ep) +{ + struct r8a66597_pipe_info info; + + info.pipenum = get_empty_pipenum(r8a66597, ep); + info.address = get_urb_to_r8a66597_addr(r8a66597, urb); + info.epnum = ep->bEndpointAddress & USB_ENDPOINT_NUMBER_MASK; + info.maxpacket = ep->wMaxPacketSize; + info.type = get_r8a66597_type(ep->bmAttributes + & USB_ENDPOINT_XFERTYPE_MASK); + info.bufnum = get_bufnum(info.pipenum); + info.buf_bsize = get_buf_bsize(info.pipenum); + info.interval = ep->bInterval; + if (ep->bEndpointAddress & USB_ENDPOINT_DIR_MASK) + info.dir_in = 1; + else + info.dir_in = 0; + + enable_r8a66597_pipe(r8a66597, urb, hep, &info); +} + +static void init_pipe_config(struct r8a66597 *r8a66597, struct urb *urb) +{ + struct r8a66597_device *dev; + + dev = get_urb_to_r8a66597_dev(r8a66597, urb); + dev->state = USB_STATE_CONFIGURED; +} + +static void pipe_irq_enable(struct r8a66597 *r8a66597, struct urb *urb, + u16 pipenum) +{ + if (pipenum == 0 && usb_pipeout(urb->pipe)) + enable_irq_empty(r8a66597, pipenum); + else + enable_irq_ready(r8a66597, pipenum); + + if (!usb_pipeisoc(urb->pipe)) + enable_irq_nrdy(r8a66597, pipenum); +} + +static void pipe_irq_disable(struct r8a66597 *r8a66597, u16 pipenum) +{ + disable_irq_ready(r8a66597, pipenum); + disable_irq_nrdy(r8a66597, pipenum); +} + +/* this function must be called with interrupt disabled */ +static void r8a66597_usb_preconnect(struct r8a66597 *r8a66597, int port) +{ + r8a66597->root_hub[port].port |= (1 << USB_PORT_FEAT_CONNECTION) + | (1 << USB_PORT_FEAT_C_CONNECTION); + r8a66597_write(r8a66597, (u16)~DTCH, get_intsts_reg(port)); + r8a66597_bset(r8a66597, DTCHE, get_intenb_reg(port)); +} + +/* this function must be called with interrupt disabled */ +static void r8a66597_usb_connect(struct r8a66597 *r8a66597, int port) +{ + u16 speed = get_rh_usb_speed(r8a66597, port); + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + + if (speed == HSMODE) + rh->port |= (1 << USB_PORT_FEAT_HIGHSPEED); + else if (speed == LSMODE) + rh->port |= (1 << USB_PORT_FEAT_LOWSPEED); + + rh->port &= ~(1 << USB_PORT_FEAT_RESET); + rh->port |= 1 << USB_PORT_FEAT_ENABLE; +} + +/* this function must be called with interrupt disabled */ +static void r8a66597_usb_disconnect(struct r8a66597 *r8a66597, int port) +{ + struct r8a66597_device *dev = r8a66597->root_hub[port].dev; + + r8a66597->root_hub[port].port &= ~(1 << USB_PORT_FEAT_CONNECTION); + r8a66597->root_hub[port].port |= (1 << USB_PORT_FEAT_C_CONNECTION); + + disable_r8a66597_pipe_all(r8a66597, dev); + free_usb_address(r8a66597, dev); + + r8a66597_bset(r8a66597, ATTCHE, get_intenb_reg(port)); +} + +/* this function must be called with interrupt disabled */ +static void prepare_setup_packet(struct r8a66597 *r8a66597, + struct r8a66597_td *td) +{ + int i; + u16 *p = (u16 *)td->urb->setup_packet; + unsigned long setup_addr = USBREQ; + + r8a66597_write(r8a66597, make_devsel(td->address) | td->maxpacket, + DCPMAXP); + r8a66597_write(r8a66597, (u16)~(SIGN | SACK), INTSTS1); + + for (i = 0; i < 4; i++) { + r8a66597_write(r8a66597, p[i], setup_addr); + setup_addr += 2; + } + r8a66597_write(r8a66597, SUREQ, DCPCTR); +} + +/* this function must be called with interrupt disabled */ +static void prepare_packet_read(struct r8a66597 *r8a66597, + struct r8a66597_td *td) +{ + struct urb *urb = td->urb; + + if (usb_pipecontrol(urb->pipe)) { + r8a66597_bclr(r8a66597, R8A66597_DIR, DCPCFG); + r8a66597_mdfy(r8a66597, 0, ISEL | CURPIPE, CFIFOSEL); + r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); + if (urb->actual_length == 0) { + r8a66597_pipe_toggle(r8a66597, td->pipe, 1); + r8a66597_write(r8a66597, BCLR, CFIFOCTR); + } + pipe_irq_disable(r8a66597, td->pipenum); + pipe_start(r8a66597, td->pipe); + pipe_irq_enable(r8a66597, urb, td->pipenum); + } else { + if (urb->actual_length == 0) { + pipe_irq_disable(r8a66597, td->pipenum); + pipe_setting(r8a66597, td); + pipe_stop(r8a66597, td->pipe); + r8a66597_write(r8a66597, (u16)~(1 << td->pipenum), + BRDYSTS); + + if (td->pipe->pipetre) { + r8a66597_write(r8a66597, TRCLR, + td->pipe->pipetre); + r8a66597_write(r8a66597, + (urb->transfer_buffer_length + + td->maxpacket - 1) + / td->maxpacket, + td->pipe->pipetrn); + r8a66597_bset(r8a66597, TRENB, + td->pipe->pipetre); + } + + pipe_start(r8a66597, td->pipe); + pipe_irq_enable(r8a66597, urb, td->pipenum); + } + } +} + +/* this function must be called with interrupt disabled */ +static void prepare_packet_write(struct r8a66597 *r8a66597, + struct r8a66597_td *td) +{ + u16 tmp; + struct urb *urb = td->urb; + + if (usb_pipecontrol(urb->pipe)) { + pipe_stop(r8a66597, td->pipe); + r8a66597_bset(r8a66597, R8A66597_DIR, DCPCFG); + r8a66597_mdfy(r8a66597, ISEL, ISEL | CURPIPE, CFIFOSEL); + r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); + if (urb->actual_length == 0) { + r8a66597_pipe_toggle(r8a66597, td->pipe, 1); + r8a66597_write(r8a66597, BCLR, CFIFOCTR); + } + } else { + if (urb->actual_length == 0) + pipe_setting(r8a66597, td); + if (td->pipe->pipetre) + r8a66597_bclr(r8a66597, TRENB, td->pipe->pipetre); + } + r8a66597_write(r8a66597, (u16)~(1 << td->pipenum), BRDYSTS); + + fifo_change_from_pipe(r8a66597, td->pipe); + tmp = r8a66597_read(r8a66597, td->pipe->fifoctr); + if (unlikely((tmp & FRDY) == 0)) + pipe_irq_enable(r8a66597, urb, td->pipenum); + else + packet_write(r8a66597, td->pipenum); + pipe_start(r8a66597, td->pipe); +} + +/* this function must be called with interrupt disabled */ +static void prepare_status_packet(struct r8a66597 *r8a66597, + struct r8a66597_td *td) +{ + struct urb *urb = td->urb; + + r8a66597_pipe_toggle(r8a66597, td->pipe, 1); + + if (urb->setup_packet[0] & USB_ENDPOINT_DIR_MASK) { + r8a66597_bset(r8a66597, R8A66597_DIR, DCPCFG); + r8a66597_mdfy(r8a66597, ISEL, ISEL | CURPIPE, CFIFOSEL); + r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); + r8a66597_write(r8a66597, BVAL | BCLR, CFIFOCTR); + r8a66597_write(r8a66597, (u16)~BEMP0, BEMPSTS); + enable_irq_empty(r8a66597, 0); + } else { + r8a66597_bclr(r8a66597, R8A66597_DIR, DCPCFG); + r8a66597_mdfy(r8a66597, 0, ISEL | CURPIPE, CFIFOSEL); + r8a66597_reg_wait(r8a66597, CFIFOSEL, CURPIPE, 0); + r8a66597_write(r8a66597, BCLR, CFIFOCTR); + r8a66597_write(r8a66597, (u16)~BRDY0, BRDYSTS); + r8a66597_write(r8a66597, (u16)~BEMP0, BEMPSTS); + enable_irq_ready(r8a66597, 0); + } + enable_irq_nrdy(r8a66597, 0); + pipe_start(r8a66597, td->pipe); +} + +/* this function must be called with interrupt disabled */ +static int start_transfer(struct r8a66597 *r8a66597, struct r8a66597_td *td) +{ + BUG_ON(!td); + + switch (td->type) { + case USB_PID_SETUP: + if (td->urb->setup_packet[1] == USB_REQ_SET_ADDRESS) { + td->set_address = 1; + td->urb->setup_packet[2] = alloc_usb_address(r8a66597, + td->urb); + if (td->urb->setup_packet[2] == 0) + return -EPIPE; + } + prepare_setup_packet(r8a66597, td); + break; + case USB_PID_IN: + prepare_packet_read(r8a66597, td); + break; + case USB_PID_OUT: + prepare_packet_write(r8a66597, td); + break; + case USB_PID_ACK: + prepare_status_packet(r8a66597, td); + break; + default: + err("invalid type."); + break; + } + + return 0; +} + +static int check_transfer_finish(struct r8a66597_td *td, struct urb *urb) +{ + if (usb_pipeisoc(urb->pipe)) { + if (urb->number_of_packets == td->iso_cnt) + return 1; + } + + /* control or bulk or interrupt */ + if ((urb->transfer_buffer_length <= urb->actual_length) || + (td->short_packet) || (td->zero_packet)) + return 1; + + return 0; +} + +/* this function must be called with interrupt disabled */ +static void set_td_timer(struct r8a66597 *r8a66597, struct r8a66597_td *td) +{ + unsigned long time; + + BUG_ON(!td); + + if (!list_empty(&r8a66597->pipe_queue[td->pipenum]) && + !usb_pipecontrol(td->urb->pipe) && usb_pipein(td->urb->pipe)) { + r8a66597->timeout_map |= 1 << td->pipenum; + switch (usb_pipetype(td->urb->pipe)) { + case PIPE_INTERRUPT: + case PIPE_ISOCHRONOUS: + time = 30; + break; + default: + time = 300; + break; + } + + mod_timer(&r8a66597->td_timer[td->pipenum], + jiffies + msecs_to_jiffies(time)); + } +} + +/* this function must be called with interrupt disabled */ +static void done(struct r8a66597 *r8a66597, struct r8a66597_td *td, + u16 pipenum, struct urb *urb) +{ + int restart = 0; + struct usb_hcd *hcd = r8a66597_to_hcd(r8a66597); + + r8a66597->timeout_map &= ~(1 << pipenum); + + if (likely(td)) { + if (td->set_address && urb->status != 0) + r8a66597->address_map &= ~(1 << urb->setup_packet[2]); + + pipe_toggle_save(r8a66597, td->pipe, urb); + list_del(&td->queue); + kfree(td); + } + + if (!list_empty(&r8a66597->pipe_queue[pipenum])) + restart = 1; + + if (likely(urb)) { + if (usb_pipeisoc(urb->pipe)) + urb->start_frame = r8a66597_get_frame(hcd); + + urb->hcpriv = NULL; + spin_unlock(&r8a66597->lock); + usb_hcd_giveback_urb(hcd, urb); + spin_lock(&r8a66597->lock); + } + + if (restart) { + td = r8a66597_get_td(r8a66597, pipenum); + if (unlikely(!td)) + return; + + start_transfer(r8a66597, td); + set_td_timer(r8a66597, td); + } +} + +/* this function must be called with interrupt disabled */ +static void finish_request(struct r8a66597 *r8a66597, struct r8a66597_td *td, + u16 pipenum, struct urb *urb) +__releases(r8a66597->lock) __acquires(r8a66597->lock) +{ + done(r8a66597, td, pipenum, urb); +} + +static void packet_read(struct r8a66597 *r8a66597, u16 pipenum) +{ + u16 tmp; + int rcv_len, bufsize, urb_len, size; + u16 *buf; + struct r8a66597_td *td = r8a66597_get_td(r8a66597, pipenum); + struct urb *urb; + int finish = 0; + + if (unlikely(!td)) + return; + urb = td->urb; + + fifo_change_from_pipe(r8a66597, td->pipe); + tmp = r8a66597_read(r8a66597, td->pipe->fifoctr); + if (unlikely((tmp & FRDY) == 0)) { + urb->status = -EPIPE; + pipe_stop(r8a66597, td->pipe); + pipe_irq_disable(r8a66597, pipenum); + err("in fifo not ready (%d)", pipenum); + finish_request(r8a66597, td, pipenum, td->urb); + return; + } + + /* prepare parameters */ + rcv_len = tmp & DTLN; + bufsize = td->maxpacket; + if (usb_pipeisoc(urb->pipe)) { + buf = (u16 *)(urb->transfer_buffer + + urb->iso_frame_desc[td->iso_cnt].offset); + urb_len = urb->iso_frame_desc[td->iso_cnt].length; + } else { + buf = (void *)urb->transfer_buffer + urb->actual_length; + urb_len = urb->transfer_buffer_length - urb->actual_length; + } + if (rcv_len < bufsize) + size = min(rcv_len, urb_len); + else + size = min(bufsize, urb_len); + + /* update parameters */ + urb->actual_length += size; + if (rcv_len == 0) + td->zero_packet = 1; + if ((size % td->maxpacket) > 0) { + td->short_packet = 1; + if (urb->transfer_buffer_length != urb->actual_length && + urb->transfer_flags & URB_SHORT_NOT_OK) + td->urb->status = -EREMOTEIO; + } + if (usb_pipeisoc(urb->pipe)) { + urb->iso_frame_desc[td->iso_cnt].actual_length = size; + urb->iso_frame_desc[td->iso_cnt].status = 0; + td->iso_cnt++; + } + + /* check transfer finish */ + if (check_transfer_finish(td, urb)) { + pipe_stop(r8a66597, td->pipe); + pipe_irq_disable(r8a66597, pipenum); + finish = 1; + } + + /* read fifo */ + if (urb->transfer_buffer) { + if (size == 0) + r8a66597_write(r8a66597, BCLR, td->pipe->fifoctr); + else + r8a66597_read_fifo(r8a66597, td->pipe->fifoaddr, + buf, size); + } + + if (finish && pipenum != 0) { + if (td->urb->status == -EINPROGRESS) + td->urb->status = 0; + finish_request(r8a66597, td, pipenum, urb); + } +} + +static void packet_write(struct r8a66597 *r8a66597, u16 pipenum) +{ + u16 tmp; + int bufsize, size; + u16 *buf; + struct r8a66597_td *td = r8a66597_get_td(r8a66597, pipenum); + struct urb *urb; + + if (unlikely(!td)) + return; + urb = td->urb; + + fifo_change_from_pipe(r8a66597, td->pipe); + tmp = r8a66597_read(r8a66597, td->pipe->fifoctr); + if (unlikely((tmp & FRDY) == 0)) { + urb->status = -EPIPE; + pipe_stop(r8a66597, td->pipe); + pipe_irq_disable(r8a66597, pipenum); + err("out write fifo not ready. (%d)", pipenum); + finish_request(r8a66597, td, pipenum, td->urb); + return; + } + + /* prepare parameters */ + bufsize = td->maxpacket; + if (usb_pipeisoc(urb->pipe)) { + buf = (u16 *)(urb->transfer_buffer + + urb->iso_frame_desc[td->iso_cnt].offset); + size = min(bufsize, + (int)urb->iso_frame_desc[td->iso_cnt].length); + } else { + buf = (u16 *)(urb->transfer_buffer + urb->actual_length); + size = min((int)bufsize, + urb->transfer_buffer_length - urb->actual_length); + } + + /* write fifo */ + if (pipenum > 0) + r8a66597_write(r8a66597, (u16)~(1 << pipenum), BEMPSTS); + if (urb->transfer_buffer) { + r8a66597_write_fifo(r8a66597, td->pipe->fifoaddr, buf, size); + if (!usb_pipebulk(urb->pipe) || td->maxpacket != size) + r8a66597_write(r8a66597, BVAL, td->pipe->fifoctr); + } + + /* update parameters */ + urb->actual_length += size; + if (usb_pipeisoc(urb->pipe)) { + urb->iso_frame_desc[td->iso_cnt].actual_length = size; + urb->iso_frame_desc[td->iso_cnt].status = 0; + td->iso_cnt++; + } + + /* check transfer finish */ + if (check_transfer_finish(td, urb)) { + disable_irq_ready(r8a66597, pipenum); + enable_irq_empty(r8a66597, pipenum); + if (!usb_pipeisoc(urb->pipe)) + enable_irq_nrdy(r8a66597, pipenum); + } else + pipe_irq_enable(r8a66597, urb, pipenum); +} + + +static void check_next_phase(struct r8a66597 *r8a66597) +{ + struct r8a66597_td *td = r8a66597_get_td(r8a66597, 0); + struct urb *urb; + u8 finish = 0; + + if (unlikely(!td)) + return; + urb = td->urb; + + switch (td->type) { + case USB_PID_IN: + case USB_PID_OUT: + if (urb->status != -EINPROGRESS) { + finish = 1; + break; + } + if (check_transfer_finish(td, urb)) + td->type = USB_PID_ACK; + break; + case USB_PID_SETUP: + if (urb->status != -EINPROGRESS) + finish = 1; + else if (urb->transfer_buffer_length == urb->actual_length) { + td->type = USB_PID_ACK; + urb->status = 0; + } else if (usb_pipeout(urb->pipe)) + td->type = USB_PID_OUT; + else + td->type = USB_PID_IN; + break; + case USB_PID_ACK: + finish = 1; + if (urb->status == -EINPROGRESS) + urb->status = 0; + break; + } + + if (finish) + finish_request(r8a66597, td, 0, urb); + else + start_transfer(r8a66597, td); +} + +static void set_urb_error(struct r8a66597 *r8a66597, u16 pipenum) +{ + struct r8a66597_td *td = r8a66597_get_td(r8a66597, pipenum); + + if (td && td->urb) { + u16 pid = r8a66597_read(r8a66597, td->pipe->pipectr) & PID; + + if (pid == PID_NAK) + td->urb->status = -ECONNRESET; + else + td->urb->status = -EPIPE; + } +} + +static void irq_pipe_ready(struct r8a66597 *r8a66597) +{ + u16 check; + u16 pipenum; + u16 mask; + struct r8a66597_td *td; + + mask = r8a66597_read(r8a66597, BRDYSTS) + & r8a66597_read(r8a66597, BRDYENB); + r8a66597_write(r8a66597, (u16)~mask, BRDYSTS); + if (mask & BRDY0) { + td = r8a66597_get_td(r8a66597, 0); + if (td && td->type == USB_PID_IN) + packet_read(r8a66597, 0); + else + pipe_irq_disable(r8a66597, 0); + check_next_phase(r8a66597); + } + + for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + check = 1 << pipenum; + if (mask & check) { + td = r8a66597_get_td(r8a66597, pipenum); + if (unlikely(!td)) + continue; + + if (td->type == USB_PID_IN) + packet_read(r8a66597, pipenum); + else if (td->type == USB_PID_OUT) + packet_write(r8a66597, pipenum); + } + } +} + +static void irq_pipe_empty(struct r8a66597 *r8a66597) +{ + u16 tmp; + u16 check; + u16 pipenum; + u16 mask; + struct r8a66597_td *td; + + mask = r8a66597_read(r8a66597, BEMPSTS) + & r8a66597_read(r8a66597, BEMPENB); + r8a66597_write(r8a66597, (u16)~mask, BEMPSTS); + if (mask & BEMP0) { + cfifo_change(r8a66597, 0); + td = r8a66597_get_td(r8a66597, 0); + if (td && td->type != USB_PID_OUT) + disable_irq_empty(r8a66597, 0); + check_next_phase(r8a66597); + } + + for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + check = 1 << pipenum; + if (mask & check) { + struct r8a66597_td *td; + td = r8a66597_get_td(r8a66597, pipenum); + if (unlikely(!td)) + continue; + + tmp = r8a66597_read(r8a66597, td->pipe->pipectr); + if ((tmp & INBUFM) == 0) { + disable_irq_empty(r8a66597, pipenum); + pipe_irq_disable(r8a66597, pipenum); + if (td->urb->status == -EINPROGRESS) + td->urb->status = 0; + finish_request(r8a66597, td, pipenum, td->urb); + } + } + } +} + +static void irq_pipe_nrdy(struct r8a66597 *r8a66597) +{ + u16 check; + u16 pipenum; + u16 mask; + + mask = r8a66597_read(r8a66597, NRDYSTS) + & r8a66597_read(r8a66597, NRDYENB); + r8a66597_write(r8a66597, (u16)~mask, NRDYSTS); + if (mask & NRDY0) { + cfifo_change(r8a66597, 0); + set_urb_error(r8a66597, 0); + pipe_irq_disable(r8a66597, 0); + check_next_phase(r8a66597); + } + + for (pipenum = 1; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + check = 1 << pipenum; + if (mask & check) { + struct r8a66597_td *td; + td = r8a66597_get_td(r8a66597, pipenum); + if (unlikely(!td)) + continue; + + set_urb_error(r8a66597, pipenum); + pipe_irq_disable(r8a66597, pipenum); + pipe_stop(r8a66597, td->pipe); + finish_request(r8a66597, td, pipenum, td->urb); + } + } +} + +static void start_root_hub_sampling(struct r8a66597 *r8a66597, int port) +{ + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + + rh->old_syssts = r8a66597_read(r8a66597, get_syssts_reg(port)) & LNST; + rh->scount = R8A66597_MAX_SAMPLING; + mod_timer(&r8a66597->rh_timer, jiffies + msecs_to_jiffies(50)); +} + +static irqreturn_t r8a66597_irq(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + u16 intsts0, intsts1, intsts2; + u16 intenb0, intenb1, intenb2; + u16 mask0, mask1, mask2; + + spin_lock(&r8a66597->lock); + + intsts0 = r8a66597_read(r8a66597, INTSTS0); + intsts1 = r8a66597_read(r8a66597, INTSTS1); + intsts2 = r8a66597_read(r8a66597, INTSTS2); + intenb0 = r8a66597_read(r8a66597, INTENB0); + intenb1 = r8a66597_read(r8a66597, INTENB1); + intenb2 = r8a66597_read(r8a66597, INTENB2); + + mask2 = intsts2 & intenb2; + mask1 = intsts1 & intenb1; + mask0 = intsts0 & intenb0 & (BEMP | NRDY | BRDY); + if (mask2) { + if (mask2 & ATTCH) { + r8a66597_write(r8a66597, (u16)~ATTCH, INTSTS2); + r8a66597_bclr(r8a66597, ATTCHE, INTENB2); + + /* start usb bus sampling */ + start_root_hub_sampling(r8a66597, 1); + } + if (mask2 & DTCH) { + r8a66597_write(r8a66597, (u16)~DTCH, INTSTS2); + r8a66597_bclr(r8a66597, DTCHE, INTENB2); + r8a66597_usb_disconnect(r8a66597, 1); + } + } + + if (mask1) { + if (mask1 & ATTCH) { + r8a66597_write(r8a66597, (u16)~ATTCH, INTSTS1); + r8a66597_bclr(r8a66597, ATTCHE, INTENB1); + + /* start usb bus sampling */ + start_root_hub_sampling(r8a66597, 0); + } + if (mask1 & DTCH) { + r8a66597_write(r8a66597, (u16)~DTCH, INTSTS1); + r8a66597_bclr(r8a66597, DTCHE, INTENB1); + r8a66597_usb_disconnect(r8a66597, 0); + } + if (mask1 & SIGN) { + r8a66597_write(r8a66597, (u16)~SIGN, INTSTS1); + set_urb_error(r8a66597, 0); + check_next_phase(r8a66597); + } + if (mask1 & SACK) { + r8a66597_write(r8a66597, (u16)~SACK, INTSTS1); + check_next_phase(r8a66597); + } + } + if (mask0) { + if (mask0 & BRDY) + irq_pipe_ready(r8a66597); + if (mask0 & BEMP) + irq_pipe_empty(r8a66597); + if (mask0 & NRDY) + irq_pipe_nrdy(r8a66597); + } + + spin_unlock(&r8a66597->lock); + return IRQ_HANDLED; +} + +/* this function must be called with interrupt disabled */ +static void r8a66597_root_hub_control(struct r8a66597 *r8a66597, int port) +{ + u16 tmp; + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + + if (rh->port & (1 << USB_PORT_FEAT_RESET)) { + unsigned long dvstctr_reg = get_dvstctr_reg(port); + + tmp = r8a66597_read(r8a66597, dvstctr_reg); + if ((tmp & USBRST) == USBRST) { + r8a66597_mdfy(r8a66597, UACT, USBRST | UACT, + dvstctr_reg); + mod_timer(&r8a66597->rh_timer, + jiffies + msecs_to_jiffies(50)); + } else + r8a66597_usb_connect(r8a66597, port); + } + + if (rh->scount > 0) { + tmp = r8a66597_read(r8a66597, get_syssts_reg(port)) & LNST; + if (tmp == rh->old_syssts) { + rh->scount--; + if (rh->scount == 0) { + if (tmp == FS_JSTS) { + r8a66597_bset(r8a66597, HSE, + get_syscfg_reg(port)); + r8a66597_usb_preconnect(r8a66597, port); + } else if (tmp == LS_JSTS) { + r8a66597_bclr(r8a66597, HSE, + get_syscfg_reg(port)); + r8a66597_usb_preconnect(r8a66597, port); + } else if (tmp == SE0) + r8a66597_bset(r8a66597, ATTCHE, + get_intenb_reg(port)); + } else { + mod_timer(&r8a66597->rh_timer, + jiffies + msecs_to_jiffies(50)); + } + } else { + rh->scount = R8A66597_MAX_SAMPLING; + rh->old_syssts = tmp; + mod_timer(&r8a66597->rh_timer, + jiffies + msecs_to_jiffies(50)); + } + } +} + +static void r8a66597_td_timer(unsigned long _r8a66597) +{ + struct r8a66597 *r8a66597 = (struct r8a66597 *)_r8a66597; + unsigned long flags; + u16 pipenum; + struct r8a66597_td *td, *new_td = NULL; + struct r8a66597_pipe *pipe; + + spin_lock_irqsave(&r8a66597->lock, flags); + for (pipenum = 0; pipenum < R8A66597_MAX_NUM_PIPE; pipenum++) { + if (!(r8a66597->timeout_map & (1 << pipenum))) + continue; + if (timer_pending(&r8a66597->td_timer[pipenum])) + continue; + + td = r8a66597_get_td(r8a66597, pipenum); + if (!td) { + r8a66597->timeout_map &= ~(1 << pipenum); + continue; + } + + if (td->urb->actual_length) { + set_td_timer(r8a66597, td); + break; + } + + pipe = td->pipe; + pipe_stop(r8a66597, pipe); + + new_td = td; + do { + list_move_tail(&new_td->queue, + &r8a66597->pipe_queue[pipenum]); + new_td = r8a66597_get_td(r8a66597, pipenum); + if (!new_td) { + new_td = td; + break; + } + } while (td != new_td && td->address == new_td->address); + + start_transfer(r8a66597, new_td); + + if (td == new_td) + r8a66597->timeout_map &= ~(1 << pipenum); + else + set_td_timer(r8a66597, new_td); + break; + } + spin_unlock_irqrestore(&r8a66597->lock, flags); +} + +static void r8a66597_timer(unsigned long _r8a66597) +{ + struct r8a66597 *r8a66597 = (struct r8a66597 *)_r8a66597; + unsigned long flags; + + spin_lock_irqsave(&r8a66597->lock, flags); + + r8a66597_root_hub_control(r8a66597, 0); + r8a66597_root_hub_control(r8a66597, 1); + + spin_unlock_irqrestore(&r8a66597->lock, flags); +} + +static int check_pipe_config(struct r8a66597 *r8a66597, struct urb *urb) +{ + struct r8a66597_device *dev = get_urb_to_r8a66597_dev(r8a66597, urb); + + if (dev && dev->address && dev->state != USB_STATE_CONFIGURED && + (urb->dev->state == USB_STATE_CONFIGURED)) + return 1; + else + return 0; +} + +static int r8a66597_start(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + int ret; + + hcd->state = HC_STATE_RUNNING; + if ((ret = enable_controller(r8a66597)) < 0) + return ret; + + return 0; +} + +static void r8a66597_stop(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + + disable_controller(r8a66597); +} + +static void set_address_zero(struct r8a66597 *r8a66597, struct urb *urb) +{ + unsigned int usb_address = usb_pipedevice(urb->pipe); + u16 root_port, hub_port; + + if (usb_address == 0) { + get_port_number(urb->dev->devpath, + &root_port, &hub_port); + set_devadd_reg(r8a66597, 0, + get_r8a66597_usb_speed(urb->dev->speed), + get_parent_r8a66597_address(r8a66597, urb->dev), + hub_port, root_port); + } +} + +static struct r8a66597_td *r8a66597_make_td(struct r8a66597 *r8a66597, + struct urb *urb, + struct usb_host_endpoint *hep, + gfp_t mem_flags) +{ + struct r8a66597_td *td; + u16 pipenum; + + td = kzalloc(sizeof(struct r8a66597_td), mem_flags); + if (td == NULL) + return NULL; + + pipenum = r8a66597_get_pipenum(urb, hep); + td->pipenum = pipenum; + td->pipe = hep->hcpriv; + td->urb = urb; + td->address = get_urb_to_r8a66597_addr(r8a66597, urb); + td->maxpacket = usb_maxpacket(urb->dev, urb->pipe, + !usb_pipein(urb->pipe)); + if (usb_pipecontrol(urb->pipe)) + td->type = USB_PID_SETUP; + else if (usb_pipein(urb->pipe)) + td->type = USB_PID_IN; + else + td->type = USB_PID_OUT; + INIT_LIST_HEAD(&td->queue); + + return td; +} + +static int r8a66597_urb_enqueue(struct usb_hcd *hcd, + struct usb_host_endpoint *hep, + struct urb *urb, + gfp_t mem_flags) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + struct r8a66597_td *td = NULL; + int ret = 0, request = 0; + unsigned long flags; + + spin_lock_irqsave(&r8a66597->lock, flags); + if (!get_urb_to_r8a66597_dev(r8a66597, urb)) { + ret = -ENODEV; + goto error; + } + + if (!hep->hcpriv) { + hep->hcpriv = kzalloc(sizeof(struct r8a66597_pipe), mem_flags); + if (!hep->hcpriv) { + ret = -ENOMEM; + goto error; + } + set_pipe_reg_addr(hep->hcpriv, R8A66597_PIPE_NO_DMA); + if (usb_pipeendpoint(urb->pipe)) + init_pipe_info(r8a66597, urb, hep, &hep->desc); + } + + if (unlikely(check_pipe_config(r8a66597, urb))) + init_pipe_config(r8a66597, urb); + + set_address_zero(r8a66597, urb); + td = r8a66597_make_td(r8a66597, urb, hep, mem_flags); + if (td == NULL) { + ret = -ENOMEM; + goto error; + } + if (list_empty(&r8a66597->pipe_queue[td->pipenum])) + request = 1; + list_add_tail(&td->queue, &r8a66597->pipe_queue[td->pipenum]); + + spin_lock(&urb->lock); + if (urb->status != -EINPROGRESS) { + spin_unlock(&urb->lock); + ret = -EPIPE; + goto error; + } + urb->hcpriv = td; + spin_unlock(&urb->lock); + + if (request) { + ret = start_transfer(r8a66597, td); + if (ret < 0) { + list_del(&td->queue); + kfree(td); + } + } else + set_td_timer(r8a66597, td); + +error: + spin_unlock_irqrestore(&r8a66597->lock, flags); + return ret; +} + +static int r8a66597_urb_dequeue(struct usb_hcd *hcd, struct urb *urb) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + struct r8a66597_td *td; + unsigned long flags; + + spin_lock_irqsave(&r8a66597->lock, flags); + if (urb->hcpriv) { + td = urb->hcpriv; + pipe_stop(r8a66597, td->pipe); + pipe_irq_disable(r8a66597, td->pipenum); + disable_irq_empty(r8a66597, td->pipenum); + done(r8a66597, td, td->pipenum, urb); + } + spin_unlock_irqrestore(&r8a66597->lock, flags); + return 0; +} + +static void r8a66597_endpoint_disable(struct usb_hcd *hcd, + struct usb_host_endpoint *hep) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + struct r8a66597_pipe *pipe = (struct r8a66597_pipe *)hep->hcpriv; + struct r8a66597_td *td; + struct urb *urb = NULL; + u16 pipenum; + unsigned long flags; + + if (pipe == NULL) + return; + pipenum = pipe->info.pipenum; + + if (pipenum == 0) { + kfree(hep->hcpriv); + hep->hcpriv = NULL; + return; + } + + spin_lock_irqsave(&r8a66597->lock, flags); + pipe_stop(r8a66597, pipe); + pipe_irq_disable(r8a66597, pipenum); + disable_irq_empty(r8a66597, pipenum); + td = r8a66597_get_td(r8a66597, pipenum); + if (td) + urb = td->urb; + done(r8a66597, td, pipenum, urb); + kfree(hep->hcpriv); + hep->hcpriv = NULL; + spin_unlock_irqrestore(&r8a66597->lock, flags); +} + +static int r8a66597_get_frame(struct usb_hcd *hcd) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + return r8a66597_read(r8a66597, FRMNUM) & 0x03FF; +} + +static void collect_usb_address_map(struct usb_device *udev, unsigned long *map) +{ + int chix; + + if (udev->state == USB_STATE_CONFIGURED && + udev->parent && udev->parent->devnum > 1 && + udev->parent->descriptor.bDeviceClass == USB_CLASS_HUB) + map[udev->devnum/32] |= (1 << (udev->devnum % 32)); + + for (chix = 0; chix < udev->maxchild; chix++) { + struct usb_device *childdev = udev->children[chix]; + + if (childdev) + collect_usb_address_map(childdev, map); + } +} + +/* this function must be called with interrupt disabled */ +static struct r8a66597_device *get_r8a66597_device(struct r8a66597 *r8a66597, + int addr) +{ + struct r8a66597_device *dev; + struct list_head *list = &r8a66597->child_device; + + list_for_each_entry(dev, list, device_list) { + if (!dev) + continue; + if (dev->usb_address != addr) + continue; + + return dev; + } + + err("get_r8a66597_device fail.(%d)\n", addr); + return NULL; +} + +static void update_usb_address_map(struct r8a66597 *r8a66597, + struct usb_device *root_hub, + unsigned long *map) +{ + int i, j, addr; + unsigned long diff; + unsigned long flags; + + for (i = 0; i < 4; i++) { + diff = r8a66597->child_connect_map[i] ^ map[i]; + if (!diff) + continue; + + for (j = 0; j < 32; j++) { + if (!(diff & (1 << j))) + continue; + + addr = i * 32 + j; + if (map[i] & (1 << j)) + set_child_connect_map(r8a66597, addr); + else { + struct r8a66597_device *dev; + + spin_lock_irqsave(&r8a66597->lock, flags); + dev = get_r8a66597_device(r8a66597, addr); + disable_r8a66597_pipe_all(r8a66597, dev); + free_usb_address(r8a66597, dev); + put_child_connect_map(r8a66597, addr); + spin_unlock_irqrestore(&r8a66597->lock, flags); + } + } + } +} + +static void r8a66597_check_detect_child(struct r8a66597 *r8a66597, + struct usb_hcd *hcd) +{ + struct usb_bus *bus; + unsigned long now_map[4]; + + memset(now_map, 0, sizeof(now_map)); + + list_for_each_entry(bus, &usb_bus_list, bus_list) { + if (!bus->root_hub) + continue; + + if (bus->busnum != hcd->self.busnum) + continue; + + collect_usb_address_map(bus->root_hub, now_map); + update_usb_address_map(r8a66597, bus->root_hub, now_map); + } +} + +static int r8a66597_hub_status_data(struct usb_hcd *hcd, char *buf) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + unsigned long flags; + int i; + + r8a66597_check_detect_child(r8a66597, hcd); + + spin_lock_irqsave(&r8a66597->lock, flags); + + *buf = 0; /* initialize (no change) */ + + for (i = 0; i < R8A66597_MAX_ROOT_HUB; i++) { + if (r8a66597->root_hub[i].port & 0xffff0000) + *buf |= 1 << (i + 1); + } + + spin_unlock_irqrestore(&r8a66597->lock, flags); + + return (*buf != 0); +} + +static void r8a66597_hub_descriptor(struct r8a66597 *r8a66597, + struct usb_hub_descriptor *desc) +{ + desc->bDescriptorType = 0x29; + desc->bHubContrCurrent = 0; + desc->bNbrPorts = R8A66597_MAX_ROOT_HUB; + desc->bDescLength = 9; + desc->bPwrOn2PwrGood = 0; + desc->wHubCharacteristics = cpu_to_le16(0x0011); + desc->bitmap[0] = ((1 << R8A66597_MAX_ROOT_HUB) - 1) << 1; + desc->bitmap[1] = ~0; +} + +static int r8a66597_hub_control(struct usb_hcd *hcd, u16 typeReq, u16 wValue, + u16 wIndex, char *buf, u16 wLength) +{ + struct r8a66597 *r8a66597 = hcd_to_r8a66597(hcd); + int ret; + int port = (wIndex & 0x00FF) - 1; + struct r8a66597_root_hub *rh = &r8a66597->root_hub[port]; + unsigned long flags; + + ret = 0; + + spin_lock_irqsave(&r8a66597->lock, flags); + switch (typeReq) { + case ClearHubFeature: + case SetHubFeature: + switch (wValue) { + case C_HUB_OVER_CURRENT: + case C_HUB_LOCAL_POWER: + break; + default: + goto error; + } + break; + case ClearPortFeature: + if (wIndex > R8A66597_MAX_ROOT_HUB) + goto error; + if (wLength != 0) + goto error; + + switch (wValue) { + case USB_PORT_FEAT_ENABLE: + rh->port &= (1 << USB_PORT_FEAT_POWER); + break; + case USB_PORT_FEAT_SUSPEND: + break; + case USB_PORT_FEAT_POWER: + r8a66597_port_power(r8a66597, port, 0); + break; + case USB_PORT_FEAT_C_ENABLE: + case USB_PORT_FEAT_C_SUSPEND: + case USB_PORT_FEAT_C_CONNECTION: + case USB_PORT_FEAT_C_OVER_CURRENT: + case USB_PORT_FEAT_C_RESET: + break; + default: + goto error; + } + rh->port &= ~(1 << wValue); + break; + case GetHubDescriptor: + r8a66597_hub_descriptor(r8a66597, + (struct usb_hub_descriptor *)buf); + break; + case GetHubStatus: + *buf = 0x00; + break; + case GetPortStatus: + if (wIndex > R8A66597_MAX_ROOT_HUB) + goto error; + *(u32 *)buf = rh->port; + break; + case SetPortFeature: + if (wIndex > R8A66597_MAX_ROOT_HUB) + goto error; + if (wLength != 0) + goto error; + + switch (wValue) { + case USB_PORT_FEAT_SUSPEND: + break; + case USB_PORT_FEAT_POWER: + r8a66597_port_power(r8a66597, port, 1); + rh->port |= (1 << USB_PORT_FEAT_POWER); + break; + case USB_PORT_FEAT_RESET: { + struct r8a66597_device *dev = rh->dev; + + rh->port |= (1 << USB_PORT_FEAT_RESET); + + disable_r8a66597_pipe_all(r8a66597, dev); + free_usb_address(r8a66597, dev); + + r8a66597_mdfy(r8a66597, USBRST, USBRST | UACT, + get_dvstctr_reg(port)); + mod_timer(&r8a66597->rh_timer, + jiffies + msecs_to_jiffies(50)); + } + break; + default: + goto error; + } + rh->port |= 1 << wValue; + break; + default: +error: + ret = -EPIPE; + break; + } + + spin_unlock_irqrestore(&r8a66597->lock, flags); + return ret; +} + +static struct hc_driver r8a66597_hc_driver = { + .description = hcd_name, + .hcd_priv_size = sizeof(struct r8a66597), + .irq = r8a66597_irq, + + /* + * generic hardware linkage + */ + .flags = HCD_USB2, + + .start = r8a66597_start, + .stop = r8a66597_stop, + + /* + * managing i/o requests and associated device resources + */ + .urb_enqueue = r8a66597_urb_enqueue, + .urb_dequeue = r8a66597_urb_dequeue, + .endpoint_disable = r8a66597_endpoint_disable, + + /* + * periodic schedule support + */ + .get_frame_number = r8a66597_get_frame, + + /* + * root hub support + */ + .hub_status_data = r8a66597_hub_status_data, + .hub_control = r8a66597_hub_control, +}; + +#if defined(CONFIG_PM) +static int r8a66597_suspend(struct platform_device *pdev, pm_message_t state) +{ + pdev->dev.power.power_state = state; + return 0; +} + +static int r8a66597_resume(struct platform_device *pdev) +{ + pdev->dev.power.power_state = PMSG_ON; + return 0; +} +#else /* if defined(CONFIG_PM) */ +#define r8a66597_suspend NULL +#define r8a66597_resume NULL +#endif + +static int __init_or_module r8a66597_remove(struct platform_device *pdev) +{ + struct r8a66597 *r8a66597 = dev_get_drvdata(&pdev->dev); + struct usb_hcd *hcd = r8a66597_to_hcd(r8a66597); + + del_timer_sync(&r8a66597->rh_timer); + iounmap((void *)r8a66597->reg); + usb_remove_hcd(hcd); + usb_put_hcd(hcd); + return 0; +} + +#define resource_len(r) (((r)->end - (r)->start) + 1) +static int __init r8a66597_probe(struct platform_device *pdev) +{ + struct resource *res = NULL; + int irq = -1; + void __iomem *reg = NULL; + struct usb_hcd *hcd = NULL; + struct r8a66597 *r8a66597; + int ret = 0; + int i; + + if (pdev->dev.dma_mask) { + ret = -EINVAL; + err("dma not support"); + goto clean_up; + } + + res = platform_get_resource_byname(pdev, IORESOURCE_MEM, + (char *)hcd_name); + if (!res) { + ret = -ENODEV; + err("platform_get_resource_byname error."); + goto clean_up; + } + + irq = platform_get_irq(pdev, 0); + if (irq < 0) { + ret = -ENODEV; + err("platform_get_irq error."); + goto clean_up; + } + + reg = ioremap(res->start, resource_len(res)); + if (reg == NULL) { + ret = -ENOMEM; + err("ioremap error."); + goto clean_up; + } + + /* initialize hcd */ + hcd = usb_create_hcd(&r8a66597_hc_driver, &pdev->dev, (char *)hcd_name); + if (!hcd) { + ret = -ENOMEM; + err("Failed to create hcd"); + goto clean_up; + } + r8a66597 = hcd_to_r8a66597(hcd); + memset(r8a66597, 0, sizeof(struct r8a66597)); + dev_set_drvdata(&pdev->dev, r8a66597); + + spin_lock_init(&r8a66597->lock); + init_timer(&r8a66597->rh_timer); + r8a66597->rh_timer.function = r8a66597_timer; + r8a66597->rh_timer.data = (unsigned long)r8a66597; + r8a66597->reg = (unsigned long)reg; + + for (i = 0; i < R8A66597_MAX_NUM_PIPE; i++) { + INIT_LIST_HEAD(&r8a66597->pipe_queue[i]); + init_timer(&r8a66597->td_timer[i]); + r8a66597->td_timer[i].function = r8a66597_td_timer; + r8a66597->td_timer[i].data = (unsigned long)r8a66597; + } + INIT_LIST_HEAD(&r8a66597->child_device); + + hcd->rsrc_start = res->start; + ret = usb_add_hcd(hcd, irq, 0); + if (ret != 0) { + err("Failed to add hcd"); + goto clean_up; + } + + return 0; + +clean_up: + if (reg) + iounmap(reg); + if (res) + release_mem_region(res->start, 1); + + return ret; +} + +static struct platform_driver r8a66597_driver = { + .probe = r8a66597_probe, + .remove = r8a66597_remove, + .suspend = r8a66597_suspend, + .resume = r8a66597_resume, + .driver = { + .name = (char *) hcd_name, + }, +}; + +static int __init r8a66597_init(void) +{ + if (usb_disabled()) + return -ENODEV; + + info("driver %s, %s", hcd_name, DRIVER_VERSION); + return platform_driver_register(&r8a66597_driver); +} +module_init(r8a66597_init); + +static void __exit r8a66597_cleanup(void) +{ + platform_driver_unregister(&r8a66597_driver); +} +module_exit(r8a66597_cleanup); + diff --git a/drivers/usb/host/r8a66597.h b/drivers/usb/host/r8a66597.h new file mode 100644 index 00000000000..97c2a71ac7a --- /dev/null +++ b/drivers/usb/host/r8a66597.h @@ -0,0 +1,634 @@ +/* + * R8A66597 HCD (Host Controller Driver) + * + * Copyright (C) 2006-2007 Renesas Solutions Corp. + * Portions Copyright (C) 2004 Psion Teklogix (for NetBook PRO) + * Portions Copyright (C) 2004-2005 David Brownell + * Portions Copyright (C) 1999 Roman Weissgaerber + * + * Author : Yoshihiro Shimoda <shimoda.yoshihiro@renesas.com> + * + * 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; version 2 of the License. + * + * 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., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA + * + */ + +#ifndef __R8A66597_H__ +#define __R8A66597_H__ + +#define SYSCFG0 0x00 +#define SYSCFG1 0x02 +#define SYSSTS0 0x04 +#define SYSSTS1 0x06 +#define DVSTCTR0 0x08 +#define DVSTCTR1 0x0A +#define TESTMODE 0x0C +#define PINCFG 0x0E +#define DMA0CFG 0x10 +#define DMA1CFG 0x12 +#define CFIFO 0x14 +#define D0FIFO 0x18 +#define D1FIFO 0x1C +#define CFIFOSEL 0x20 +#define CFIFOCTR 0x22 +#define CFIFOSIE 0x24 +#define D0FIFOSEL 0x28 +#define D0FIFOCTR 0x2A +#define D1FIFOSEL 0x2C +#define D1FIFOCTR 0x2E +#define INTENB0 0x30 +#define INTENB1 0x32 +#define INTENB2 0x34 +#define BRDYENB 0x36 +#define NRDYENB 0x38 +#define BEMPENB 0x3A +#define SOFCFG 0x3C +#define INTSTS0 0x40 +#define INTSTS1 0x42 +#define INTSTS2 0x44 +#define BRDYSTS 0x46 +#define NRDYSTS 0x48 +#define BEMPSTS 0x4A +#define FRMNUM 0x4C +#define UFRMNUM 0x4E +#define USBADDR 0x50 +#define USBREQ 0x54 +#define USBVAL 0x56 +#define USBINDX 0x58 +#define USBLENG 0x5A +#define DCPCFG 0x5C +#define DCPMAXP 0x5E +#define DCPCTR 0x60 +#define PIPESEL 0x64 +#define PIPECFG 0x68 +#define PIPEBUF 0x6A +#define PIPEMAXP 0x6C +#define PIPEPERI 0x6E +#define PIPE1CTR 0x70 +#define PIPE2CTR 0x72 +#define PIPE3CTR 0x74 +#define PIPE4CTR 0x76 +#define PIPE5CTR 0x78 +#define PIPE6CTR 0x7A +#define PIPE7CTR 0x7C +#define PIPE8CTR 0x7E +#define PIPE9CTR 0x80 +#define PIPE1TRE 0x90 +#define PIPE1TRN 0x92 +#define PIPE2TRE 0x94 +#define PIPE2TRN 0x96 +#define PIPE3TRE 0x98 +#define PIPE3TRN 0x9A +#define PIPE4TRE 0x9C +#define PIPE4TRN 0x9E +#define PIPE5TRE 0xA0 +#define PIPE5TRN 0xA2 +#define DEVADD0 0xD0 +#define DEVADD1 0xD2 +#define DEVADD2 0xD4 +#define DEVADD3 0xD6 +#define DEVADD4 0xD8 +#define DEVADD5 0xDA +#define DEVADD6 0xDC +#define DEVADD7 0xDE +#define DEVADD8 0xE0 +#define DEVADD9 0xE2 +#define DEVADDA 0xE4 + +/* System Configuration Control Register */ +#define XTAL 0xC000 /* b15-14: Crystal selection */ +#define XTAL48 0x8000 /* 48MHz */ +#define XTAL24 0x4000 /* 24MHz */ +#define XTAL12 0x0000 /* 12MHz */ +#define XCKE 0x2000 /* b13: External clock enable */ +#define PLLC 0x0800 /* b11: PLL control */ +#define SCKE 0x0400 /* b10: USB clock enable */ +#define PCSDIS 0x0200 /* b9: not CS wakeup */ +#define LPSME 0x0100 /* b8: Low power sleep mode */ +#define HSE 0x0080 /* b7: Hi-speed enable */ +#define DCFM 0x0040 /* b6: Controller function select */ +#define DRPD 0x0020 /* b5: D+/- pull down control */ +#define DPRPU 0x0010 /* b4: D+ pull up control */ +#define USBE 0x0001 /* b0: USB module operation enable */ + +/* System Configuration Status Register */ +#define OVCBIT 0x8000 /* b15-14: Over-current bit */ +#define OVCMON 0xC000 /* b15-14: Over-current monitor */ +#define SOFEA 0x0020 /* b5: SOF monitor */ +#define IDMON 0x0004 /* b3: ID-pin monitor */ +#define LNST 0x0003 /* b1-0: D+, D- line status */ +#define SE1 0x0003 /* SE1 */ +#define FS_KSTS 0x0002 /* Full-Speed K State */ +#define FS_JSTS 0x0001 /* Full-Speed J State */ +#define LS_JSTS 0x0002 /* Low-Speed J State */ +#define LS_KSTS 0x0001 /* Low-Speed K State */ +#define SE0 0x0000 /* SE0 */ + +/* Device State Control Register */ +#define EXTLP0 0x0400 /* b10: External port */ +#define VBOUT 0x0200 /* b9: VBUS output */ +#define WKUP 0x0100 /* b8: Remote wakeup */ +#define RWUPE 0x0080 /* b7: Remote wakeup sense */ +#define USBRST 0x0040 /* b6: USB reset enable */ +#define RESUME 0x0020 /* b5: Resume enable */ +#define UACT 0x0010 /* b4: USB bus enable */ +#define RHST 0x0007 /* b1-0: Reset handshake status */ +#define HSPROC 0x0004 /* HS handshake is processing */ +#define HSMODE 0x0003 /* Hi-Speed mode */ +#define FSMODE 0x0002 /* Full-Speed mode */ +#define LSMODE 0x0001 /* Low-Speed mode */ +#define UNDECID 0x0000 /* Undecided */ + +/* Test Mode Register */ +#define UTST 0x000F /* b3-0: Test select */ +#define H_TST_PACKET 0x000C /* HOST TEST Packet */ +#define H_TST_SE0_NAK 0x000B /* HOST TEST SE0 NAK */ +#define H_TST_K 0x000A /* HOST TEST K */ +#define H_TST_J 0x0009 /* HOST TEST J */ +#define H_TST_NORMAL 0x0000 /* HOST Normal Mode */ +#define P_TST_PACKET 0x0004 /* PERI TEST Packet */ +#define P_TST_SE0_NAK 0x0003 /* PERI TEST SE0 NAK */ +#define P_TST_K 0x0002 /* PERI TEST K */ +#define P_TST_J 0x0001 /* PERI TEST J */ +#define P_TST_NORMAL 0x0000 /* PERI Normal Mode */ + +/* Data Pin Configuration Register */ +#define LDRV 0x8000 /* b15: Drive Current Adjust */ +#define VIF1 0x0000 /* VIF = 1.8V */ +#define VIF3 0x8000 /* VIF = 3.3V */ +#define INTA 0x0001 /* b1: USB INT-pin active */ + +/* DMAx Pin Configuration Register */ +#define DREQA 0x4000 /* b14: Dreq active select */ +#define BURST 0x2000 /* b13: Burst mode */ +#define DACKA 0x0400 /* b10: Dack active select */ +#define DFORM 0x0380 /* b9-7: DMA mode select */ +#define CPU_ADR_RD_WR 0x0000 /* Address + RD/WR mode (CPU bus) */ +#define CPU_DACK_RD_WR 0x0100 /* DACK + RD/WR mode (CPU bus) */ +#define CPU_DACK_ONLY 0x0180 /* DACK only mode (CPU bus) */ +#define SPLIT_DACK_ONLY 0x0200 /* DACK only mode (SPLIT bus) */ +#define DENDA 0x0040 /* b6: Dend active select */ +#define PKTM 0x0020 /* b5: Packet mode */ +#define DENDE 0x0010 /* b4: Dend enable */ +#define OBUS 0x0004 /* b2: OUTbus mode */ + +/* CFIFO/DxFIFO Port Select Register */ +#define RCNT 0x8000 /* b15: Read count mode */ +#define REW 0x4000 /* b14: Buffer rewind */ +#define DCLRM 0x2000 /* b13: DMA buffer clear mode */ +#define DREQE 0x1000 /* b12: DREQ output enable */ +#define MBW 0x0400 /* b10: Maximum bit width for FIFO access */ +#define MBW_8 0x0000 /* 8bit */ +#define MBW_16 0x0400 /* 16bit */ +#define BIGEND 0x0100 /* b8: Big endian mode */ +#define BYTE_LITTLE 0x0000 /* little dendian */ +#define BYTE_BIG 0x0100 /* big endifan */ +#define ISEL 0x0020 /* b5: DCP FIFO port direction select */ +#define CURPIPE 0x000F /* b2-0: PIPE select */ + +/* CFIFO/DxFIFO Port Control Register */ +#define BVAL 0x8000 /* b15: Buffer valid flag */ +#define BCLR 0x4000 /* b14: Buffer clear */ +#define FRDY 0x2000 /* b13: FIFO ready */ +#define DTLN 0x0FFF /* b11-0: FIFO received data length */ + +/* Interrupt Enable Register 0 */ +#define VBSE 0x8000 /* b15: VBUS interrupt */ +#define RSME 0x4000 /* b14: Resume interrupt */ +#define SOFE 0x2000 /* b13: Frame update interrupt */ +#define DVSE 0x1000 /* b12: Device state transition interrupt */ +#define CTRE 0x0800 /* b11: Control transfer stage transition interrupt */ +#define BEMPE 0x0400 /* b10: Buffer empty interrupt */ +#define NRDYE 0x0200 /* b9: Buffer not ready interrupt */ +#define BRDYE 0x0100 /* b8: Buffer ready interrupt */ + +/* Interrupt Enable Register 1 */ +#define OVRCRE 0x8000 /* b15: Over-current interrupt */ +#define BCHGE 0x4000 /* b14: USB us chenge interrupt */ +#define DTCHE 0x1000 /* b12: Detach sense interrupt */ +#define ATTCHE 0x0800 /* b11: Attach sense interrupt */ +#define EOFERRE 0x0040 /* b6: EOF error interrupt */ +#define SIGNE 0x0020 /* b5: SETUP IGNORE interrupt */ +#define SACKE 0x0010 /* b4: SETUP ACK interrupt */ + +/* BRDY Interrupt Enable/Status Register */ +#define BRDY9 0x0200 /* b9: PIPE9 */ +#define BRDY8 0x0100 /* b8: PIPE8 */ +#define BRDY7 0x0080 /* b7: PIPE7 */ +#define BRDY6 0x0040 /* b6: PIPE6 */ +#define BRDY5 0x0020 /* b5: PIPE5 */ +#define BRDY4 0x0010 /* b4: PIPE4 */ +#define BRDY3 0x0008 /* b3: PIPE3 */ +#define BRDY2 0x0004 /* b2: PIPE2 */ +#define BRDY1 0x0002 /* b1: PIPE1 */ +#define BRDY0 0x0001 /* b1: PIPE0 */ + +/* NRDY Interrupt Enable/Status Register */ +#define NRDY9 0x0200 /* b9: PIPE9 */ +#define NRDY8 0x0100 /* b8: PIPE8 */ +#define NRDY7 0x0080 /* b7: PIPE7 */ +#define NRDY6 0x0040 /* b6: PIPE6 */ +#define NRDY5 0x0020 /* b5: PIPE5 */ +#define NRDY4 0x0010 /* b4: PIPE4 */ +#define NRDY3 0x0008 /* b3: PIPE3 */ +#define NRDY2 0x0004 /* b2: PIPE2 */ +#define NRDY1 0x0002 /* b1: PIPE1 */ +#define NRDY0 0x0001 /* b1: PIPE0 */ + +/* BEMP Interrupt Enable/Status Register */ +#define BEMP9 0x0200 /* b9: PIPE9 */ +#define BEMP8 0x0100 /* b8: PIPE8 */ +#define BEMP7 0x0080 /* b7: PIPE7 */ +#define BEMP6 0x0040 /* b6: PIPE6 */ +#define BEMP5 0x0020 /* b5: PIPE5 */ +#define BEMP4 0x0010 /* b4: PIPE4 */ +#define BEMP3 0x0008 /* b3: PIPE3 */ +#define BEMP2 0x0004 /* b2: PIPE2 */ +#define BEMP1 0x0002 /* b1: PIPE1 */ +#define BEMP0 0x0001 /* b0: PIPE0 */ + +/* SOF Pin Configuration Register */ +#define TRNENSEL 0x0100 /* b8: Select transaction enable period */ +#define BRDYM 0x0040 /* b6: BRDY clear timing */ +#define INTL 0x0020 /* b5: Interrupt sense select */ +#define EDGESTS 0x0010 /* b4: */ +#define SOFMODE 0x000C /* b3-2: SOF pin select */ +#define SOF_125US 0x0008 /* SOF OUT 125us Frame Signal */ +#define SOF_1MS 0x0004 /* SOF OUT 1ms Frame Signal */ +#define SOF_DISABLE 0x0000 /* SOF OUT Disable */ + +/* Interrupt Status Register 0 */ +#define VBINT 0x8000 /* b15: VBUS interrupt */ +#define RESM 0x4000 /* b14: Resume interrupt */ +#define SOFR 0x2000 /* b13: SOF frame update interrupt */ +#define DVST 0x1000 /* b12: Device state transition interrupt */ +#define CTRT 0x0800 /* b11: Control transfer stage transition interrupt */ +#define BEMP 0x0400 /* b10: Buffer empty interrupt */ +#define NRDY 0x0200 /* b9: Buffer not ready interrupt */ +#define BRDY 0x0100 /* b8: Buffer ready interrupt */ +#define VBSTS 0x0080 /* b7: VBUS input port */ +#define DVSQ 0x0070 /* b6-4: Device state */ +#define DS_SPD_CNFG 0x0070 /* Suspend Configured */ +#define DS_SPD_ADDR 0x0060 /* Suspend Address */ +#define DS_SPD_DFLT 0x0050 /* Suspend Default */ +#define DS_SPD_POWR 0x0040 /* Suspend Powered */ +#define DS_SUSP 0x0040 /* Suspend */ +#define DS_CNFG 0x0030 /* Configured */ +#define DS_ADDS 0x0020 /* Address */ +#define DS_DFLT 0x0010 /* Default */ +#define DS_POWR 0x0000 /* Powered */ +#define DVSQS 0x0030 /* b5-4: Device state */ +#define VALID 0x0008 /* b3: Setup packet detected flag */ +#define CTSQ 0x0007 /* b2-0: Control transfer stage */ +#define CS_SQER 0x0006 /* Sequence error */ +#define CS_WRND 0x0005 /* Control write nodata status stage */ +#define CS_WRSS 0x0004 /* Control write status stage */ +#define CS_WRDS 0x0003 /* Control write data stage */ +#define CS_RDSS 0x0002 /* Control read status stage */ +#define CS_RDDS 0x0001 /* Control read data stage */ +#define CS_IDST 0x0000 /* Idle or setup stage */ + +/* Interrupt Status Register 1 */ +#define OVRCR 0x8000 /* b15: Over-current interrupt */ +#define BCHG 0x4000 /* b14: USB bus chenge interrupt */ +#define DTCH 0x1000 /* b12: Detach sense interrupt */ +#define ATTCH 0x0800 /* b11: Attach sense interrupt */ +#define EOFERR 0x0040 /* b6: EOF-error interrupt */ +#define SIGN 0x0020 /* b5: Setup ignore interrupt */ +#define SACK 0x0010 /* b4: Setup acknowledge interrupt */ + +/* Frame Number Register */ +#define OVRN 0x8000 /* b15: Overrun error */ +#define CRCE 0x4000 /* b14: Received data error */ +#define FRNM 0x07FF /* b10-0: Frame number */ + +/* Micro Frame Number Register */ +#define UFRNM 0x0007 /* b2-0: Micro frame number */ + +/* USB Address / Low Power Status Recovery Register */ +//#define USBADDR 0x007F /* b6-0: USB address */ + +/* Default Control Pipe Maxpacket Size Register */ +/* Pipe Maxpacket Size Register */ +#define DEVSEL 0xF000 /* b15-14: Device address select */ +#define MAXP 0x007F /* b6-0: Maxpacket size of default control pipe */ + +/* Default Control Pipe Control Register */ +#define BSTS 0x8000 /* b15: Buffer status */ +#define SUREQ 0x4000 /* b14: Send USB request */ +#define CSCLR 0x2000 /* b13: complete-split status clear */ +#define CSSTS 0x1000 /* b12: complete-split status */ +#define SUREQCLR 0x0800 /* b11: stop setup request */ +#define SQCLR 0x0100 /* b8: Sequence toggle bit clear */ +#define SQSET 0x0080 /* b7: Sequence toggle bit set */ +#define SQMON 0x0040 /* b6: Sequence toggle bit monitor */ +#define PBUSY 0x0020 /* b5: pipe busy */ +#define PINGE 0x0010 /* b4: ping enable */ +#define CCPL 0x0004 /* b2: Enable control transfer complete */ +#define PID 0x0003 /* b1-0: Response PID */ +#define PID_STALL11 0x0003 /* STALL */ +#define PID_STALL 0x0002 /* STALL */ +#define PID_BUF 0x0001 /* BUF */ +#define PID_NAK 0x0000 /* NAK */ + +/* Pipe Window Select Register */ +#define PIPENM 0x0007 /* b2-0: Pipe select */ + +/* Pipe Configuration Register */ +#define R8A66597_TYP 0xC000 /* b15-14: Transfer type */ +#define R8A66597_ISO 0xC000 /* Isochronous */ +#define R8A66597_INT 0x8000 /* Interrupt */ +#define R8A66597_BULK 0x4000 /* Bulk */ +#define R8A66597_BFRE 0x0400 /* b10: Buffer ready interrupt mode select */ +#define R8A66597_DBLB 0x0200 /* b9: Double buffer mode select */ +#define R8A66597_CNTMD 0x0100 /* b8: Continuous transfer mode select */ +#define R8A66597_SHTNAK 0x0080 /* b7: Transfer end NAK */ +#define R8A66597_DIR 0x0010 /* b4: Transfer direction select */ +#define R8A66597_EPNUM 0x000F /* b3-0: Eendpoint number select */ + +/* Pipe Buffer Configuration Register */ +#define BUFSIZE 0x7C00 /* b14-10: Pipe buffer size */ +#define BUFNMB 0x007F /* b6-0: Pipe buffer number */ +#define PIPE0BUF 256 +#define PIPExBUF 64 + +/* Pipe Maxpacket Size Register */ +#define MXPS 0x07FF /* b10-0: Maxpacket size */ + +/* Pipe Cycle Configuration Register */ +#define IFIS 0x1000 /* b12: Isochronous in-buffer flush mode select */ +#define IITV 0x0007 /* b2-0: Isochronous interval */ + +/* Pipex Control Register */ +#define BSTS 0x8000 /* b15: Buffer status */ +#define INBUFM 0x4000 /* b14: IN buffer monitor (Only for PIPE1 to 5) */ +#define CSCLR 0x2000 /* b13: complete-split status clear */ +#define CSSTS 0x1000 /* b12: complete-split status */ +#define ATREPM 0x0400 /* b10: Auto repeat mode */ +#define ACLRM 0x0200 /* b9: Out buffer auto clear mode */ +#define SQCLR 0x0100 /* b8: Sequence toggle bit clear */ +#define SQSET 0x0080 /* b7: Sequence toggle bit set */ +#define SQMON 0x0040 /* b6: Sequence toggle bit monitor */ +#define PBUSY 0x0020 /* b5: pipe busy */ +#define PID 0x0003 /* b1-0: Response PID */ + +/* PIPExTRE */ +#define TRENB 0x0200 /* b9: Transaction counter enable */ +#define TRCLR 0x0100 /* b8: Transaction counter clear */ + +/* PIPExTRN */ +#define TRNCNT 0xFFFF /* b15-0: Transaction counter */ + +/* DEVADDx */ +#define UPPHUB 0x7800 +#define HUBPORT 0x0700 +#define USBSPD 0x00C0 +#define RTPORT 0x0001 + +#define R8A66597_MAX_NUM_PIPE 10 +#define R8A66597_BUF_BSIZE 8 +#define R8A66597_MAX_DEVICE 10 +#define R8A66597_MAX_ROOT_HUB 2 +#define R8A66597_MAX_SAMPLING 10 +#define R8A66597_MAX_DMA_CHANNEL 2 +#define R8A66597_PIPE_NO_DMA R8A66597_MAX_DMA_CHANNEL +#define check_bulk_or_isoc(pipenum) ((pipenum >= 1 && pipenum <= 5)) +#define check_interrupt(pipenum) ((pipenum >= 6 && pipenum <= 9)) +#define make_devsel(addr) (addr << 12) + +struct r8a66597_pipe_info { + u16 pipenum; + u16 address; /* R8A66597 HCD usb addres */ + u16 epnum; + u16 maxpacket; + u16 type; + u16 bufnum; + u16 buf_bsize; + u16 interval; + u16 dir_in; +}; + +struct r8a66597_pipe { + struct r8a66597_pipe_info info; + + unsigned long fifoaddr; + unsigned long fifosel; + unsigned long fifoctr; + unsigned long pipectr; + unsigned long pipetre; + unsigned long pipetrn; +}; + +struct r8a66597_td { + struct r8a66597_pipe *pipe; + struct urb *urb; + struct list_head queue; + + u16 type; + u16 pipenum; + int iso_cnt; + + u16 address; /* R8A66597's USB address */ + u16 maxpacket; + + unsigned zero_packet:1; + unsigned short_packet:1; + unsigned set_address:1; +}; + +struct r8a66597_device { + u16 address; /* R8A66597's USB address */ + u16 hub_port; + u16 root_port; + + unsigned short ep_in_toggle; + unsigned short ep_out_toggle; + unsigned char pipe_cnt[R8A66597_MAX_NUM_PIPE]; + unsigned char dma_map; + + enum usb_device_state state; + + struct usb_device *udev; + int usb_address; + struct list_head device_list; +}; + +struct r8a66597_root_hub { + u32 port; + u16 old_syssts; + int scount; + + struct r8a66597_device *dev; +}; + +struct r8a66597 { + spinlock_t lock; + unsigned long reg; + + struct r8a66597_device device0; + struct r8a66597_root_hub root_hub[R8A66597_MAX_ROOT_HUB]; + struct list_head pipe_queue[R8A66597_MAX_NUM_PIPE]; + + struct timer_list rh_timer; + struct timer_list td_timer[R8A66597_MAX_NUM_PIPE]; + + unsigned short address_map; + unsigned short timeout_map; + unsigned char pipe_cnt[R8A66597_MAX_NUM_PIPE]; + unsigned char dma_map; + + struct list_head child_device; + unsigned long child_connect_map[4]; +}; + +static inline struct r8a66597 *hcd_to_r8a66597(struct usb_hcd *hcd) +{ + return (struct r8a66597 *)(hcd->hcd_priv); +} + +static inline struct usb_hcd *r8a66597_to_hcd(struct r8a66597 *r8a66597) +{ + return container_of((void *)r8a66597, struct usb_hcd, hcd_priv); +} + +static inline struct r8a66597_td *r8a66597_get_td(struct r8a66597 *r8a66597, + u16 pipenum) +{ + if (unlikely(list_empty(&r8a66597->pipe_queue[pipenum]))) + return NULL; + + return list_entry(r8a66597->pipe_queue[pipenum].next, + struct r8a66597_td, queue); +} + +static inline struct urb *r8a66597_get_urb(struct r8a66597 *r8a66597, + u16 pipenum) +{ + struct r8a66597_td *td; + + td = r8a66597_get_td(r8a66597, pipenum); + return (td ? td->urb : NULL); +} + +static inline u16 r8a66597_read(struct r8a66597 *r8a66597, unsigned long offset) +{ + return inw(r8a66597->reg + offset); +} + +static inline void r8a66597_read_fifo(struct r8a66597 *r8a66597, + unsigned long offset, u16 *buf, + int len) +{ + len = (len + 1) / 2; + insw(r8a66597->reg + offset, buf, len); +} + +static inline void r8a66597_write(struct r8a66597 *r8a66597, u16 val, + unsigned long offset) +{ + outw(val, r8a66597->reg + offset); +} + +static inline void r8a66597_write_fifo(struct r8a66597 *r8a66597, + unsigned long offset, u16 *buf, + int len) +{ + unsigned long fifoaddr = r8a66597->reg + offset; + int odd = len & 0x0001; + + len = len / 2; + outsw(fifoaddr, buf, len); + if (unlikely(odd)) { + buf = &buf[len]; + outb((unsigned char)*buf, fifoaddr); + } +} + +static inline void r8a66597_mdfy(struct r8a66597 *r8a66597, + u16 val, u16 pat, unsigned long offset) +{ + u16 tmp; + tmp = r8a66597_read(r8a66597, offset); + tmp = tmp & (~pat); + tmp = tmp | val; + r8a66597_write(r8a66597, tmp, offset); +} + +#define r8a66597_bclr(r8a66597, val, offset) \ + r8a66597_mdfy(r8a66597, 0, val, offset) +#define r8a66597_bset(r8a66597, val, offset) \ + r8a66597_mdfy(r8a66597, val, 0, offset) + +static inline unsigned long get_syscfg_reg(int port) +{ + return port == 0 ? SYSCFG0 : SYSCFG1; +} + +static inline unsigned long get_syssts_reg(int port) +{ + return port == 0 ? SYSSTS0 : SYSSTS1; +} + +static inline unsigned long get_dvstctr_reg(int port) +{ + return port == 0 ? DVSTCTR0 : DVSTCTR1; +} + +static inline unsigned long get_intenb_reg(int port) +{ + return port == 0 ? INTENB1 : INTENB2; +} + +static inline unsigned long get_intsts_reg(int port) +{ + return port == 0 ? INTSTS1 : INTSTS2; +} + +static inline u16 get_rh_usb_speed(struct r8a66597 *r8a66597, int port) +{ + unsigned long dvstctr_reg = get_dvstctr_reg(port); + + return r8a66597_read(r8a66597, dvstctr_reg) & RHST; +} + +static inline void r8a66597_port_power(struct r8a66597 *r8a66597, int port, + int power) +{ + unsigned long dvstctr_reg = get_dvstctr_reg(port); + + if (power) + r8a66597_bset(r8a66597, VBOUT, dvstctr_reg); + else + r8a66597_bclr(r8a66597, VBOUT, dvstctr_reg); +} + +#define get_pipectr_addr(pipenum) (PIPE1CTR + (pipenum - 1) * 2) +#define get_pipetre_addr(pipenum) (PIPE1TRE + (pipenum - 1) * 4) +#define get_pipetrn_addr(pipenum) (PIPE1TRN + (pipenum - 1) * 4) +#define get_devadd_addr(address) (DEVADD0 + address * 2) + +#define enable_irq_ready(r8a66597, pipenum) \ + enable_pipe_irq(r8a66597, pipenum, BRDYENB) +#define disable_irq_ready(r8a66597, pipenum) \ + disable_pipe_irq(r8a66597, pipenum, BRDYENB) +#define enable_irq_empty(r8a66597, pipenum) \ + enable_pipe_irq(r8a66597, pipenum, BEMPENB) +#define disable_irq_empty(r8a66597, pipenum) \ + disable_pipe_irq(r8a66597, pipenum, BEMPENB) +#define enable_irq_nrdy(r8a66597, pipenum) \ + enable_pipe_irq(r8a66597, pipenum, NRDYENB) +#define disable_irq_nrdy(r8a66597, pipenum) \ + disable_pipe_irq(r8a66597, pipenum, NRDYENB) + +#endif /* __R8A66597_H__ */ + diff --git a/drivers/usb/host/uhci-hcd.c b/drivers/usb/host/uhci-hcd.c index d22da26ff16..76c555a67da 100644 --- a/drivers/usb/host/uhci-hcd.c +++ b/drivers/usb/host/uhci-hcd.c @@ -730,10 +730,9 @@ static int uhci_rh_resume(struct usb_hcd *hcd) int rc = 0; spin_lock_irq(&uhci->lock); - if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) { - dev_warn(&hcd->self.root_hub->dev, "HC isn't running!\n"); + if (!test_bit(HCD_FLAG_HW_ACCESSIBLE, &hcd->flags)) rc = -ESHUTDOWN; - } else if (!uhci->dead) + else if (!uhci->dead) wakeup_rh(uhci); spin_unlock_irq(&uhci->lock); return rc; |